@canva/prettier
What is this?
This is Canva's fork of prettier
so that we can customize it to suit our style.
Prettier is (by intentional design) not very customizable, so in order for us to tweak the formatting we need to fork it.
Because forking is a very heavy process to maintain whilst keeping up-to-date with upstream, we aim to make as few changes as possible.
Differences
Place Logical Operators at the start of the line
Prettier places logical operators at the end of the line. The canva style prefers operators at the start of the line.
Comparison
Input:
function f() {
const appEntities = getAppEntities(loadObject).filter(
(entity) =>
entity
&& entity.isInstallAvailable()
&& !entity.isQueue()
&& entity.isDisabled()
);
}
prettier
Upstream:
function f() {
const appEntities = getAppEntities(loadObject).filter(
(entity) =>
entity &&
entity.isInstallAvailable() &&
!entity.isQueue() &&
entity.isDisabled()
);
}
@canva/prettier
:
function f() {
const appEntities = getAppEntities(loadObject).filter(
(entity) =>
entity
&& entity.isInstallAvailable()
&& !entity.isQueue()
&& entity.isDisabled()
);
}
Array Pattern and Object Pattern newline retention
Prettier will forcefully remove newlines from array patterns and object patterns unless the node does not fit onto one line.
Our previous formatter, dprint
, would retain the manual newline. In order to reduce migration churn, we opted to retain these newlines.
Comparison
Input:
const [x, y] = [x, y];
const [
x, y] = [x, y];
const [x, y] = [
x, y];
const [
x, y] = [
x, y];
const {x, y} = {x, y};
const {
x, y} = {x, y};
const {x, y} = {
x, y};
const {
x, y} = {
x, y};
prettier
Upstream:
const [x, y] = [x, y];
const [x, y] = [x, y];
const [x, y] = [x, y];
const [x, y] = [x, y];
const { x, y } = { x, y };
const { x, y } = { x, y };
const { x, y } = {
x,
y,
};
const { x, y } = {
x,
y,
};
@canva/prettier
:
const [x, y] = [x, y];
const [
x,
y,
] = [x, y];
const [x, y] = [
x,
y,
];
const [
x,
y,
] = [
x,
y,
];
const {x, y} = {x, y};
const {
x,
y,
} = {x, y};
const {x, y} = {
x,
y,
};
const {
x,
y,
} = {
x,
y,
};
Delimit Object Literal Types with ,
and Interfaces with ;
Prettier uses ;
to delimit all object type-like members. The canva style uses ,
for object literal types so they look closer to object expressions.
Comparison
Input:
type T = {
a: 1
b(): void
new(): T
[c: string]: 1
};
interface T {
a: 1
b(): void
new(): T
[c: string]: 1
}
prettier
Upstream:
type T = {
a: 1;
b(): void;
new(): T;
[c: string]: 1;
};
interface T {
a: 1;
b(): void;
new(): T;
[c: string]: 1;
}
@canva/prettier
:
type T = {
a: 1,
b(): void,
new(): T,
[c: string]: 1,
};
interface T {
a: 1;
b(): void;
new(): T;
[c: string]: 1;
}
Format intersection types the same as union types
Prettier formats intersections in their own, bespoke style that doesn't match union types or binary expressions.. This leads to many edge-cases of subpar formatting and also creates inconsistent styling between types.
Comparison
Input:
type T1 = 'type one' & 'type two' & 'type three' & 'type four' & 'type five' & 'type six';
type T2 = Pick<TTTTTTTTTTTTTTTTTTTTTTTTTTT, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'> & Fooooooooo;
type T3 =
& {
type: JSONSchema4TypeName[];
}
& JSONSchema4ObjectSchema
& JSONSchema4ArraySchema
& JSONSchema4StringSchema
& JSONSchema4NumberSchema
& JSONSchema4BoleanSchema
& JSONSchema4NullSchema
& JSONSchema4AnySchema;
prettier
Upstream:
type T1 = "type one" &
"type two" &
"type three" &
"type four" &
"type five" &
"type six";
type T2 = Pick<
TTTTTTTTTTTTTTTTTTTTTTTTTTT,
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
> &
Fooooooooo;
type T3 = {
type: JSONSchema4TypeName[],
} & JSONSchema4ObjectSchema &
JSONSchema4ArraySchema &
JSONSchema4StringSchema &
JSONSchema4NumberSchema &
JSONSchema4BoleanSchema &
JSONSchema4NullSchema &
JSONSchema4AnySchema;
@canva/prettier
:
type T1 =
& "type one"
& "type two"
& "type three"
& "type four"
& "type five"
& "type six";
type T2 =
& Pick<TTTTTTTTTTTTTTTTTTTTTTTTTTT, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">
& Fooooooooo;
type T3 =
& {
type: JSONSchema4TypeName[],
}
& JSONSchema4ObjectSchema
& JSONSchema4ArraySchema
& JSONSchema4StringSchema
& JSONSchema4NumberSchema
& JSONSchema4BoleanSchema
& JSONSchema4NullSchema
& JSONSchema4AnySchema;
Don't apply special formatting to 2D arrays
Prettier applies special logic to detect 2-dimensional arrays and enforces that each sub-array is placed on its own line. This detection is inconsistent, however, as it requires all sub-arrays to be at least of length 2; if even one sub-array isn't that long then the formatting uses different.
Comparison
Input:
const x = [[1, 2], [1, 2], [1, 2]];
const x = [[1, 2], [1, 2], [1]];
prettier
Upstream:
const x = [
[1, 2],
[1, 2],
[1, 2],
];
const x = [[1, 2], [1, 2], [1]];
@canva/prettier
:
const x = [[1, 2], [1, 2], [1, 2]];
const x = [[1, 2], [1, 2], [1]];
Enforce that control-flow statements must have a block body
Prettier endeavors to avoid changing the AST with its formatting pass. It does this on purpose because it makes it easy to prevent accidental runtime changes, however it does limit the scope of formats a little. In our ESLint config we use the curly
ESLint rule which enforce that all control flow statements have their body "wrapped in curly braces".
Instead of relying on a lint rule and applying its fixer after prettier runs, we instead integrate this directly into prettier itself so that the formatting can be done in one pass.
Comparison
Input:
if (true) expression;
if (true) expression; else expression;
if (true) expression; else if (true) expression;
while (true) expression;
for (; ;) expression;
for (let x of y) expression;
for (let x in y) expression;
do expression; while (true);
prettier
Upstream:
if (true) expression;
if (true) expression;
else expression;
if (true) expression;
else if (true) expression;
while (true) expression;
for (;;) expression;
for (let x of y) expression;
for (let x in y) expression;
do expression;
while (true);
@canva/prettier
:
if (true) {
expression;
}
if (true) {
expression;
} else {
expression;
}
if (true) {
expression;
} else if (true) {
expression;
}
while (true) {
expression;
}
for (;;) {
expression;
}
for (let x of y) {
expression;
}
for (let x in y) {
expression;
}
do {
expression;
} while (true);
Ignore files with // @formatter:off
in their contents
By default prettier formats every single file unless it's listed in the .prettierignore
.
At canva this can be cumbersome to list every ignored file in the config and sometimes we opt files out by adding a
// @formatter:off
comment in the code.
Comparison
Input:
const yolo = "";
prettier
Upstream:
const yolo = '';
@canva/prettier
:
const yolo = "";
Contributing
Merging upstream
- Clone this repo
- Add a new remote
git remote add upstream git@github.com:prettier/prettier.git
- Fetch upstream
git fetch upstream
- Checkout the local upstream branch
git switch -c prettier/main origin/prettier/main
- Merge the upstream changes
git merge upstream/main
- Push the changes
git push
- Checkout our fork's main branch
git checkout main
- Merge the upstream branch
git merge prettier/main
- Push the changes
git push
Making changes
- Clone this repo
yarn install
- Make your changes
- Add tests for your changes
- Ensure all snapshots are updated
yarn test -u
- Ensure lints pass
yarn lint
- Commit your changes
- Raise a PR titled
Prettiest: <change you made>
Publishing
- Draft a new release
- The tag must be named in the form
X.X.X-canva.Y
- where X.X.X
is the current prettier version and Y
is the incremental release number starting at 0.
- Once you're ready, Publish the release.
- Publishing will trigger the
canva-npm-publish
workflow on the tag and do the actual publish to npm.