Skip to content

Commit

Permalink
test(plugin-eslint): integration test loading rules from flat config
Browse files Browse the repository at this point in the history
  • Loading branch information
matejchalk committed Nov 23, 2024
1 parent 11901ff commit 8221bdc
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 2 deletions.
205 changes: 205 additions & 0 deletions packages/plugin-eslint/src/lib/meta/versions/flat.integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import type { ESLint, Linter, Rule } from 'eslint';
import { mkdir, rm, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import type { RuleData } from '../parse';
import { loadRulesForFlatConfig } from './flat';

describe('loadRulesForFlatConfig', () => {
const workDir = join(
process.cwd(),
'tmp',
'plugin-eslint',
'flat-config-tests',
);

beforeEach(async () => {
vi.spyOn(process, 'cwd').mockReturnValue(workDir);
await mkdir(workDir, { recursive: true });
});

afterEach(async () => {
await rm(workDir, { force: true, recursive: true });
});

it('should load built-in rules from implicit flat config location', async () => {
const config: Linter.FlatConfig = {
rules: {
'no-unused-vars': 'error',
'prefer-const': 'warn',
},
};
await writeFile(
join(workDir, 'eslint.config.js'),
`export default ${JSON.stringify(config, null, 2)}`,
);

const expectedMeta = expect.objectContaining<Rule.RuleMetaData>({
docs: expect.objectContaining<Rule.RuleMetaData['docs']>({
description: expect.stringMatching(/\w+/),
url: expect.stringContaining('https://eslint.org/'),
}),
});
await expect(loadRulesForFlatConfig({})).resolves.toEqual([
{ ruleId: 'no-unused-vars', meta: expectedMeta, options: [] },
{ ruleId: 'prefer-const', meta: expectedMeta, options: [] },
] satisfies RuleData[]);
});

it('should load plugin rules from explicit flat config path', async () => {
const tseslint = {
rules: {
'no-explicit-any': {
meta: {
docs: {
description: 'Disallow the `any` type',
url: 'https://typescript-eslint.io/rules/no-explicit-any/',
},
},
} as Rule.RuleModule,
'no-unsafe-call': {
meta: {
docs: {
description: 'Disallow calling a value with type `any`',
url: 'https://typescript-eslint.io/rules/no-unsafe-call/',
},
},
} as Rule.RuleModule,
},
} as ESLint.Plugin;
const reactHooks = {
rules: {
'rules-of-hooks': {
meta: {
docs: {
description: 'enforces the Rules of Hooks',
url: 'https://reactjs.org/docs/hooks-rules.html',
},
},
} as Rule.RuleModule,
},
} as ESLint.Plugin;
const config: Linter.FlatConfig[] = [
{
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
'@typescript-eslint/no-explicit-any': 'error',
},
},
{
plugins: {
'react-hooks': reactHooks,
},
rules: {
'react-hooks/rules-of-hooks': 'error',
},
},
{
rules: {
'@typescript-eslint/no-unsafe-call': 'error',
},
},
];
await writeFile(
join(workDir, 'code-pushup.eslint.config.js'),
`export default ${JSON.stringify(config, null, 2)}`,
);

await expect(
loadRulesForFlatConfig({ eslintrc: 'code-pushup.eslint.config.js' }),
).resolves.toEqual([
{
ruleId: '@typescript-eslint/no-explicit-any',
meta: {
docs: {
description: 'Disallow the `any` type',
url: 'https://typescript-eslint.io/rules/no-explicit-any/',
},
},
options: [],
},
{
ruleId: 'react-hooks/rules-of-hooks',
meta: {
docs: {
description: 'enforces the Rules of Hooks',
url: 'https://reactjs.org/docs/hooks-rules.html',
},
},
options: [],
},
{
ruleId: '@typescript-eslint/no-unsafe-call',
meta: {
docs: {
description: 'Disallow calling a value with type `any`',
url: 'https://typescript-eslint.io/rules/no-unsafe-call/',
},
},
options: [],
},
] satisfies RuleData[]);
});

it('should load custom rule options', async () => {
const config: Linter.FlatConfig[] = [
{
rules: {
complexity: ['warn', 30],
eqeqeq: ['error', 'always', { null: 'never' }],
},
},
];
await writeFile(
join(workDir, 'eslint.config.cjs'),
`module.exports = ${JSON.stringify(config, null, 2)}`,
);

await expect(loadRulesForFlatConfig({})).resolves.toEqual([
{
ruleId: 'complexity',
meta: expect.any(Object),
options: [30],
},
{
ruleId: 'eqeqeq',
meta: expect.any(Object),
options: ['always', { null: 'never' }],
},
] satisfies RuleData[]);
});

it('should create multiple rule instances when different options used', async () => {
const config: Linter.FlatConfig[] = [
{
rules: {
'max-lines': ['warn', { max: 300 }],
},
},
{
files: ['**/*.test.js'],
rules: {
'max-lines': ['warn', { max: 500 }],
},
},
];
await writeFile(
join(workDir, 'eslint.config.mjs'),
`export default ${JSON.stringify(config, null, 2)}`,
);

await expect(loadRulesForFlatConfig({})).resolves.toEqual([
{
ruleId: 'max-lines',
meta: expect.any(Object),
options: [{ max: 300 }],
},
{
ruleId: 'max-lines',
meta: expect.any(Object),
options: [{ max: 500 }],
},
] satisfies RuleData[]);
});
});
4 changes: 2 additions & 2 deletions packages/plugin-eslint/src/lib/meta/versions/flat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {

export async function loadRulesForFlatConfig({
eslintrc,
}: ESLintTarget): Promise<RuleData[]> {
}: Pick<ESLintTarget, 'eslintrc'>): Promise<RuleData[]> {
const config = eslintrc
? await loadConfigByPath(eslintrc)
: await loadConfigByDefaultLocation();
Expand Down Expand Up @@ -48,7 +48,7 @@ async function loadConfigByDefaultLocation(): Promise<FlatConfig> {
throw new Error(
[
`ESLint config file not found - expected ${flatConfigFileNames.join('/')} in ${process.cwd()} or some parent directory`,
'If your ESLint config is a non-standard location, use the `eslintrc` parameter to specify the path.',
'If your ESLint config is in a non-standard location, use the `eslintrc` parameter to specify the path.',
].join('\n'),
);
}
Expand Down

0 comments on commit 8221bdc

Please sign in to comment.