尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
eslint-plugin-unicorn 规则详解:no-selector-as-dom-name 禁止在 DOM 名称中使用选择器语法
eslint-plugin-unicorn 规则详解no-selector-as-dom-name 禁止在 DOM 名称中使用选择器语法【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn导读no-selector-as-dom-name是 eslint-plugin-unicorn 中的一条可自动修复auto-fixable规则它专门拦截classList.add(.active)、getElementById(#app)这类把 CSS 选择器语法误传给期望接收原始类名/ID 的 DOM API的错误写法。本文将以该规则的官方文档为主体结合本仓库的源码实现rules/no-selector-as-dom-name.js与测试用例test/no-selector-as-dom-name.js完整讲解其检测范围、自动修复边界、与prefer-query-selector的联动机制以及各类边界情况的处理方式。一、规则解决的问题DOM API 需要的是原始名称不是选择器许多 DOM API 的参数期望的是原始类名class name或 ID而不是 CSS 选择器。把两者混用是前端开发中非常常见的隐性 bugclassList.add(active)是正确的而classList.add(.active)会在实际运行时抛出SyntaxErrorclassList 不接受带.前缀的 token。getElementsByClassName(item)期望的是类名getElementById(app)期望的是 ID而getElementsByClassName(.item)与getElementById(#app)则完全匹配不到任何元素。该规则的作用就是检测并报告这类问题并在满足条件时自动移除多余的前缀。官方文档给出的错误/正确示例对比如下// ❌ 错误在 DOM 名称中混入选择器前缀 element.classList.add(.active); element.classList.remove(.hidden); element.classList.contains(.selected); element.classList.toggle(.expanded); element.classList.replace(.old, .new); document.getElementsByClassName(.item); document.getElementById(#app); // ✅ 正确使用原始名称 element.classList.add(active); element.classList.remove(hidden); element.classList.contains(selected); element.classList.toggle(expanded); element.classList.replace(old, new); document.getElementsByClassName(item); document.getElementById(app);需要特别强调的是这条规则只针对期望接收原始名称的 DOM API并不会误伤querySelector/querySelectorAll这类本就接收 CSS 选择器的方法。从源码实现看规则只在classList方法与getElementById/getElementsByClassName上生效详见下文检测范围。二、规则元信息推荐配置、可修复性与注册方式规则头部标注了三条关键元信息可从源码 rules/no-selector-as-dom-name.js 的meta字段得到印证规则类型typeproblem即它标记的是代码中可能引发运行时错误的真实问题而非风格问题。推荐配置recommendedtrue即该规则在 eslint-plugin-unicorn 的 ✅recommended配置中默认开启而在 ☑️unopinionated不干预观点配置中默认关闭。可修复性fixablecode表示规则提供自动修复可通过 ESLint CLI 的--fix选项或编辑器快速修复Quick Fix直接改写代码。适用语言languages[js/js]面向 JavaScript/TypeScript 代码。从 rules/index.js 可见该规则通过export {default as no-selector-as-dom-name} from ./no-selector-as-dom-name.js;注册进规则索引使用者无需手动导入只要引入 unicorn 插件即可按规则名直接配置{ rules: { unicorn/no-selector-as-dom-name: error } }三、检测范围哪些调用会被报告从源码 rules/no-selector-as-dom-name.js 的getDomNameArguments函数可以精确定位规则覆盖的 API 集合以及每个 API 期望的前缀1.classList方法族classList.add、remove、contains、toggle、replace这五个方法源码第 9 行classListMethods数组都在检测范围内但只在前缀为.类名时报告方法检测的实参位置期望前缀特殊逻辑add/remove全部实参.每个参数独立检测replace前 2 个参数旧名、新名.若第一个参数是SpreadElement展开符则跳过检测contains/toggle仅第 1 个参数.toggle的第二个参数force不检测这里有一个值得注意的细节toggle(foo, .bar)的第二个参数是布尔强制位force从测试用例test/no-selector-as-dom-name.js可见它被列为valid合法即第二个参数即使写.bar也不会被报告。同理replace(old, new, .extra)中第三个参数是多余的同样不在检测范围test/no-selector-as-dom-name.js。2.getElementById与getElementsByClassName这两个方法要求恰好 1 个参数argumentsLength: 1会依据方法名决定期望前缀rules/no-selector-as-dom-name.jsgetElementById(...)期望#前缀的 ID因此document.getElementById(#app)会被报告。getElementsByClassName(...)期望.前缀的类名因此document.getElementsByClassName(.item)会被报告。反之前缀不匹配同样会被报告例如document.getElementsByClassName(#foo)或document.getElementById(.foo)——因为这两个值仍然以选择器前缀开头明显不是合法的原始名称测试用例见 test/no-selector-as-dom-name.js。3. 收窄条件只有看起来像 DOM 节点的接收者才检测为了减少误报规则通过isNotDomNode守卫rules/utils/is-node-value-not-dom-node.js判断调用者是否可能是一个 DOM 节点凡是[]、() {}、class Node {}、数字/字符串/正则/null/布尔字面量、{}、模板字符串、undefined等绝不可能返回 DOM 节点的表达式都会被排除在检测之外对应的类型清单见 test/utils/not-dom-node-types.js。因此以下写法不会被报告均出现在 valid 测试用例中// 接收者不可能是 DOM 节点跳过检测 (string as any).classList.add(.foo); (0).classList.add(.foo); (undefined as any).getElementsByClassName(.foo);此外规则还通过unwrapTypeScriptExpression穿透 TS 类型断言。测试覆盖了.foo as string、.foo!非空断言、.foo satisfies string、string.foo等写法它们同样会被检测并修复[test/no-selector-as-dom-name.js](https://link.gitcode.com/i/a5e8e216b509cfbb3317dc9fd3a114f8#L68-L72、L100。4. 明确不检测的写法以下情况在 valid 用例中被明确放行test/no-selector-as-dom-name.js接收者并非classListelement.notClassList.add(.foo)、裸变量classList.add(.foo)。成员访问方式不对element.classListadd使用计算属性访问方法名不会被检测。类名本身含点classList.add(foo.bar)——foo.bar是一个包含点号的合法类名而不是选择器前缀。无法静态求值的参数classList.add(className)、getElementById(id)这类变量参数无法确定前缀不报告。展开参数classList.add(...classNames)不检测。选择器类 APIdocument.querySelector(#foo)、element.setAttribute(class, .foo)等不在范围内。四、字符串与模板字面量的求值策略规则需要静态地判断实参是否以.或#开头其求值逻辑位于getDomNameValuerules/no-selector-as-dom-name.js分两层静态字符串求值优先调用仓库通用的getStaticStringValuerules/ast/literal.js。它对string字面量直接返回node.value对不含表达式的模板字符串foo返回其 cooked 值对含表达式的模板字符串或变量则返回undefined。模板字符串首段回退当静态求值失败返回undefined且节点是TemplateLiteral时规则退而求其次读取node.quasis[0].value.cooked即首个准静态片段quasi的内容用于判断模板是否以选择器前缀开头。正是这一策略支撑了官方文档强调的行为当选择器前缀位于模板字面量的开头时同样适用。例如element.classList.add(.${className}); // ❌ 报告模板以 . 开头 document.getElementById(#${id}); // ❌ 报告模板以 # 开头需要说明的是模板的 cooked 值意味着\u002e这样的转义序列会被正确解算为.。测试用例专门覆盖了element.classList.add(\u002efoo)与document.getElementById(\u0023foo)[test/no-selector-as-dom-name.js](https://link.gitcode.com/i/a5e8e216b509cfbb3317dc9fd3a114f8#L80-L82、L101-L102——即使前缀通过 Unicode 转义书写规则依然能够识别并报告。五、自动修复什么时候修、怎么修、什么时候不修规则通过getFixrules/no-selector-as-dom-name.js生成修复核心逻辑是先定位前缀字符在源码中的范围getPrefixRange即第 2 个字符的位置再决定是否安全地删除它。1. 可自动修复的情形简单选择器与单一动态模板情形 A简单选择器前缀值必须匹配simpleSelectorPattern源码第 11 行/^[#.]-?[A-Z_a-z][\w-]*$/u即.active、#app、.-foo、.foo-bar这类前缀 合法标识符的简单形式。同时要求静态值确实以期望前缀开头、且源码第 2 个字符就是该前缀字符。满足条件时修复即删除前缀字符element.classList.add(.active); // → element.classList.add(active) document.getElementById(#app); // → document.getElementById(app)情形 B单一动态模板对于含表达式的模板只有恰好一个表达式、且形如.${className}首个 quasi 恰为期望前缀、第二个 quasi 为空字符串时才修复修复同样是删除前缀字符element.classList.add(.${className}); // → element.classList.add(${className}) document.getElementById(#${id}); // → document.getElementById(${id})2. 只报告不修复的情形复杂选择器形态官方文档明确指出Only simple selector prefixes that match the API are autofixed…… More complex selector-looking values are reported without autofix. 即复杂的选择器形态只报告、不自动修复。这一点与源码中的simpleSelectorPattern判定完全对应测试中的 invalid 用例也揭示了哪些写法仅报告element.classList.add(.foo .bar); // 后代选择器 element.classList.add(.foo, .bar); // 选择器列表 element.classList.add(.foo:hover); // 伪类 element.classList.add(.123); // 数字开头不匹配标识符规则 element.classList.add(.${className}.bar); // 模板前后都有内容无法仅删前缀 element.classList.add(.foo${suffix}); // 模板中间有动态片段 element.classList.add(.foo.bar); // 类名含点号但会报告为复杂值这类场景中删除前缀并不能让参数变成合法的原始名称因此规则选择保守地只报错误、不提供修复避免产生误导性的自动改写。六、与prefer-query-selector的联动先修 DOM 名再换查询方法官方文档专门说明了一条联动机制Withprefer-query-selectorenabled, this rule fixes the DOM name first.prefer-query-selectormay then rewrite the DOM query method.意思是当两条规则同时开启时no-selector-as-dom-name先执行把参数修正为原始名称随后prefer-query-selector同样属于 recommended 配置再执行把getElementById/getElementsByClassName这类旧式查询方法重写为querySelector/querySelectorAll。该联动在测试中被显式验证test/no-selector-as-dom-name.js测试用真实 Linter 以 flat config 同时开启两条规则并通过verifyAndFix断言最终输出。例如document.getElementsByClassName(.foo); // → document.querySelectorAll(.foo); document.getElementsByClassName(.foo)[0]; // → document.querySelector(.foo); element.getElementsByClassName(.foo); // → element.querySelectorAll(:scope .foo); document.getElementById(#foo); // → document.querySelector(#foo);有趣的是这里的前缀语义在两条规则间发生了接力no-selector-as-dom-name移除./#前缀后prefer-query-selector会在重写为querySelector时重新引入选择器前缀#foo、.foo——因为querySelector本就需要完整选择器。最终修复结果既消除了旧 API 的错误参数又统一了 DOM 查询风格这正是两条规则设计上互补的体现。七、与其他边界情况的处理综合源码与测试还有几个值得记录的边界行为可选链optional chaining完整支持element?.classList.add(.foo)、document?.getElementsByClassName(.foo)、element.classList?.add(.foo)、document.getElementById?.(#foo)均能被检测test/no-selector-as-dom-name.js。括号包裹不影响检测element.classList.add((.foo))、document.getElementById((#foo))均被报告并修复。单双引号一视同仁.foo与.foo等价处理。replace中的展开参数保护element.classList.replace(...tokens, .new)是合法用例test/no-selector-as-dom-name.js因为第一个参数为展开符时规则直接跳过避免误伤可变参数场景。错误消息统一所有报告使用统一的消息Do not use selector syntax in DOM names.消息 IDno-selector-as-dom-namerules/no-selector-as-dom-name.js语义明确便于在配置中引用或按需关闭。八、配置建议与使用场景作为recommended配置默认开启的problem级规则no-selector-as-dom-name适合直接开箱即用无需任何额外选项。在实际项目中它最常见的价值体现在拦截隐性运行时错误classList.add(.active)这类写法在旧版浏览器中会直接抛异常规则在编码阶段即可拦截。配合prefer-query-selector形成现代化重构链先修正参数再统一查询方法是迁移到querySelector体系时最省心的自动修复路径。在 TypeScript 项目中同样生效规则穿透as断言、非空断言、satisfies表达式与尖括号泛型断言TypeScript 项目无需额外配置即可享受同等检测与修复能力对应测试见 test/no-selector-as-dom-name.js。若你的团队希望统一使用querySelector系列 API并允许以变量形式传参给旧式方法可同时结合 prefer-query-selector 的allowWithVariables选项true时允许变量实参做更精细的取舍。九、小结no-selector-as-dom-name是一条小而精的problem级规则它精准识别classList方法与getElementById/getElementsByClassName中混入的./#选择器前缀对简单前缀含模板首段前缀提供安全的自动修复对复杂选择器形态保守地仅报告并通过isNodeValueNotDomNode守卫有效控制误报。它与prefer-query-selector的前后接力修复机制更是体现了 eslint-plugin-unicorn 规则体系在发现问题—修复问题—现代化重构链路中的整体设计思路。理解了它的求值策略与修复边界你就能在项目中放心地启用它并在遇到只报告不修复的场景时依据本文的判定逻辑快速判断应当如何手动改写。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

Unity Shader冰冻效果实现:原理、代码与优化

Unity Shader冰冻效果实现:原理、代码与优化

做冰系技能或者被冰冻住的机关时,绕不开的一件事就是 unity shader 实现冰冻效果。我第一次接手这类需求的时候,脑子里想的很简单——贴一张冰的贴图,调个蓝色,加点透明不就行了。结果做完给主美看,对方只说了句"…

📅 2026/9/18 18:21:04
把 CherryStudio 的大模型通道改到 TaoToken 之后,FastMCP 的 add 工具跑通 100+100

把 CherryStudio 的大模型通道改到 TaoToken 之后,FastMCP 的 add 工具跑通 100+100

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

📅 2026/9/18 18:16:04
OpenClaw 原生应用跑 AI 代理:移动端 Key 用 TaoToken

OpenClaw 原生应用跑 AI 代理:移动端 Key 用 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/18 18:16:04
MORE NEWS

更多资讯

📰

pyasc 算子编程接口 asc.language.basic.div 完全指南:按元素求商的三种调用形态与底层实现

pyasc 算子编程接口 asc.language.basic.div 完全指南:按元素求商的三种调用形态与底层实现 【免费下载链接】pyasc 本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。 项目…

📰

STM32CubeIDE Attach调试实战:无侵入式运行时诊断核心技术

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

📰

EDA课程设计游戏机:Verilog/VGA状态机与嘉立创EDA画板实战

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

📰

StarRocks json_string 函数详解:将 JSON 对象序列化为 JSON 字符串

StarRocks json_string 函数详解:将 JSON 对象序列化为 JSON 字符串 【免费下载链接】starrocks The worlds fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, Star…

📰

Zotero快速创建Bibliography:文献管理与参考文献排版实战指南

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

📰

Android App开发基础入门:环境配置、项目结构、界面、存储与打包上架

1. 从零上手 Android App 开发基础,先把认知框架立起来1.1 为什么很多人卡在"装完 Android Studio 就不知道干嘛"我带过几个刚入行的朋友,几乎所有人都经历过同一个尴尬期:Android Studio 装好了,SDK 下完了&#xff0c…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬