Prettier is an opinionated code formatter
npm install @canva/prettier@canva/prettierThis 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.
1. Install pnpm i prettier@npm:@canva/prettier
- Installing as an alias means that it will "just work" with all existing infra
2. Add a .prettierrc.json file with the contents "prettier/canva-prettier"
3. ???
4. Profit!
Prettier places logical operators at the end of the line. The canva style prefers operators at the start of the line.
Comparison
_Input:_
``ts`
function f() {
const appEntities = getAppEntities(loadObject).filter(
(entity) =>
entity
&& entity.isInstallAvailable()
&& !entity.isQueue()
&& entity.isDisabled()
);
}
_prettier Upstream:_
`ts`
function f() {
const appEntities = getAppEntities(loadObject).filter(
(entity) =>
entity &&
entity.isInstallAvailable() &&
!entity.isQueue() &&
entity.isDisabled()
);
}
_@canva/prettier:_
`ts`
function f() {
const appEntities = getAppEntities(loadObject).filter(
(entity) =>
entity
&& entity.isInstallAvailable()
&& !entity.isQueue()
&& entity.isDisabled()
);
}
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:_
`ts`
type T1 = 'type one' & 'type two' & 'type three' & 'type four' & 'type five' & 'type six';
type T2 = Pick
type T3 =
& {
type: JSONSchema4TypeName[];
}
& JSONSchema4ObjectSchema
& JSONSchema4ArraySchema
& JSONSchema4StringSchema
& JSONSchema4NumberSchema
& JSONSchema4BoleanSchema
& JSONSchema4NullSchema
& JSONSchema4AnySchema;
_prettier Upstream:_
`ts`
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:_
`ts`
type T1 =
& "type one"
& "type two"
& "type three"
& "type four"
& "type five"
& "type six";
type T2 =
& Pick
& Fooooooooo;
type T3 =
& {
type: JSONSchema4TypeName[],
}
& JSONSchema4ObjectSchema
& JSONSchema4ArraySchema
& JSONSchema4StringSchema
& JSONSchema4NumberSchema
& JSONSchema4BoleanSchema
& JSONSchema4NullSchema
& JSONSchema4AnySchema;
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:_
`ts`
const x = [[1, 2], [1, 2], [1, 2]];
const x = [[1, 2], [1, 2], [1]];
_prettier Upstream:_
`ts`
const x = [
[1, 2],
[1, 2],
[1, 2],
];
const x = [[1, 2], [1, 2], [1]];
_@canva/prettier:_
`ts`
const x = [[1, 2], [1, 2], [1, 2]];
const x = [[1, 2], [1, 2], [1]];
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:_
`ts
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:_
`ts
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:_
`ts
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 tries to special-case inline the ternary operator tokens for things like JSX or other "parenthesized" expressions. We remove the special casing so that the operators are on new lines always
Comparison
_Input:_
`ts
true
? test({
a: 1
})
: a={123412342314}
b={123412341234}
c={123412341234}
d={123412341234}
e={123412341234}
f={123412341234}
g={123412341234}
/>;
const funnelSnapshotCard = (report === MY_OVERVIEW &&
!ReportGK.xar_metrics_active_capitol_v2) ||
(report === COMPANY_OVERVIEW &&
!ReportGK.xar_metrics_active_capitol_v2_company_metrics)
?
: null;
`
_prettier Upstream:_
`ts
true ? (
test({
a: 1,
})
) : (
a={123412342314}
b={123412341234}
c={123412341234}
d={123412341234}
e={123412341234}
f={123412341234}
g={123412341234}
/>
);
const funnelSnapshotCard =
(report === MY_OVERVIEW && !ReportGK.xar_metrics_active_capitol_v2) ||
(report === COMPANY_OVERVIEW &&
!ReportGK.xar_metrics_active_capitol_v2_company_metrics) ? (
) : null;
`
_@canva/prettier:_
`ts
true
? (
test({
a: 1,
})
)
: (
a={123412342314}
b={123412341234}
c={123412341234}
d={123412341234}
e={123412341234}
f={123412341234}
g={123412341234}
/>
);
const funnelSnapshotCard =
(report === MY_OVERVIEW && !ReportGK.xar_metrics_active_capitol_v2) ||
(report === COMPANY_OVERVIEW &&
!ReportGK.xar_metrics_active_capitol_v2_company_metrics)
?
: null;
`
By default prettier formats every single file unless it's listed in the .prettierignore.// @formatter:off
At canva this can be cumbersome to list every ignored file in the config and sometimes we opt files out by adding a comment in the code.
Comparison
_Input:_
`ts
// @formatter:off
const yolo = "";
`
_prettier Upstream:_
`ts
// @formatter:off
const yolo = '';
`
_@canva/prettier:_
`ts
// @formatter:off
const yolo = "";
`
1. Clone this repo
1. Add a new remote git remote add upstream git@github.com:prettier/prettier.gitgit fetch upstream
1. Fetch upstream git switch -c prettier/main origin/prettier/main
1. Checkout the local upstream branch git merge upstream/main
1. Merge the upstream changes git push
1. Push the changes git checkout main
1. Checkout our fork's main branch git merge prettier/main
1. Merge the upstream branch git push
1. Push the changes
1. Clone this repo
1. yarn installyarn test -u
1. Make your changes
1. Add tests for your changes
1. Ensure all snapshots are updated yarn lint
1. Ensure lints pass Prettiest:
1. Commit your changes
1. Raise a PR titled
1. 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.canva-npm-publish` workflow on the tag and do the actual publish to npm.
1. Once you're ready, Publish the release.
- Publishing will trigger the