Skip to content

fix(typescript-eslint): error on nested extends in tseslint.config() #11361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions packages/typescript-eslint/src/config-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ function configImpl(...configs: unknown[]): ConfigArray {
extends?: unknown;
files?: unknown;
ignores?: unknown;
basePath?: unknown;
};

if (extendsArr == null) {
Expand Down Expand Up @@ -163,11 +164,27 @@ function configImpl(...configs: unknown[]): ConfigArray {
}
if (extension == null || typeof extension !== 'object') {
nonObjectExtensions.push(extensionIndex);
continue;
}

// https://github.com/eslint/rewrite/blob/82d07fd0e8e06780b552a41f8bcbe2a4f8741d42/packages/config-helpers/src/define-config.js#L448-L450
if ('basePath' in extension) {
throw new TypeError(
`tseslint.config(): Config at index ${configIndex}${nameErrorPhrase} has an 'extends' array that contains a config with a 'basePath' property at index ${extensionIndex}.` +
` 'basePath' in 'extends' is not allowed.`,
);
}

if ('extends' in extension) {
throw new TypeError(
`tseslint.config(): Config at index ${configIndex}${nameErrorPhrase} has an 'extends' array that contains a config with an 'extends' property at index ${extensionIndex}.` +
` Nested 'extends' is not allowed.`,
);
}
}
if (nonObjectExtensions.length > 0) {
const extensionIndices = nonObjectExtensions.join(', ');
throw new Error(
throw new TypeError(
`tseslint.config(): Config at index ${configIndex}${nameErrorPhrase} contains non-object` +
` extensions at the following indices: ${extensionIndices}.`,
);
Expand All @@ -181,6 +198,7 @@ function configImpl(...configs: unknown[]): ConfigArray {
files?: unknown;
ignores?: unknown;
};

const resolvedConfigName = [name, extension.name]
.filter(Boolean)
.join('__');
Expand All @@ -195,6 +213,7 @@ function configImpl(...configs: unknown[]): ConfigArray {
...extension,
...(config.files ? { files: config.files } : {}),
...(config.ignores ? { ignores: config.ignores } : {}),
...(config.basePath ? { basePath: config.basePath } : {}),
...(resolvedConfigName !== '' ? { name: resolvedConfigName } : {}),
});
}
Expand All @@ -218,5 +237,7 @@ function configImpl(...configs: unknown[]): ConfigArray {
* the return value can still be true.
*/
function isPossiblyGlobalIgnores(config: object): boolean {
return Object.keys(config).every(key => ['name', 'ignores'].includes(key));
return Object.keys(config).every(key =>
['name', 'ignores', 'basePath'].includes(key),
);
}
57 changes: 57 additions & 0 deletions packages/typescript-eslint/tests/config-helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,61 @@ describe('config helper', () => {
"tseslint.config(): Config at index 0 has a 'name' property that is not a string.",
);
});

it('basePath works with unextended config', () => {
expect(
tseslint.config({
basePath: 'base/path',
rules: { rule1: 'error' },
}),
).toStrictEqual([
{
basePath: 'base/path',
rules: { rule1: 'error' },
},
]);
});

it('basePath works with extended config', () => {
expect(
tseslint.config({
basePath: 'base/path',
extends: [{ rules: { rule1: 'error' } }, { rules: { rule2: 'error' } }],
}),
).toStrictEqual([
{
basePath: 'base/path',
rules: { rule1: 'error' },
},
{
basePath: 'base/path',
rules: { rule2: 'error' },
},
]);
});

it('basePath cannot be used in an extension', () => {
expect(() => {
tseslint.config({
extends: [{ basePath: 'base/path', rules: { rule1: 'error' } }],
});
}).toThrow(
"tseslint.config(): Config at index 0 (anonymous) has an 'extends' array that contains a config with a 'basePath' property at index 0. 'basePath' in 'extends' is not allowed.",
);
});

it('should error when trying to use nested extends', () => {
expect(() => {
tseslint.config({
extends: [
{
extends: [
{ rules: { rule1: 'error' } },
{ rules: { rule2: 'error' } },
],
},
],
});
}).toThrow();
});
});
9 changes: 9 additions & 0 deletions packages/utils/src/ts-eslint/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,15 @@ export namespace FlatConfig {
// it's not a json schema so it's nowhere near as nice to read and convert...
// https://github.com/eslint/eslint/blob/v8.45.0/lib/config/flat-config-schema.js
export interface Config {
/**
* The base path for files and ignores.
*
* Note that this is not permitted inside an `extends` array.
*
* Since ESLint 9.30.0
*/
basePath?: string;

/**
* An array of glob patterns indicating the files that the configuration object should apply to.
* If not specified, the configuration object applies to all files matched by any other configuration object.
Expand Down
9 changes: 9 additions & 0 deletions packages/utils/tests/ts-eslint/ESLint.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { FlatESLint, LegacyESLint } from 'eslint/use-at-your-own-risk';

import type { FlatConfig } from '../../src/ts-eslint';

import * as ESLint from '../../src/ts-eslint/ESLint';

describe('ESLint', () => {
Expand All @@ -12,6 +14,7 @@ describe('ESLint', () => {
});
expect(eslint).toBeInstanceOf(FlatESLint);
});

it('legacy', () => {
// eslint-disable-next-line @typescript-eslint/no-deprecated
const eslint = new ESLint.LegacyESLint({
Expand All @@ -27,5 +30,11 @@ describe('ESLint', () => {
});
expect(eslint).toBeInstanceOf(LegacyESLint);
});

it('config type permits basePath (type test)', () => {
const __config: FlatConfig.Config = {
basePath: 'some/path',
};
});
});
});
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy