Skip to content

Conversation

@fasttime
Copy link
Member

@fasttime fasttime commented Oct 24, 2025

Prerequisites checklist

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

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

What did you do? Please include the actual source code causing the issue.

import type { AST, Rule } from 'eslint';

function getAllComments(node: Rule.Node): AST.Token[] {
    while (node.parent) {
        node = node.parent;
    }
    return node.comments;
}

Repro

What did you expect to happen?

The above TypeScript code should compile.

What actually happened? Please include the actual, raw output from ESLint.

A compiler error:

error TS2339: Property 'comments' does not exist on type 'Node'.
  Property 'comments' does not exist on type 'AssignmentProperty & NodeParentExtension'.

7   return node.comments;
                ~~~~~~~~

What changes did you make? (Give an overview)

This PR changes the type of the Program node in the Rule.Node union from ESTree.Program to AST.Program & { parent: null; }. This add the properties comments and tokens that were previously missing and changes the type of the parent property from Rule.Node to null.

I also updated and simplified Rule.NodeListener with the updated Rule.Node union using a mapped type. This change also fixes another unrelated bug in the type of the CallExpression visitor, exemplified in the code below:

import type { JSRuleDefinition } from 'eslint';

const rule: JSRuleDefinition = {
  create() {
    return {
      CallExpression(node) {
        node.type satisfies 'CallExpression';
      },
    };
  },
};

This currently fails with a compiler error:

error TS1360: Type '"CallExpression" | "NewExpression"' does not satisfy the expected type '"CallExpression"'.
  Type '"NewExpression"' is not assignable to type '"CallExpression"'.

7         node.type satisfies 'CallExpression';

The reason is that ESTree.CallExpression is confusingly defined as union of nodes where type can be one of CallExpression or NewExpression (reference):

export type CallExpression = SimpleCallExpression | NewExpression;

export interface SimpleCallExpression extends BaseCallExpression {
    type: "CallExpression";
    optional: boolean;
}

export interface NewExpression extends BaseCallExpression {
    type: "NewExpression";
}

Repro

The new definition avoids such bugs by ensuring that a visitor's key and node type are identical for all standard nodes.

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

@github-project-automation github-project-automation bot moved this to Needs Triage in Triage Oct 24, 2025
@eslint-github-bot eslint-github-bot bot added the bug ESLint is working incorrectly label Oct 24, 2025
@netlify
Copy link

netlify bot commented Oct 24, 2025

Deploy Preview for docs-eslint ready!

Name Link
🔨 Latest commit 1390a45
🔍 Latest deploy log https://app.netlify.com/projects/docs-eslint/deploys/68fb5f8a113efd000899cb52
😎 Deploy Preview https://deploy-preview-20244--docs-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment on lines +68 to +73
/** Adds matching `:exit` selectors for all properties of a `RuleVisitor`. */
type WithExit<RuleVisitorType extends RuleVisitor> = {
[Key in keyof RuleVisitorType as
| Key
| `${Key & string}:exit`]: RuleVisitorType[Key];
};
Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines +675 to +676
// A `Program` visitor's node type has no `parent` property.
Program?: ((node: AST.Program) => void) | undefined;
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 Program visitor type prevents access to the parent property of the node (which is null). This is covered by tests:

Program(node) {
// @ts-expect-error
node.parent;
},

This PR maintains the current behavior, but we could consider changing it, since parent is always set for the JS language.

@fasttime fasttime added the types Related to TypeScript types label Oct 24, 2025
@fasttime fasttime marked this pull request as ready for review October 24, 2025 12:59
@fasttime fasttime requested a review from a team as a code owner October 24, 2025 12:59
Copy link
Contributor

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

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

👏 nice! I really like the dynamic :exit type, that's a great use for a mapped type that adds properties.

typescript-eslint's done some work around parent types too that might be useful for reference:

Leaving open for >=1 other review as these are tricky API portions.

@JoshuaKGoldberg JoshuaKGoldberg moved this from Needs Triage to Second Review Needed in Triage Oct 24, 2025
@lumirlumir lumirlumir added the accepted There is consensus among the team that this change meets the criteria for inclusion label Oct 27, 2025
Copy link
Member

@lumirlumir lumirlumir 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!

Would like another review before merging.

Copy link
Member

@nzakas nzakas 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!

@nzakas nzakas merged commit 37f76d9 into main Oct 29, 2025
32 checks passed
@nzakas nzakas deleted the fix-node-type branch October 29, 2025 15:02
@github-project-automation github-project-automation bot moved this from Second Review Needed to Complete in Triage Oct 29, 2025
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 bug ESLint is working incorrectly types Related to TypeScript types

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

5 participants