From 88bfd8dec8c7dd6bd8e5b4b924518434ad55acdd Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sat, 25 Oct 2025 11:14:02 +0900 Subject: [PATCH 1/9] feat: expose rule name via RuleModule interface --- packages/rule-tester/src/RuleTester.ts | 3 ++- packages/utils/src/eslint-utils/RuleCreator.ts | 4 ++++ packages/utils/src/ts-eslint/Rule.ts | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/rule-tester/src/RuleTester.ts b/packages/rule-tester/src/RuleTester.ts index 723bc6b17227..25a3651bb0b5 100644 --- a/packages/rule-tester/src/RuleTester.ts +++ b/packages/rule-tester/src/RuleTester.ts @@ -444,9 +444,10 @@ export class RuleTester extends TestFramework { return normalizedTests; } - defineRule(name: string, rule: AnyRuleModule): void { + defineRule(name: string, rule: Omit): void { this.#rules[name] = { ...rule, + name, // Create a wrapper rule that freezes the `context` properties. create(context): RuleListener { freezeDeeply(context.options); diff --git a/packages/utils/src/eslint-utils/RuleCreator.ts b/packages/utils/src/eslint-utils/RuleCreator.ts index cd06aee6231f..714350e8ed9f 100644 --- a/packages/utils/src/eslint-utils/RuleCreator.ts +++ b/packages/utils/src/eslint-utils/RuleCreator.ts @@ -36,6 +36,7 @@ export interface RuleWithMeta< Docs = unknown, > extends RuleCreateAndOptions { meta: RuleMetaData; + name?: string; } export interface RuleWithMetaAndName< @@ -76,6 +77,7 @@ export function RuleCreator( url: urlCreator(name), }, }, + name, ...rule, }); }; @@ -89,6 +91,7 @@ function createRule< create, defaultOptions, meta, + name, }: Readonly>): RuleModule< MessageIds, Options, @@ -101,6 +104,7 @@ function createRule< }, defaultOptions, meta, + name: name ?? '', }; } diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index 1a4920a3e79e..be28d4a0b9ec 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -735,6 +735,11 @@ export interface RuleModule< * Metadata about the rule */ meta: RuleMetaData; + + /** + * Rule name + */ + name: string; } export type AnyRuleModule = RuleModule; From d9193927fb5b59f7be890b548c06a8b9e4528fea Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sat, 25 Oct 2025 11:14:28 +0900 Subject: [PATCH 2/9] test: update tests --- packages/rule-tester/tests/RuleTester.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/rule-tester/tests/RuleTester.test.ts b/packages/rule-tester/tests/RuleTester.test.ts index b7a276c69e4c..dfe060cb7be9 100644 --- a/packages/rule-tester/tests/RuleTester.test.ts +++ b/packages/rule-tester/tests/RuleTester.test.ts @@ -94,6 +94,7 @@ const NOOP_RULE: RuleModule<'error'> = { schema: [], type: 'problem', }, + name: 'rule', }; function windowsToPosixPath(p: string): string { @@ -1107,6 +1108,7 @@ describe('RuleTester - hooks', () => { schema: [], type: 'problem', }, + name: 'rule', }; const ruleTester = new RuleTester(); @@ -1330,6 +1332,7 @@ describe('RuleTester - multipass fixer', () => { schema: [], type: 'problem', }, + name: 'rule', }; it('passes with no output', () => { @@ -1415,6 +1418,7 @@ describe('RuleTester - multipass fixer', () => { schema: [], type: 'problem', }, + name: 'rule', }; it('passes with correct string output', () => { @@ -1537,6 +1541,7 @@ describe('RuleTester - multipass fixer', () => { schema: [], type: 'problem', }, + name: 'rule', }; it('passes with correct array output', () => { @@ -1643,6 +1648,7 @@ describe('RuleTester - run types', () => { ], type: 'suggestion', }, + name: 'rule', }; describe('infer from `rule` parameter', () => { From 91e97bc63a186b3cdd1b6560dbf2e6ee40eded8e Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Tue, 4 Nov 2025 08:28:30 +0900 Subject: [PATCH 3/9] test: add test --- .../tests/eslint-utils/RuleCreator.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/utils/tests/eslint-utils/RuleCreator.test.ts b/packages/utils/tests/eslint-utils/RuleCreator.test.ts index 625ee3646f2f..6b82308b5128 100644 --- a/packages/utils/tests/eslint-utils/RuleCreator.test.ts +++ b/packages/utils/tests/eslint-utils/RuleCreator.test.ts @@ -43,4 +43,28 @@ describe(ESLintUtils.RuleCreator, () => { type: 'problem', }); }); + + it('withoutDocs should not add docs url', () => { + const rule = ESLintUtils.RuleCreator.withoutDocs({ + create() { + return {}; + }, + defaultOptions: [], + meta: { + docs: { + description: 'some description', + }, + messages: { + foo: 'some message', + }, + schema: [], + type: 'problem', + }, + }); + + expect(rule.meta.docs).toEqual({ + description: 'some description', + }); + expect(rule.name).toBe(''); + }); }); From 213983b66883c620c016a3ce589e88d80595c16f Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Tue, 18 Nov 2025 22:50:27 +0900 Subject: [PATCH 4/9] fix: add optional --- packages/utils/src/eslint-utils/RuleCreator.ts | 2 +- packages/utils/src/ts-eslint/Rule.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/eslint-utils/RuleCreator.ts b/packages/utils/src/eslint-utils/RuleCreator.ts index 714350e8ed9f..8453e4a500da 100644 --- a/packages/utils/src/eslint-utils/RuleCreator.ts +++ b/packages/utils/src/eslint-utils/RuleCreator.ts @@ -104,7 +104,7 @@ function createRule< }, defaultOptions, meta, - name: name ?? '', + name, }; } diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index be28d4a0b9ec..9ec91b12d577 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -739,7 +739,7 @@ export interface RuleModule< /** * Rule name */ - name: string; + name?: string; } export type AnyRuleModule = RuleModule; From c2d2e381d73f84ec69819dea5343eda812d94b58 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:23:56 -0700 Subject: [PATCH 5/9] fix up test --- packages/utils/tests/eslint-utils/RuleCreator.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/tests/eslint-utils/RuleCreator.test.ts b/packages/utils/tests/eslint-utils/RuleCreator.test.ts index 6b82308b5128..9b66b3c3132b 100644 --- a/packages/utils/tests/eslint-utils/RuleCreator.test.ts +++ b/packages/utils/tests/eslint-utils/RuleCreator.test.ts @@ -65,6 +65,6 @@ describe(ESLintUtils.RuleCreator, () => { expect(rule.meta.docs).toEqual({ description: 'some description', }); - expect(rule.name).toBe(''); + expect(rule.name).toBeUndefined(); }); }); From 4326e2709e03cf6c11ece8088873b7a60a3ec0bf Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> Date: Sat, 6 Dec 2025 12:23:32 -0700 Subject: [PATCH 6/9] tweaks --- packages/rule-tester/src/RuleTester.ts | 2 +- packages/rule-tester/tests/RuleTester.test.ts | 6 ----- .../tests/eslint-utils/RuleCreator.test.ts | 27 ++++++++++++++++++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/packages/rule-tester/src/RuleTester.ts b/packages/rule-tester/src/RuleTester.ts index 25a3651bb0b5..cf1b05e7bd0d 100644 --- a/packages/rule-tester/src/RuleTester.ts +++ b/packages/rule-tester/src/RuleTester.ts @@ -444,7 +444,7 @@ export class RuleTester extends TestFramework { return normalizedTests; } - defineRule(name: string, rule: Omit): void { + defineRule(name: string, rule: AnyRuleModule): void { this.#rules[name] = { ...rule, name, diff --git a/packages/rule-tester/tests/RuleTester.test.ts b/packages/rule-tester/tests/RuleTester.test.ts index 5646884de296..83d57fd3eb32 100644 --- a/packages/rule-tester/tests/RuleTester.test.ts +++ b/packages/rule-tester/tests/RuleTester.test.ts @@ -94,7 +94,6 @@ const NOOP_RULE: RuleModule<'error'> = { schema: [], type: 'problem', }, - name: 'rule', }; function windowsToPosixPath(p: string): string { @@ -1108,7 +1107,6 @@ describe('RuleTester - hooks', () => { schema: [], type: 'problem', }, - name: 'rule', }; const ruleTester = new RuleTester(); @@ -1332,7 +1330,6 @@ describe('RuleTester - multipass fixer', () => { schema: [], type: 'problem', }, - name: 'rule', }; it('passes with no output', () => { @@ -1418,7 +1415,6 @@ describe('RuleTester - multipass fixer', () => { schema: [], type: 'problem', }, - name: 'rule', }; it('passes with correct string output', () => { @@ -1541,7 +1537,6 @@ describe('RuleTester - multipass fixer', () => { schema: [], type: 'problem', }, - name: 'rule', }; it('passes with correct array output', () => { @@ -1648,7 +1643,6 @@ describe('RuleTester - run types', () => { ], type: 'suggestion', }, - name: 'rule', }; describe('infer from `rule` parameter', () => { diff --git a/packages/utils/tests/eslint-utils/RuleCreator.test.ts b/packages/utils/tests/eslint-utils/RuleCreator.test.ts index 9b66b3c3132b..c27559a52b1d 100644 --- a/packages/utils/tests/eslint-utils/RuleCreator.test.ts +++ b/packages/utils/tests/eslint-utils/RuleCreator.test.ts @@ -44,7 +44,7 @@ describe(ESLintUtils.RuleCreator, () => { }); }); - it('withoutDocs should not add docs url', () => { + it('withoutDocs should work without a `name`', () => { const rule = ESLintUtils.RuleCreator.withoutDocs({ create() { return {}; @@ -67,4 +67,29 @@ describe(ESLintUtils.RuleCreator, () => { }); expect(rule.name).toBeUndefined(); }); + + it('withoutDocs should work with a `name`', () => { + const rule = ESLintUtils.RuleCreator.withoutDocs({ + create() { + return {}; + }, + defaultOptions: [], + meta: { + docs: { + description: 'some description', + }, + messages: { + foo: 'some message', + }, + schema: [], + type: 'problem', + }, + name: 'some-name', + }); + + expect(rule.meta.docs).toEqual({ + description: 'some description', + }); + expect(rule.name).toBe('some-name'); + }); }); From 19d23934cd5a21c4f822277a3e4bd84f70de40de Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> Date: Sat, 6 Dec 2025 12:30:25 -0700 Subject: [PATCH 7/9] ensure name is forwarded --- packages/utils/tests/eslint-utils/RuleCreator.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/utils/tests/eslint-utils/RuleCreator.test.ts b/packages/utils/tests/eslint-utils/RuleCreator.test.ts index c27559a52b1d..314402fcde07 100644 --- a/packages/utils/tests/eslint-utils/RuleCreator.test.ts +++ b/packages/utils/tests/eslint-utils/RuleCreator.test.ts @@ -42,6 +42,7 @@ describe(ESLintUtils.RuleCreator, () => { schema: [], type: 'problem', }); + expect(rule.name).toBe('test'); }); it('withoutDocs should work without a `name`', () => { From 083f6b50ec70a91f43a432a3af0ae595cfd67852 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Sun, 7 Dec 2025 14:46:51 +0900 Subject: [PATCH 8/9] feat: Ensure RuleCreator returns named rules with string name --- packages/utils/src/eslint-utils/RuleCreator.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/eslint-utils/RuleCreator.ts b/packages/utils/src/eslint-utils/RuleCreator.ts index 8453e4a500da..c0ebc590ca88 100644 --- a/packages/utils/src/eslint-utils/RuleCreator.ts +++ b/packages/utils/src/eslint-utils/RuleCreator.ts @@ -48,6 +48,15 @@ export interface RuleWithMetaAndName< name: string; } +type RuleModuleWithName< + MessageIds extends string, + Options extends readonly unknown[] = [], + Docs = unknown, + ExtendedRuleListener extends RuleListener = RuleListener, +> = RuleModule & { + name: string; +}; + /** * Creates reusable function to create rules with default options and docs URLs. * @@ -68,8 +77,8 @@ export function RuleCreator( ...rule }: Readonly< RuleWithMetaAndName - >): RuleModule { - return createRule({ + >): RuleModuleWithName { + const ruleWithDocs = createRule({ meta: { ...meta, docs: { @@ -80,6 +89,11 @@ export function RuleCreator( name, ...rule, }); + + return { + ...ruleWithDocs, + name, + }; }; } From 28af1c9d88cb852085267e800724fe50621088fc Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Tue, 9 Dec 2025 09:31:48 +0900 Subject: [PATCH 9/9] refactor: use type assertion --- packages/utils/src/eslint-utils/RuleCreator.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/utils/src/eslint-utils/RuleCreator.ts b/packages/utils/src/eslint-utils/RuleCreator.ts index c0ebc590ca88..ec1ceec5b381 100644 --- a/packages/utils/src/eslint-utils/RuleCreator.ts +++ b/packages/utils/src/eslint-utils/RuleCreator.ts @@ -90,10 +90,7 @@ export function RuleCreator( ...rule, }); - return { - ...ruleWithDocs, - name, - }; + return ruleWithDocs as RuleModuleWithName; }; }