From 257360f94ed8c6894f9afbb6c018c0cf18097bdd Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:22:14 +0000 Subject: [PATCH] fix(linter/plugins): fill in TS type def for `RuleMeta` (#15629) Fill out the TS type def for `RuleMeta`, based on [ESLint's docs](https://eslint.org/docs/latest/extend/custom-rules). Most of the properties can be ignored at runtime in Oxlint (they're primarily useful for generating docs), but this has also surfaced a few features we need to implement. These are marked with "TODO" comments, and I'll also open issues for them shortly. --- apps/oxlint/src-js/index.ts | 9 +- apps/oxlint/src-js/plugins/load.ts | 3 +- apps/oxlint/src-js/plugins/rule_meta.ts | 170 ++++++++++++++++++++++++ apps/oxlint/src-js/plugins/types.ts | 8 -- 4 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 apps/oxlint/src-js/plugins/rule_meta.ts diff --git a/apps/oxlint/src-js/index.ts b/apps/oxlint/src-js/index.ts index d7ea5e8166f7c..46b2e1ec57da8 100644 --- a/apps/oxlint/src-js/index.ts +++ b/apps/oxlint/src-js/index.ts @@ -25,6 +25,14 @@ export type { } from './plugins/scope.ts'; export type { SourceCode } from './plugins/source_code.ts'; export type { CountOptions, FilterFn, RangeOptions, SkipOptions } from './plugins/tokens.ts'; +export type { + RuleMeta, + RuleDocs, + RuleOptionsSchema, + RuleDeprecatedInfo, + RuleReplacedByInfo, + RuleReplacedByExternalSpecifier, +} from './plugins/rule_meta.ts'; export type { AfterHook, BeforeHook, @@ -35,7 +43,6 @@ export type { NodeOrToken, Range, Ranged, - RuleMeta, Span, Token, Visitor, diff --git a/apps/oxlint/src-js/plugins/load.ts b/apps/oxlint/src-js/plugins/load.ts index 477b5567c21fa..20069a1b5969b 100644 --- a/apps/oxlint/src-js/plugins/load.ts +++ b/apps/oxlint/src-js/plugins/load.ts @@ -5,7 +5,8 @@ import { getErrorMessage } from './utils.js'; import type { Writable } from 'type-fest'; import type { Context } from './context.ts'; -import type { AfterHook, BeforeHook, RuleMeta, Visitor, VisitorWithHooks } from './types.ts'; +import type { RuleMeta } from './rule_meta.ts'; +import type { AfterHook, BeforeHook, Visitor, VisitorWithHooks } from './types.ts'; const ObjectKeys = Object.keys; diff --git a/apps/oxlint/src-js/plugins/rule_meta.ts b/apps/oxlint/src-js/plugins/rule_meta.ts new file mode 100644 index 0000000000000..8c30a87a71b73 --- /dev/null +++ b/apps/oxlint/src-js/plugins/rule_meta.ts @@ -0,0 +1,170 @@ +/** + * Rule metadata. + * `meta` property of `Rule`. + */ +export interface RuleMeta { + /** + * Type of rule. + * + * - `problem`: The rule is identifying code that either will cause an error or may cause a confusing behavior. + * Developers should consider this a high priority to resolve. + * - `suggestion`: The rule is identifying something that could be done in a better way but no errors will occur + * if the code isn’t changed. + * - `layout`: The rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts + * of the program that determine how the code looks rather than how it executes. + * These rules work on parts of the code that aren’t specified in the AST. + */ + type?: 'problem' | 'suggestion' | 'layout'; + /** + * Rule documentation. + */ + docs?: RuleDocs; + /** + * Templates for error/warning messages. + */ + messages?: Record; + /** + * Type of fixes that the rule provides. + * Must be `'code'` or `'whitespace'` if the rule provides fixes. + */ + fixable?: 'code' | 'whitespace'; + /** + * Specifies whether rule can return suggestions. + * Must be `true` if the rule provides suggestions. + * @default false + */ + hasSuggestions?: boolean; + /** + * Shape of options for the rule. + * Mandatory if the rule has options. + */ + schema?: RuleOptionsSchema; + /** + * Default options for the rule. + * If present, any user-provided options in their config will be merged on top of them recursively. + */ + // TODO: Make this more precise. + // TODO: Use this to alter options passed to rules. + defaultOptions?: unknown[]; + /** + * Indicates whether the rule has been deprecated, and info about the deprecation and possible replacements. + */ + deprecated?: boolean | RuleDeprecatedInfo; + /** + * Information about available replacements for the rule. + * This may be an empty array to explicitly state there is no replacement. + * @deprecated Use `deprecated.replacedBy` instead. + */ + replacedBy?: RuleReplacedByInfo[]; +} + +/** + * Rule documentation. + * `docs` property of `RuleMeta`. + * + * Often used for documentation generation and tooling. + */ +export interface RuleDocs { + /** + * Short description of the rule. + */ + description?: string; + /** + * Typically a boolean, representing whether the rule is enabled by the recommended config. + */ + recommended?: unknown; + /** + * URL for rule documentation. + */ + url?: string; + /** + * Other arbitrary user-defined properties. + */ + [key: string]: unknown; +} + +/** + * Schema describing valid options for a rule. + * `schema` property of `RuleMeta`. + */ +// TODO: Make this more precise. +// TODO: Use this to validate options in configs. +export type RuleOptionsSchema = Record | unknown[] | false; + +/** + * Info about deprecation of a rule, and possible replacements. + * `deprecated` property of `RuleMeta`. + */ +// Note: ESLint docs specifically say "Every property is optional." +export interface RuleDeprecatedInfo { + /** + * General message presentable to the user. May contain why this rule is deprecated or how to replace the rule. + */ + message?: string; + /** + * URL with more information about this rule deprecation. + */ + url?: string; + /** + * Information about available replacements for the rule. + * This may be an empty array to explicitly state there is no replacement. + */ + replacedBy?: RuleReplacedByInfo[]; + /** + * Version (as semver string) deprecating the rule. + */ + deprecatedSince?: string; + /** + * Version (as semver string) likely to remove the rule. + * e.g. the next major version. + * + * The special value `null` means the rule will no longer be changed, but will be kept available indefinitely. + */ + availableUntil?: string | null; +} + +/** + * Info about a possible replacement for a rule. + */ +// Note: ESLint docs specifically say "Every property is optional." +export interface RuleReplacedByInfo { + /** + * A general message about this rule replacement. + */ + message?: string; + /** + * A URL with more information about this rule replacement. + */ + url?: string; + /** + * Which plugin has the replacement rule. + * + * The `name` property should be the package name, and should be: + * - `"oxlint"` if the replacement is an Oxlint core rule. + * - `"eslint"` if the replacement is an ESLint core rule. + * + * This property should be omitted if the replacement rule is in the same plugin. + */ + plugin?: RuleReplacedByExternalSpecifier; + /** + * Name of replacement rule. + * May be omitted if the plugin only contains a single rule, or has the same name as the rule. + */ + rule?: RuleReplacedByExternalSpecifier; +} + +/** + * Details about a plugin or rule that replaces a deprecated rule. + */ +// Note: ESLint docs specifically say "Every property is optional." +export interface RuleReplacedByExternalSpecifier { + /** + * For a plugin, the package name. + * For a rule, the rule name. + */ + name?: string; + /** + * URL pointing to documentation for the plugin / rule. + */ + url?: string; +} diff --git a/apps/oxlint/src-js/plugins/types.ts b/apps/oxlint/src-js/plugins/types.ts index 5ff4775aef667..0ca2c885a8757 100644 --- a/apps/oxlint/src-js/plugins/types.ts +++ b/apps/oxlint/src-js/plugins/types.ts @@ -82,14 +82,6 @@ export interface EnterExit { exit: VisitFn | null; } -// Rule metadata. -// TODO: Fill in all properties. -export interface RuleMeta { - fixable?: 'code' | 'whitespace' | null | undefined; - messages?: Record; - [key: string]: unknown; -} - // Buffer with typed array views of itself stored as properties. export interface BufferWithArrays extends Uint8Array { uint32: Uint32Array;