Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit09bd7fe

Browse files
authored
feat!: move AST traversal into SourceCode (#18167)
* feat: Implement SourceCode#traverse()Refs#16999* Add more debugging* Clean up sourcecode* Try to fix code paths* Fix rules and update migration guide* Cache traversal steps* Fix lint error* Fix fuzz test* Simplify TraversalStep constructor* Remove unused parent arg* Fix constructor-super
1 parentb91f9dc commit09bd7fe

File tree

9 files changed

+334
-65
lines changed

9 files changed

+334
-65
lines changed

‎docs/src/use/migrate-to-9.0.0.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ The lists below are ordered roughly by the number of users each change is expect
4343
*[Removed multiple`context` methods](#removed-context-methods)
4444
*[Removed`sourceCode.getComments()`](#removed-sourcecode-getcomments)
4545
*[Removed`CodePath#currentSegments`](#removed-codepath-currentsegments)
46+
*[Code paths are now precalculated](#codepath-precalc)
4647
*[Function-style rules are no longer supported](#drop-function-style-rules)
4748
*[`meta.schema` is required for rules with options](#meta-schema-required)
4849
*[`FlatRuleTester` is now`RuleTester`](#flat-rule-tester)
@@ -490,6 +491,19 @@ ESLint v9.0.0 removes the deprecated `CodePath#currentSegments` property.
490491

491492
**Related Issues(s):**[#17457](https://github.com/eslint/eslint/issues/17457)
492493

494+
##<aname="codepath-precalc"></a> Code paths are now precalculated
495+
496+
Prior to ESLint v9.0.0, code paths were calculated during the same AST traversal used by rules, meaning that the information passed to methods like`onCodePathStart` and`onCodePathSegmentStart` was incomplete. Specifically, array properties like`CodePath#childCodePaths` and`CodePathSegment#nextSegments` began empty and then were filled with the appropriate information as the traversal completed, meaning that those arrays could have different elements depending on when you checked their values.
497+
498+
ESLint v9.0.0 now precalculates code path information before the traversal used by rules. As a result, the code path information is now complete regardless of where it is accessed inside of a rule.
499+
500+
**To address:** If you are accessing any array properties on`CodePath` or`CodePathSegment`, you'll need to update your code. Specifically:
501+
502+
* If you are checking the`length` of any array properties, ensure you are using relative comparison operators like`<`,`>`,`<=`, and`>=` instead of equals.
503+
* If you are accessing the`nextSegments`,`prevSegments`,`allNextSegments`, or`allPrevSegments` properties on a`CodePathSegment`, or`CodePath#childCodePaths`, verify that your code will still work as expected. To be backwards compatible, consider moving the logic that checks these properties into`onCodePathEnd`.
504+
505+
**Related Issues(s):**[#16999](https://github.com/eslint/eslint/issues/16999)
506+
493507
##<aname="drop-function-style-rules"></a> Function-style rules are no longer supported
494508

495509
ESLint v9.0.0 drops support for function-style rules. Function-style rules are rules created by exporting a function rather than an object with a`create()` method. This rule format was deprecated in 2016.

‎lib/linter/code-path-analysis/code-path-analyzer.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ function forwardCurrentToHead(analyzer, node) {
222222
:"onUnreachableCodePathSegmentStart";
223223

224224
debug.dump(`${eventName}${headSegment.id}`);
225-
226225
CodePathSegment.markUsed(headSegment);
227226
analyzer.emitter.emit(
228227
eventName,

‎lib/linter/linter.js‎

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ const
3030
}=require("@eslint/eslintrc/universal"),
3131
Traverser=require("../shared/traverser"),
3232
{ SourceCode}=require("../source-code"),
33-
CodePathAnalyzer=require("./code-path-analysis/code-path-analyzer"),
3433
applyDisableDirectives=require("./apply-disable-directives"),
3534
ConfigCommentParser=require("./config-comment-parser"),
3635
NodeEventGenerator=require("./node-event-generator"),
@@ -53,6 +52,8 @@ const commentParser = new ConfigCommentParser();
5352
constDEFAULT_ERROR_LOC={start:{line:1,column:0},end:{line:1,column:1}};
5453
constparserSymbol=Symbol.for("eslint.RuleTester.parser");
5554
const{LATEST_ECMA_VERSION}=require("../../conf/ecma-version");
55+
constSTEP_KIND_VISIT=1;
56+
constSTEP_KIND_CALL=2;
5657

5758
//------------------------------------------------------------------------------
5859
// Typedefs
@@ -986,22 +987,13 @@ function createRuleListeners(rule, ruleContext) {
986987
*@param {string} physicalFilename The full path of the file on disk without any code block information
987988
*@param {Function} ruleFilter A predicate function to filter which rules should be executed.
988989
*@returns {LintMessage[]} An array of reported problems
990+
*@throws {Error} If traversal into a node fails.
989991
*/
990992
functionrunRules(sourceCode,configuredRules,ruleMapper,parserName,languageOptions,settings,filename,disableFixes,cwd,physicalFilename,ruleFilter){
991993
constemitter=createEmitter();
992-
constnodeQueue=[];
993-
letcurrentNode=sourceCode.ast;
994-
995-
Traverser.traverse(sourceCode.ast,{
996-
enter(node,parent){
997-
node.parent=parent;
998-
nodeQueue.push({isEntering:true, node});
999-
},
1000-
leave(node){
1001-
nodeQueue.push({isEntering:false, node});
1002-
},
1003-
visitorKeys:sourceCode.visitorKeys
1004-
});
994+
995+
// must happen first to assign all node.parent properties
996+
consteventQueue=sourceCode.traverse();
1005997

1006998
/*
1007999
* Create a frozen object with the ruleContext properties and methods that are shared by all rules.
@@ -1131,25 +1123,34 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
11311123
});
11321124
});
11331125

1134-
// only run code path analyzer if the top level node is "Program", skip otherwise
1135-
consteventGenerator=nodeQueue[0].node.type==="Program"
1136-
?newCodePathAnalyzer(newNodeEventGenerator(emitter,{visitorKeys:sourceCode.visitorKeys,fallback:Traverser.getKeys}))
1137-
:newNodeEventGenerator(emitter,{visitorKeys:sourceCode.visitorKeys,fallback:Traverser.getKeys});
1126+
consteventGenerator=newNodeEventGenerator(emitter,{visitorKeys:sourceCode.visitorKeys,fallback:Traverser.getKeys});
11381127

1139-
nodeQueue.forEach(traversalInfo=>{
1140-
currentNode=traversalInfo.node;
1128+
for(conststepofeventQueue){
1129+
switch(step.kind){
1130+
caseSTEP_KIND_VISIT:{
1131+
try{
1132+
if(step.phase===1){
1133+
eventGenerator.enterNode(step.target);
1134+
}else{
1135+
eventGenerator.leaveNode(step.target);
1136+
}
1137+
}catch(err){
1138+
err.currentNode=step.target;
1139+
throwerr;
1140+
}
1141+
break;
1142+
}
11411143

1142-
try{
1143-
if(traversalInfo.isEntering){
1144-
eventGenerator.enterNode(currentNode);
1145-
}else{
1146-
eventGenerator.leaveNode(currentNode);
1144+
caseSTEP_KIND_CALL:{
1145+
emitter.emit(step.target, ...step.args);
1146+
break;
11471147
}
1148-
}catch(err){
1149-
err.currentNode=currentNode;
1150-
throwerr;
1148+
1149+
default:
1150+
thrownewError(`Invalid traversal step found: "${step.type}".`);
11511151
}
1152-
});
1152+
1153+
}
11531154

11541155
returnlintingProblems;
11551156
}

‎lib/rules/constructor-super.js‎

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,30 @@ function isPossibleConstructor(node) {
119119
}
120120
}
121121

122+
/**
123+
* A class to store information about a code path segment.
124+
*/
125+
classSegmentInfo{
126+
127+
/**
128+
* Indicates if super() is called in all code paths.
129+
*@type {boolean}
130+
*/
131+
calledInEveryPaths=false;
132+
133+
/**
134+
* Indicates if super() is called in any code paths.
135+
*@type {boolean}
136+
*/
137+
calledInSomePaths=false;
138+
139+
/**
140+
* The nodes which have been validated and don't need to be reconsidered.
141+
*@type {ASTNode[]}
142+
*/
143+
validNodes=[];
144+
}
145+
122146
//------------------------------------------------------------------------------
123147
// Rule Definition
124148
//------------------------------------------------------------------------------
@@ -159,12 +183,8 @@ module.exports = {
159183
*/
160184
letfuncInfo=null;
161185

162-
/*
163-
* {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
164-
* Information for each code path segment.
165-
* - calledInSomePaths: A flag of be called `super()` in some code paths.
166-
* - calledInEveryPaths: A flag of be called `super()` in all code paths.
167-
* - validNodes:
186+
/**
187+
*@type {Record<string, SegmentInfo>}
168188
*/
169189
letsegInfoMap=Object.create(null);
170190

@@ -174,7 +194,16 @@ module.exports = {
174194
*@returns {boolean} The flag which shows `super()` is called in some paths
175195
*/
176196
functionisCalledInSomePath(segment){
177-
returnsegment.reachable&&segInfoMap[segment.id].calledInSomePaths;
197+
returnsegment.reachable&&segInfoMap[segment.id]?.calledInSomePaths;
198+
}
199+
200+
/**
201+
* Determines if a segment has been seen in the traversal.
202+
*@param {CodePathSegment} segment A code path segment to check.
203+
*@returns {boolean} `true` if the segment has been seen.
204+
*/
205+
functionhasSegmentBeenSeen(segment){
206+
return!!segInfoMap[segment.id];
178207
}
179208

180209
/**
@@ -190,10 +219,10 @@ module.exports = {
190219
* If not skipped, this never becomes true after a loop.
191220
*/
192221
if(segment.nextSegments.length===1&&
193-
segment.nextSegments[0].isLoopedPrevSegment(segment)
194-
){
222+
segment.nextSegments[0]?.isLoopedPrevSegment(segment)){
195223
returntrue;
196224
}
225+
197226
returnsegment.reachable&&segInfoMap[segment.id].calledInEveryPaths;
198227
}
199228

@@ -250,9 +279,9 @@ module.exports = {
250279
}
251280

252281
// Reports if `super()` lacked.
253-
constsegments=codePath.returnedSegments;
254-
constcalledInEveryPaths=segments.every(isCalledInEveryPath);
255-
constcalledInSomePaths=segments.some(isCalledInSomePath);
282+
constseenSegments=codePath.returnedSegments.filter(hasSegmentBeenSeen);
283+
constcalledInEveryPaths=seenSegments.every(isCalledInEveryPath);
284+
constcalledInSomePaths=seenSegments.some(isCalledInSomePath);
256285

257286
if(!calledInEveryPaths){
258287
context.report({
@@ -278,18 +307,16 @@ module.exports = {
278307
}
279308

280309
// Initialize info.
281-
constinfo=segInfoMap[segment.id]={
282-
calledInSomePaths:false,
283-
calledInEveryPaths:false,
284-
validNodes:[]
285-
};
310+
constinfo=segInfoMap[segment.id]=newSegmentInfo();
286311

287312
// When there are previous segments, aggregates these.
288313
constprevSegments=segment.prevSegments;
289314

290315
if(prevSegments.length>0){
291-
info.calledInSomePaths=prevSegments.some(isCalledInSomePath);
292-
info.calledInEveryPaths=prevSegments.every(isCalledInEveryPath);
316+
constseenPrevSegments=prevSegments.filter(hasSegmentBeenSeen);
317+
318+
info.calledInSomePaths=seenPrevSegments.some(isCalledInSomePath);
319+
info.calledInEveryPaths=seenPrevSegments.every(isCalledInEveryPath);
293320
}
294321
},
295322

@@ -326,12 +353,12 @@ module.exports = {
326353
funcInfo.codePath.traverseSegments(
327354
{first:toSegment,last:fromSegment},
328355
segment=>{
329-
constinfo=segInfoMap[segment.id];
330-
constprevSegments=segment.prevSegments;
356+
constinfo=segInfoMap[segment.id]??newSegmentInfo();
357+
constseenPrevSegments=segment.prevSegments.filter(hasSegmentBeenSeen);
331358

332359
// Updates flags.
333-
info.calledInSomePaths=prevSegments.some(isCalledInSomePath);
334-
info.calledInEveryPaths=prevSegments.every(isCalledInEveryPath);
360+
info.calledInSomePaths=seenPrevSegments.some(isCalledInSomePath);
361+
info.calledInEveryPaths=seenPrevSegments.every(isCalledInEveryPath);
335362

336363
// If flags become true anew, reports the valid nodes.
337364
if(info.calledInSomePaths||isRealLoop){
@@ -348,6 +375,9 @@ module.exports = {
348375
});
349376
}
350377
}
378+
379+
// save just in case we created a new SegmentInfo object
380+
segInfoMap[segment.id]=info;
351381
}
352382
);
353383
},

‎lib/rules/no-this-before-super.js‎

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,29 @@ function isConstructorFunction(node) {
3030
);
3131
}
3232

33+
/*
34+
* Information for each code path segment.
35+
* - superCalled: The flag which shows `super()` called in all code paths.
36+
* - invalidNodes: The array of invalid ThisExpression and Super nodes.
37+
*/
38+
/**
39+
*
40+
*/
41+
classSegmentInfo{
42+
43+
/**
44+
* Indicates whether `super()` is called in all code paths.
45+
*@type {boolean}
46+
*/
47+
superCalled=false;
48+
49+
/**
50+
* The array of invalid ThisExpression and Super nodes.
51+
*@type {ASTNode[]}
52+
*/
53+
invalidNodes=[];
54+
}
55+
3356
//------------------------------------------------------------------------------
3457
// Rule Definition
3558
//------------------------------------------------------------------------------
@@ -64,13 +87,7 @@ module.exports = {
6487
*/
6588
letfuncInfo=null;
6689

67-
/*
68-
* Information for each code path segment.
69-
* Each key is the id of a code path segment.
70-
* Each value is an object:
71-
* - superCalled: The flag which shows `super()` called in all code paths.
72-
* - invalidNodes: The array of invalid ThisExpression and Super nodes.
73-
*/
90+
/**@type {Record<string, SegmentInfo>} */
7491
letsegInfoMap=Object.create(null);
7592

7693
/**
@@ -79,7 +96,7 @@ module.exports = {
7996
*@returns {boolean} `true` if `super()` is called.
8097
*/
8198
functionisCalled(segment){
82-
return!segment.reachable||segInfoMap[segment.id].superCalled;
99+
return!segment.reachable||segInfoMap[segment.id]?.superCalled;
83100
}
84101

85102
/**
@@ -285,7 +302,7 @@ module.exports = {
285302
funcInfo.codePath.traverseSegments(
286303
{first:toSegment,last:fromSegment},
287304
(segment,controller)=>{
288-
constinfo=segInfoMap[segment.id];
305+
constinfo=segInfoMap[segment.id]??newSegmentInfo();
289306

290307
if(info.superCalled){
291308
controller.skip();
@@ -295,6 +312,8 @@ module.exports = {
295312
){
296313
info.superCalled=true;
297314
}
315+
316+
segInfoMap[segment.id]=info;
298317
}
299318
);
300319
},

‎lib/rules/no-useless-return.js‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ module.exports = {
146146
continue;
147147
}
148148

149-
uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns);
149+
if(segmentInfoMap.has(segment)){
150+
uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns);
151+
}
150152
}
151153

152154
returnuselessReturns;
@@ -182,6 +184,10 @@ module.exports = {
182184

183185
constinfo=segmentInfoMap.get(segment);
184186

187+
if(!info){
188+
return;
189+
}
190+
185191
info.uselessReturns=info.uselessReturns.filter(node=>{
186192
if(scopeInfo.traversedTryBlockStatements&&scopeInfo.traversedTryBlockStatements.length>0){
187193
constreturnInitialRange=node.range[0];
@@ -275,7 +281,6 @@ module.exports = {
275281
* NOTE: This event is notified for only reachable segments.
276282
*/
277283
onCodePathSegmentStart(segment){
278-
279284
scopeInfo.currentSegments.add(segment);
280285

281286
constinfo={

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp