eslint-plugin-unicorn 的 no-incorrect-template-string-interpolation 规则:从快照测试解读错误插值语法检测
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
导读
no-incorrect-template-string-interpolation是 eslint-plugin-unicorn 中用于捕获模板字符串(Template Literal)错误插值语法的一条规则,它专门识别把${expression}误写成{name}或$name}的常见手误。本文以该规则的快照测试报告(test/snapshots/no-incorrect-template-string-interpolation.js.md)为主体,逐条解读其 21 个错误用例的检测行为与自动修复建议,并结合规则源码与测试用例说明其判定边界、排除逻辑与配置方式,帮助你在实际项目中安全启用并理解其行为。
规则定位:纠正模板字符串插值语法手误
ES 的模板字符串插值语法是${expression},形如:
const greeting = `Hello ${name}`;当开发者(尤其是习惯 PHP$name、Pythonf"{name}"或各类代码生成模板语法的人)在未打标签(untagged)的模板字符串中写出{name}或$name}时,它并不会被当作插值执行,而只是普通文本——这可能是一个静默的 bug。
该规则的官方描述是 "Disallow incorrect template literal interpolation syntax."(禁止不正确的模板字面量插值语法),元数据中标记为type: 'problem'、recommended: true(因此包含在 ✅recommended配置中,而在 ☑️unopinionated配置中禁用),并通过editor suggestions提供手动可应用的修复(hasSuggestions: true)。相关声明见 rules/no-incorrect-template-string-interpolation.js。
规则刻意保持"窄":只抓最确定的错误
规则文档明确指出其检测范围是刻意收窄的(见 docs/rules/no-incorrect-template-string-interpolation.md):
- 只匹配简单标识符或成员表达式:如
{name}、{user.name}、$user.name}; - 不报告任意表达式文本:如
{name + suffix}、{user?.name}、{user[name]}; - 不报告
{{name}}这类花括号占位符语法; - 忽略带标签的模板字符串(tagged template literal),因为标签(如
html、gql、i18n 模板)中常使用花括号表示其他语言语法; - 忽略命名导入/导出说明符(
import {foo} from "bar")、解构声明(const {foo} = bar),以及内嵌块注释(如 JSDoc 的@param {string})中的花括号——这些在代码生成模板中都很常见。
同时,对于确实有意使用简单花括号占位符(如 URL 路径/users/{id})或包含对象字面量(`const x = {foo}`)的文件,官方建议直接禁用此规则。
源码实现:两条正则识别两类插值错误
规则源码 rules/no-incorrect-template-string-interpolation.js 中定义了两个正则来识别两类错误:
const identifier = String.raw`[$A-Z_a-z][\w$]*`; const memberExpression = String.raw`${identifier}(?:\.${identifier})*`; const missingDollar = new RegExp(String.raw`(?<![$\\])(?<!\{)(?<!\\u)\{(?<expression>${memberExpression})\}(?!\})`, 'gv'); const missingOpeningBrace = new RegExp(String.raw`(?<!\\)(?<!\{)\$(?<expression>${memberExpression})\}(?!\})`, 'gv');missingDollar(缺$):匹配{name}、{user.name}这种缺了$的形式。前瞻断言(?<!\{)与(?!\})让它跳过{{name}};(?<![$\\])避免误伤已转义文本;(?<!\\u)用于排除\u{FEFF}这类 Unicode 码点转义。missingOpeningBrace(缺{):匹配$name}、$user.name}这种少了左花括号的形式,修复时补全为${name}。
规则遍历TemplateLiteral节点的每个 quasi(文本片段),对每一处命中生成一个独立的 lint 报告与修复建议。检测前的关键预处理包括:
- 跳过带标签模板:调用 rules/ast/is-tagged-template-literal.js 判断节点是否为 TaggedTemplateExpression 的 quasi(对应 AST 助手 rules/ast/index.js),是则直接返回;
- 屏蔽已解析的表达式区域:
getTemplateRawWithExpressionsMasked把${...}表达式内容替换为空格,避免把表达式内部的花括号误判为占位符; - 排除绑定声明上下文:
isBindingDeclaration只对import/export/const/let/var行首的{foo}放行(详见下文"排除项"); - 排除块注释区域:
getBlockCommentRanges通过/\*[\s\S]*?\*\//g找到所有已闭合的/* … */注释范围,命中范围内的花括号一律不报。
快照测试解读:21 个错误用例的行为全览
快照报告由 AVA 测试框架生成(Generated by AVA),文件头部指明实际快照保存在no-incorrect-template-string-interpolation.js.snap,而测试定义位于 test/no-incorrect-template-string-interpolation.js。每条用例输出统一的消息格式:
Use `${correct}` for template literal interpolation.并附带一条建议(Suggestion):
Replace `{incorrect}` with `${correct}`.下面按错误形态分类解读快照中的 21 个用例。
1. 最简单的{name}缺$错误
- invalid(1):
const greeting = \Hello {name}`;→ 报错并建议改为Hello ${name}`; - invalid(7):
Hello {user.name}→ 支持成员表达式,建议改为${user.name}; - invalid(13)/invalid(14):
${salutation}, {name}与${salutation}, {name} ${punctuation}→ 证明规则不会把${...}中的表达式误判,只精确指向缺少$的{name}。
2.$name}缺{的错误
- invalid(8):
Hello $name}→ 建议改为Hello ${name}; - invalid(9):
Hello $user.name}→ 同样支持成员表达式,改为${user.name}。
3. 同一模板中的多处错误:逐个独立报告
- invalid(10):
Hello {firstName} {lastName}→ 产生Error 1/2 与 Error 2/2 两条独立报告,每条建议分别把对应占位符修正为${firstName}与${lastName}; - invalid(11):
Hello $firstName} $lastName}→ 同样是两条报告,各自修复; - invalid(12):
Hello {firstName} ${middleName} {lastName}→ 中间的${middleName}完全不受影响,仅报告两侧的两处错误。
这印证了源码中yield逐处产出报告的实现方式(rules/no-incorrect-template-string-interpolation.js),每处错误都有独立的行列位置与修复建议。
4. 绑定声明保护:只放过真正的 specifier/解构
规则通过isBindingDeclaration(rules/no-incorrect-template-string-interpolation.js)识别"该行是绑定声明"的场景,避免把代码生成模板中的合法语法误报:
- invalid(2):
`const x = {name};`→仍然报告。因为声明要求{foo}出现在行首(=之前),{name}位于=之后属于对象字面量,会被判为疑似插值错误; - invalid(3):
Please import {name} now→ 报告。import出现在句中而不是行首,不属于说明符; - invalid(4):
important {name}→ 报告。important不是关键字import; - invalid(5):
constant {name}→ 报告。同理constant不是const; - invalid(6):
const $foo} = bar;→ 报告并建议${foo}。测试注释明确指出:isBindingDeclaration只保护{foo}形态,不保护缺左花括号的$foo}形态。
5. 块注释边界:只放行已闭合的注释
- invalid(15):
`/** ${description}\n*/ {name}`→ 报告。注释在*/处已闭合,闭合后的{name}是真实错误; - invalid(16):
`/* doc */ Hello {name}`→ 报告。同理,闭合注释不抑制其后的错误; - invalid(17):
`{name} /* {type} */`→ 报告。注释之前的真实错误照常报出,注释内部的{type}被忽略——两条行为同时发生; - invalid(18):
`const re = "/*"; {name}`→ 报告。字符串里的/*没有闭合的*/,不构成块注释(源码注释明确说明只有/* … */成对闭合才计数),因此不抑制错误; - invalid(19):
`// {name}`→ 报告。行注释刻意不支持,//后的{name}仍被判定为错误; - invalid(20):
`\u{FEFF}{name}`→ 报告。Unicode 码点转义\u{FEFF}本身被排除,但紧邻其后的真实错误{name}仍然报告,建议改为\u{FEFF}${name}。
对照 valid 用例:理解规则的排除清单
快照只覆盖 invalid 输出,而规则完整行为需要结合测试文件中的 valid 用例理解(test/no-incorrect-template-string-interpolation.js):
| 场景 | 示例 | 不报原因 |
|---|---|---|
| 正确插值 | `Hello ${name}`、`Hello ${user.name}` | 语法正确 |
| 转义文本 | `Hello \${name}`、`Hello \{name}` | 反斜杠转义被前瞻断言排除 |
| 带标签模板 | html`{name}`、gql`query { user { name } }`、String.raw`{name}` | 标签常用于 HTML/GraphQL 等语言 |
| 非模板字符串 | "{name}"、"$name}" | 规则只处理 TemplateLiteral 节点 |
| 复杂表达式 | `Hello {name + suffix}`、`Hello {user?.name}`、`Hello {user[name]}` | 只匹配简单标识符/成员表达式 |
| 双花括号 | `Hello {{name}}`、`Hello {{$name}}` | (?!\})/(?<!\{)断言 |
| 导入/导出说明符 | `import {foo} from "bar";`、`export {foo};`、`import foo, {bar} from "baz";` | isBindingDeclaration行首保护 |
| 解构声明 | `const {foo} = bar;`、`let {foo} = bar;` | 同上 |
| JSDoc/块注释 | `/**\n * @param {string} [id]\n */`、`/* {number} */`、`/* {foo.Bar} */` | getBlockCommentRanges排除 |
| 注释与插值混合 | `/** ${description} @param {string} id */` | 表达式被屏蔽后注释仍被识别 |
| Unicode 转义 | `\u{FEFF}${csv}`、`\u{Face}` | (?<!\\u)排除码点转义 |
从 valid 用例还能看出两个重要的实现细节:import type {foo} from "bar"(TypeScript 代码生成)和缩进的多行 import(`\n\timport {foo} from "bar";\n`)都被正确放行;多说明符import {foo, bar}因含逗号根本不进入匹配范围。
运行测试与验证行为
测试通过test.snapshot(...)组织(test/no-incorrect-template-string-interpolation.js),使用项目统一的快照测试器(基于eslint-ava-rule-tester,见 test/utils/test.js)。若需在本地复现快照中的输出,可运行:
# 运行全部测试 npm test # 仅运行该规则相关测试 npx ava test/no-incorrect-template-string-interpolation.js # 快照不匹配时更新快照(注意:仓库只读,更新结果仅用于本地观察) npx ava test/no-incorrect-template-string-interpolation.js --update-snapshots规则已在 rules/index.js 中注册为no-incorrect-template-string-interpolation,可通过标准 ESLint 配置启用,例如:
{ "rules": { "unicorn/no-incorrect-template-string-interpolation": "error" } }由于规则通过编辑器建议(而非fix)提供修复,IDE 中会显示"Replace{name}with${name}"的可手动应用修复;命令行下则需配合--fix-type suggestion语义或手动接受建议。
实际项目中的应用建议
- 确认占位符语法:若项目大量使用
/users/{id}这类 URL 模板或代码生成模板,评估后可在相关文件顶部用/* eslint-disable unicorn/no-incorrect-template-string-interpolation */局部禁用; - 善用标签模板:对 HTML、GraphQL、CSS 等内嵌语言,优先使用带标签的模板字符串,规则天然放行;
- 注意行注释与未闭合注释:规则只识别成对的
/* … */,`// {name}`与字符串中的孤立/*仍会被报告,编写代码生成模板时应使用规范注释; - 消息与修复的自动化集成:错误消息与建议文本格式稳定(
Use \${correct}` for template literal interpolation./Replace `{incorrect}` with `${correct}`.),可接入自定义工具做批量迁移(例如把旧模板语法批量改写为${}`),但批量改写前应结合 valid 用例清单复核排除场景。
总结
no-incorrect-template-string-interpolation通过"两条正则 + 三层排除(绑定声明、块注释、表达式屏蔽)"实现了对模板字符串插值手误的精确检测。快照测试报告完整展示了其 21 个错误用例的行为:单标识符与成员表达式都支持、多处错误逐处独立报告、绑定声明保护与块注释边界处理细致入微。理解这些边界行为,能帮助你在启用规则时准确预判误报风险,并让代码生成模板类项目也能安全受益。
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考