-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat!: move AST traversal into SourceCode #18167
Changes from 9 commits
6365d91
4d09c62
5c6c7d4
12d7ebc
3795c60
ce17554
6c9ca84
f70559f
549c080
6495471
63ee6bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -119,6 +119,30 @@ function isPossibleConstructor(node) { | |
} | ||
} | ||
|
||
/** | ||
* A class to store information about a code path segment. | ||
*/ | ||
class SegmentInfo { | ||
|
||
/** | ||
* Indicates if super() is called in all code paths. | ||
* @type {boolean} | ||
*/ | ||
calledInEveryPaths = false; | ||
|
||
/** | ||
* Indicates if super() is called in any code paths. | ||
* @type {boolean} | ||
*/ | ||
calledInSomePaths = false; | ||
|
||
/** | ||
* The nodes which have been validated and don't need to be reconsidered. | ||
* @type {ASTNode[]} | ||
*/ | ||
validNodes = []; | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
//------------------------------------------------------------------------------ | ||
|
@@ -159,12 +183,8 @@ module.exports = { | |
*/ | ||
let funcInfo = null; | ||
|
||
/* | ||
* {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>} | ||
* Information for each code path segment. | ||
* - calledInSomePaths: A flag of be called `super()` in some code paths. | ||
* - calledInEveryPaths: A flag of be called `super()` in all code paths. | ||
* - validNodes: | ||
/** | ||
* @type {Record<string, SegmentInfo>} | ||
*/ | ||
let segInfoMap = Object.create(null); | ||
|
||
|
@@ -174,7 +194,7 @@ module.exports = { | |
* @returns {boolean} The flag which shows `super()` is called in some paths | ||
*/ | ||
function isCalledInSomePath(segment) { | ||
return segment.reachable && segInfoMap[segment.id].calledInSomePaths; | ||
return segment.reachable && segInfoMap[segment.id]?.calledInSomePaths; | ||
} | ||
|
||
/** | ||
|
@@ -189,12 +209,12 @@ module.exports = { | |
* skip the segment. | ||
* If not skipped, this never becomes true after a loop. | ||
*/ | ||
if (segment.nextSegments.length === 1 && | ||
if (segment.nextSegments.length && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this introduces new false negatives. For example: /* eslint constructor-super: "error" */
class C extends D {
// no errors here
constructor() {
do {
something();
} while (foo);
}
} I'm not sure exactly what this condition was targeting. When we figure that out, it would be good to explain it better in the comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I comment out this condition altogether, these valid tests fails:
So it seems like it's designed to catch loops that occur after I couldn't figure out anything else about how this rule works, though, so any ideas would be greatly appreciated. |
||
segment.nextSegments[0].isLoopedPrevSegment(segment) | ||
) { | ||
return true; | ||
} | ||
return segment.reachable && segInfoMap[segment.id].calledInEveryPaths; | ||
return segment.reachable && segInfoMap[segment.id]?.calledInEveryPaths; | ||
} | ||
|
||
return { | ||
|
@@ -278,11 +298,7 @@ module.exports = { | |
} | ||
|
||
// Initialize info. | ||
const info = segInfoMap[segment.id] = { | ||
calledInSomePaths: false, | ||
calledInEveryPaths: false, | ||
validNodes: [] | ||
}; | ||
const info = segInfoMap[segment.id] = new SegmentInfo(); | ||
|
||
// When there are previous segments, aggregates these. | ||
const prevSegments = segment.prevSegments; | ||
|
@@ -326,7 +342,7 @@ module.exports = { | |
funcInfo.codePath.traverseSegments( | ||
{ first: toSegment, last: fromSegment }, | ||
segment => { | ||
const info = segInfoMap[segment.id]; | ||
const info = segInfoMap[segment.id] ?? new SegmentInfo(); | ||
const prevSegments = segment.prevSegments; | ||
|
||
// Updates flags. | ||
|
@@ -348,6 +364,9 @@ module.exports = { | |
}); | ||
} | ||
} | ||
|
||
// save just in case we created a new SegmentInfo object | ||
segInfoMap[segment.id] = info; | ||
} | ||
); | ||
}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is too general to understand the advice, I think we should leave just the second bullet (if you're accessing
nextSegments
and others, verify that your code will still work as expected).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is one of the bugs I ran into when implementing this, where we were doing
=== 1
in one spot. I think it's worth calling this out because it took me a while to realize why that was a problem.