多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

eslint-plugin-unicorn 的 no-unreadable-new-expression 规则:源码、测试快照与修复实践

eslint-plugin-unicorn 的 no-unreadable-new-expression 规则:源码、测试快照与修复实践 eslint-plugin-unicorn 的 no-unreadable-new-expression 规则源码、测试快照与修复实践【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn导读no-unreadable-new-expression是 eslint-plugin-unicorn 中一条专注于「可读性」的 ESLint 规则用于禁止直接对new表达式进行成员访问并禁止使用复杂表达式作为构造器。本文以规则官方文档 docs/rules/no-unreadable-new-expression.md 为主体结合规则实现 rules/no-unreadable-new-expression.js、单元测试 test/no-unreadable-new-expression.js 及其测试快照 test/snapshots/no-unreadable-new-expression.js.md完整解析该规则的报错消息、判定逻辑、20 个无效用例的触发细节、16 个有效用例的边界以及可落地的修复写法。规则定位与启用方式该规则在 readme.md 的规则表中被描述为 Disallow unreadablenewexpressions.meta.type为suggestion默认不推荐启用recommended: false且官方文档明确标注它在recommended与unopinionated两套推荐配置中均处于禁用状态。这意味着它是一条纯风格取向的规则需要开发者按需手动开启。在 ESLint 配置中按如下方式启用即可// eslint.config.js export default [ { rules: { unicorn/no-unreadable-new-expression: error, }, }, ];为什么需要这条规则new的优先级陷阱规则文档的 Why 章节点出了核心动机new运算符对括号极其敏感。带参数列表的new与不带参数列表的new在 JS 运算符优先级中位于不同层级一对括号的差别会完全翻转语义new Foo().Bar; // 解析为 (new Foo()).Bar读取实例上的 Bar 属性 new Foo.Bar(); // 解析为 new (Foo.Bar)()将 Foo.Bar 作为构造器实例化这两行代码外观几乎一致行为却完全相反。更麻烦的是由于new Foo与new Foo()等价括号的存在并不能可靠地提示new的作用范围——在new Date(string).getTime()中你还得先确认这对括号是参数列表而不是优先级分组而new (foo().Bar)()这类写法则更难一眼读对。类名的首字母大写并不能成为阅读依据因为 JavaScript 语言本身并不强制命名规范。因此规则文档给出的结论是把构造与使用拆成两条语句先命名实例再使用它既能消除猜测也方便后续复用、日志或断点调试。规则实现原理两种消息与两类判定规则源码 rules/no-unreadable-new-expression.js 定义了两个消息 IDmember-access「Do not access members directly from anewexpression.」不要直接从new表达式访问成员complex-constructor「Do not use a complex expression as a constructor.」不要使用复杂表达式作为构造器判定逻辑依赖两个纯函数辅助函数const isStaticMemberExpression node node.type MemberExpression !node.computed node.property.type Identifier ( node.object.type Identifier || isStaticMemberExpression(node.object) ); const isSimpleConstructor node node.type Identifier || isStaticMemberExpression(node);isSimpleConstructor判定「简单构造器」只允许标识符如Foo或静态成员表达式如Foo.Bar、foo.bar.Baz且必须是非计算属性、属性名为标识符。规则通过context.on分别监听两种节点监听MemberExpression当成员表达式的object节点类型为NewExpression时对property报告member-access监听NewExpression当callee不满足isSimpleConstructor时对callee报告complex-constructor。由此可以推断这条规则是纯报错规则源码中没有任何fix逻辑测试也使用快照snapshot而非可修复output断言——它只负责提示修复动作完全交给开发者。快照测试解析20 个无效用例全拆解测试文件 test/no-unreadable-new-expression.js 通过test.snapshot({...})驱动其快照报告保存在 test/snapshots/no-unreadable-new-expression.js.md 中报告头注明由 AVA 生成真实快照数据另存为同目录二进制.snap文件。快照中 20 个无效用例按报错类型可归为两大类。第一类直接从new表达式访问成员messageId: member-access直接调用链用例 1、2const bar new Foo().getBar(); // 报告位置getBar const bar (new Foo()).getBar(); // 报告位置getBar属性读取用例 3、4、5包括无括号调用new Foo的情况const Bar new Foo().Bar; const Bar (new Foo()).Bar; const Bar (new Foo).Bar;计算属性访问用例 6、7const Bar new Foo()[Bar]; const Bar (new Foo())[Bar];可选链访问用例 8const bar (new Foo)?.getBar();链式成员访问用例 9只对紧邻new表达式的第一个成员bar报告const baz new Foo().bar.baz;标签模板调用用例 10将new Foo().bar当作标签模板函数使用同样被拦截new Foo().barx;带括号的无名实例成员调用用例 13、14const bar (new foo).Bar(); const bar new foo().Bar();真实场景用例用例 19、20规则对内置对象同样生效例如new Date().getTime()与new Intl.ListFormat(...).format(words)说明它并不区分用户代码与标准库 API。第二类复杂表达式作为构造器messageId: complex-constructor以下写法中new的callee不是简单标识符或静态成员均被报告且报告位置落在整个复杂 callee 表达式上const Bar new (Foo().Bar); // (Foo().Bar) const bar new foo[Bar](); // foo[Bar]计算属性 const bar new (foo().Bar)(); // (foo().Bar) const bar new (foo())(); // (foo()) const bar new (foo ? Foo : Bar)(); // (foo ? Foo : Bar) const bar new class {}(); // class {}匿名类从快照的^标注可以清楚看到complex-constructor的报错区间是完整的 callee 表达式如new (foo ? Foo : Bar)()中高亮foo ? Foo : Bar而member-access则精确指向被访问的成员如new Foo().getBar()中高亮getBar。快照格式说明每个用例的快照以invalid(n): 代码为标题依次呈现输入代码带行号与缩进的Input块和逐条错误Error 1/1包含消息文本、行号与^指示的报错位置。这种「输入 错误定位」的格式本身就是很好的规则行为参考手册——无需运行 ESLint 即可精确知道某段代码会被如何报告。有效用例规则不误报的边界测试文件同文件列出的 16 个 valid 用例定义了规则的「不干预」边界值得逐类理解简单构造器全部放行const foo new Foo(); const foo new Foo; const foo new Foo(bar); const foo new Foo(...bar); const foo new Foo.Bar; const bar new foo.Bar(); const bar new foo.bar.Baz(); const formatter new Intl.ListFormat(en-US, {type: disjunction});非new表达式的成员访问放行const foo Foo().Bar; const foo Foo().Bar(); const foo Foo.Bar();先构造、再访问的分离写法放行这正是规则倡导的修复方向const foo new Foo(); foo.getBar(); const foo new Foo(); const Bar foo.Bar; const {Bar} foo; const bar new Bar(); const Bar foo.Bar; const bar new Bar(); const bar Foo ? new Foo() : foo.Bar;从源码可以印证这些边界member-access只检查object类型为NewExpression的成员访问complex-constructor只拦截callee不满足isSimpleConstructor的new。new Foo.Bar之所以合法是因为它被解析为new (Foo.Bar)()callee 是静态成员表达式属于isSimpleConstructor放行范围。TypeScript 场景的补充覆盖测试文件末尾通过test.typescript(...)额外覆盖了 TypeScript 语法有效const foo new FooType(); const foo new FooType;无效各报 1 个错误const foo new (Foo as typeof Bar)(); // 类型断言使 callee 复杂化 const bar (new FooType()).bar; // 泛型实例化后仍直接取成员这印证了isSimpleConstructor只认Identifier与静态MemberExpression类型断言表达式TSAsExpression与泛型参数都不属于简单构造器因而对泛型new表达式做成员访问依然会被报告。推荐的修复写法规则文档给出了四组 ❌/✅ 对照其中两种典型场景的修复为// ❌ const bar new Foo().getBar(); // ✅ 先命名实例再访问成员 const foo new Foo(); const bar foo.getBar();// ❌ const Bar new Foo().Bar; // ✅ const foo new Foo(); const Bar foo.Bar;针对复杂构造器文档建议先把构造器表达式赋给一个语义清晰的名称// ❌ const bar new (foo().Bar)(); // ✅ 解构出构造器后使用 const {Bar} foo(); const bar new Bar();// ❌ const bar new foo[Bar](); // ✅ const Bar foo[Bar]; const bar new Bar();将new Date().getTime()这类代码改写为两步后可读性与可调试性都会明显提升const date new Date(); const timestamp date.getTime();如何运行与验证该仓库的规则测试通过 AVA 快照机制执行测试基建位于 test/utils/test.js。若要验证本规则的 20 个无效用例与快照一致性可在仓库根目录运行对应的测试文件npx ava test/no-unreadable-new-expression.js快照驱动的测试意味着任何对规则消息文案或报告位置的改动都需要同步更新快照新快照可通过 AVA 的-u参数更新生成这也解释了为何 test/snapshots/no-unreadable-new-expression.js.md 中的消息文本必须与规则源码中的messages定义逐字一致。小结no-unreadable-new-expression是一条「少即是多」的提示型规则它不做自动修复而是用两条精确的消息把 JavaScript 中new的优先级陷阱显性化——既禁止从new表达式直接取成员也禁止把复杂表达式塞进new。通过阅读其源码两条监听器 两个判定函数、测试16 valid 20 invalid TypeScript 覆盖与快照报告开发者既能快速理解每条报错的触发形态也能据此形成「先命名、再使用」的稳健编码习惯。若你的项目追求极致可读性可将该规则加入配置并配合上述修复范式落地。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表