diff --git a/packages/plugin-kit/src/source-code.js b/packages/plugin-kit/src/source-code.js index c02da02f3..659626cb1 100644 --- a/packages/plugin-kit/src/source-code.js +++ b/packages/plugin-kit/src/source-code.js @@ -22,6 +22,11 @@ * @typedef {import("@eslint/core").TextSourceCode} TextSourceCode * @template {SourceCodeBaseTypeOptions} [Options=SourceCodeBaseTypeOptions] */ +/** @typedef {import("@eslint/core").RuleVisitor} RuleVisitor */ +/** + * @typedef {import("./types.ts").CustomRuleVisitorWithExit} CustomRuleVisitorWithExit + * @template {RuleVisitor} RuleVisitorType + */ //----------------------------------------------------------------------------- // Helpers diff --git a/packages/plugin-kit/src/types.ts b/packages/plugin-kit/src/types.ts index d3f6a888d..50c7c8d9a 100644 --- a/packages/plugin-kit/src/types.ts +++ b/packages/plugin-kit/src/types.ts @@ -3,5 +3,31 @@ * @author Nicholas C. Zakas */ +//------------------------------------------------------------------------------ +// Imports +//------------------------------------------------------------------------------ + +import type { RuleVisitor } from "@eslint/core"; + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * Adds matching `:exit` selector properties for each key of a `RuleVisitor`. + */ +export type CustomRuleVisitorWithExit = { + [Key in keyof RuleVisitorType as + | Key + | `${Key & string}:exit`]: RuleVisitorType[Key]; +}; + +/** + * A map of names to string values, or `null` when no value is provided. + */ export type StringConfig = Record; + +/** + * A map of names to boolean flags. + */ export type BooleanConfig = Record; diff --git a/packages/plugin-kit/tests/types/types.test.ts b/packages/plugin-kit/tests/types/types.test.ts index 0f2eaa0de..3580f3fa6 100644 --- a/packages/plugin-kit/tests/types/types.test.ts +++ b/packages/plugin-kit/tests/types/types.test.ts @@ -8,15 +8,16 @@ //----------------------------------------------------------------------------- import { - BooleanConfig, + type BooleanConfig, CallMethodStep, ConfigCommentParser, + type CustomRuleVisitorWithExit, Directive, - DirectiveType, - RulesConfig, - SourceLocation, - SourceRange, - StringConfig, + type DirectiveType, + type RulesConfig, + type SourceLocation, + type SourceRange, + type StringConfig, TextSourceCodeBase, VisitNodeStep, } from "@eslint/plugin-kit"; @@ -182,3 +183,41 @@ step1.kind satisfies 1; step1.phase satisfies 1 | 2; step1.target satisfies object; step1.type satisfies "visit"; + +type TestVisitor = { + Program: (node: { type: "Program" }) => void; + Identifier: (node: { type: "Identifier"; name: string }) => void; + "FunctionDeclaration > Identifier": (node: { type: "Identifier" }) => void; +}; + +type VisitorWithExit = CustomRuleVisitorWithExit; + +const visitor: VisitorWithExit = { + Program(node) { + node.type satisfies "Program"; + }, + Identifier(node) { + node.type satisfies "Identifier"; + node.name satisfies string; + }, + "FunctionDeclaration > Identifier"(node) { + node.type satisfies "Identifier"; + }, + "Program:exit"(node) { + node.type satisfies "Program"; + }, + "Identifier:exit"(node) { + node.type satisfies "Identifier"; + node.name satisfies string; + }, + "FunctionDeclaration > Identifier:exit"(node) { + node.type satisfies "Identifier"; + }, + // @ts-expect-error -- Extra keys should not be allowed + Foo() {}, +}; + +visitor.Program satisfies TestVisitor["Program"]; +visitor["Program:exit"] satisfies TestVisitor["Program"]; +// @ts-expect-error -- Exit key must correspond to an existing selector +visitor["Expression:exit"] = () => {}; diff --git a/tools/build-cts.js b/tools/build-cts.js index 35a976bb8..bed0ae74a 100644 --- a/tools/build-cts.js +++ b/tools/build-cts.js @@ -24,9 +24,6 @@ if (!newFilename) { } const oldSourceText = await readFile(filename, "utf-8"); -const newSourceText = oldSourceText.replaceAll( - ' from "./types.ts";\n', - ' from "./types.cts";\n', -); +const newSourceText = oldSourceText.replaceAll('"./types.ts"', '"./types.cts"'); await writeFile(newFilename, newSourceText);