From d068cd58be932d1a5256fd6c392bfd1f5f6b4b9b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 13 Nov 2021 14:12:59 -0500 Subject: [PATCH 1/2] feat: add RuleCreator.withoutDocs --- .../src/eslint-utils/RuleCreator.ts | 104 +++++++++++++----- .../experimental-utils/src/ts-eslint/Rule.ts | 2 +- 2 files changed, 79 insertions(+), 27 deletions(-) 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 7750d35dc68..a353789875a 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. */ From ba132ea13b159ea9f7d3d701b9745765896f695b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 14 Nov 2021 10:30:28 -0500 Subject: [PATCH 2/2] fix: docs.test typing --- packages/eslint-plugin/tests/docs.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(); });