Skip to content

Change Request: Update RuleVisitor interface #134

@nzakas

Description

@nzakas

Which packages would you like to change?

  • @eslint/compat
  • @eslint/config-array
  • @eslint/core
  • @eslint/migrate-config
  • @eslint/object-schema
  • @eslint/plugin-kit

What problem do you want to solve?

The RuleVisitor interface is currently defined as follows:

export interface RuleVisitor {
	/**
	 * Called for each node in the AST or at specific times during the traversal.
	 */
	[key: string]: (...args: any[]) => void;
}

However, this is causing problems when trying to extend RuleVisitor under strict TypeScript mode, as seen in these PRs:

The errors take the form of the following:

error TS2411: Property 'Document' of type '((node: DocumentNode$1) => void) | undefined' is not assignable to 'string' index type '(...args: any[]) => void'.

What do you think is the correct solution?

The problem is that the method defined in RuleVisitor is not optional.

I can see a few different options:

  1. Remove all methods. We can remove any predefined methods from RuleVisitor and leave essentially a blank interface:
export interface RuleVisitor {
	/**
	 * Called for each node in the AST or at specific times during the traversal.
	 */
-	[key: string]: (...args: any[]) => void;
}

That would give consumers something to extend from so that it logically looks like there's some relationship between the core types and consumer types. I'm not sure it's valuable.

  1. Convert to a mapped type. We can change it to a mapped type as suggested by @fasttime here:
- export interface RuleVisitor {
+ export type RuleVisitor = {
	/**
	 * Called for each node in the AST or at specific times during the traversal.
	 */
-	[key: string]: (...args: any[]) => void;
+	[key in string]?: (...args: any[]) => void;
}

Defining all methods to be optional also seems to make RuleVisitor mostly useless.

  1. Eliminate RuleVisitor interface completely. Because we don't know what the visitor will look like for any particular language, maybe it's a bad idea to try and enforce anything in particular. We could just define the RuleDefinitionTypeOptions#Visitor as Object, which would allow any non-primitive type to be used, and then we don't need RuleVisitor at all:
export interface RuleDefinitionTypeOptions {
	LangOptions: LanguageOptions;
	Code: SourceCode;
	RuleOptions: unknown[];
-	Visitor: RuleVisitor;
+	Visitor: Object;
	Node: unknown;
	MessageIds: string;
	ExtRuleDocs: unknown;
}

I think this is my favorite approach, but I'd like some insights from @JoshuaKGoldberg and @fasttime on all of the options and their ramifications.

Participation

  • I am willing to submit a pull request for this change.

Additional comments

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    Complete

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions