首页/新闻资讯/正文详情

Prettier 设计哲学全解析:opinionated formatter 背后每一个格式决策的来龙去脉

发布时间:2026/9/20 23:13:03 来源:云帆数科 栏目:资讯中心
Prettier 设计哲学全解析:opinionated formatter 背后每一个格式决策的来龙去脉
Prettier 设计哲学全解析opinionated formatter 背后每一个格式决策的来龙去脉【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettierPrettier 自诩为 opinionated code formatter有主见的代码格式化器这句自述的背后是数十个具体的、深思熟虑的格式决策。本文以仓库中的 Rationale 文档 为骨架逐条拆解 Prettier 在正确性、引号、空行、多行对象、装饰器、模板字符串、分号、行宽、JSX 与注释等主题上的取舍逻辑并结合本仓库源码如引号选择算法、语句序列打印器、选项定义佐证其底层实现帮助你理解Prettier 为什么这样格式化从而在团队代码评审与配置调优中做出更合理的判断。总纲Prettier 关心什么Prettier 的格式化行为并非随心所欲而是围绕一组明确的关注点展开。理解这些关注点就理解了它几乎所有格式决策的出发点。正确性Correctness第一优先级Prettier 的第一要求是输出合法代码且格式化前后的代码行为完全一致。这一点在 Rationale 文档 中被明确为最高优先级——如果发现 Prettier 格式化后改变了代码行为那是一个需要修复的 bug。这条原则贯穿全文它解释了许多看似奇怪的决策为什么 Prettier 要在无分号风格的行首插入;、为什么它不修复已有的 ASI bug、为什么它拒绝做排序等转换操作。所有格式决策都必须服从行为不变这一铁律。字符串双引号还是单引号选择逻辑以最少转义为准Prettier 选择引号的标准是哪种引号能让字符串内部的转义最少。Its gettin better!而非It\s gettin\ better!——字符串内含单引号用双引号包裹就不需要转义平局或字符串内没有任何引号时默认使用双引号默认行为可通过singleQuote选项改变对应 CLI 参数--single-quote、API 参数singleQuote: bool。源码级验证getPreferredQuote 算法这一规则在仓库中有精确的实现src/utilities/get-preferred-quote.js#L31-L53中的getPreferredQuote(text, preferredQuoteOrPreferSingleQuote)函数for (let index 0; index length; index) { const codePoint text.charCodeAt(index); if (codePoint preferred.codePoint) { preferredQuoteCount; } else if (codePoint alternate.codePoint) { alternateQuoteCount; } } return (preferredQuoteCount alternateQuoteCount ? alternate : preferred) .character;其算法与文档描述完全一致统计首选引号与备选引号在字符串中出现的次数首选引号出现更多则改用备选引号从而保证转义最少。默认首选双引号preferredQuoteOrPreferSingleQuote为false时singleQuote: true时首选单引号。这个工具函数并非 JavaScript 语言专属它被多个语言模块复用例如 handlebars 打印机、HTML 打印机 和 Markdown 打印机说明最少转义选引号是 Prettier 的通用策略。JSX 引号独立的 jsxSingleQuoteJSX 拥有独立的引号选项jsxSingleQuoteCLI--jsx-single-quote默认false。原因是 JSX 源于 HTMLHTML 属性主流使用双引号浏览器开发者工具也始终以双引号展示 HTML。独立选项让你可以在 JS 中用单引号、在 JSXHTML中继续用双引号。在源码中JSX 属性引号处理位于src/language-js/print/jsx.js#L540-L571的printJsxAttribute它先剥离外层引号并反转义apos;/quot;然后调用getPreferredQuote(final, options.jsxSingleQuote)计算引号再按需把引号转义回 HTML 实体。转义风格保持不变Prettier维护你字符串的转义方式不会被格式化成\uD83D\uDE42反之亦然。字符串的字符编码表示由你决定Prettier 只处理包裹引号本身。空行保留为主折叠为辅自动生成空行极其困难因此 Prettier 的策略是保留源代码中的空行另加两条规则多个连续空行折叠为单个空行块的开头与结尾以及整个文件的开头结尾的空行被移除——但文件总是以单个换行符结束。源码佐证在src/language-js/print/statement-sequence.js#L16-L39的printStatementSequence打印语句序列时非最后一条语句后追加hardline若下一行原本是空行isNextLineEmpty(node, options)则追加第二个hardline从而保留原空行同时跳过EmptyStatement节点避免留下游离分号。多行对象以{后是否换行为准Prettier 的打印算法默认是能在一行放下就打印在一行。但对象在 JavaScript 中承载太多用途对象列表、嵌套配置、样式表、键方法集合等很难找到一条普适规则因此 Prettier 采用如下启发式如果源代码中{与第一个键之间存在换行则保持对象多行。由此推出两个推论长的单行对象会自动展开为多行短的多行对象永远不会被折叠回单行。手工控制对象折行的操作指引原文档给出了一套可复现的手工操作把多行对象收成单行——只需删除{后面的换行const user { name: John Doe, age: 30 };删除换行后const user { name: John Doe, age: 30 };运行 Prettier 后const user { name: John Doe, age: 30 };把单行对象展开成多行——在{后加一个换行const user { name: John Doe, age: 30 };运行 Prettier 后const user { name: John Doe, age: 30, };objectWrap 选项这种跟着原始换行走的条件行为可以通过objectWrap选项关闭。该选项在src/common/common-options.evaluate.js#L12-L28中定义属 Common 类别默认值为preservepreserve默认如果{与第一个属性之间有换行保持多行collapse尽可能折叠到单行。关于格式化可逆性的说明原文档特别强调对象字面量的这种半手工格式化其实是权宜之计而非特性——当年没有找到好的启发式、又急需修复才实现了它。它违反了 Prettier 避免不可逆格式化的一般策略团队仍在寻找更好的启发式以彻底移除或至少减少其适用场景。什么叫不可逆对象一旦变成多行Prettier 就不会把它折叠回去。设想在已格式化的代码中给对象加一个属性、运行 Prettier、然后反悔删除该属性、再运行 Prettier——最终格式可能与最初不一致。这种无谓的变动甚至可能混进提交记录而这正是 Prettier 想避免的情况。装饰器跟随你的书写位置与对象类似装饰器的用法也多种多样有时写在被装饰行上方有时写在同一行更合适。Prettier 没有找到统一规则所以保持你书写的位置只要放得下Component({ selector: hero-button, template: button{{ label }}/button, }) class HeroButtonComponent { // These decorators were written inline and fit on the line so they stay // inline. Output() change new EventEmitter(); Input() label: string; // These were written multiline, so they stay multiline. readonly nonenumerable NODE_TYPE: 2; }唯一的例外是类Prettier 认为类的装饰器永远不该内联因此总是移动到独立一行// Before running Prettier: observer class OrderLine { observable price: number 0; }// After running Prettier: observer class OrderLine { observable price: number 0; }历史兼容注意Prettier 1.14.x 及更早版本会尝试自动移动装饰器。如果你用旧版 Prettier 格式化过代码可能需要手动把部分装饰器重新并到同一行以避免不一致observer class OrderLine { observable price: number 0; observable amount: number 0; }模板字符串插值内的换行决定是否拆分模板字符串的插值中是否适合插入换行取决于模板的语义内容——例如在自然语言句子中间换行通常不可取。Prettier 没有足够信息做此判断因此采用与对象类似的启发式只有当插值${...}内部原本就有换行时Prettier 才会把插值表达式拆成多行。这意味着下面这个字面量即使超出打印宽度也不会被拆开this is a long message which contains an interpolation: ${format(data)} - like this;如果你想让它拆分必须在${...}内部某处放一个换行否则无论多长都会保持单行。团队也承认不希望依赖原始格式但这是目前能找到的最佳启发式。分号为 ASI 安全而在行首补;这是关于semi选项CLI--no-semi的话题。在无分号风格下考虑这段代码if (shouldAddLines) { [-1, 1].forEach(delta addLine(delta * 20)) }这段代码不加分号也能正常运行但 Prettier 实际会把它变成if (shouldAddLines) { ;[-1, 1].forEach(delta addLine(delta * 20)) }为什么要插入这个分号设想 Prettier没有插入那个分号而你在前面加了一行if (shouldAddLines) { console.log(Do we even get here??) [-1, 1].forEach(delta addLine(delta * 20)) }糟糕由于自动分号插入ASI上面的代码实际含义变成了if (shouldAddLines) { console.log(Do we even get here??)[-1, 1].forEach(delta addLine(delta * 20)) }在[前放一个分号可以杜绝此类问题——它让这一行与其他行相互独立移动或新增行时不必再思考 ASI 规则。这一做法在无分号风格的 standard 规范中也很常见。不会修复你已有的分号 bug注意如果程序里已存在分号相关 bugPrettier 不会自动修复。它只重新格式化不改变行为。例如开发者忘了在(前加分号console.log(Running a background task) (async () { await doBackgroundWork() })()Prettier 会按这段代码实际运行时的行为来排版console.log(Running a background task)(async () { await doBackgroundWork(); })();从选项定义看semi默认值为true其oppositeDescription正是不打印分号除非在可能需要它们的行首——与文档描述的仅在可能引发 ASI 失败的行首加分号完全对应。打印宽度printWidth指导线而非硬限制printWidth默认80它是给 Prettier 的粗略期望而不是允许的行长上限。Prettier 会输出比它短、也比它长的行但总体会向该宽度靠拢。确实存在无法折行的边缘情况超长字符串字面量、正则、注释和变量名在不做代码转换的前提下无法跨行拆分或者代码嵌套 50 层时行大部分是缩进。除此之外还有几个 Prettier故意超出打印宽度的场景。导入语句单元素 import 保持单行长import可以跨行拆分import { CollectionDashboard, DashboardPlaceholder, } from ../components/collections/collection-dashboard/main;但下面这个超宽的例子 Prettier 会坚持单行import { CollectionDashboard } from ../components/collections/collection-dashboard/main;原因保持单元素import单行是社区的常见诉求require调用同理。测试函数长描述保持单行另一个常见诉求是让冗长的测试描述保持单行——此时把参数换行并不能改善可读性describe(NodeRegistry, () { it(makes no request if there are no nodes to prefetch, even if the cache is stale, async () { // The above line exceeds the print width but stayed on one line anyway. }); });Prettier 为describe、it、test等常见测试框架函数设有特例。JSX与普通 JS 不同的打印策略JSX 的打印与其他 JS 略有差异function greet(user) { return user ? Welcome back, ${user.name}! : Greetings, traveler! Sign up today!; } function Greet({ user }) { return ( div {user ? ( pWelcome back, {user.name}!/p ) : ( pGreetings, traveler! Sign up today!/p )} /div ); }有两个原因跟随惯例很多人尤其在return语句中已经习惯给 JSX 加括号Prettier 顺应这一主流风格便于编辑这种排版更容易发现遗留的分号。与普通 JS 不同JSX 中遗留的分号会作为纯文本渲染到页面上div pGreetings, traveler! Sign up today!/p; {/* -- Oops! */} /div注释内容不动位置尽力保留内容注释可以包含散文、被注释掉的代码、ASCII 示意图等任何内容Prettier 无法知道如何格式化或换行因此保持原样。唯一例外是 JSDoc 风格注释每行以*开头的块注释Prettier 可以修正其缩进。位置注释放哪里是公认的难题。Prettier 尽力把注释保留在它原本的大致位置但注释几乎可以出现在任何地方。最佳实践把注释放在独立行而不是行尾。优先// eslint-disable-next-line而不是// eslint-disable-line。注意eslint-disable-next-line、$FlowFixMe这类魔法注释有时会因 Prettier 把表达式拆成多行而失效需要手动移动。例如// eslint-disable-next-line no-eval const result safeToEval ? eval(input) : fallback(input);你加了一个条件后// eslint-disable-next-line no-eval const result safeToEval settings.allowNativeEval ? eval(input) : fallback(input);Prettier 会变成// eslint-disable-next-line no-eval const result safeToEval settings.allowNativeEval ? eval(input) : fallback(input);此时eslint-disable-next-line不再作用于eval所在行你需要移动注释const result // eslint-disable-next-line no-eval safeToEval settings.allowNativeEval ? eval(input) : fallback(input);如果可能优先使用作用于行范围的注释如eslint-disable/eslint-enable或语句级注释如/* istanbul ignore next */它们更安全。也可以用eslint-plugin-eslint-comments这类规则在项目中禁止使用eslint-disable-line和eslint-disable-next-line。两个免责声明非标准语法Prettier 常能识别并格式化非标准语法如 ECMAScript 早期提案和未纳入任何规范的 Markdown 语法扩展。此类支持被视为尽力而为、实验性质任何版本都可能引入不兼容且不应视为破坏性变更。机器生成文件package.json、composer.lock等文件由包管理器定期自动生成和更新。如果 Prettier 用与其他文件相同的 JSON 规则格式化它们就会与这些工具频繁冲突。因此 Prettier 对这类文件改用基于JSON.stringify的格式化器。你可能注意到其中的差异如垂直空行被移除但这是有意为之。边界Prettier 不关心什么Prettier 只打印代码不做转换——这是刻意的范围限制让我们专注打印并把它做好。原文档列出的越界示例把单引号/双引号字符串转成模板字面量或反向转换用把长字符串字面量拆成适合打印宽度的片段添加/删除可选的{}与return把?:转成if-else语句排序/移动 imports、对象键、类成员、JSX 键、CSS 属性等。最后一点尤其值得展开排序不仅是转换而非打印还可能因副作用而危险如 imports并且会让最核心的正确性目标难以验证。小结一条贯穿始终的哲学回看全部决策可以归纳出 Prettier 的几条深层原则正确性压倒一切——任何可能改变行为的输出都是 bug打印而非转换——不做语义层面的代码改写这保证了可预测性与安全性能保留就保留——空行、装饰器位置、模板插值换行、对象多行形态都是尊重原始写法的启发式因为自动生成这些结构缺乏可靠规则启发式是权宜之计——对象多行、模板插值拆分等行为团队明确视为待改进的 workaround而非最终形态。理解这些设计动机后当你面对Prettier 为什么把这段代码排成这样的疑问时就能快速定位到对应的启发式与选项singleQuote、semi、printWidth、objectWrap、jsxSingleQuote等并判断是该调整配置、遵循建议的书写习惯还是接受现状。本仓库的 options 文档 提供了全部选项的默认值、CLI 与 API 对应关系是进一步查阅的入口。【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

上海网站制作公司介绍避坑:新手入门3招搞定需求
上海网站制作公司介绍避坑:新手入门3招搞定需求

上海网站制作公司介绍避坑:新手入门3招搞定需求 改个需求建站公司拖一周,这种憋屈事你遇到过吗?别急着换人,先看看合同里是不是把“开发周期”和“需求变更”混为一谈了。很多新手入门做网站,或者刚接手公司官网项目,最容易栽在需求不明确和流程不规范上。在上海这样的互联网高地,网站制作公司如过江之鲫,报价从几… · 2026/9/20 23:12:38

x64dbg 表达式系统完全指南:从命令栏计算器到源码级求值原理
x64dbg 表达式系统完全指南:从命令栏计算器到源码级求值原理

逆向工程调试器开发工具应用安全 【免费下载链接】x64dbg An open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis. 项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg 点击查看 免费下载 x64dbg 内置了一套功… · 2026/9/20 23:12:02

fail2ban.client.csocket 源码解析:Fail2Ban 客户端与守护进程的 Unix Socket 通信协议
fail2ban.client.csocket 源码解析:Fail2Ban 客户端与守护进程的 Unix Socket 通信协议

fail2ban.client.csocket 源码解析:Fail2Ban 客户端与守护进程的 Unix Socket 通信协议 【免费下载链接】fail2ban Daemon to ban hosts that cause multiple authentication errors 项目地址: https://gitcode.com/gh_mirrors/fa/fail2ban 导读 fail2ban.c… · 2026/9/20 23:12:02

Hugging Face 上的 Kimi K2.7 Code 权重,TaoToken 当默认供应商
Hugging Face 上的 Kimi K2.7 Code 权重,TaoToken 当默认供应商

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/20 23:53:15

Remote-SSH 连不上时,把 Codex 的 Base URL 改到 TaoToken 再查 config
Remote-SSH 连不上时,把 Codex 的 Base URL 改到 TaoToken 再查 config

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/20 23:53:15

rrweb 事件顺序 ID 插件实战:用 `@rrweb/rrweb-plugin-sequential-id-record` 为录制事件打上连续编号
rrweb 事件顺序 ID 插件实战:用 `@rrweb/rrweb-plugin-sequential-id-record` 为录制事件打上连续编号

rrweb 事件顺序 ID 插件实战:用 rrweb/rrweb-plugin-sequential-id-record 为录制事件打上连续编号 【免费下载链接】rrweb record and replay the web 项目地址: https://gitcode.com/gh_mirrors/rr/rrweb 本文介绍 rrweb 官方插件 rrweb/rrweb-plugin-sequ… · 2026/9/20 23:53:15

Aider 实战:TaoToken 跑通 Flask 博客仓库的分页与搜索修复
Aider 实战:TaoToken 跑通 Flask 博客仓库的分页与搜索修复

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/20 23:53:15

Teleport 会话录制存储接入 Google Cloud Storage(GCS)实战指南
Teleport 会话录制存储接入 Google Cloud Storage(GCS)实战指南

网络安全认证鉴权运维后端 【免费下载链接】teleport The easiest, and most secure way to access and protect all of your infrastructure. 项目地址: https://gitcode.com/gh_mirrors/tel/teleport 点击查看 免费下载 导读 本文聚焦 Teleport 开源仓库中的 li… · 2026/9/20 23:53:15

小爱音箱免费听全网音乐:XiaoMusic 三步完整部署与进阶玩法指南
小爱音箱免费听全网音乐:XiaoMusic 三步完整部署与进阶玩法指南

小爱音箱免费听全网音乐:XiaoMusic 三步完整部署与进阶玩法指南 【免费下载链接】xiaomusic 使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。 项目地址: https://gitcode.com/GitHub_Trending/xia/xiaomusic XiaoMusic 是一个开源项目,能… · 2026/9/20 23:52:15

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化
Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡… · 2026/9/20 0:00:41

Word表格编号全攻略:从列表编号到题注交叉引用
Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技… · 2026/9/20 0:00:41

从第一个站到第二个站:独立开发者的静态网站选型与落地实践
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&… · 2026/9/20 0:00:41

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化
Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡… · 2026/9/20 0:00:41

Word表格编号全攻略:从列表编号到题注交叉引用
Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技… · 2026/9/20 0:00:41

从第一个站到第二个站:独立开发者的静态网站选型与落地实践
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&… · 2026/9/20 0:00:41

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码