Back to Prettier

Prettier 3.2: Support JSONC and Angular’s ICU expression

website/blog/2024-01-12-3.2.0.md

3.8.310.6 KB
Original Source

This release includes new features such as adding a JSONC parser, adding Angular’s ICU expressions, and many bug fixes.

We are still seeking feedback for the --experimental-ternaries option released in Prettier 3.1. Please read A curious case of the ternaries and respond via the Google Forms link provided.

Additionally, we recommend reading Prettier's CLI: A Performance Deep Dive by Fabio Spampinato. This faster CLI is slated to be released as version 4.0.

<!-- truncate -->

If you appreciate Prettier and would like to support our work, please consider sponsoring us directly via our OpenCollective or by sponsoring the projects we depend on, such as typescript-eslint, remark, and Babel. Thank you for your continued support!

Highlights

JSON

New jsonc parser added (#15831 by @fisker)

Previously, we infer the parser of .jsonc files to be json, but if we want keep the trailing comma, we'll have to use a hacky workaround config {parser: "json5", quoteProps: "preserve", singleQuote: false}.

The new added jsonc parser:

  • Always quote the object keys.
  • Wrap strings with double quotes.
  • Of course, respect the trailingComma option.

Angular

Support formatting for Angular ICU expression (#15777 by @sosukesuzuki)

Support two kinds of Angular ICU expressions: plural and select.

<!-- prettier-ignore -->
html
<span i18n>
  Updated:
  {minutes, plural,
    =0 {just now}
    =1 {one minute ago}
    other {{{minutes}} minutes ago}
  }
</span>

<span i18n>
  The author is {gender, select, male {male} female {female} other {other}}
</span>

Other Changes

JavaScript

Avoid introducing linebreaks in template interpolations (#15209 by @bakkot)

In a template string like

<!-- prettier-ignore -->
js
`this is a long message which contains an interpolation: ${format(data)} <- like this`;

avoid adding a linebreak when formatting the expression unless one is already present or it's unavoidable due to e.g. a nested function. Previously a linebreak could be introduced whenever some interpolation in the template was sufficiently "not simple":

<!-- prettier-ignore -->
js
`this is a long message which contains an interpolation: ${format(
  data,
)} <- like this`;

Now it will instead be left alone.

If a linebreak is already present within the ${...}, format as normal.

Fix non-idempotent formatting of method chain with empty line (#15522 by @seiyab)

<!-- prettier-ignore -->
js
// Input
Foo.a()

.b();

// Prettier 3.1 (first format)
Foo.a()
.b();

// Prettier 3.1 (second format)
Foo.a().b();


// Prettier 3.2
Foo.a()

  .b();

Fix formatting of ternary in function call (#15677 by @fisker)

<!-- prettier-ignore -->
jsx
// Input
stopDirectory = await (useCache
  ? memoizedFindProjectRoot
  : findProjectRootWithoutCache)(path.dirname(path.resolve(filePath)));

// Prettier 3.1
stopDirectory = await (useCache
  ? memoizedFindProjectRoot
  : findProjectRootWithoutCache)(path.dirname(path.resolve(filePath)));

// Prettier 3.2
stopDirectory = await (
  useCache ? memoizedFindProjectRoot : findProjectRootWithoutCache
)(path.dirname(path.resolve(filePath)));

Fix inconsistencies for optional-chaining (#15806 by @fisker)

Only happens when using typescript, meriyah or other ESTree parsers except babel.

<!-- prettier-ignore -->
js
// Input
function someFunctionName() {
  return isEqual(a.map(([t, _]) => t?.id), b.map(([t, _]) => t?.id));
  return isEqual(a?.map(([t, _]) => t?.id), b?.map(([t, _]) => t?.id));
}
theValue = Object.entries(someLongObjectName).filter(
  ([listingId]) => someListToCompareToHere.includes(listingId),
);
theValue = Object.entries(someLongObjectName).filter(
  ([listingId]) => someListToCompareToHere?.includes(listingId),
);

// Prettier 3.1
function someFunctionName() {
  return isEqual(
    a.map(([t, _]) => t?.id),
    b.map(([t, _]) => t?.id),
  );
  return isEqual(a?.map(([t, _]) => t?.id), b?.map(([t, _]) => t?.id));
}
theValue = Object.entries(someLongObjectName).filter(([listingId]) =>
  someListToCompareToHere.includes(listingId),
);
theValue = Object.entries(someLongObjectName).filter(
  ([listingId]) => someListToCompareToHere?.includes(listingId),
);

// Prettier 3.2
function someFunctionName() {
  return isEqual(
    a.map(([t, _]) => t?.id),
    b.map(([t, _]) => t?.id),
  );
  return isEqual(
    a?.map(([t, _]) => t?.id),
    b?.map(([t, _]) => t?.id),
  );
}
theValue = Object.entries(someLongObjectName).filter(([listingId]) =>
  someListToCompareToHere.includes(listingId),
);
theValue = Object.entries(someLongObjectName).filter(([listingId]) =>
  someListToCompareToHere?.includes(listingId),
);

Fix comments in if (#15826 by @fisker)

<!-- prettier-ignore -->
js
// Input
if (foo) for (i = 2; i > 0; i--) console.log(i); // comment 1
else bar();

for (;;){
  if (foo) continue; // comment 2
  else bar();
}

// Prettier 3.1
Error: Comment "comment 2" was not printed. Please report this error!

// Prettier 3.2
if (foo)
  for (i = 2; i > 0; i--) console.log(i); // comment 1
else bar();

for (;;) {
  if (foo)
    continue; // comment 2
  else bar();
}

TypeScript

Improve conditional type alias layout (#15811 by @seiyab)

<!-- prettier-ignore -->
ts
// Input
type FallbackFlags<F extends Flags | undefined> =
  Equals<NonNullableFlag<F>["flags"], {}> extends true
    ? Dict<any>
    : NonNullableFlag<F>["flags"];

// Prettier 3.1
type FallbackFlags<F extends Flags | undefined> = Equals<
  NonNullableFlag<F>["flags"],
  {}
> extends true
  ? Dict<any>
  : NonNullableFlag<F>["flags"];

// Prettier 3.2
type FallbackFlags<F extends Flags | undefined> =
  Equals<NonNullableFlag<F>["flags"], {}> extends true
    ? Dict<any>
    : NonNullableFlag<F>["flags"];

HTML

Fix formatting of prettier-ignored unclosed elements (#15748 by @fisker)

<!-- prettier-ignore -->
html
<!-- Input -->
<!-- prettier-ignore -->
<h1>
Hello <span>world!

<!-- Prettier 3.1 -->
<!-- prettier-ignore -->
<h1>

<!-- Prettier 3.2 -->
<!-- prettier-ignore -->
<h1>
Hello <span>world!

Angular

Fix prettier-ignored angular control flow block (#15827 by @fisker)

<!-- prettier-ignore -->
html
<!-- Input -->
<!-- prettier-ignore -->
@if (condition) {
  Foo
} @else {
  Other
}

<!-- Prettier 3.1 -->
<!-- prettier-ignore -->
@if (condition) {
  Foo
}
} @else {
  Other
}

<!-- Prettier 3.2 -->
<!-- prettier-ignore -->
@if (condition) {
  Foo
}
@else {
  Other
}

Avoid adding colon for track in 3rd expression of for blocks (#15887 by @sosukesuzuki)

<!-- prettier-ignore -->
html
<!-- Input -->
@for (item of items; let i = $index; track block) {}

<!-- Prettier 3.1 -->
@for (item of items; let i = $index; track: block) {}

<!-- Prettier 3.2 -->
@for (item of items; let i = $index; track block) {}

Ember / Handlebars

Preserve path literal segments (#15605 by @maxpowa)

Fixes scenarios where an input handlebars file containing literal segments would be reformatted to unwrap the literal segments, causing syntax errors in the resulting output.

<!-- prettier-ignore -->
hbs
<!-- Input -->
{{input.[funky<api!response]}}
{{input.[this one has spaces]}}
{{input.[anotherone].[0]}}

<!-- Prettier 3.1 -->
{{input.funky<api!response}}
{{input.this one has spaces}}
{{input.anotherone.0}}

<!-- Prettier 3.2 -->
{{input.[funky<api!response]}}
{{input.[this one has spaces]}}
{{input.anotherone.[0]}}

GraphQL

Improve GraphQL union types formatting (#15870 by @ArchitGajjar)

<!-- prettier-ignore -->
gql
# Input
union SearchResult = Conference| Festival | Concert | Venue | Conference| Festival | Concert | Venue

# Prettier 3.1
union SearchResult =
    Conference
  | Festival
  | Concert
  | Venue
  | Conference
  | Festival
  | Concert
  | Venue

# Prettier 3.2
union SearchResult =
  | Conference
  | Festival
  | Concert
  | Venue
  | Conference
  | Festival
  | Concert
  | Venue

API

Support absolute path as plugin in config file (#15666 by @fisker)

js
// prettier.config.cjs
module.exports = {
  plugins: [
    // posix style
    "/path/to/plugin.js",
    // Windows style
    "D:\\\\path\\to\\plugin.js",
    // Use `require.resolve`
    require.resolve("my-awesome-prettier-plugin"),
  ],
};

Fix getFileInfo and getSupportInfo type definitions (#15854 by @auvred)

ts
const plugin: Plugin = {};

prettier.getFileInfo("./file.ext", {
  plugins: [plugin],
});

prettier.getSupportInfo({ plugins: [plugin], showDeprecated: true });

Miscellaneous

Fix false claim in docs that cursorOffset is incompatible with rangeStart/rangeEnd (#15750 by @ExplodingCabbage)

The cursorOffset option has in fact been compatible with rangeStart/rangeEnd for over 5 years, thanks to work by @ds300. However, Prettier's documentation (including the CLI --help text) continued to claim otherwise, falsely. The documentation is now fixed.