diff --git a/packages/core/package.json b/packages/core/package.json index 130edde..3708043 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -36,6 +36,7 @@ }, "homepage": "https://github.com/eslint/rewrite#readme", "devDependencies": { + "json-schema": "^0.4.0", "typescript": "^5.4.5" }, "engines": { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 3514e6a..9d2447c 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -2,6 +2,12 @@ * @fileoverview Shared types for ESLint Core. */ +//------------------------------------------------------------------------------ +// Imports +//------------------------------------------------------------------------------ + +import { JSONSchema4 } from "json-schema"; + //------------------------------------------------------------------------------ // Helper Types //------------------------------------------------------------------------------ @@ -30,14 +36,6 @@ export interface FileProblem { // ASTs //------------------------------------------------------------------------------ -/** - * Represents an AST node or token with location information in ESLint format. - */ -export interface SyntaxElement { - loc: SourceLocation; - range: SourceRange; -} - /** * Represents the start and end coordinates of a node inside the source. */ @@ -75,6 +73,407 @@ export interface PositionWithOffset extends Position { */ export type SourceRange = [number, number]; +//------------------------------------------------------------------------------ +// Rules +//------------------------------------------------------------------------------ + +/** + * What the rule is responsible for finding: + * - `problem` means the rule has noticed a potential error. + * - `suggestion` means the rule suggests an alternate or better approach. + * - `layout` means the rule is looking at spacing, indentation, etc. + */ +export type RuleType = "problem" | "suggestion" | "layout"; + +/** + * The type of fix the rule can provide: + * - `code` means the rule can fix syntax. + * - `whitespace` means the rule can fix spacing and indentation. + */ +export type RuleFixType = "code" | "whitespace"; + +/* eslint-disable @typescript-eslint/consistent-indexed-object-style -- Needs to be interface so people can extend it. */ +/* eslint-disable @typescript-eslint/no-explicit-any -- Necessary to allow subclasses to work correctly */ +/** + * An object containing visitor information for a rule. Each method is either the + * name of a node type or a selector, or is a method that will be called at specific + * times during the traversal. + */ +export interface RuleVisitor { + /** + * Called for each node in the AST or at specific times during the traversal. + */ + [key: string]: (...args: any[]) => void; +} +/* eslint-enable @typescript-eslint/consistent-indexed-object-style -- Needs to be interface so people can extend it. */ +/* eslint-enable @typescript-eslint/no-explicit-any -- Necessary to allow subclasses to work correctly */ + +/** + * Rule meta information used for documentation. + */ +export interface RulesMetaDocs { + /** + * A short description of the rule. + */ + description?: string | undefined; + + /** + * The URL to the documentation for the rule. + */ + url?: string | undefined; + + /** + * The category the rule falls under. + * @deprecated No longer used. + */ + category?: string | undefined; + + /** + * Indicates if the rule is generally recommended for all users. + */ + recommended?: boolean | undefined; +} + +/** + * Meta information about a rule. + */ +export interface RulesMeta< + MessageIds extends string = string, + ExtRuleDocs = unknown, +> { + /** + * Properties that are used when documenting the rule. + */ + docs?: (RulesMetaDocs & ExtRuleDocs) | undefined; + + /** + * The type of rule. + */ + type?: RuleType | undefined; + + /** + * The schema for the rule options. Required if the rule has options. + */ + schema?: JSONSchema4 | JSONSchema4[] | false | undefined; + + /** + * The messages that the rule can report. + */ + messages?: Record; + + /** + * The deprecated rules for the rule. + */ + deprecated?: boolean | undefined; + + /** + * When a rule is deprecated, indicates the rule ID(s) that should be used instead. + */ + replacedBy?: string[] | undefined; + + /** + * Indicates if the rule is fixable, and if so, what type of fix it provides. + */ + fixable?: RuleFixType | undefined; + + /** + * Indicates if the rule may provide suggestions. + */ + hasSuggestions?: boolean | undefined; +} + +/** + * Generic type for `RuleContext`. + */ +export interface RuleContextTypeOptions { + LangOptions?: LanguageOptions; + Code?: SourceCode; + RuleOptions?: unknown[]; + Node?: unknown; +} + +/** + * Represents the context object that is passed to a rule. This object contains + * information about the current state of the linting process and is the rule's + * view into the outside world. + */ +export interface RuleContext< + Options extends RuleContextTypeOptions = { + LangOptions: LanguageOptions; + Code: SourceCode; + RuleOptions: unknown[]; + Node: unknown; + }, +> { + /** + * The current working directory for the session. + */ + cwd: string; + + /** + * Returns the current working directory for the session. + * @deprecated Use `cwd` instead. + */ + getCwd(): string; + + /** + * The filename of the file being linted. + */ + filename: string; + + /** + * Returns the filename of the file being linted. + * @deprecated Use `filename` instead. + */ + getFilename(): string; + + /** + * The physical filename of the file being linted. + */ + physicalFilename: string; + + /** + * Returns the physical filename of the file being linted. + * @deprecated Use `physicalFilename` instead. + */ + getPhysicalFilename(): string; + + /** + * The source code object that the rule is running on. + */ + sourceCode: Options["Code"]; + + /** + * Returns the source code object that the rule is running on. + * @deprecated Use `sourceCode` instead. + */ + getSourceCode(): Options["Code"]; + + /** + * Shared settings for the configuration. + */ + settings: SettingsConfig; + + /** + * Parser-specific options for the configuration. + * @deprecated Use `languageOptions.parserOptions` instead. + */ + parserOptions: Record; + + /** + * The language options for the configuration. + */ + languageOptions: Options["LangOptions"]; + + /** + * The CommonJS path to the parser used while parsing this file. + * @deprecated No longer used. + */ + parserPath: string | undefined; + + /** + * The rule ID. + */ + id: string; + + /** + * The rule's configured options. + */ + options: Options["RuleOptions"]; + + /** + * The report function that the rule should use to report problems. + * @param violation The violation to report. + */ + report(violation: ViolationReport): void; +} + +// #region Rule Fixing + +/** + * Manager of text edits for a rule fix. + */ +export interface RuleTextEditor { + /** + * Inserts text after the specified node or token. + * @param syntaxElement The node or token to insert after. + * @param text The edit to insert after the node or token. + */ + insertTextAfter( + syntaxElement: EditableSyntaxElement, + text: string, + ): RuleTextEdit; + + /** + * Inserts text after the specified range. + * @param range The range to insert after. + * @param text The edit to insert after the range. + */ + insertTextAfterRange(range: SourceRange, text: string): RuleTextEdit; + + /** + * Inserts text before the specified node or token. + * @param syntaxElement A syntax element with location information to insert before. + * @param text The edit to insert before the node or token. + */ + insertTextBefore( + syntaxElement: EditableSyntaxElement, + text: string, + ): RuleTextEdit; + + /** + * Inserts text before the specified range. + * @param range The range to insert before. + * @param text The edit to insert before the range. + */ + insertTextBeforeRange(range: SourceRange, text: string): RuleTextEdit; + + /** + * Removes the specified node or token. + * @param syntaxElement A syntax element with location information to remove. + * @returns The edit to remove the node or token. + */ + remove(syntaxElement: EditableSyntaxElement): RuleTextEdit; + + /** + * Removes the specified range. + * @param range The range to remove. + * @returns The edit to remove the range. + */ + removeRange(range: SourceRange): RuleTextEdit; + + /** + * Replaces the specified node or token with the given text. + * @param syntaxElement A syntax element with location information to replace. + * @param text The text to replace the node or token with. + * @returns The edit to replace the node or token. + */ + replaceText( + syntaxElement: EditableSyntaxElement, + text: string, + ): RuleTextEdit; + + /** + * Replaces the specified range with the given text. + * @param range The range to replace. + * @param text The text to replace the range with. + * @returns The edit to replace the range. + */ + replaceTextRange(range: SourceRange, text: string): RuleTextEdit; +} + +/** + * Represents a fix for a rule violation implemented as a text edit. + */ +export interface RuleTextEdit { + /** + * The range to replace. + */ + range: SourceRange; + + /** + * The text to insert. + */ + text: string; +} + +// #endregion + +interface ViolationReportBase { + /** + * The type of node that the violation is for. + * @deprecated May be removed in the future. + */ + nodeType?: string | undefined; + + /** + * The data to insert into the message. + */ + data?: Record | undefined; + + /** + * The fix to be applied for the violation. + * @param fixer The text editor to apply the fix. + * @returns The fix(es) for the violation. + */ + fix?(fixer: RuleTextEditor): RuleTextEdit | Iterable | null; + + /** + * An array of suggested fixes for the problem. These fixes may change the + * behavior of the code, so they are not applied automatically. + */ + suggest?: SuggestedEdit[]; +} + +type ViolationMessage = { message: string } | { messageId: string }; +type ViolationLocation = { loc: SourceLocation } | { node: Node }; + +export type ViolationReport = ViolationReportBase & + ViolationMessage & + ViolationLocation; + +// #region Suggestions + +interface SuggestedEditBase { + /** + * The data to insert into the message. + */ + data?: Record | undefined; + + /** + * The fix to be applied for the suggestion. + * @param fixer The text editor to apply the fix. + * @returns The fix for the suggestion. + */ + fix?(fixer: RuleTextEditor): RuleTextEdit | Iterable | null; +} + +type SuggestionMessage = { desc: string } | { messageId: string }; + +/** + * A suggested edit for a rule violation. + */ +export type SuggestedEdit = SuggestedEditBase & SuggestionMessage; + +// #endregion + +/** + * Generic options for the `RuleDefinition` type. + */ +export interface RuleDefinitionTypeOptions { + LangOptions?: LanguageOptions; + Code?: SourceCode; + RuleOptions?: unknown[]; + Visitor?: RuleVisitor; + Node?: unknown; + MessageIds?: string; + ExtRuleDocs?: unknown; +} + +/** + * The definition of an ESLint rule. + */ +export interface RuleDefinition { + /** + * The meta information for the rule. + */ + meta?: RulesMeta; + + /** + * Creates the visitor that ESLint uses to apply the rule during traversal. + * @param context The rule context. + * @returns The rule visitor. + */ + create( + context: RuleContext<{ + LangOptions: Options["LangOptions"]; + Code: Options["Code"]; + RuleOptions: Options["RuleOptions"]; + Node: Options["Node"]; + }>, + ): Options["Visitor"]; +} + //------------------------------------------------------------------------------ // Config //------------------------------------------------------------------------------ @@ -90,7 +489,6 @@ export type SeverityName = "off" | "warn" | "error"; * - `0` means off. * - `1` means warn. * - `2` means error. - * */ export type SeverityLevel = 0 | 1 | 2; @@ -133,10 +531,27 @@ export type RulesConfig = Record; // Languages //------------------------------------------------------------------------------ +/** + * Generic options for the `Language` type. + */ +export interface LanguageTypeOptions { + LangOptions?: LanguageOptions; + Code?: SourceCode; + RootNode?: unknown; + Node: unknown; +} + /** * Represents a plugin language. */ -export interface Language { +export interface Language< + Options extends LanguageTypeOptions = { + LangOptions: LanguageOptions; + Code: SourceCode; + RootNode: unknown; + Node: unknown; + }, +> { /** * Indicates how ESLint should read the file. */ @@ -170,7 +585,7 @@ export interface Language { /** * Validates languageOptions for this language. */ - validateLanguageOptions(languageOptions: LanguageOptions): void; + validateLanguageOptions(languageOptions: Options["LangOptions"]): void; /** * Helper for esquery that allows languages to match nodes against @@ -180,8 +595,8 @@ export interface Language { */ matchesSelectorClass?( className: string, - node: object, - ancestry: object[], + node: Options["Node"], + ancestry: Options["Node"][], ): boolean; /** @@ -189,16 +604,19 @@ export interface Language { * throws errors for parsing errors but rather should return any parsing * errors as parse of the ParseResult object. */ - parse(file: File, context: LanguageContext): ParseResult; // Future: | Promise; + parse( + file: File, + context: LanguageContext, + ): ParseResult; // Future: | Promise; /** * Creates SourceCode object that ESLint uses to work with a file. */ createSourceCode( file: File, - input: OkParseResult, - context: LanguageContext, - ): SourceCode; // Future: | Promise; + input: OkParseResult, + context: LanguageContext, + ): Options["Code"]; // Future: | Promise; } /** @@ -209,8 +627,8 @@ export type LanguageOptions = Record; /** * The context object that is passed to the language plugin methods. */ -export interface LanguageContext { - languageOptions: LanguageOptions; +export interface LanguageContext { + languageOptions: LangOptions; } /** @@ -245,7 +663,7 @@ export interface File { /** * Represents the successful result of parsing a file. */ -export interface OkParseResult { +export interface OkParseResult { /** * Indicates if the parse was successful. If true, the parse was successful * and ESLint should continue on to create a SourceCode object and run rules; @@ -257,7 +675,7 @@ export interface OkParseResult { /** * The abstract syntax tree created by the parser. (only when ok: true) */ - ast: T; + ast: RootNode; /** * Any additional data that the parser wants to provide. @@ -290,8 +708,8 @@ export interface NotOkParseResult { [key: string]: any; } -export type ParseResult = - | OkParseResult +export type ParseResult = + | OkParseResult | NotOkParseResult; /** @@ -311,14 +729,31 @@ interface InlineConfigElement { }; } +/** + * Generic options for the `SourceCodeBase` type. + */ +interface SourceCodeBaseTypeOptions { + LangOptions?: LanguageOptions; + RootNode?: unknown; + SyntaxElementWithLoc?: unknown; + ConfigNode?: unknown; +} + /** * Represents the basic interface for a source code object. */ -interface SourceCodeBase { +interface SourceCodeBase< + Options extends SourceCodeBaseTypeOptions = { + LangOptions: LanguageOptions; + RootNode: unknown; + SyntaxElementWithLoc: unknown; + ConfigNode: unknown; + }, +> { /** * Root of the AST. */ - ast: object; + ast: Options["RootNode"]; /** * The traversal path that tools should take when evaluating the AST. @@ -329,13 +764,17 @@ interface SourceCodeBase { /** * Retrieves the equivalent of `loc` for a given node or token. + * @param syntaxElement The node or token to get the location for. + * @returns The location of the node or token. */ - getLoc(nodeOrToken: object): SourceLocation; + getLoc(syntaxElement: Options["SyntaxElementWithLoc"]): SourceLocation; /** * Retrieves the equivalent of `range` for a given node or token. + * @param syntaxElement The node or token to get the range for. + * @returns The range of the node or token. */ - getRange(nodeOrToken: object): SourceRange; + getRange(syntaxElement: Options["SyntaxElementWithLoc"]): SourceRange; /** * Traversal of AST. @@ -345,7 +784,7 @@ interface SourceCodeBase { /** * Applies language options passed in from the ESLint core. */ - applyLanguageOptions?(languageOptions: LanguageOptions): void; + applyLanguageOptions?(languageOptions: Options["LangOptions"]): void; /** * Return all of the inline areas where ESLint should be disabled/enabled @@ -360,7 +799,7 @@ interface SourceCodeBase { * Returns an array of all inline configuration nodes found in the * source code. */ - getInlineConfigNodes?(): object[]; + getInlineConfigNodes?(): Options["ConfigNode"][]; /** * Applies configuration found inside of the source code. This method is only @@ -383,7 +822,14 @@ interface SourceCodeBase { /** * Represents the source of a text file being linted. */ -export interface TextSourceCode extends SourceCodeBase { +export interface TextSourceCode< + Options extends SourceCodeBaseTypeOptions = { + LangOptions: LanguageOptions; + RootNode: unknown; + SyntaxElementWithLoc: unknown; + ConfigNode: unknown; + }, +> extends SourceCodeBase { /** * The body of the file that you'd like rule developers to access. */ @@ -393,14 +839,28 @@ export interface TextSourceCode extends SourceCodeBase { /** * Represents the source of a binary file being linted. */ -export interface BinarySourceCode extends SourceCodeBase { +export interface BinarySourceCode< + Options extends SourceCodeBaseTypeOptions = { + LangOptions: LanguageOptions; + RootNode: unknown; + SyntaxElementWithLoc: unknown; + ConfigNode: unknown; + }, +> extends SourceCodeBase { /** * The body of the file that you'd like rule developers to access. */ body: Uint8Array; } -export type SourceCode = TextSourceCode | BinarySourceCode; +export type SourceCode< + Options extends SourceCodeBaseTypeOptions = { + LangOptions: LanguageOptions; + RootNode: unknown; + SyntaxElementWithLoc: unknown; + ConfigNode: unknown; + }, +> = TextSourceCode | BinarySourceCode; /** * Represents a traversal step visiting the AST.