Skip to content

Commit

Permalink
feat!: support eslint@9 and flat config
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed May 29, 2024
1 parent 799968c commit 4d8d63d
Show file tree
Hide file tree
Showing 17 changed files with 138 additions and 384 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"conventional-changelog-cli": "^5.0.0",
"eslint": "^9.3.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-local-rules": "^2.0.1",
"eslint-plugin-prettier": "^5.1.3",
"pkg-pr-new": "^0.0.5",
"prettier": "^3.2.5",
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module.exports = {
import js from '@eslint/js';

/** @type {import('./src/config/types').Config} */
const config = {
repositories: [
'AriPerkkio/eslint-remote-tester-integration-test-target',
'AriPerkkio/aria-live-capture',
Expand All @@ -17,20 +20,7 @@ module.exports = {

concurrentTasks: 3,

eslintrc: {
root: true,
env: {
es6: true,
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
extends: ['eslint:recommended'],
},
eslintConfig: [js.configs.recommended],

cache: true,

Expand Down Expand Up @@ -61,3 +51,5 @@ module.exports = {
*/
onComplete: undefined,
};

export default config;
11 changes: 5 additions & 6 deletions packages/eslint-remote-tester/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@
"scripts": {
"prebuild": "rm -rf ./dist",
"build": "tsc --project tsconfig.prod.json",
"start": "node dist --config eslint-remote-tester.config.cjs",
"start": "node dist --config eslint-remote-tester.config.js",
"start:memory-limit-crash": "NODE_OPTIONS=--max_old_space_size=50 node dist",
"lint": "eslint . --max-warnings 0 --ext .js,.ts,.tsx && publint",
"test:integration": "vitest run --config test/integration/vitest.config.integration.ts",
"test:smoke": "vitest run --config test/smoke/vitest.config.smoke.ts",
"validate": "pnpm build && pnpm lint && pnpm test && pnpm test:integration"
Expand All @@ -45,15 +44,15 @@
"simple-git": "^3.24.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
"@types/babel__code-frame": "^7.0.6",
"@types/eslint": "^8.56.10",
"@types/node": "^20.12.12",
"@types/object-hash": "^3.0.6",
"@types/react": "^17.0.80",
"@typescript-eslint/eslint-plugin": "^7.11.0",
"@typescript-eslint/parser": "^7.11.0",
"conventional-changelog-cli": "^5.0.0",
"eslint": "^8.57.0",
"eslint": "^9.3.0",
"eslint-plugin-local-rules": "^2.0.1",
"eslint-remote-tester-repositories": "workspace:*",
"ink-testing-library": "^2.1.0",
"node-pty": "^1.0.0",
Expand All @@ -63,7 +62,7 @@
"vitest": "^1.6.0"
},
"peerDependencies": {
"eslint": ">=7",
"eslint": ">=9",
"ts-node": ">=9.0.0"
},
"peerDependenciesMeta": {
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-remote-tester/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { loadConfig } from './load.js';
import { WorkerData } from '../engine/types.js';

const DEFAULT_CONFIGURATION_FILE_NAME = 'eslint-remote-tester.config';
const DEFAULT_CONFIGURATION_FILE_JS = `${DEFAULT_CONFIGURATION_FILE_NAME}.cjs`;
const DEFAULT_CONFIGURATION_FILE_JS = `${DEFAULT_CONFIGURATION_FILE_NAME}.js`;
const DEFAULT_CONFIGURATION_FILE_TS = `${DEFAULT_CONFIGURATION_FILE_NAME}.ts`;
const CLI_ARGS_CONFIG = ['-c', '--config'];

Expand Down Expand Up @@ -48,7 +48,7 @@ if (!fs.existsSync(CONFIGURATION_FILE)) {
process.exit();
}

const configFileContents = loadConfig(path.resolve(CONFIGURATION_FILE));
const configFileContents = await loadConfig(path.resolve(CONFIGURATION_FILE));
const config = getConfigWithDefaults(configFileContents);

export default config;
11 changes: 5 additions & 6 deletions packages/eslint-remote-tester/src/config/load.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createRequire } from 'node:module';
import type { Service, RegisterOptions } from 'ts-node';
import type { Service } from 'ts-node';

let registerer: Service | null = null;
const require = createRequire(import.meta.url);
Expand All @@ -11,9 +11,7 @@ const interopRequireDefault = (obj: any): { default: any } =>
/** @internal */
export const loadTSConfig = (configPath: string) => {
try {
registerer ||= require('ts-node').register({
moduleTypes: { '**/*.ts': 'cjs' },
} satisfies RegisterOptions) as Service;
registerer ||= require('ts-node').register() as Service;
} catch (e: any) {
if (e.code === 'MODULE_NOT_FOUND') {
throw new Error(
Expand All @@ -33,10 +31,11 @@ export const loadTSConfig = (configPath: string) => {
return configObject;
};

export const loadConfig = (configPath: string) => {
export const loadConfig = async (configPath: string) => {
if (configPath.endsWith('.ts')) {
return loadTSConfig(configPath);
}

return require(configPath);
const { default: config } = await import(configPath);
return config;
};
11 changes: 7 additions & 4 deletions packages/eslint-remote-tester/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ export interface Config {
* Supports lazy initialization based on currently tested repository when a function is passed.
* Function is called with current repository and its location on filesystem.
*/
eslintrc:
| Linter.Config
eslintConfig:
| Linter.FlatConfig
| ((options?: {
repository: string;
location: string;
}) => Linter.Config);
}) => Linter.FlatConfig | Promise<Linter.FlatConfig>);

/** Flag used to set CI mode. `process.env.CI` is used when not set. */
CI: boolean;
Expand Down Expand Up @@ -102,7 +102,10 @@ export interface Config {
) => Promise<void> | void;
}

type RequiredFields = Pick<Config, 'repositories' | 'extensions' | 'eslintrc'>;
type RequiredFields = Pick<
Config,
'repositories' | 'extensions' | 'eslintConfig'
>;
type OptionalFields = AllKeysOptional<
Pick<
Config,
Expand Down
26 changes: 14 additions & 12 deletions packages/eslint-remote-tester/src/config/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default async function validate(
rulesUnderTesting,
resultParser,
concurrentTasks,
eslintrc,
eslintConfig,
CI,
logLevel,
slowLintTimeLimit,
Expand All @@ -127,32 +127,34 @@ export default async function validate(

errors.push(validateStringArray('extensions', extensions));

if (!eslintrc) {
errors.push(`Missing eslintrc.`);
if (!eslintConfig) {
errors.push(`Missing eslintConfig.`);
} else {
try {
// This will throw when eslintrc is invalid
// This will throw when eslintConfig is invalid
const linter = new ESLint({
useEslintrc: false,
// @ts-expect-error -- `@types/eslint` for v9 are unavailable
overrideConfig:
typeof eslintrc === 'function' ? eslintrc() : eslintrc,
typeof eslintConfig === 'function'
? await eslintConfig()
: eslintConfig,
});

errors.push(await validateEslintRules(linter));
} catch (e) {
errors.push(`eslintrc: ${e.message}`);
errors.push(`eslintConfig: ${e.message}`);

if (typeof eslintrc === 'function') {
if (typeof eslintConfig === 'function') {
errors.push(
'Note that "config.eslintrc" is called with empty options during configuration validation.'
'Note that "config.eslintConfig" is called with empty options during configuration validation.'
);
}
}
}

// Optional fields

// TODO nice-to-have: Validate rules match eslintrc config
// TODO nice-to-have: Validate rules match eslintConfig config
// https://eslint.org/docs/developer-guide/nodejs-api#lintergetrules
if (rulesUnderTesting) {
if (Array.isArray(rulesUnderTesting)) {
Expand Down Expand Up @@ -294,7 +296,7 @@ export function getConfigWithDefaults(config: ConfigWithOptionals): Config {
}

/**
* Validate given rules of `config.eslintrc.rules`
* Validate given rules of `config.eslintConfig.rules`
* - When unknown rules are defined, or known ones are misspelled they are not
* reported during linting. We need to specifically look for them.
*/
Expand All @@ -313,7 +315,7 @@ async function validateEslintRules(
}

if (errors.length) {
return `Configuration validation errors at eslintrc.rules: \n - ${errors.join(
return `Configuration validation errors at eslintConfig.rules: \n - ${errors.join(
'\n - '
)}`;
}
Expand Down
18 changes: 11 additions & 7 deletions packages/eslint-remote-tester/src/engine/worker-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,25 +211,29 @@ export default async function workerTask(): Promise<void> {
onReadFailure: () => postMessage({ type: 'READ_FAILURE' }),
});

const eslintrc =
typeof config.eslintrc === 'function'
? config.eslintrc({
const eslintConfig =
typeof config.eslintConfig === 'function'
? await config.eslintConfig({
repository,
location: resolve(`${CACHE_LOCATION}/${repository}`),
})
: config.eslintrc;
: config.eslintConfig;

const linter = new ESLint({
useEslintrc: false,
overrideConfig: eslintrc,
// @ts-expect-error -- `@types/eslint` for v9 are unavailable
overrideConfigFile: true,

// @ts-expect-error -- `@types/eslint` for v9 are unavailable
overrideConfig: eslintConfig,

// Only rules set in configuration are expected.
// Ignore all inline configurations found from target repositories.
allowInlineConfig: false,

// Lint all given files, ignore none. Cache is located under node_modules.
// config.pathIgnorePattern is used for exclusions.
ignore: false,
ignore: true,
ignorePatterns: ['!**/node_modules/'],
});

postMessage({ type: 'LINT_START', payload: files.length });
Expand Down
20 changes: 0 additions & 20 deletions packages/eslint-remote-tester/test/integration/base.config.cjs

This file was deleted.

26 changes: 26 additions & 0 deletions packages/eslint-remote-tester/test/integration/base.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** @type {import('../../src/config/types').Config} */
const config = {
repositories: ['AriPerkkio/eslint-remote-tester-integration-test-target'],
extensions: ['.js'],
pathIgnorePattern: '(expected-to-be-excluded)',
rulesUnderTesting: [
'no-unreachable',
'no-undef',
'no-empty',
'getter-return',
'no-compare-neg-zero',
],
eslintConfig: async function initialize() {
const { default: js } = await eval(`import('@eslint/js')`);
const { FlatCompat } = await eval(`import('@eslint/eslintrc')`);
const compat = new FlatCompat({ baseDirectory: process.cwd() });

return [
js.configs.recommended,
...compat.plugins('eslint-plugin-local-rules'),
{ rules: { 'local-rules/some-unstable-rule': 'error' } },
];
},
};

export default config;
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Config } from '../../src/config/types';

const DEBUG_LOG_PATTERN = /\[DEBUG (\S|:)*\] /g;

test('results are rendered on CI mode', async () => {
test.only('results are rendered on CI mode', async () => {
const { output } = await runProductionBuild({ CI: true });
const finalLog = output.pop();

Expand All @@ -35,16 +35,16 @@ test('results are rendered on CI mode', async () => {
TypeError: Cannot read property 'someAttribute' of undefined
Occurred while linting <removed>/node_modules/.cache-eslint-remote-tester/AriPerkkio/eslint-remote-tester-integration-test-target/expected-to-crash-linter.js
Rule: "local-rules/some-unstable-rule"
at Identifier (<removed>/eslint-local-rules.js)
at Identifier (<removed>/eslint-local-rules.cjs:22:56)
at ruleErrorHandler (<removed>/<package-manager-path>/node_modules/eslint/lib/linter/linter.js)
at <removed>/<package-manager-path>/node_modules/eslint/lib/linter/safe-emitter.js
at Array.forEach (<anonymous>)
at Object.emit (<removed>/<package-manager-path>/node_modules/eslint/lib/linter/safe-emitter.js)
at NodeEventGenerator.applySelector (<removed>/<package-manager-path>/node_modules/eslint/lib/linter/node-event-generator.js)
at NodeEventGenerator.applySelectors (<removed>/<package-manager-path>/node_modules/eslint/lib/linter/node-event-generator.js)
at NodeEventGenerator.enterNode (<removed>/<package-manager-path>/node_modules/eslint/lib/linter/node-event-generator.js)
at CodePathAnalyzer.enterNode (<removed>/<package-manager-path>/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js)
at <removed>/<package-manager-path>/node_modules/eslint/lib/linter/linter.js
at runRules (<removed>/<package-manager-path>/node_modules/eslint/lib/linter/linter.js)
at Linter._verifyWithFlatConfigArrayAndWithoutProcessors (<removed>/<package-manager-path>/node_modules/eslint/lib/linter/linter.js)
Repository: AriPerkkio/eslint-remote-tester-integration-test-target
Rule: no-undef
Expand Down
15 changes: 0 additions & 15 deletions packages/eslint-remote-tester/test/smoke/base.config.cjs

This file was deleted.

20 changes: 20 additions & 0 deletions packages/eslint-remote-tester/test/smoke/base.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/** @type {import('../../src/config/types').Config} */
const config = {
repositories: ['AriPerkkio/eslint-remote-tester-integration-test-target'],
extensions: ['.js'],
rulesUnderTesting: [
'local-rules/verbose-rule',
'local-rules/verbose-rule-2',
],
eslintConfig: async function initialize() {
const { FlatCompat } = await eval(`import('@eslint/eslintrc')`);
const compat = new FlatCompat({ baseDirectory: process.cwd() });

return [
...compat.plugins('eslint-plugin-local-rules'),
{ rules: { 'local-rules/verbose-rule': 'error' } },
];
},
};

export default config;
Loading

0 comments on commit 4d8d63d

Please sign in to comment.