diff --git a/packages/typescript-eslint/src/config-helper.ts b/packages/typescript-eslint/src/config-helper.ts index 77085838c76a..58866686d13c 100644 --- a/packages/typescript-eslint/src/config-helper.ts +++ b/packages/typescript-eslint/src/config-helper.ts @@ -85,11 +85,34 @@ 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; } + const undefinedExtensions = extendsArr.reduce( + (acc, extension, extensionIndex) => { + const maybeExtension = extension as + | TSESLint.FlatConfig.Config + | undefined; + if (maybeExtension == null) { + acc.push(extensionIndex); + } + return acc; + }, + [], + ); + if (undefinedExtensions.length) { + const configName = + configWithExtends.name != null + ? `, named "${configWithExtends.name}",` + : ' (anonymous)'; + const extensionIndices = undefinedExtensions.join(', '); + throw new Error( + `Your config at index ${configIndex}${configName} contains undefined` + + ` extensions at the following indices: ${extensionIndices}.`, + ); + } return [ ...extendsArr.map(extension => { diff --git a/packages/typescript-eslint/tests/configs.test.ts b/packages/typescript-eslint/tests/configs.test.ts index 63bf079e1b79..e020dba758b5 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, @@ -368,6 +369,59 @@ describe('config helper', () => { ]); }); + it('throws error containing 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'], + name: 'my-config-2', + rules: { rule: 'error' }, + }, + ), + ).toThrow( + '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', + ); + }); + it('flattens extended configs with config name', () => { expect( plugin.config({