Skip to content
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

Merged
merged 11 commits into from Mar 21, 2024
Merged

feat!: move AST traversal into SourceCode #18167

merged 11 commits into from Mar 21, 2024

Conversation

nzakas
Copy link
Member

@nzakas nzakas commented Mar 4, 2024

Prerequisites checklist

What is the purpose of this pull request? (put an "X" next to an item)

[ ] Documentation update
[ ] Bug fix (template)
[ ] New rule (template)
[ ] Changes an existing rule (template)
[ ] Add autofix to a rule
[ ] Add a CLI option
[x] Add something to the core
[ ] Other, please explain:

What changes did you make? (Give an overview)

  • Moved AST traversal out of Linter and into SourceCode#traverse().
  • Updated no-useless-returns, constructor-super, and no-this-before-super to they still work. These rules were also tested against v8.57.0 to ensure that such changes would be backwards compatible.
  • Updated the v9 migration guide

Refs #16999

Is there anything you'd like reviewers to focus on?

I deviated from the RFC in how I represented the traversal steps. I realized while implementing that relying on type and phase as string comparisons would be a big performance hit, so I added the kind property as a number (keeping type for easier debugging, though not sure if that's needed) and switched phase to a number. That saves us a lot of string comparisons during traversal.

@nzakas nzakas requested a review from a team as a code owner March 4, 2024 18:53
@eslint-github-bot eslint-github-bot bot added breaking This change is backwards-incompatible feature This change adds a new feature to ESLint labels Mar 4, 2024
@nzakas nzakas added the tsc agenda This issue will be discussed by ESLint's TSC at the next meeting label Mar 4, 2024
Copy link

netlify bot commented Mar 4, 2024

Deploy Preview for docs-eslint canceled.

Name Link
🔨 Latest commit 63ee6bd
🔍 Latest deploy log https://app.netlify.com/sites/docs-eslint/deploys/65f8b2f67f0959000850f5ef

@github-actions github-actions bot added rule Relates to ESLint's core rules core Relates to ESLint's core APIs and features labels Mar 4, 2024
@nzakas
Copy link
Member Author

nzakas commented Mar 4, 2024

TSC Summary: As part of this work, we are moving the AST traversal from Linter into SourceCode. For normal AST traversal, this works exactly the same way; for code path analysis, there is actually a difference. With the traversal in Linter, the code paths are incomplete during the traversal and are only completed upon complete traversal of the AST. That means CodePathSegment fields like nextSegments and prevSegments would be empty when onCodePathSegment is called with Linter traversal, whereas they would be filled with SourceCode traversal. Rules relying on the Linter-based traversal of incomplete code path data might not work correctly when all data is present.

In our codebase we have three rules that broke: no-useless-returns, no-this-before-super, and super-constructor.

TSC Question: This would be another breaking change for v9.0.0. Are we okay with that?

Comment on lines 1074 to 1088
enterNode(node, parent) {
steps.push(new TraversalStep({
type: "visit",
target: node,
phase: 1,
args: [node, parent]
}));
},
leaveNode(node, parent) {
steps.push(new TraversalStep({
type: "visit",
target: node,
phase: 2,
args: [node, parent]
}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions:

  1. Do these enterNode and leaveNode ever get called with the parent argument?
  2. Are we using args property of type: "visit" steps anywhere?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good questions. I need to look into it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The answers are:

  1. No, they don't, so I'll update that.
  2. We aren't using args right now because everything is going through NodeEventGenerator, but I'd still like to keep these here as the foundation for the rewrite.

Comment on lines +1123 to +1135
Traverser.traverse(this.ast, {
enter(node, parent) {

// save the parent node on a property for backwards compatibility
node.parent = parent;

analyzer.enterNode(node);
},
leave(node) {
analyzer.leaveNode(node);
},
visitorKeys: this.visitorKeys
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure that the current implementation of code path analysis doesn't expect parent property to be pre-set on all nodes (including not yet traversed ones)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can only go by all of the tests that are passing. I'm assuming if there was a timing issue, that something would break.


**To address:** If you are accessing any array properties on `CodePath` or `CodePathSegment`, you'll need to update your code. Specifically:

* If you are checking the `length` of any array properties, ensure you are using relative comparison operators like `<`, `>`, `<=`, and `>=` instead of equals.
Copy link
Member

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).

Copy link
Member Author

@nzakas nzakas Mar 7, 2024

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.

Comment on lines 192 to 212
if (segment.nextSegments.length === 1 &&
if (segment.nextSegments.length &&
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I comment out this condition altogether, these valid tests fails:

class A extends B { constructor(a) { super(); for (const b of a) { this.a(); } } }:

class A extends Object {
    constructor() {
        super();
        for (let i = 0; i < 0; i++);
    }
}

class A extends B {
    constructor(props) {
        super(props);

        try {
            let arr = [];        
            for (let a of arr) { 
            }
        } catch (err) {
        }
    }
}

So it seems like it's designed to catch loops that occur after super().

I couldn't figure out anything else about how this rule works, though, so any ideas would be greatly appreciated.

@sam3k sam3k removed the tsc agenda This issue will be discussed by ESLint's TSC at the next meeting label Mar 11, 2024
@sam3k
Copy link
Contributor

sam3k commented Mar 11, 2024

Per 2024-03-07 TSC meeting, this will be included in v9.

@mdjermanovic mdjermanovic added the accepted There is consensus among the team that this change meets the criteria for inclusion label Mar 21, 2024
@nzakas nzakas self-assigned this Mar 21, 2024
Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@mdjermanovic mdjermanovic merged commit 09bd7fe into main Mar 21, 2024
19 checks passed
@mdjermanovic mdjermanovic deleted the traverse branch March 21, 2024 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion breaking This change is backwards-incompatible core Relates to ESLint's core APIs and features feature This change adds a new feature to ESLint rule Relates to ESLint's core rules
Projects
Status: Complete
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

3 participants