跳至主内容区

Prettier 3.1:新增实验性三元表达式格式化方案与 Angular 控制流语法!

· 1 分钟阅读
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

本次更新重新为嵌套三元表达式添加了缩进,并新增了 --experimental-ternaries 标志来尝试一种创新的"好奇三元"格式,该格式能更好地适应深层嵌套条件表达式。在年底默认启用该格式前,我们热切期待您通过反馈表单提供宝贵意见!

同时新增了对 Angular v17 控制流语法的支持。语法详情请阅读Angular 官方发布说明

如果您认可 Prettier 的价值并希望支持我们的工作,请考虑通过 我们的 OpenCollective 直接赞助,或赞助我们依赖的项目如 typescript-eslintremarkBabel。感谢您持续的支持!

重要更新

JavaScript

为嵌套三元表达式恢复缩进 (#9559 by @rattrayalex)

// Input
const message =
i % 3 === 0 && i % 5 === 0
? "fizzbuzz"
: i % 3 === 0
? "fizz"
: i % 5 === 0
? "buzz"
: String(i);


// Prettier 3.0
const message =
i % 3 === 0 && i % 5 === 0
? "fizzbuzz"
: i % 3 === 0
? "fizz"
: i % 5 === 0
? "buzz"
: String(i);

// Prettier 3.1
const message =
i % 3 === 0 && i % 5 === 0
? "fizzbuzz"
: i % 3 === 0
? "fizz"
: i % 5 === 0
? "buzz"
: String(i);

实验性三元表达式格式:好奇三元之谜 (#13183 by @rattrayalex)

该功能通过 --experimental-ternaries 标志启用。

我们将多行三元表达式中的 ? 移至首行末尾而非次行开头,并实施多项关联调整。

虽然初看可能略显奇特,但测试表明开发者使用数小时后,普遍认为该格式显著提升了嵌套三元表达式的可读性与实用性。

本次更新解决了我们高票反馈的议题,同时规避了原解决方案可能引发的其他问题。

详情请参阅博客文章《好奇三元表达式之谜》

示例
// "Questioning" ternaries for simple ternaries:
const content =
children && !isEmptyChildren(children) ?
render(children)
: renderDefaultChildren();

// "Case-style" ternaries for chained ternaries:
const message =
i % 3 === 0 && i % 5 === 0 ? "fizzbuzz"
: i % 3 === 0 ? "fizz"
: i % 5 === 0 ? "buzz"
: String(i);

// Smoothly transitions between "case-style" and "questioning" when things get complicated:
const reactRouterResult =
children && !isEmptyChildren(children) ? children
: props.match ?
component ? React.createElement(component, props)
: render ? render(props)
: null
: null

支持 Babel 7.23.0 新增语法 (#15485, #15486, #15487, #15488 by @sosukesuzuki)

现已支持 Babel 7.23.0 引入的新 JavaScript 语法!

源码阶段导入 (Source Phase Imports)

详见 https://github.com/tc39/proposal-source-phase-imports

import source x from "mod";
延迟导入求值 (Deferred Import Evaluation)

详见 https://github.com/tc39/proposal-defer-import-eval

import defer * as ns from "mod";
可选链式赋值 (Optional Chaining Assignments)

详见 https://github.com/tc39/proposal-optional-chaining-assignment

maybeObj?.prop1 = value;

Angular

支持 Angular 控制流语法 (#15606 by @DingWeizhe, @fisker)

新增对 Angular 17 内置控制流的支持。如果您发现任何问题,请随时向我们反馈。

有关控制流语法的更多详情,请参阅官方博客文章。

https://blog.angular.io/introducing-angular-v17-4d7033312e4b

其他变更

JavaScript

修复括号与函数体之间的注释格式 (#15326 by @fisker)

// Input
function function_declaration()
// this is a function
{
return 42
}

(function function_expression()
// this is a function
{
return 42
})();

// Prettier 3.0
function function_declaration() {
// this is a function
return 42;
}

(function function_expression() // this is a function
{
return 42;
})();

// Prettier 3.1
function function_declaration() {
// this is a function
return 42;
}

(function function_expression() {
// this is a function
return 42;
})();
// Input
function function_declaration()
// this is a function
{
return 42
}

export default function()
// this is a function
{
return 42
}

// Prettier 3.0
TypeError: Cannot read properties of null (reading 'range')

// Prettier 3.1
function function_declaration() {
// this is a function
return 42;
}

export default function () {
// this is a function
return 42;
}

消除 instanceof 和 in 运算符左侧一元表达式的歧义 (#15468 by @lucacasonato)

现在会在 instanceofin 表达式左侧的一元表达式周围添加括号,以区分左侧一元表达式与应用于整个二元表达式的一元运算符。

这有助于避免常见错误:用户本意是写 !("x" in y) 却误写为 !"x" in y(实际会被解析为无意义的 (!"x") in y)。

// Input
!"x" in y;
!("x" in y);

// Prettier 3.0
!"x" in y;
!("x" in y);

// Prettier 3.1
(!"x") in y;
!("x" in y);

修复 styled components 插值中选择器的大小写问题 (#15472 by @lucasols)

// Input
const StyledComponent = styled.div`
margin-right: -4px;

${Container}.isExpanded & {
transform: rotate(-180deg);
}
`;

const StyledComponent2 = styled.div`
margin-right: -4px;

${abc}.camelCase + ${def}.camelCase & {
transform: rotate(-180deg);
}
`;

// Prettier 3.0
const StyledComponent = styled.div`
margin-right: -4px;

${Container}.isexpanded & {
transform: rotate(-180deg);
}
`;

const StyledComponent2 = styled.div`
margin-right: -4px;

${abc}.camelcase + ${def}.camelCase & {
transform: rotate(-180deg);
}
`;

// Prettier 3.1 -- same as input

统一包含转义字符的字符串格式化 (#15525 by @sosukesuzuki)

// Input
export const MSG_GENERIC_OPERATION_FAILURE_BODY_1 =
goog.getMsg("That's all we know");

export const MSG_GENERIC_OPERATION_FAILURE_BODY_2 =
goog.getMsg("That\'s all we know");

// Prettier 3.0
export const MSG_GENERIC_OPERATION_FAILURE_BODY_1 =
goog.getMsg("That's all we know");

export const MSG_GENERIC_OPERATION_FAILURE_BODY_2 = goog.getMsg(
"That's all we know",
);

// Prettier 3.1
export const MSG_GENERIC_OPERATION_FAILURE_BODY_1 =
goog.getMsg("That's all we know");

export const MSG_GENERIC_OPERATION_FAILURE_BODY_2 =
goog.getMsg("That's all we know");

改进左侧可能换行的赋值语句格式 (#15547 by @sosukesuzuki)

// Input
params["redirectTo"] =
`${window.location.pathname}${window.location.search}${window.location.hash}`;

// Prettier 3.0
params[
"redirectTo"
] = `${window.location.pathname}${window.location.search}${window.location.hash}`;

// Prettier 3.1
params["redirectTo"] =
`${window.location.pathname}${window.location.search}${window.location.hash}`;

TypeScript

修复最后一个参数属性后的注释不稳定问题 (#15324 by @fisker)

// Input
class Class {
constructor(
private readonly paramProp: Type,
// comment
) {
}
}

// Prettier 3.0
class Class {
constructor(private readonly paramProp: Type) // comment
{}
}

// Prettier 3.0 (Second format)
class Class {
constructor(
private readonly paramProp: Type, // comment
) {}
}

// Prettier 3.1
class Class {
constructor(
private readonly paramProp: Type,
// comment
) {}
}

支持在带有 as const 注解的模板字面量中进行嵌入式格式化 (#15408 by @sosukesuzuki)

// Input
const GQL_QUERY_WITH_CONST = /* GraphQL */ `
query S { shop }
` as const;

// Prettier 3.0
const GQL_QUERY_WITH_CONST = /* GraphQL */ `
query S { shop }
` as const;

// Prettier 3.1
const GQL_QUERY_WITH_CONST = /* GraphQL */ `
query S {
shop
}
` as const;

修复联合类型最后一个操作数的注释打印问题 (#15409 by @sosukesuzuki)

// Input
type Foo1 = (
| "thing1" // Comment1
| "thing2" // Comment2
)[]; // Final comment1
type Foo2 = (
| "thing1" // Comment1
| "thing2" // Comment2
) & Bar; // Final comment2

// Prettier 3.0
type Foo1 = (
| "thing1" // Comment1
| "thing2"
)[]; // Comment2 // Final comment1
type Foo2 = (
| "thing1" // Comment1
| "thing2"
) & // Comment2
Bar; // Final comment2

// Prettier 3.1
type Foo1 = (
| "thing1" // Comment1
| "thing2" // Comment2
)[]; // Final comment1
type Foo2 = (
| "thing1" // Comment1
| "thing2" // Comment2
) &
Bar; // Final comment2

在 satisfies / as 表达式语句中为特定关键字标识符保留必需括号 (#15514 by @seiyab)

// Input
(type) satisfies never;


// Prettier 3.0
type satisfies never;


// Prettier 3.1
(type) satisfies never;

Flow

支持 Flow 中的 assatisfies 表达式 (#15130 by @gkz)

// Input
const x = y as T;

// Prettier 3.0
// <error: unsupported>

// Prettier 3.1
const x = y as T;

支持在 Flow 的 JSX 开始元素上使用类型参数 (#15429 by @SamChou19815)

// Input
<Foo<bar> />;

// Prettier 3.0
<Foo />;

// Prettier 3.1
<Foo<bar> />;

支持在 typeof 后使用类型参数 (#15466 by @sosukesuzuki)

支持自 Flow v0.127.0 起新增的 typeof 语法后类型参数:

type Foo = typeof MyGenericClass<string, number>;

SCSS

修复带前导破折号的 SCSS 函数调用不分割问题 (#15370 by @auvred)

/* Input */
div {
width: -double(-double(3));
}

/* Prettier 3.0 */
div {
width: -double(- double(3));
}

/* Prettier 3.1 */
div {
width: -double(-double(3));
}

HTML

修复 menumarquee 元素的格式化问题 (#15334 by @fisker)

<!-- Input -->
<menu><li><button onclick="copy()">Copy</button></li>
<li><button onclick="cut()">Cut</button></li>
<li><button onclick="paste()">Paste</button></li></menu>
<marquee
direction="down"
width="250"
height="200"
behavior="alternate"
style="border:solid"><marquee behavior="alternate"> This text will bounce </marquee></marquee>

<!-- Prettier 3.0 -->
<menu
><li><button onclick="copy()">Copy</button></li>
<li><button onclick="cut()">Cut</button></li>
<li><button onclick="paste()">Paste</button></li></menu
>
<marquee
direction="down"
width="250"
height="200"
behavior="alternate"
style="border: solid"
><marquee behavior="alternate"> This text will bounce </marquee></marquee
>

<!-- Prettier 3.1 -->
<menu>
<li><button onclick="copy()">Copy</button></li>
<li><button onclick="cut()">Cut</button></li>
<li><button onclick="paste()">Paste</button></li>
</menu>
<marquee
direction="down"
width="250"
height="200"
behavior="alternate"
style="border: solid"
>
<marquee behavior="alternate">This text will bounce</marquee>
</marquee>

Markdown

在 Markdown URL 中对 <> 进行编码 (#15400 by @vivekjoshi556)

<!-- Input -->
[link](https://www.google.fr/()foo->bar)

<!-- Prettier 3.0 -->
[link](<https://www.google.fr/()foo->bar>)

<!-- Prettier 3.1 -->
[link](<https://www.google.fr/()foo-%3Ebar>)
<!-- Input -->
![link](<https://www.google.fr/()foo->bar>)

<!-- Prettier 3.0 -->
![link](<https://www.google.fr/()foo->bar>)

<!-- Prettier 3.1 -->
![link](<https://www.google.fr/()foo-%3Ebar>)

禁止在日文假名与 COMBINING KATAKANA-HIRAGANA (SEMI-)VOICED SOUND MARK 之间换行 (#15411 by @tats-u)

此 PR 修复了 #15410

日语中的半浊音假名字符可以拆分为两个代码点。例如,以下平假名字符 /ka/ 可以表示为:

が (U+304C) → か (U+304B) + ゙ (U+3099) → が (U+304C U+3099)

大多数用户不会使用或遇到这样的表达方式,除了在 macOS 的文件路径中。然而,有些字符只能以这种方式表示。一些日语文本需要区分 /ŋa̠/(尽管如今有不少日本人不再使用)和常见的 /ga/,会使用 "か゚" (U+304B U+309A) 这种表达方式。

nasalか゚き゚く゚け゚こ゚か゚き゚く゚け゚こ゚か゚き゚く゚け゚こ゚か゚き゚く゚け゚こ゚

以上 Markdown 在 Prettier 3.0 中的格式化效果如下:

nasalか゚き゚く゚け゚こ゚か゚き゚く゚け゚こ゚か゚き゚く゚け゚こ゚か゚き゚く゚け
゚こ゚

半浊音符号会换到下一行,这是不正确的。通过此 PR,源代码 Markdown 现在被格式化为:

nasalか゚き゚く゚け゚こ゚か゚き゚く゚け゚こ゚か゚き゚く゚け゚こ゚か゚き゚く゚
け゚こ゚

半浊音符号现在会紧贴在平假名"け"后面。

API

prettier.{resolveConfig,resolveConfigFile,getFileInfo}() 中接受 URL (#15332, #15354, #15360, #15364 by @fisker)

prettier.resolveConfig()prettier.resolveConfigFile()prettier.getFileInfo() 现在接受带有 file: 协议的 URL 或以 file:// 开头的 URL 字符串。

// `URL`
await prettier.resolveConfig(new URL("./path/to/file", import.meta.url));
await prettier.resolveConfigFile(new URL("./path/to/file", import.meta.url));
await prettier.getFileInfo(new URL("./path/to/file", import.meta.url));
await prettier.getFileInfo("/path/to/file", {
ignorePath: new URL("./.eslintignore", import.meta.url),
});

// URL string
await prettier.resolveConfig("file:///path/to/file");
await prettier.resolveConfigFile("file:///path/to/file");
await prettier.getFileInfo("file:///path/to/file");
await prettier.getFileInfo("/path/to/file", {
ignorePath: "file:///path/to/.eslintignore",
});

CLI

仅处理插件支持的文件 (#15433 by @sosukesuzuki)

在 Prettier 3.0 中,通过 CLI 指定目录时,仅会处理带有默认支持扩展名的文件。

在以下场景中,不仅 foo.js 应该被格式化,foo.astro 同样需要被格式化:

# Prettier 3.0 version

$ ls .
foo.js foo.astro

$ cat .prettierrc
{ "plugins": ["prettier-plugin-astro"] }

$ prettier --write .
foo.js 20ms

通过此更新,现在 foo.jsfoo.astro 都将被格式化:

# Prettier 3.1 branch

$ prettier --write .
foo.js 20ms
foo.astro 32ms

您现在可以用 prettier . 替代 prettier "**/*" --ignore-unknown,因为两者现已等效。

在 CLI --write 中显示 (unchanged) 关键字以提高可访问性 (#15467 by @ADTC)

此前,已更改文件与未更改文件的唯一区别在于文件名的灰色显示。如下方示例所示,当颜色信息缺失时,我们无法区分 a.jsb.js。通过添加 (unchanged) 关键字,现在无需依赖颜色即可明确区分。

prettier --write .

# Prettier 3.0
a.js 0ms
b.js 0ms
c.js 0ms (cached)

# Prettier 3.1
a.js 0ms
b.js 0ms (unchanged)
c.js 0ms (unchanged) (cached)

修复格式化包含特殊字符的文件名时报错的问题 (#15597 by @fisker)

prettier "[with-square-brackets].js" --list

# Prettier 3.0
[error] Explicitly specified file was ignored due to negative glob patterns: "[with-square-brackets].js".

# Prettier 3.1
[with-square-brackets].js