从一对 TypeScript 括号看 Prettier、Biome、dprint 和 Oxfmt
在写 Nuxt 组件的根 Block 命名规则时,我从 .nuxt/types/components.d.ts 摘了一条声明。文章里的代码围栏会被单独交给 Prettier,检查器认为这里少了一对括号。下面保留原始写法和格式化后的写法,方便对照。Nuxt 组件名的例子
- CommonEmptyState: typeof import('../../app/components/common/empty-state.vue')['default'];
+ CommonEmptyState: (typeof import('../../app/components/common/empty-state.vue'))['default'];
这两种写法在 TypeScript 里都表示取模块类型的 default 成员。但如果把这条声明分别交给 Prettier、Biome、dprint 和 Oxfmt,有的会加括号,有的会删,有的两种写法都保留。既然格式化器要决定这些细节,换一个工具到底能多控制多少?我又拿博客里的 TS 围栏和 Vue SFC 跑了一轮。
类型查询怎样进入索引访问
TypeScript 类型位置的 typeof registry['default'] 会先取 registry 的类型,再用 'default' 索引它。同一串字符出现在 JavaScript 表达式位置时,typeof 的操作数是 registry['default']。两种上下文的结合顺序不同,加括号可以把类型位置的顺序写明白。对于这里的 typeof import(...),有无括号都能表达同一个索引访问。Oxfmt 对括号规则的说明
我把有括号和无括号的输入都跑了一遍。决定输出的地方有两层,先看解析器交给打印器哪类节点,再看这个节点有没有补括号或省略括号的规则。
Prettier 和 Oxfmt 会补上括号
Prettier 3.9.10 的 typescript 和 babel-ts 两条解析路径,都会把本例交给打印器当作 TSIndexedAccessType。它的 objectType 是 TSTypeQuery,类型查询的操作数则是 TSImportType。源码的括号判断直接匹配前两个条件。节选只保留与索引访问有关的判断。
key === "objectType" && parent.type === "TSIndexedAccessType"
Oxfmt 0.77.0 的解析器也会在 TSImportType 外创建 TSTypeQuery。它的打印器检查当前节点是否是索引对象,并在满足时加括号。['default'] 这一侧即使出现类型查询,也不走这条判断。Prettier 的括号判断 · Oxc 的类型查询解析 · Oxfmt 的括号判断
Biome 把这条声明当作导入类型打印
Biome 2.6.6 的解析器遇到 typeof import,会生成自带 typeof token 的 TsImportType,这个 token 与 import、路径等字段在同一个节点里。格式化前,重写器会拆掉本例输入里的括号。随后 TsImportType 的打印逻辑把这些字段依次输出,不会补回括号。普通的 typeof registry['default'] 则进入 TsTypeofType 的规则,我用同版本实测,它会被打成 (typeof registry)['default']。两个结果的分界就在节点类型。Biome 解析分支 · 括号重写 · 导入类型打印器 · 普通类型查询的括号规则
dprint 保留索引对象上已有的括号
dprint TypeScript 插件 0.99.1 依次生成索引对象和索引部分。对象上有括号时,它会遇到单独的 TsParenthesizedType 节点,再按父节点判断是否省略。索引访问不在这条省略规则的范围内,所以会保留。无括号的输入直接打印类型查询,生成器也没有额外包一层。索引访问的生成逻辑 · 省略类型括号的条件
这四个版本都没有给这种括号提供「始终加上」和「始终省略」的配置。几个工具都有名字带括号的选项,其中 arrowParens、arrowParentheses 和 arrowFunction.useParentheses 管的是单参数箭头函数。需要忠实引用生成声明时,Prettier 的 // prettier-ignore 可以让当前属性按输入输出,围栏内其他代码照常检查。Prettier 局部忽略 · Biome 的箭头函数选项 · dprint 的箭头函数选项 · Oxfmt 配置
用博客里的围栏和主题组件跑一遍
2026 年 11 月 9 日,我在 Windows 上从 94 篇文章提取了 455 个围栏,其中 445 个是 TS,10 个是 TSX。提取和跳过规则沿用博客检查器的口径,有 30 个标记跳过的围栏留在样本之外。Vue 则取 Sheng 主题格式化命令覆盖的全部 19 个 SFC,模板、脚本和样式一起交给工具处理。
每种工具都读取相同的输入。先用默认排版跑一轮,再尽量对齐现用配置,比较结果与仓库当前 Prettier 输出。多数文章和主题用 120 列,JavaScript 最佳实践系列用 180 列,并省略单参数箭头函数的括号。Biome 的 Vue 测试启用了完整文件支持和 HTML 格式化,dprint 用 TypeScript 0.99.1、markup_fmt 0.27.6 和 Malva 0.16.0 共同处理 SFC。版本、配置和实际输出节选见实测记录弹窗。Biome 的 Vue 支持说明 · dprint 的 markup_fmt 插件 · Malva 插件
表里的两个数字分别表示 TypeScript 围栏和 Vue 文件的差异数,TSX 计入 TypeScript 一列。比较前统一换行符,只要一个样本的输出有差异就计一次。Biome 拒绝格式化的围栏单独计入失败数。
| 工具 | 默认排版差异(TS / Vue) | 对齐常用选项后(TS / Vue) | 定向调整 |
|---|---|---|---|
| Prettier 3.9.10 | 298 / 14 | 0 / 0 | 仓库现用输出作为基准 |
| Oxfmt 0.77.0 | 282 / 14 | 1 / 0 | CLI 与 Node API 在 474 个样本上输出一致 |
| Biome 2.6.6 | 381 / 19 | 1 / 6 | 两轮各拒绝 17 个 TS 围栏,Vue 可进一步调到 0 |
| dprint 0.64.3 | 281 / 14 | 51 / 17 | 再细调后是 23 / 6 |
Prettier 自己换回默认配置,也会改动 298 个围栏。默认轮更像是在比较工具预设了哪些风格。对齐引号、行宽、缩进等设置之后,才看得清哪些差异需要单独处理。
dprint 的细分选项和 Biome 的 Vue 空白策略
这两组配置都能缩小差异,但它们的影响范围不同。改 dprint 的类型分隔符会同时改动单字段和多字段类型。改 Biome 的模板空白策略则可能改变最终页面上的文字。
dprint 可以分别管导入、运算符和类型成员
dprint 默认会对导入语句和花括号内的命名导入排序。主题的 document.vue 在默认轮里换了 ../components 与 ../server/types 的先后顺序。将 module.sortImportDeclarations 和 importDeclaration.sortNamedImports 设为 maintain 就可以保留顺序。跨行表达式里的 &&、+ 可以通过 binaryExpression.operatorPosition: 'sameLine' 放回行尾。dprint 的导入与导出选项 · 运算符位置选项
有些选项会连带改变别处。dprint 默认会把 defineProps<{ foo: string }>() 打成 defineProps<{ foo: string; }>()。把 typeLiteral.separatorKind.singleLine 设成 comma,可以去掉这个单字段类型的尾分号,但多个字段之间也会改用逗号。对齐这项、运算符位置、JSX 属性引号、for...of 的换行偏好和 Malva 的属性选择器引号之后,仍有 23 个 TS 围栏和 6 个 Vue 文件的输出不同。Vue 文件的差异主要集中在类型成员的分隔符。博客里的 defineProps 示例 · dprint 类型分隔符 · Malva 引号选项
Vue 的斜杠和模板文字空格是两件事
Biome 会把原有的 <hr /> 打成 <hr>,通过 html.formatter.selfCloseVoidElements: 'always' 就能调回。空白处理要单独验。把现用 Prettier 的 ignore 空白策略直接映射到 Biome 后,归档标题原有的 共计 {{ page.totalPosts }} 篇文章 变成了 共计{{ page.totalPosts }} 篇文章。文章页 发布于 后的空格也被删掉了。我核对了 Vue 的 DOM 和 SSR 模板编译结果,删掉的空格确实进了产物。把 Biome 的 whitespaceSensitivity 改成 css,同时保留自闭合斜杠,19 个 SFC 的文本输出都回到了基准。Biome 的 HTML 选项
哪些差异需要调整检查方式
Biome 拒绝的 17 个围栏都是从函数或循环里截取的代码,单独提取以后,return 或 continue 缺少外层语句。开启 formatter.formatWithErrors 后可以得到输出,但命令仍报告解析错误并以 1 退出。博客把围栏当作摘录格式化,所以如果直接替换 CLI,这些例子会让检查失败。另有一个合法示例 [/* 省略菜单项 */] 被展开为多行,expand 的三个值都给出相同结果。Biome 的错误容忍选项 · 循环体摘录
Oxfmt 对齐后只剩一个不同的围栏,new Set(/* ... */) 里的注释被移到了参数列表外,变成 new Set /* ... */()。该版本的公开配置里没有控制这一位置的选项。它也保留了 Nuxt 声明现有的 // prettier-ignore。Vue 输出接近还有实现上的原因,Oxfmt 用内置的 Prettier 处理 Vue 文件,嵌入的 JS 和 TS 则交给自己的格式化器。Oxfmt 配置 · Oxfmt 的语言支持
这个博客继续用 Prettier
Prettier 把自己称为 opinionated 的格式化工具。它把类型查询的括号作为打印器的决定,项目只需要在引用 Nuxt 生成声明的地方加一条局部忽略。这次横评没有找到专管这对括号的开关,博客继续用现有的 Prettier 检查。Prettier 的配置理念
如果将来需要尽量少改已有输出,Oxfmt 是这批样本里最接近的候选。如果希望把导入顺序、跨行运算符或类型分隔符交给项目决定,dprint 的选项更细。选型时,我会先拿已有文档摘录和实际组件跑一轮,重点看默认差异能消掉多少、剩下的差异会不会影响编译或阻断检查。
格式化横评实测记录
打开原文件 formatting-comparison.json
{
"measuredOn": "2026-11-09",
"runtime": {
"platform": "Windows x64",
"node": "26.12.0"
},
"versions": {
"prettier": "3.9.10",
"@biomejs/biome": "2.6.6",
"dprint": "0.64.3",
"oxfmt": "0.77.0",
"@dprint/typescript": "0.99.1",
"dprint-plugin-markup": "0.27.6",
"dprint-plugin-malva": "0.16.0"
},
"scope": {
"markdownScanned": 301,
"postsWithIncludedFences": 94,
"ts": 445,
"tsx": 10,
"ignoredFences": 30,
"sharedConfigFences": 8,
"vue": 19
},
"method": [
"从博客正文提取 TypeScript 和 TSX 围栏,跳过已登记的例外。以独立 .ts 或 .tsx 文件运行格式化,不补外围语句。",
"把 Sheng 主题格式化入口覆盖的全部 Vue SFC 交给各工具处理。Biome 默认轮也开启完整 HTML 支持与格式化,dprint 各轮都加载三个插件。",
"所有工具读取同一份源文本,以仓库当前的 Prettier 输出为基准,换行符统一为 LF。没有输出的样本只计入失败数。",
"使用 vue/compiler-sfc 对改动前后分别执行 DOM 和 SSR 模板编译,再比较规范化后的生成代码。未测性能。",
"dprint 的插件路径保留 npm 包名和包内文件名,实际运行时从隔离安装目录加载 WASM。"
],
"baseline": {
"normal": {
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "ignore",
"embeddedLanguageFormatting": "auto"
},
"shared": {
"printWidth": 180,
"tabWidth": 2,
"singleQuote": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "ignore",
"embeddedLanguageFormatting": "auto",
"semi": true,
"arrowParens": "avoid"
}
},
"comparableConfigs": {
"normal": {
"biome": {
"html": {
"experimentalFullSupportEnabled": true,
"formatter": {
"enabled": true,
"whitespaceSensitivity": "ignore",
"indentScriptAndStyle": false
}
},
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 120,
"lineEnding": "lf",
"formatWithErrors": false
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "always",
"trailingCommas": "all",
"arrowParentheses": "always",
"quoteProperties": "asNeeded"
}
}
},
"dprint": {
"plugins": [
"@dprint/typescript/plugin.wasm",
"dprint-plugin-markup/plugin.wasm",
"dprint-plugin-malva/plugin.wasm"
],
"incremental": false,
"lineWidth": 120,
"indentWidth": 2,
"useTabs": false,
"newLineKind": "lf",
"typescript": {
"quoteStyle": "preferSingle",
"semicolons": "always",
"trailingCommas": "onlyMultiLine",
"arrowFunction.useParentheses": "force",
"quoteProps": "asNeeded",
"module.sortImportDeclarations": "maintain",
"module.sortExportDeclarations": "maintain",
"importDeclaration.sortNamedImports": "maintain",
"exportDeclaration.sortNamedExports": "maintain"
},
"markup": {
"printWidth": 120,
"whitespaceSensitivity": "ignore",
"vue.scriptIndent": false,
"vue.styleIndent": false
}
},
"oxfmt": {
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "ignore",
"embeddedLanguageFormatting": "auto",
"arrowParens": "always"
}
},
"shared": {
"biome": {
"html": {
"experimentalFullSupportEnabled": true,
"formatter": {
"enabled": true,
"whitespaceSensitivity": "ignore",
"indentScriptAndStyle": false
}
},
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 180,
"lineEnding": "lf",
"formatWithErrors": false
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "always",
"trailingCommas": "all",
"arrowParentheses": "asNeeded",
"quoteProperties": "asNeeded"
}
}
},
"dprint": {
"plugins": [
"@dprint/typescript/plugin.wasm",
"dprint-plugin-markup/plugin.wasm",
"dprint-plugin-malva/plugin.wasm"
],
"incremental": false,
"lineWidth": 180,
"indentWidth": 2,
"useTabs": false,
"newLineKind": "lf",
"typescript": {
"quoteStyle": "preferSingle",
"semicolons": "always",
"trailingCommas": "onlyMultiLine",
"arrowFunction.useParentheses": "preferNone",
"quoteProps": "asNeeded",
"module.sortImportDeclarations": "maintain",
"module.sortExportDeclarations": "maintain",
"importDeclaration.sortNamedImports": "maintain",
"exportDeclaration.sortNamedExports": "maintain"
},
"markup": {
"printWidth": 180,
"whitespaceSensitivity": "ignore",
"vue.scriptIndent": false,
"vue.styleIndent": false
}
},
"oxfmt": {
"printWidth": 180,
"tabWidth": 2,
"singleQuote": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "ignore",
"embeddedLanguageFormatting": "auto",
"arrowParens": "avoid"
}
}
},
"measurements": {
"prettierDefault": {
"tsDifferences": 298,
"vueDifferences": 14
},
"prettierRepo": {
"tsDifferences": 0,
"vueDifferences": 0
},
"biome/default": {
"tsDifferences": 381,
"vueDifferences": 19,
"tsRejected": 17,
"tsWithParseDiagnostics": 17,
"exitCodes": {
"normal": {
"first": 1,
"second": 1
},
"shared": {
"first": 0,
"second": 0
}
},
"vueTemplateDifferences": [
{
"file": "themes/sheng/layout/document.vue",
"domDiff": true,
"ssrDiff": true
},
{
"file": "themes/sheng/components/search-panel/index.vue",
"domDiff": true,
"ssrDiff": true
}
]
},
"biome/aligned": {
"tsDifferences": 1,
"vueDifferences": 6,
"tsRejected": 17,
"tsWithParseDiagnostics": 17,
"exitCodes": {
"normal": {
"first": 1,
"second": 1
},
"shared": {
"first": 0,
"second": 0
}
},
"vueTemplateDifferences": [
{
"file": "themes/sheng/layout/archive.vue",
"domDiff": true,
"ssrDiff": true
},
{
"file": "themes/sheng/layout/post.vue",
"domDiff": true,
"ssrDiff": true
}
]
},
"biome/tolerant": {
"tsDifferences": 1,
"vueDifferences": 6,
"tsRejected": 0,
"tsWithParseDiagnostics": 17,
"exitCodes": {
"normal": {
"first": 1,
"second": 1
},
"shared": {
"first": 0,
"second": 0
}
},
"vueTemplateDifferences": [
{
"file": "themes/sheng/layout/archive.vue",
"domDiff": true,
"ssrDiff": true
},
{
"file": "themes/sheng/layout/post.vue",
"domDiff": true,
"ssrDiff": true
}
]
},
"dprint/default": {
"tsDifferences": 281,
"vueDifferences": 14,
"tsRejected": 0,
"tsWithParseDiagnostics": 0,
"exitCodes": {
"normal": {
"first": 0,
"second": 0
},
"shared": {
"first": 0,
"second": 0
}
},
"vueTemplateDifferences": [
{
"file": "themes/sheng/layout/document.vue",
"domDiff": true,
"ssrDiff": true
},
{
"file": "themes/sheng/components/search-panel/index.vue",
"domDiff": true,
"ssrDiff": true
}
]
},
"dprint/aligned": {
"tsDifferences": 51,
"vueDifferences": 17,
"tsRejected": 0,
"tsWithParseDiagnostics": 0,
"exitCodes": {
"normal": {
"first": 0,
"second": 0
},
"shared": {
"first": 0,
"second": 0
}
},
"vueTemplateDifferences": []
},
"oxfmt/default": {
"tsDifferences": 282,
"vueDifferences": 14,
"tsRejected": 0,
"tsWithParseDiagnostics": 0,
"exitCodes": {
"normal": {
"first": 0,
"second": 0
},
"shared": {
"first": 0,
"second": 0
}
},
"vueTemplateDifferences": [
{
"file": "themes/sheng/layout/document.vue",
"domDiff": true,
"ssrDiff": true
},
{
"file": "themes/sheng/components/search-panel/index.vue",
"domDiff": true,
"ssrDiff": true
}
]
},
"oxfmt/aligned": {
"tsDifferences": 1,
"vueDifferences": 0,
"tsRejected": 0,
"tsWithParseDiagnostics": 0,
"exitCodes": {
"normal": {
"first": 0,
"second": 0
},
"shared": {
"first": 0,
"second": 0
}
},
"vueTemplateDifferences": []
}
},
"targetedRuns": [
{
"tool": "biome",
"mode": "vue-ignore",
"group": "normal",
"exit": 0,
"secondExit": 0,
"unstable": 0,
"tsDiff": 0,
"vueDiff": 2,
"semanticDiff": [
{
"file": "themes/sheng/layout/archive.vue",
"errors": [],
"domDiff": true,
"ssrDiff": true
},
{
"file": "themes/sheng/layout/post.vue",
"errors": [],
"domDiff": true,
"ssrDiff": true
}
]
},
{
"tool": "biome",
"mode": "vue-css",
"group": "normal",
"exit": 0,
"secondExit": 0,
"unstable": 0,
"tsDiff": 0,
"vueDiff": 0,
"semanticDiff": []
},
{
"tool": "biome",
"mode": "vue-strict",
"group": "normal",
"exit": 0,
"secondExit": 0,
"unstable": 0,
"tsDiff": 0,
"vueDiff": 1,
"semanticDiff": []
},
{
"tool": "biome",
"mode": "expand-never",
"group": "normal",
"exit": 0,
"secondExit": 0,
"unstable": 0,
"tsDiff": 1,
"vueDiff": 0,
"semanticDiff": []
},
{
"tool": "biome",
"mode": "expand-always",
"group": "normal",
"exit": 0,
"secondExit": 0,
"unstable": 0,
"tsDiff": 1,
"vueDiff": 0,
"semanticDiff": []
},
{
"tool": "dprint",
"mode": "tuned-final",
"group": "normal",
"exit": 0,
"secondExit": 0,
"unstable": 0,
"tsDiff": 23,
"vueDiff": 6,
"semanticDiff": []
},
{
"tool": "dprint",
"mode": "tuned-final",
"group": "shared",
"exit": 0,
"secondExit": 0,
"unstable": 0,
"tsDiff": 0,
"vueDiff": 0,
"semanticDiff": []
}
],
"targetedSettings": {
"biome/vue-ignore": {
"html.formatter.selfCloseVoidElements": "always",
"html.formatter.whitespaceSensitivity": "ignore"
},
"biome/vue-css": {
"html.formatter.selfCloseVoidElements": "always",
"html.formatter.whitespaceSensitivity": "css"
},
"biome/vue-strict": {
"html.formatter.selfCloseVoidElements": "always",
"html.formatter.whitespaceSensitivity": "strict"
},
"biome/expand-never": {
"javascript.formatter.expand": "never"
},
"biome/expand-always": {
"javascript.formatter.expand": "always"
},
"dprint/tuned-final": {
"typescript": {
"binaryExpression.operatorPosition": "sameLine",
"jsx.quoteStyle": "preferDouble",
"forOfStatement.preferHanging": true,
"typeLiteral.separatorKind.singleLine": "comma"
},
"malva": {
"attrSelector.quotes": "preferSingle"
}
}
},
"biomeRejected": [
{
"file": "source/_posts/2026-01-29 - Node 文件遍历工具的迭代:从 walk 到 readdir,再到 tinyglobby.md",
"line": 85,
"language": "ts"
},
{
"file": "source/_posts/2026-05-26 - Vue script setup 中组件静默消失的原因:一次命名冲突排查/2026-05-26 - Vue script setup 中组件静默消失的原因:一次命名冲突排查(1):从静默消失定位到命名冲突.md",
"line": 686,
"language": "ts"
},
{
"file": "source/_posts/2026-05-26 - Vue script setup 中组件静默消失的原因:一次命名冲突排查/2026-05-26 - Vue script setup 中组件静默消失的原因:一次命名冲突排查(2):顶层绑定怎样参与模板名字解析.md",
"line": 152,
"language": "ts"
},
{
"file": "source/_posts/2026-06-22 - Teleport 弹窗尺寸为什么会丢/2026-06-22 - Teleport 弹窗尺寸为什么会丢(1):Teleport 怎样管理两个 DOM 位置.md",
"line": 207,
"language": "ts"
},
{
"file": "source/_posts/2026-06-29 - Header 不要被页面反向控制:一次 Nuxt SSR hydration 错位复盘.md",
"line": 176,
"language": "ts"
},
{
"file": "source/_posts/2026-07-03 - 别再用 length 统计用户文本:一次 Unicode 字符数和 ESLint 护栏复盘/2026-07-03 - 别再用 length 统计用户文本:一次 Unicode 字符数和 ESLint 护栏复盘(3):用 ESLint 发现用户文本的危险操作.md",
"line": 19,
"language": "ts"
},
{
"file": "source/_posts/2026-07-08 - 浏览器为什么播不了 App 语音:一次 AMR 互通和策略模式改造.md",
"line": 93,
"language": "ts"
},
{
"file": "source/_posts/2026-07-09 - 别让一个 composable 承担两种身份:一次 Profile 全局状态重构.md",
"line": 68,
"language": "ts"
},
{
"file": "source/_posts/2026-07-09 - 别让一个 composable 承担两种身份:一次 Profile 全局状态重构.md",
"line": 74,
"language": "ts"
},
{
"file": "source/_posts/2026-07-09 - 别让一个 composable 承担两种身份:一次 Profile 全局状态重构.md",
"line": 95,
"language": "ts"
},
{
"file": "source/_posts/2026-08-14 - Vue KeepAlive 内部滚动恢复怎么封:库、生命周期和跨版本适配.md",
"line": 250,
"language": "ts"
},
{
"file": "source/_posts/2026-10-19 - Happy DOM 和 jsdom:差异、选型与测试迁移/2026-10-19 - Happy DOM 和 jsdom:差异、选型与测试迁移(1):能力差异与测试环境选型.md",
"line": 93,
"language": "ts"
},
{
"file": "source/_posts/2026-10-25 - 我所理解的 UnoCSS 最佳实践/2026-10-25 - 我所理解的 UnoCSS 最佳实践(4):Tailwind CSS 与 UnoCSS 的兼容边界.md",
"line": 275,
"language": "ts"
},
{
"file": "source/_posts/2026-10-28 - Happy DOM 为什么会误判复杂的 not 选择器/2026-10-28 - Happy DOM 为什么会误判复杂的 not 选择器(2):选择器的解析、匹配与缓存.md",
"line": 137,
"language": "ts"
},
{
"file": "source/_posts/2026-10-28 - Happy DOM 为什么会误判复杂的 not 选择器/2026-10-28 - Happy DOM 为什么会误判复杂的 not 选择器(4):not() 的复现与故障定位.md",
"line": 116,
"language": "ts"
},
{
"file": "source/_posts/2026-10-28 - Happy DOM 为什么会误判复杂的 not 选择器/2026-10-28 - Happy DOM 为什么会误判复杂的 not 选择器(4):not() 的复现与故障定位.md",
"line": 148,
"language": "ts"
},
{
"file": "source/_posts/2026-11-03 - 我所理解的 TypeScript 最佳实践/2026-11-03 - 我所理解的 TypeScript 最佳实践(2):严格模式.md",
"line": 383,
"language": "ts"
}
],
"examples": {
"biomeFragment": {
"file": "source/_posts/2026-01-29 - Node 文件遍历工具的迭代:从 walk 到 readdir,再到 tinyglobby.md",
"line": 85,
"language": "ts",
"outputs": {
"baseline": "if (!entry.isFile()) {\n continue;\n}",
"formatWithErrors": "if (!entry.isFile()) {\n continue;\n}"
}
},
"biomeCommentArray": {
"file": "source/_posts/2026-07-08 - 不要用屏幕宽度判断触摸交互:一次照片网格拖拽排序修复.md",
"line": 84,
"language": "ts",
"outputs": {
"baseline": "const popoverActions = [/* 省略菜单项 */];\nconst actionSheetActions = [/* 省略菜单项 */];",
"auto": "const popoverActions = [\n /* 省略菜单项 */\n];\nconst actionSheetActions = [\n /* 省略菜单项 */\n];",
"never": "const popoverActions = [\n /* 省略菜单项 */\n];\nconst actionSheetActions = [\n /* 省略菜单项 */\n];",
"always": "const popoverActions = [\n /* 省略菜单项 */\n];\nconst actionSheetActions = [\n /* 省略菜单项 */\n];"
}
},
"biomeVueWhitespace": {
"file": "themes/sheng/layout/archive.vue",
"line": 1,
"language": "vue",
"outputs": {
"baseline": "<h2 class=\"archive-page__count\">共计 {{ page.totalPosts }} 篇文章</h2>",
"aligned": "<h2 class=\"archive-page__count\">共计{{ page.totalPosts }} 篇文章</h2>",
"css": "<h2 class=\"archive-page__count\">共计 {{ page.totalPosts }} 篇文章</h2>"
}
},
"dprintTypeMembers": {
"file": "source/_posts/2026-05-23 - Vue script setup 中 defineProps 的编译边界与错误排查.md",
"line": 10,
"language": "ts",
"outputs": {
"baseline": "const props = defineProps<{ foo: string }>();",
"aligned": "const props = defineProps<{ foo: string; }>();",
"tuned": "const props = defineProps<{ foo: string }>();"
}
},
"oxfmtNewArgumentsComment": {
"file": "source/_posts/2026-05-26 - Vue script setup 中组件静默消失的原因:一次命名冲突排查/2026-05-26 - Vue script setup 中组件静默消失的原因:一次命名冲突排查(1):从静默消失定位到命名冲突.md",
"line": 686,
"language": "ts",
"outputs": {
"baseline": "directives: new Set(/* ... */),",
"aligned": "directives: new Set /* ... */(),"
}
}
}
}