feat(linter): implement constructor-super rule#14506
Merged
camc314 merged 28 commits intooxc-project:mainfrom Oct 17, 2025
Merged
feat(linter): implement constructor-super rule#14506camc314 merged 28 commits intooxc-project:mainfrom
camc314 merged 28 commits intooxc-project:mainfrom
Conversation
CodSpeed Performance ReportMerging #14506 will not alter performanceComparing Summary
Footnotes
|
camc314
reviewed
Oct 13, 2025
camc314
reviewed
Oct 13, 2025
810163d to
819a57d
Compare
camc314
reviewed
Oct 15, 2025
zzt1224
reviewed
Oct 15, 2025
zzt1224
reviewed
Oct 15, 2025
819a57d to
cee68d2
Compare
camc314
reviewed
Oct 16, 2025
5b2ba58 to
ed66efe
Compare
2ce7edd to
cdf2d07
Compare
Contributor
|
ugh i don't know why graphite closed this. |
Implements the constructor-super ESLint rule that validates super() calls in class constructors. This rule ensures: - Derived classes (extends) must call super() in constructors - Non-derived classes must not call super() - super() is called in all code paths (no conditional/missing calls) - No duplicate super() calls except in mutually exclusive branches Implementation uses AST-only structural analysis without CFG: - Classifies superclass expressions (valid/invalid/null/none) - Detects guaranteed super() execution via control flow analysis - Handles complex patterns: if/else, switch with fallthrough, ternaries, try-finally, loops with fallback, acceptable exits (throw/return) - Distinguishes duplicate calls from mutually exclusive branches Key edge cases handled: - extends null requires return statement or throws error - Invalid superclass expressions (literals, arithmetic assignments) - Switch fallthrough detection prevents duplicate super() calls - Loop-with-fallback pattern (super() inside loop + after loop) - Conditional exits (throw/return-with-value) allow super() after Passes all 165 valid cases and 80 invalid cases from ESLint test suite. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Refactored the `constructor-super` ESLint rule to leverage the Control Flow Graph (CFG) instead of iterating over all AST nodes, significantly improving performance and code clarity. ## Key Changes ### CFG-Based Super Call Detection - Replaced full AST iteration with targeted CFG traversal using `neighbors_filtered_by_edge_weight` - Only examines CFG block instructions, avoiding unnecessary node visits - Properly stops at function boundaries (NewFunction edges) to avoid counting nested function super() calls ### Per-Block Super Call Counting - Changed from tracking boolean "has super" to counting super() calls per basic block - Enables detection of multiple super() calls in single block - Properly handles ternary expressions where both branches are in the same CFG block (e.g., `a ? super() : super()` counts as 1, not 2) ### DFS-Based Path Analysis - Implemented custom DFS traversal to analyze all execution paths - Tracks super() call counts per path from constructor entry to exit - Only reports results at actual path terminations (Return, Throw, ImplicitReturn instructions) - Uses per-path visited tracking to explore all paths while avoiding infinite loops ### Proper Edge Type Handling - **Jump/Normal/Join edges**: Follow with accumulated super count - **Error edges**: Reset to pre-block count (exception paths discard try block execution) - **Finalize edges**: Preserve super count (finally blocks execute after try/catch) - **NewFunction/Unreachable edges**: Stop traversal - **Backedge handling**: Detects loops containing super() (in progress) ### Early Exit Detection - Distinguishes between acceptable and unacceptable early exits - `return <value>` and `throw`: acceptable without super() - `return;` (implicit undefined): requires super() - Uses `ReturnInstructionKind::NotImplicitUndefined` to differentiate ### Conditional Expression Support - Handles ternary operators within ExpressionStatements - Checks both consequent and alternate branches for super() calls - CFG instructions point to ExpressionStatement, not inner expressions ## Implementation Details - Added `Direction` import from petgraph for edge traversal - Changed return type from `FxHashSet<BlockNodeId>` to `FxHashMap<BlockNodeId, usize>` to track call counts - Added `ExitedWithoutSuper` path result variant for early exits - Maintains span tracking for diagnostic reporting ## Known Limitations Loop detection (Backedge handling) needs refinement to accurately detect super() calls within loop bodies vs. before loops. Current implementation may have false positives/negatives for loops. ## Performance Impact Eliminates O(n) iteration over all AST nodes, replacing with O(paths) CFG traversal where paths << total nodes in most cases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…h analysis
Fix critical bug in DFS path traversal where implicit error propagation edges
were being followed as real execution paths, causing false positives.
The issue occurred when CFG blocks had Error(Implicit) edges leading to error
sink blocks. These edges represent exception propagation/escape routes, not
actual execution paths within the constructor. Following them caused the DFS
to visit error sink blocks with super_count=0, incorrectly recording
PathResult::NoSuper.
Changes:
- Distinguish between Error(Explicit) and Error(Implicit) edge types
- Only follow Error(Explicit) edges (try/catch exception handlers)
- Skip Error(Implicit) edges (error propagation to outer scopes)
- For explicit error edges, use super_count from before the block
(exception thrown = rest of try block doesn't execute)
This fixes false positives like:
- `if (true) { super(); } else { super(); }` incorrectly flagging missing super
- Other cases where all paths called super() but error sinks created phantom paths
Improved diagnostics:
- `a && super()` now correctly reports "Expected to call 'super()'" instead of
"Lacked a call in some code paths" (no super() call at all in false path)
- `super() || super()` similarly improved (short-circuit behavior)
- Duplicate diagnostics now consistently point to first super() call
All 804 linter tests passing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Apply clippy suggestions: - Merge empty match arms (NewFunction, Unreachable, Error::Implicit) - Collapse nested if statement using let-chain pattern 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Improved performance and readability of the constructor-super rule's CFG-based super() detection: - Simplified edge filtering by checking only for NewFunction boundaries - Hoisted nested scope check to avoid redundant ancestor traversals (reduced from ~4x to 1x per instruction) - Extracted record_super closure to eliminate code duplication - Simplified ternary expression handling using helper closure with .or_else() chaining (~50 lines reduced to ~16) - Removed unnecessary count capping (simple addition suffices) - Simplified dead-end path condition check These changes reduce code size by ~70 lines while maintaining all correct CFG-based behavior and improving performance. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ression with super() The CFG-based approach had a bug where LogicalExpressions containing super() calls (e.g., `a && super()` or `super() || super()`) were not being detected correctly, producing "Expected to call 'super()'" instead of "Lacked a call of 'super()' in some code paths". This commit adds a hybrid approach: - Added `has_logical_expression_super()` to recursively search the AST for LogicalExpressions containing super() calls - Modified `find_super_calls_in_cfg()` to return a `has_conditional_super` flag when it encounters LogicalExpression with super() in the CFG - Updated validation logic to check the conditional flag first and emit the appropriate diagnostic The fix ensures that logical expressions with super() are correctly identified as conditional paths, matching the behavior of the original AST-based implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ot the first one
Previously, the duplicate super() diagnostic would highlight the first
super() call encountered during CFG traversal, which could be either
the first or a subsequent call depending on block traversal order.
This commit improves the diagnostic by:
- Changing super_call_spans from HashMap to Vec to track all super() calls
- Sorting spans by source position before reporting duplicates
- Reporting only the duplicate calls (all except the first in source order)
Now diagnostics correctly highlight only the duplicate super() calls:
- `super(); super();` → highlights the second super()
- `if (a) super(); super();` → highlights the second super()
- `switch (a) { case 0: super(); default: super(); }` → highlights the second super()
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
…match ESLint Updated all diagnostic messages to include periods at the end, matching the exact format of the original ESLint constructor-super rule: - "Expected to call 'super()'." (was: "Expected to call 'super()'") - "Lacked a call of 'super()' in some code paths." (was: "Lacked a call of 'super()' in some code paths") - "Unexpected duplicate 'super()'." (was: "Unexpected duplicate 'super()'") - "Unexpected 'super()' because 'super' is not a constructor." (was: "Unexpected 'super()' because 'super' is not a constructor") 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Remove redundant clones when sorting super() call spans - Remove unused parameters from nested helper functions
Simplified comments throughout the constructor-super rule implementation while preserving important technical context. Removed redundant comments and condensed multi-line explanations into more concise descriptions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Update snapshot to reflect changes in output format after rebasing onto main. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Update snapshot to reflect changes in output format after rebasing onto main. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…nstructor-super The CFG now properly handles logical expressions with condition basic blocks, so the AST-based workaround is no longer needed. The rule now relies entirely on CFG path analysis, with a special case for 'super() || super()' which always executes both calls since super() returns undefined (falsy).
ed66efe to
a778820
Compare
Member
|
From #479:
This was the last one. Full house! 🎉 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2280
I implemented the constructor-super eslint rule. I think the implementation is pretty straightforward. I tried to keep it as simple as possible within the constraints of the rule.
I'm open to any feedback you have on it!
I used Claude to help generate a high level overview description of the code changes and reviewed it myself for accuracy.
Implementation Overview:
constructor-superRuleHybrid AST + CFG Approach
This implementation uses a sophisticated hybrid approach combining AST analysis with Control Flow Graph (CFG) traversal to achieve accurate detection of
super()violations:a && super(), so these are detected using AST traversalsuper()calls across different execution paths to detect:super()calls (never called)super()calls (not in all paths)super()calls (called multiple times)super()calls (invalid due to 0 or N executions)Key Features
extends 5), arithmetic assignments (extends (B += C)), etc.Superclass Classification
The rule classifies superclasses into four types:
extends null(special case requiring explicit return)extends 5,extends (B += C))Path Analysis Results
CFG path analysis produces four possible results per path:
NoSuper: Path doesn't callsuper()CalledOnce: Path callssuper()exactly once (valid)CalledMultiple: Path callssuper()multiple times (duplicate)ExitedWithoutSuper: Path exits early via throw/return with value (acceptable)Test Coverage
Changes
constructor_super.rswith 873 lines of implementationrules.rsand generated rule runner implementationsnursery(pending promotion after validation)Related