ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ESLint constructor-super 规则详解:强制派生类构造函数正确调用 `super()`

ESLint constructor-super 规则详解:强制派生类构造函数正确调用 `super()` ESLint constructor-super 规则详解强制派生类构造函数正确调用super()【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint在 JavaScript 的类继承体系中派生类derived class的构造函数必须在访问this之前调用super()否则引擎会直接抛出运行时错误。ESLint 内置规则constructor-super正是用来在静态分析阶段提前发现这一类问题的——它校验构造函数中是否存在合法的super()调用派生类的构造函数必须调用super()而非派生类的构造函数禁止调用super()。读完本文你将完整掌握该规则的触发场景、错误消息语义、底层基于代码路径分析Code Path Analysis的实现原理以及它在 ESLint 默认推荐配置中的启用方式。规则概述一条面向运行时错误的静态检查constructor-super属于 ESLint 规则分类中的problem类型即专门标记可能导致程序行为异常或运行时错误的代码模式。该规则的完整元信息定义在 lib/rules/constructor-super.js 中meta: { type: problem, docs: { description: Require super() calls in constructors, recommended: true, url: https://eslint.org/docs/latest/rules/constructor-super, }, schema: [], messages: { missingSome: Lacked a call of super() in some code paths., missingAll: Expected to call super()., duplicate: Unexpected duplicate super()., badSuper: Unexpected super() because super is not a constructor., }, },从源码可以确认几个关键事实规则类型为problem且recommended: true说明它被包含在eslint:recommended推荐配置中开箱即用。schema为空数组即该规则不接受任何配置选项与 docs/src/rules/constructor-super.md 文档中 This rule has no options 的描述一致。不可自动修复无fixable字段因为它涉及构造流程的控制流语义无法通过简单的文本替换安全修复。该规则会输出四种不同语义的错误消息missingSome部分代码路径缺失、missingAll全部路径缺失、duplicate重复调用、badSupersuper并非构造函数。在eslint:recommended配置中它被直接映射为error级别见 packages/js/src/configs/eslint-recommended.jsconstructor-super: error,同样eslint:all配置即启用全部规则的配置中也将其设为error见 packages/js/src/configs/eslint-all.js。Rule Details规则的核心检查逻辑ESLint 官方文档 docs/src/rules/constructor-super.md 对本规则的定位是检查构造函数中是否存在合法的super()调用。两条基本判定是派生类有extends子句的构造函数必须调用super()非派生类没有extends子句的构造函数不得调用super()。如果违反上述规则JavaScript 引擎会在运行时抛错——例如在无extends子句的类构造函数中调用super()本身就是语法错误class A { constructor() { super(); } }不正确的代码示例以下代码均会被constructor-super标记为错误/*eslint constructor-super: error*/ class A extends B { constructor() { } // 会抛出 ReferenceError。 } // 继承自非构造函数null的类永远是问题代码。 class C extends null { constructor() { super(); // 会抛出 TypeError。 } } class D extends null { constructor() { } // 会抛出 ReferenceError。 }逐个解读这三个错误场景class A extends B定义了显式构造函数却没有调用super()。在派生类构造函数中this的初始化依赖基类构造过程缺省调用会触发ReferenceError。注意如果派生类不定义构造函数则引擎会隐式调用super()因此只有显式定义构造函数时才需要检查。class C extends null中null不是构造函数任何对super()的调用都会触发TypeError。class D extends null中既不调用super()基类又是null同样属于问题代码引擎会抛出ReferenceError。正确的代码示例/*eslint constructor-super: error*/ class A { constructor() { } } class B extends C { constructor() { super(); } }class A没有extends子句其构造函数不调用super()合法。class B extends C是派生类其构造函数在所有路径中调用了super()合法。源码剖析规则如何判定构造函数与可能的构造函数constructor-super的实现lib/rules/constructor-super.js比文档描述复杂得多核心难点在于两点识别真正的构造函数节点以及判断extends右侧表达式在运行时能否求值出构造函数。识别构造函数节点isConstructorFunction通过 AST 节点结构精确定位构造函数function isConstructorFunction(node) { return ( node.type FunctionExpression node.parent.type MethodDefinition node.parent.kind constructor ); }即一个节点只有在满足函数表达式 父节点为方法定义 方法类型为constructor时才被认定为构造函数。规则通过代码路径Code Path事件在进入函数时onCodePathStart据此建立构造函数的上下文信息栈onCodePathStart(codePath, node) { if (isConstructorFunction(node)) { // Class ClassBody MethodDefinition FunctionExpression const classNode node.parent.parent.parent; const superClass classNode.superClass; funcInfo { upper: funcInfo, isConstructor: true, hasExtends: Boolean(superClass), superIsConstructor: isPossibleConstructor(superClass), codePath, currentSegments: new Set(), }; } // ... }这里通过node.parent.parent.parent从构造函数函数表达式回溯到类节点Class ClassBody MethodDefinition FunctionExpression从而读取superClass字段判断是否有extends子句并进一步用isPossibleConstructor判断基类表达式是否可能是构造函数。判断extends表达式是否可能是构造函数isPossibleConstructor是规则中最具巧思的部分。extends后面不一定是简单的标识符它可以是任意表达式因此该函数对各类 AST 节点做递归分析直接判定为可能的构造函数ClassExpression、FunctionExpression、ThisExpression、MemberExpression、CallExpression、NewExpression、ChainExpression可选链、YieldExpression、TaggedTemplateExpression、MetaProperty标识符只要名字不是undefined就视为可能是构造函数null、字面量等则不行赋值表达式对于和结果取决于右侧对于||和??左右两侧任一可能即可对于算术/位运算赋值如、-、**、|、结果要么是原始值要么抛错不可能是构造函数直接返回false逻辑表达式短路时取左侧假值未短路时取右侧因此只有右侧必须总是可能的构造函数||和??则左右任一可能即可条件表达式consequent或alternate任一可能是构造函数即可序列表达式整个表达式的值等于最后一个子表达式的值因此只需递归检查最后一个表达式。注释中还提到一个已知的未来改进点的左操作数如果能在静态上确定为恒假字面量例如false B那么整体必为假值应当判为无效但当前实现刻意放行了这类代码tests/lib/rules/constructor-super.js 中的 valid 用例中有对应注释。代码路径分析如何验证所有路径都调用了 super()super()是否被调用不能只看字面数量——构造函数里可能含有if、switch、try/catch、循环等分支结构。规则必须回答一个控制流问题从构造函数入口到每个可到达的返回点是否都经过了一次super()调用这正是 ESLint 代码路径分析Code Path Analysis机制的用武之地。ESLint 会为每个函数构建由CodePath和CodePathSegment组成的有向图其中CodePathSegment以类似双向链表的方式表达执行段支持分叉if语句与合并分支汇合点。关于该机制的完整说明见 docs/src/extend/code-path-analysis.md。constructor-super为每个可达段维护一个SegmentInfoclass SegmentInfo { calledInEveryPaths false; // 该段的所有前置路径都调用过 super() calledInSomePaths false; // 该段的某些前置路径调用过 super() validNodes []; // 已验证合法的 super() 节点用于后续查重 }当代码路径分段开始时onCodePathSegmentStart规则会聚合前驱段的调用标记当super()调用出现时CallExpression:exit遍历当前所在的所有可达段将它们标记为已调用CallExpression:exit(node) { if (!(funcInfo.isConstructor funcInfo.hasExtends)) { return; } // 只处理 callee 为 Super 的调用 if (node.callee.type ! Super) { return; } // 遍历当前段置位 calledInSomePaths / calledInEveryPaths // 若某个段此前已标记过则判定为重复调用duplicate }当代码路径结束时onCodePathEnd规则检查所有returnedSegments若全部返回段都标记为已调用则通过若部分返回段未调用报missingSomeLacked a call of super() in some code paths.若所有返回段均未调用报missingAllExpected to call super().。可见missingSome与missingAll的区别正是部分代码路径与全部代码路径的差异例如if (a) super();这类分支调用会报missingSome而完全没有任何调用会报missingAll。测试用例中对此有精确覆盖tests/lib/rules/constructor-super.js// lacked in some code path. class A extends B { constructor() { if (a) super(); } }, // missingSome class A extends B { constructor() { if (a); else super(); } }, // missingSome class A extends B { constructor() { a super(); } }, // missingSome class A extends B { constructor() { switch (a) { case 0: super(); } } }, // missingSome class A extends B { constructor() { try { super(); } catch (err) {} } }, // missingSome边界情况循环、重复调用、return 替代与不可达路径循环中的重复调用检测循环for、while、do...while会导致代码路径回环这是代码路径分析中最棘手的场景。constructor-super通过onCodePathSegmentLoop事件处理当循环使控制流从某个段回流到前驱段时规则在fromSegment到toSegment区间内重新遍历段合并循环带来的调用标记并对此前已标记为合法的super()节点做延迟查重lib/rules/constructor-super.js 的onCodePathSegmentLoop处理器。一个典型的检测结果是while (a) super();由于循环可能不执行会同时报missingSome循环入口路径未调用和duplicate循环体内反复调用测试用例中对此有明确验证{ code: class A extends B { constructor(a) { while (a) super(); } }, errors: [ { messageId: missingSome }, { messageId: duplicate, column: 48 }, ], },此外源码对for语句的update段做了特殊处理这些段在分析早期就被预创建、没有可见的前驱段为避免干扰calledInEveryPaths的聚合计算规则直接将它们的calledInEveryPaths置为true相当于在计算中忽略它们。return返回值是super()的替代品JavaScript 规定如果构造函数显式return一个对象则该对象会取代默认的this成为构造结果此时即使派生类构造函数没有调用super()也不会报错。规则对此有专门的ReturnStatement处理器——当构造函数return了带参数的值时将该段标记为已调用ReturnStatement(node) { if (!(funcInfo.isConstructor funcInfo.hasExtends)) { return; } // 跳过无参数的 return; if (!node.argument) { return; } // 带参数的 return 视为 super() 的替代 // 置位当前段的 calledInSomePaths / calledInEveryPaths }对应的有效与无效用例// valid: 带参数 return 是 super() 的替代 class A extends B { constructor() { if (true) return a; super(); } }, class A extends null { constructor() { return a; } }, // invalid: 无参数 return 不是替代 class A extends B { constructor() { if (a) return; super(); } }, // missingSome不可达路径被忽略规则只统计segment.reachable的可达段。return; super();中的super()位于不可达路径会被忽略因此以下代码报missingAll因为没有可达路径调用super(){ code: class A extends B { constructor() { return; super(); } }, errors: [{ messageId: missingAll }], },嵌套类的隔离规则通过funcInfo栈式管理上下文onCodePathStart压栈、onCodePathEnd弹栈因此嵌套在构造函数内部的类声明不会干扰外层构造函数的检查。测试中的嵌套用例验证了这一点// valid内外层各自独立判断 class A extends B { constructor() { super(); class C extends D { constructor() { super(); } } } }, // invalid外层缺失 super()尽管内层调用了 class A extends B { constructor() { var c class extends D { constructor() { super(); } } } }, // missingAll配置方式与适用范围配置语法由于该规则无选项配置只需指定严重级别{ rules: { constructor-super: error } }在 flat config扁平配置中同样只需写规则名export default [ { rules: { constructor-super: error, }, }, ];当然由于它已包含在eslint:recommended中见 packages/js/src/configs/eslint-recommended.js使用推荐配置即可默认开启无需手动添加。与相关规则的关系constructor-super通常与 no-this-before-super 配合使用前者保证派生类构造函数调用了super()后者保证在调用super()之前不访问this两者共同覆盖了在正确的位置、以正确的方式调用super()这一主题。此外constructor-super的文档元数据中标注了handled_by_typescript: true见 docs/src/rules/constructor-super.md 的 frontmatter表明 TypeScript 编译器自身的类型检查也覆盖了同类问题因此在使用 TypeScript 的项目中该规则可能有所冗余。When Not To Use It何时关闭该规则如果你不希望收到构造函数中无效/缺失super()调用的提醒可以安全地关闭此规则{ rules: { constructor-super: off } }不过需要留意的是该规则捕获的问题在运行时必然导致ReferenceError或TypeError属于确定性的运行时错误而非风格偏好因此除非项目有特殊原因例如全部代码由其他工具保证正确性或正处于迁移过渡期否则不建议关闭。结语一条规则背后的控制流工程constructor-super表面上只是检查 super() 调用的一条小规则但其实现融合了 AST 节点识别、表达式可达性分析isPossibleConstructor与完整的代码路径分析CodePath/CodePathSegment三大机制并能正确处理循环回环、条件分支、return语义替代、不可达路径和嵌套类等复杂场景。理解它的实现不仅能帮你用好这条规则也能为你编写自己的基于代码路径分析的 ESLint 自定义规则提供一份高质量的参考范例——相关基础可进一步阅读 docs/src/extend/code-path-analysis.md规则的完整测试套件位于 tests/lib/rules/constructor-super.js其中包含 30 余个有效/无效用例是理解其所有边界行为的最佳入口。【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进