Skip to content

fix(typescript-eslint): address bugs in config() around global ignores #11065

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

41 changes: 33 additions & 8 deletions packages/typescript-eslint/src/config-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,42 @@ export function config(
);
}

return [
...extendsArrFlattened.map(extension => {
const name = [config.name, extension.name].filter(Boolean).join('__');
return {
const configArray = [];

for (const extension of extendsArrFlattened) {
const name = [config.name, extension.name].filter(Boolean).join('__');
if (isPossiblyGlobalIgnores(extension)) {
// If it's a global ignores, then just pass it along
configArray.push({
...extension,
...(name && { name }),
});
} else {
configArray.push({
...extension,
...(config.files && { files: config.files }),
...(config.ignores && { ignores: config.ignores }),
...(name && { name }),
};
}),
config,
];
});
}
}

// If the base config could form a global ignores object, then we mustn't include
// it in the output. Otherwise, we must add it in order for it to have effect.
if (!isPossiblyGlobalIgnores(config)) {
configArray.push(config);
}

return configArray;
});
}

/**
* This utility function returns false if the config objects contains any field
* that would prevent it from being considered a global ignores object and true
* otherwise. Note in particular that the `ignores` field may not be present and
* the return value can still be true.
*/
function isPossiblyGlobalIgnores(config: object): boolean {
return Object.keys(config).every(key => ['name', 'ignores'].includes(key));
}
69 changes: 69 additions & 0 deletions packages/typescript-eslint/tests/config-helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,73 @@ describe('config helper', () => {
{ rules: { rule: 'error' } },
]);
});

it('does not create global ignores in extends', () => {
const configWithIgnores = plugin.config({
extends: [{ rules: { rule1: 'error' } }, { rules: { rule2: 'error' } }],
ignores: ['ignored'],
});

expect(configWithIgnores).toEqual([
{ ignores: ['ignored'], rules: { rule1: 'error' } },
{ ignores: ['ignored'], rules: { rule2: 'error' } },
]);
expect(configWithIgnores).not.toContainEqual(
// Should not create global ignores
{ ignores: ['ignored'] },
);
});

it('creates noop config in extends', () => {
const configWithMetadata = plugin.config({
extends: [{ rules: { rule1: 'error' } }, { rules: { rule2: 'error' } }],
files: ['file'],
ignores: ['ignored'],
name: 'my-config',
});

expect(configWithMetadata).toEqual([
{
files: ['file'],
ignores: ['ignored'],
name: 'my-config',
rules: { rule1: 'error' },
},
{
files: ['file'],
ignores: ['ignored'],
name: 'my-config',
rules: { rule2: 'error' },
},
// it would also be ok for this not to be present, but we want to align
// with the eslint `defineConfig()` behavior.
{
files: ['file'],
ignores: ['ignored'],
name: 'my-config',
},
]);
});

it('does not create global ignores when extending empty configs', () => {
expect(
plugin.config({
extends: [{ rules: { rule1: 'error' } }, {}],
ignores: ['ignored'],
}),
).toEqual([
{ ignores: ['ignored'], rules: { rule1: 'error' } },
// Should not create global ignores
{},
]);
});

it('handles name field when global-ignoring in extension', () => {
expect(
plugin.config({
extends: [{ ignores: ['files/**/*'], name: 'global-ignore-stuff' }],
ignores: ['ignored'],
}),
).toEqual([{ ignores: ['files/**/*'], name: 'global-ignore-stuff' }]);
});
});