diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index 5ad79c88ec7..e0ab041c160 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -111,7 +111,7 @@ describe('Validating rule metadata', () => { // validate if rule name is same as url // there is no way to access this field but its used only in generation of docs url expect( - rule.meta.docs?.url.endsWith(`rules/${ruleName}.md`), + rule.meta.docs?.url?.endsWith(`rules/${ruleName}.md`), ).toBeTruthy(); }); diff --git a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts index f02b7589862..bfbd54f2246 100644 --- a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts +++ b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts @@ -8,33 +8,61 @@ import { import { applyDefault } from './applyDefault'; // we automatically add the url -type CreateRuleMetaDocs = Omit; -type CreateRuleMeta = { - docs: CreateRuleMetaDocs; +type NamedCreateRuleMetaDocs = Omit; +type NamedCreateRuleMeta = { + docs: NamedCreateRuleMetaDocs; } & Omit, 'docs'>; +interface CreateAndOptions< + TOptions extends readonly unknown[], + TMessageIds extends string, + TRuleListener extends RuleListener, +> { + create: ( + context: Readonly>, + optionsWithDefault: Readonly, + ) => TRuleListener; + defaultOptions: Readonly; +} + +interface RuleWithMeta< + TOptions extends readonly unknown[], + TMessageIds extends string, + TRuleListener extends RuleListener, +> extends CreateAndOptions { + meta: RuleMetaData; +} + +interface RuleWithMetaAndName< + TOptions extends readonly unknown[], + TMessageIds extends string, + TRuleListener extends RuleListener, +> extends CreateAndOptions { + meta: NamedCreateRuleMeta; + name: string; +} + +/** + * Creates reusable function to create rules with default options and docs URLs. + * + * @param urlCreator Creates a documentation URL for a given rule name. + * @returns Function to create a rule with the docs URL format. + */ function RuleCreator(urlCreator: (ruleName: string) => string) { // This function will get much easier to call when this is merged https://github.com/Microsoft/TypeScript/pull/26349 // TODO - when the above PR lands; add type checking for the context.report `data` property - return function createRule< + return function createNamedRule< TOptions extends readonly unknown[], TMessageIds extends string, TRuleListener extends RuleListener = RuleListener, >({ name, meta, - defaultOptions, - create, - }: Readonly<{ - name: string; - meta: CreateRuleMeta; - defaultOptions: Readonly; - create: ( - context: Readonly>, - optionsWithDefault: Readonly, - ) => TRuleListener; - }>): RuleModule { - return { + ...rule + }: Readonly< + RuleWithMetaAndName + >): RuleModule { + return createRule({ meta: { ...meta, docs: { @@ -42,17 +70,41 @@ function RuleCreator(urlCreator: (ruleName: string) => string) { url: urlCreator(name), }, }, - create( - context: Readonly>, - ): TRuleListener { - const optionsWithDefault = applyDefault( - defaultOptions, - context.options, - ); - return create(context, optionsWithDefault); - }, - }; + ...rule, + }); }; } +/** + * Creates a well-typed TSESLint custom ESLint rule without a docs URL. + * + * @returns Well-typed TSESLint custom ESLint rule. + * @remarks It is generally better to provide a docs URL function to RuleCreator. + */ +function createRule< + TOptions extends readonly unknown[], + TMessageIds extends string, + TRuleListener extends RuleListener = RuleListener, +>({ + create, + defaultOptions, + meta, +}: Readonly>): RuleModule< + TMessageIds, + TOptions, + TRuleListener +> { + return { + meta, + create( + context: Readonly>, + ): TRuleListener { + const optionsWithDefault = applyDefault(defaultOptions, context.options); + return create(context, optionsWithDefault); + }, + }; +} + +RuleCreator.withoutDocs = createRule; + export { RuleCreator }; diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts index 1c4c6eb64ae..cf3c5b8218b 100644 --- a/packages/experimental-utils/src/ts-eslint/Rule.ts +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -19,7 +19,7 @@ interface RuleMetaDataDocs { /** * The URL of the rule's docs */ - url: string; + url?: string; /** * Specifies whether the rule can return suggestions. */