ESLint no-restricted-syntax 规则详解用 AST 选择器精准禁用任意语法【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslintno-restricted-syntax是 ESLint 提供的一条通用语法禁用规则它允许开发者通过配置ESTree 节点类型或AST 选择器一次性禁用任意不希望出现在代码库中的语法结构如try-catch、class、with、in运算符等从而避免为每一个想禁用的特性单独编写规则。读完本文你将掌握该规则的字符串/对象两种配置格式、AST 选择器的完整语法、常见实战拦截场景以及其底层基于 esquery 的实现原理可直接在自己的 ESLint 配置中投入使用。规则定位为什么需要一条通用禁用规则JavaScript 语言特性众多不同团队对语言特性的偏好差异巨大。有些项目会明确禁止某些语法结构的出现——例如禁用try-catch、禁用class、禁用in运算符甚至禁用未加花括号的if语句体。如果为每个想禁用的特性单独创建一条 ESLint 规则规则数量将难以维护。no-restricted-syntax的设计目标正是用一条规则覆盖所有禁用语法诉求你只需把要禁用的语法元素以AST 选择器的形式配置进去即可。在 JavaScript 语境下每个语法元素都对应一个 ESTree 节点类型例如函数声明对应FunctionDeclarationwith语句对应WithStatement。你可以借助代码解析工具如 ESLint 官方 Code Explorer查看一段代码会被解析成哪些节点从而确定需要禁用的节点类型。在 lib/rules/no-restricted-syntax.js 的规则元信息中可以看到该规则的type为suggestion建议类默认不加入recommended配置属于按需开启的可选规则meta: { type: suggestion, docs: { description: Disallow specified syntax, recommended: false, }, schema: { /* ... */ }, defaultOptions: [], }从源码结构看该规则不提供自动修复fix因为禁用某类语法通常需要人工改写代码无法机械替换。核心概念ESTree 节点与 AST 选择器no-restricted-syntax的配置项本质上是一系列AST 选择器AST Selector。选择器是一种用于匹配抽象语法树AST中节点的字符串其语法与 CSS 选择器高度相似熟悉 CSS 的开发者可以很快上手。最简单的选择器就是节点类型本身。例如选择器Identifier会匹配程序中的所有标识符节点选择器WithStatement只匹配with语句节点。而更高级的组合选择器可以精确描述某种特定形态的语法VariableDeclarator Identifier匹配直接父节点为VariableDeclarator的IdentifierFunctionDeclaration[params.length2]匹配参数个数大于 2 的函数声明CallExpression[callee.namesetTimeout][arguments.length!2]匹配setTimeout调用但参数个数不为 2 的调用表达式。关于选择器的完整语法可参考 AST 选择器文档本文后续章节也会系统展开。配置方式一字符串形式节点类型该规则接受一个字符串列表每个字符串就是一个 AST 选择器。命中任一选择器的语法都会触发报错{ rules: { no-restricted-syntax: [error, FunctionExpression, WithStatement, BinaryExpression[operatorin]] } }上述配置的含义是禁用函数表达式FunctionExpression、with语句WithStatement以及使用in运算符的二元表达式BinaryExpression[operatorin]。错误代码示例以下代码分别使用了with语句、函数表达式和in运算符均会被规则拦截/* eslint no-restricted-syntax: [error, FunctionExpression, WithStatement, BinaryExpression[operatorin]] */ with (me) { dontMess(); } const doSomething function () {}; foo in bar;正确代码示例改用等价的替代写法后代码可以通过检查/* eslint no-restricted-syntax: [error, FunctionExpression, WithStatement, BinaryExpression[operatorin]] */ me.dontMess(); function doSomething() {}; foo instanceof bar;注意with语句在严格模式下本身就不被允许因此示例配置需要配合非严格模式script 模式才能体现出with的拦截效果。配置方式二对象形式选择器 自定义消息除字符串外规则还接受对象形式的配置项对象包含selector必填和message可选两个属性{ rules: { no-restricted-syntax: [ error, { selector: FunctionExpression, message: Function expressions are not allowed. }, { selector: CallExpression[callee.namesetTimeout][arguments.length!2], message: setTimeout must always be invoked with two arguments. } ] } }当通过message属性指定了自定义消息后ESLint 在报告该选择器命中的语法时会使用这条自定义消息而不是默认消息。这一能力在需要向团队传达为什么禁用、应该怎么写时非常有用——报错信息可以直接承载规范说明。字符串和对象两种格式可以在配置中自由混用例如前两条用字符串、后两条用对象。Schema 约束从 lib/rules/no-restricted-syntax.js 的 schema 定义可以确认配置项的约束规则schema: { type: array, items: { oneOf: [ { type: string }, { type: object, properties: { selector: { type: string }, message: { type: string }, }, required: [selector], additionalProperties: false, }, ], }, uniqueItems: true, minItems: 0, }对应地配置必须是数组允许为空数组minItems: 0数组元素只能是字符串或{ selector, message }对象对象格式中selector为必填且不允许出现selector、message之外的额外属性uniqueItems: true要求所有配置项互不重复。默认消息与自定义消息的生成逻辑在规则实现中create(context)函数会对context.options数组做reduce处理把每个配置项编译成一个以选择器为键的监听器create(context) { return context.options.reduce((result, selectorOrObject) { const isStringFormat typeof selectorOrObject string; const hasCustomMessage !isStringFormat Boolean(selectorOrObject.message); const selector isStringFormat ? selectorOrObject : selectorOrObject.selector; const message hasCustomMessage ? selectorOrObject.message : Using ${selector} is not allowed.; return Object.assign(result, { selector { context.report({ node, messageId: restrictedSyntax, data: { message }, }); }, }); }, {}); }这段实现揭示了几个值得注意的细节选择器即监听键返回的对象中[selector]作为键名意味着 ESLint 会在 AST 遍历过程中对每一个匹配该选择器的节点调用此回调并上报问题——这正是该规则底层的工作机制。默认消息模板字符串格式或未提供message的对象格式使用Using selector is not allowed.作为默认报错消息消息中直接回显完整的选择器文本。消息统一通过messageId上报messages中定义了restrictedSyntax: {{message}}自定义消息和默认消息都以数据形式注入测试文件 tests/lib/rules/no-restricted-syntax.js 中的断言也验证了这一行为例如{ code: var foo 41;, options: [VariableDeclaration], errors: [ { messageId: restrictedSyntax, data: { message: Using VariableDeclaration is not allowed. }, }, ], }AST 选择器语法全览要充分发挥no-restricted-syntax的威力需要系统掌握 AST 选择器的语法。根据 AST 选择器文档ESLint 支持的选择器语法如下语法类别示例说明节点类型ForStatement匹配指定类型的节点通配符*匹配所有节点属性存在[attr]匹配拥有该属性的节点属性值[attrfoo]、[attr123]匹配属性等于指定值的节点属性正则[attr/foo.*/]属性值匹配正则的节点属性条件[attr!foo]、[attr2]、[attr3]、[attr2]、[attr3]属性值满足比较条件的节点嵌套属性[attr.level2foo]匹配嵌套属性的节点字段FunctionDeclaration Identifier.id匹配特定字段上的节点首/末子节点:first-child、:last-child匹配父节点的第一个/最后一个子节点第 N 个子节点:nth-child(2)匹配第 2 个子节点不支持axb形式倒数第 N 个子节点:nth-last-child(1)匹配倒数第 1 个子节点后代FunctionExpression ReturnStatement匹配某节点的后代节点直接子节点UnaryExpression Literal匹配直接子节点后续兄弟VariableDeclaration ~ VariableDeclaration匹配后续兄弟节点相邻兄弟ArrayExpression Literal SpreadElement匹配紧邻的兄弟节点否定:not(ForStatement)匹配不满足括号内选择器的节点匹配任意:matches([attr] :first-child, :last-child)或:is(...)匹配括号内任一选择器命中的节点节点类别:statement、:expression、:declaration、:function、:pattern按节点类别匹配其中:function在源码实现lib/linter/esquery.js中会被展开为FunctionDeclaration、FunctionExpression、ArrowFunctionExpression三种节点类型的集合。属性值中使用正则表达式选择器的属性值支持正则表达式例如Identifier[name/^foo/]会匹配所有名称以foo开头的标识符。正则中如果需要包含/字符必须转义为\/以免被解析为正则结束符又因为选择器本身处于 JSON 字符串中反斜杠还需要再转义一次\\/。例如禁用从some/path导入{ rules: { no-restricted-syntax: [ error, ImportDeclaration[source.value/^some\\/path$/] ] } }对应的测试用例tests/lib/rules/no-restricted-syntax.js验证了该选择器能命中import values from some/path;。实战场景用选择器拦截具体模式结合文档与测试用例以下实战场景可以直接复制到你的 ESLint 配置中。禁用不带代码块的 if 语句不写花括号的单行if容易引发后续维护隐患可用两种等价写法禁用{ rules: { no-restricted-syntax: [ error, IfStatement :not(BlockStatement).consequent ] } }等价写法{ rules: { no-restricted-syntax: [ error, IfStatement[consequent.type!BlockStatement] ] } }禁用 require() 调用在推行 ESM 的代码库中可以禁止 CommonJS 的require{ rules: { no-restricted-syntax: [ error, CallExpression[callee.namerequire] ] } }强制 setTimeout 必须传两个参数{ rules: { no-restricted-syntax: [ error, CallExpression[callee.namesetTimeout][arguments.length!2] ] } }禁用一个参数超过 2 个的函数声明{ rules: { no-restricted-syntax: [ error, FunctionDeclaration[params.length2] ] } }禁用带标签的 break 语句{ rules: { no-restricted-syntax: [ error, BreakStatement[label] ] } }禁用可选链Optional Chaining与正则字面量测试用例中还覆盖了如下选择器均可在 tests/lib/rules/no-restricted-syntax.js 中查到ChainExpression命中foo?.bar?.()这类可选链整体[optionaltrue]分别命中可选链中的每个可选访问/调用Literal[regex.flags/./]命中带标志位flags的正则字面量VariableDeclaration[kindusing]命中using声明ECMAScript 显式资源管理语法。组合选择器:is() 一次匹配多个目标foo bar baz中的三个标识符可以这样一次命中{ rules: { no-restricted-syntax: [ error, :is(Identifier[namefoo], Identifier[namebar], Identifier[namebaz]) ] } }底层原理esquery 与选择器解析no-restricted-syntax之所以能用字符串当监听器键名依赖的是 ESLint 内置的 esquery 封装层 lib/linter/esquery.js。该模块负责解析选择器调用 esquery 的parse将选择器字符串解析为可执行的结构对简单的纯字母选择器走快速路径trySimpleParseSelector避免不必要的解析开销解析失败时抛出带位置的SyntaxError方便定位配置错误。缓存解析结果通过selectorCacheMap缓存已解析的选择器同一选择器在多次运行时无需重复解析。计算优先级specificityESQueryParsedSelector.compare按属性/伪类数量 → 节点类型标识符数量 → 字典序排序监听器当多个选择器同时命中同一节点时监听器按优先级由低到高调用优先级相同时按字母序调用。这些机制保证了即使在配置大量选择器的情况下no-restricted-syntax依然能高效、稳定地工作同时让多个选择器命中同一节点时每个节点都会被完整报告。多语言场景不仅适用于 JavaScript文档特别指出该规则可以用于你使用 ESLint 检查的任何语言。由于选择器基于通用 AST 概念只要相应语言的解析器产出符合 ESTree 兼容结构的节点就能用同样的方式禁用语法使用typescript-eslint检查 TypeScript 时可通过其 Playground 查看 TS 代码对应的节点类型使用 ESLint 检查 JavaScript、JSON、Markdown 或 CSS 时可通过 ESLint Code Explorer 查看对应语言的 AST 节点。这意味着团队可以在同一套no-restricted-syntax规则框架下为不同语言的文件制定各自的语法禁令。与相关规则的协同与区别no-restricted-syntax的规则头frontmatter声明了四条相关规则详见 no-restricted-syntax.md规则职责no-alert禁用alert、confirm、promptno-console禁用consoleno-debugger禁用debugger语句no-restricted-properties禁用对象上的特定属性访问其中no-console 文档 明确提到如果不希望手动在每个console调用处添加eslint-disable-next-line注释就可以改用no-restricted-syntax达到同样效果——例如{ rules: { no-restricted-syntax: [error, CallExpression[callee.object.nameconsole]] } }由此可见no-restricted-syntax是精确到属性/调用形态的更细粒度拦截手段而上述专用规则更适合语义明确、使用频繁的场景。当你想禁用的是语法形态而非某个 API 名字时no-restricted-syntax是唯一的选择。何时不使用该规则如果你不希望限制代码使用任何 JavaScript 特性或语法就不应开启此规则。此外若需求只是禁用console、debugger、alert等有专属规则的常见对象优先考虑专用规则语义更清晰、报错消息也更友好。小结no-restricted-syntax以极小的配置成本换来了对任意语法形态的拦截能力字符串形式适合简单禁用节点类型对象形式支持自定义报错消息两种格式可混合使用结合属性选择器、正则、:is()、:not()等丰富语法还能精确锁定带标签的 break超过 N 个参数的函数声明可选链调用等复杂模式。其实现上基于 esquery 选择器解析与优先级机制lib/linter/esquery.js整套行为都有对应的单元测试佐证tests/lib/rules/no-restricted-syntax.js你可以在当前仓库中进一步阅读这些源码来加深理解。【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考