From 80f6ba5684678cb9b53c2a0edfba85fe8f03a7a9 Mon Sep 17 00:00:00 2001 From: Maxim Stykow Date: Sat, 19 Oct 2024 18:43:11 +0200 Subject: [PATCH 1/5] feat(typescript-eslint): improve undefined extension handling Addresses #10176 by checking if any of the extensions are undefined and throwing a helpful error if true. Signed-off-by: Maxim Stykow --- packages/typescript-eslint/src/config-helper.ts | 5 +++++ packages/typescript-eslint/tests/configs.test.ts | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/packages/typescript-eslint/src/config-helper.ts b/packages/typescript-eslint/src/config-helper.ts index 77085838c76a..bd4f177b73e1 100644 --- a/packages/typescript-eslint/src/config-helper.ts +++ b/packages/typescript-eslint/src/config-helper.ts @@ -90,6 +90,11 @@ export function config( if (extendsArr == null || extendsArr.length === 0) { return config; } + if (!extendsArr.every(Boolean)) { + throw new Error( + 'Some of your extensions are undefined, likely due to a problem with their import path.', + ); + } return [ ...extendsArr.map(extension => { diff --git a/packages/typescript-eslint/tests/configs.test.ts b/packages/typescript-eslint/tests/configs.test.ts index 63bf079e1b79..fae4845204ff 100644 --- a/packages/typescript-eslint/tests/configs.test.ts +++ b/packages/typescript-eslint/tests/configs.test.ts @@ -368,6 +368,19 @@ describe('config helper', () => { ]); }); + it('throws error when some extensions are undefined', () => { + expect(() => + plugin.config({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + extends: [undefined as any], + files: ['common-file'], + ignores: ['common-ignored'], + name: 'my-config', + rules: { rule: 'error' }, + }), + ).toThrow('Some of your extensions are undefined'); + }); + it('flattens extended configs with config name', () => { expect( plugin.config({ From 75c0c840ae399ac30dda68a0273d78d588f6076a Mon Sep 17 00:00:00 2001 From: Maxim Stykow Date: Tue, 22 Oct 2024 10:51:44 +0200 Subject: [PATCH 2/5] feat(typescript-eslint): improve error message to include indices Signed-off-by: Maxim Stykow --- .../typescript-eslint/src/config-helper.ts | 21 ++++++++++-- .../typescript-eslint/tests/configs.test.ts | 33 ++++++++++++++----- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/typescript-eslint/src/config-helper.ts b/packages/typescript-eslint/src/config-helper.ts index bd4f177b73e1..7b4d234a229d 100644 --- a/packages/typescript-eslint/src/config-helper.ts +++ b/packages/typescript-eslint/src/config-helper.ts @@ -85,14 +85,29 @@ export interface ConfigWithExtends extends TSESLint.FlatConfig.Config { export function config( ...configs: ConfigWithExtends[] ): TSESLint.FlatConfig.ConfigArray { - return configs.flatMap(configWithExtends => { + return configs.flatMap((configWithExtends, configIndex) => { const { extends: extendsArr, ...config } = configWithExtends; if (extendsArr == null || extendsArr.length === 0) { return config; } - if (!extendsArr.every(Boolean)) { + const undefinedExtensions = extendsArr.reduce( + (acc, extension, extensionIndex) => { + const maybeExtension = extension as + | TSESLint.FlatConfig.Config + | undefined; + if (!maybeExtension) { + acc.push(extensionIndex); + } + return acc; + }, + [], + ); + if (undefinedExtensions.length) { throw new Error( - 'Some of your extensions are undefined, likely due to a problem with their import path.', + `Your config at index ${configIndex} contains undefined extensions ` + + `at the following indices: ${undefinedExtensions.join(', ')}.\n` + + 'This is likely due to a problem with how you are specifying your ' + + "extension's import path.", ); } diff --git a/packages/typescript-eslint/tests/configs.test.ts b/packages/typescript-eslint/tests/configs.test.ts index fae4845204ff..044c11b4a41f 100644 --- a/packages/typescript-eslint/tests/configs.test.ts +++ b/packages/typescript-eslint/tests/configs.test.ts @@ -1,3 +1,4 @@ +import type { TSESLint } from '@typescript-eslint/utils'; import type { FlatConfig, RuleRecommendation, @@ -369,16 +370,30 @@ describe('config helper', () => { }); it('throws error when some extensions are undefined', () => { + const extension: TSESLint.FlatConfig.Config = { rules: { rule1: 'error' } }; + expect(() => - plugin.config({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - extends: [undefined as any], - files: ['common-file'], - ignores: ['common-ignored'], - name: 'my-config', - rules: { rule: 'error' }, - }), - ).toThrow('Some of your extensions are undefined'); + plugin.config( + { + extends: [extension], + files: ['common-file'], + ignores: ['common-ignored'], + name: 'my-config', + rules: { rule: 'error' }, + }, + { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + extends: [undefined as any, extension, undefined as any], + files: ['common-file'], + ignores: ['common-ignored'], + name: 'my-config', + rules: { rule: 'error' }, + }, + ), + ).toThrow( + 'Your config at index 1 contains undefined extensions at the following' + + ' indices: 0, 2', + ); }); it('flattens extended configs with config name', () => { From d3bd7aaed742b3531e6565ee0231a2c336bc07cc Mon Sep 17 00:00:00 2001 From: Maxim Stykow Date: Tue, 22 Oct 2024 17:17:14 +0200 Subject: [PATCH 3/5] feat(typescript-eslint): minor rewording Signed-off-by: Maxim Stykow --- packages/typescript-eslint/src/config-helper.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/typescript-eslint/src/config-helper.ts b/packages/typescript-eslint/src/config-helper.ts index 7b4d234a229d..68ecbfcb4304 100644 --- a/packages/typescript-eslint/src/config-helper.ts +++ b/packages/typescript-eslint/src/config-helper.ts @@ -106,8 +106,7 @@ export function config( throw new Error( `Your config at index ${configIndex} contains undefined extensions ` + `at the following indices: ${undefinedExtensions.join(', ')}.\n` + - 'This is likely due to a problem with how you are specifying your ' + - "extension's import path.", + 'This is likely due to a problem with extension import paths.', ); } From 900f45d35d6fb6fc46a818654261ebdb05427ce5 Mon Sep 17 00:00:00 2001 From: Maxim Stykow Date: Wed, 23 Oct 2024 23:37:02 +0200 Subject: [PATCH 4/5] feat(typescript-eslint): add config name to error message Signed-off-by: Maxim Stykow --- .../typescript-eslint/src/config-helper.ts | 12 ++++--- .../typescript-eslint/tests/configs.test.ts | 36 ++++++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/packages/typescript-eslint/src/config-helper.ts b/packages/typescript-eslint/src/config-helper.ts index 68ecbfcb4304..b09a8309a913 100644 --- a/packages/typescript-eslint/src/config-helper.ts +++ b/packages/typescript-eslint/src/config-helper.ts @@ -95,7 +95,7 @@ export function config( const maybeExtension = extension as | TSESLint.FlatConfig.Config | undefined; - if (!maybeExtension) { + if (maybeExtension == null) { acc.push(extensionIndex); } return acc; @@ -103,10 +103,14 @@ export function config( [], ); if (undefinedExtensions.length) { + const configName = + configWithExtends.name != null + ? `, named "${configWithExtends.name},"` + : ' (anonymous)'; + const extensionIndices = undefinedExtensions.join(', '); throw new Error( - `Your config at index ${configIndex} contains undefined extensions ` + - `at the following indices: ${undefinedExtensions.join(', ')}.\n` + - 'This is likely due to a problem with extension import paths.', + `Your config at index ${configIndex}${configName} contains undefined` + + ` extensions at the following indices: ${extensionIndices}.`, ); } diff --git a/packages/typescript-eslint/tests/configs.test.ts b/packages/typescript-eslint/tests/configs.test.ts index 044c11b4a41f..292f543e7309 100644 --- a/packages/typescript-eslint/tests/configs.test.ts +++ b/packages/typescript-eslint/tests/configs.test.ts @@ -369,7 +369,7 @@ describe('config helper', () => { ]); }); - it('throws error when some extensions are undefined', () => { + it('throws error containing config name when some extensions are undefined', () => { const extension: TSESLint.FlatConfig.Config = { rules: { rule1: 'error' } }; expect(() => @@ -378,7 +378,7 @@ describe('config helper', () => { extends: [extension], files: ['common-file'], ignores: ['common-ignored'], - name: 'my-config', + name: 'my-config-1', rules: { rule: 'error' }, }, { @@ -386,13 +386,39 @@ describe('config helper', () => { extends: [undefined as any, extension, undefined as any], files: ['common-file'], ignores: ['common-ignored'], - name: 'my-config', + name: 'my-config-2', rules: { rule: 'error' }, }, ), ).toThrow( - 'Your config at index 1 contains undefined extensions at the following' + - ' indices: 0, 2', + 'Your config at index 1, named "my-config-2," contains undefined ' + + 'extensions at the following indices: 0, 2', + ); + }); + + it('throws error without config name when some extensions are undefined', () => { + const extension: TSESLint.FlatConfig.Config = { rules: { rule1: 'error' } }; + + expect(() => + plugin.config( + { + extends: [extension], + files: ['common-file'], + ignores: ['common-ignored'], + name: 'my-config-1', + rules: { rule: 'error' }, + }, + { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + extends: [undefined as any, extension, undefined as any], + files: ['common-file'], + ignores: ['common-ignored'], + rules: { rule: 'error' }, + }, + ), + ).toThrow( + 'Your config at index 1 (anonymous) contains undefined extensions at ' + + 'the following indices: 0, 2', ); }); From 4d6ad8ba37b3fc1bc26985643e5864a792bc5b76 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Wed, 23 Oct 2024 16:46:10 -0600 Subject: [PATCH 5/5] quotes around what is quoted --- packages/typescript-eslint/src/config-helper.ts | 2 +- packages/typescript-eslint/tests/configs.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typescript-eslint/src/config-helper.ts b/packages/typescript-eslint/src/config-helper.ts index b09a8309a913..58866686d13c 100644 --- a/packages/typescript-eslint/src/config-helper.ts +++ b/packages/typescript-eslint/src/config-helper.ts @@ -105,7 +105,7 @@ export function config( if (undefinedExtensions.length) { const configName = configWithExtends.name != null - ? `, named "${configWithExtends.name},"` + ? `, named "${configWithExtends.name}",` : ' (anonymous)'; const extensionIndices = undefinedExtensions.join(', '); throw new Error( diff --git a/packages/typescript-eslint/tests/configs.test.ts b/packages/typescript-eslint/tests/configs.test.ts index 292f543e7309..e020dba758b5 100644 --- a/packages/typescript-eslint/tests/configs.test.ts +++ b/packages/typescript-eslint/tests/configs.test.ts @@ -391,7 +391,7 @@ describe('config helper', () => { }, ), ).toThrow( - 'Your config at index 1, named "my-config-2," contains undefined ' + + 'Your config at index 1, named "my-config-2", contains undefined ' + 'extensions at the following indices: 0, 2', ); });