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 Jun 8, 2024
1 parent 799968c commit f20858c
Show file tree
Hide file tree
Showing 20 changed files with 574 additions and 696 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ module.exports = {
};
```

Configuration file can also be written in TypeScript if [`ts-node`](https://www.npmjs.com/package/ts-node) is installed. Use `--config` argument for TypeScript configuration file, e.g. `--config eslint-remote-tester.config.ts`.
Configuration file can also be written in TypeScript. Use `--config` argument for TypeScript configuration file, e.g. `--config eslint-remote-tester.config.ts`.

```ts
import type { Config } from 'eslint-remote-tester';
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@
"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",
"pkg-pr-new": "^0.0.8",
"prettier": "^3.2.5",
"publint": "^0.2.8",
"typescript": "^5.4.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;
36 changes: 36 additions & 0 deletions packages/eslint-remote-tester/eslint-remote-tester.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import js from '@eslint/js';
import type { Config } from './src/types';

export default defineConfig({
repositories: [
'AriPerkkio/eslint-remote-tester-integration-test-target',
'AriPerkkio/aria-live-capture',
'AriPerkkio/extend-to-be-announced',
'AriPerkkio/state-mgmt-examples',
'AriPerkkio/suspense-examples',
],

extensions: ['js', 'jsx', 'ts', 'tsx'],

maxFileSizeBytes: undefined,

rulesUnderTesting: [],

resultParser: undefined,

concurrentTasks: 3,

eslintConfig: [js.configs.recommended] as any,

cache: true,

compare: false,

updateComparisonReference: true,

onComplete: undefined,
});

function defineConfig(config: Config) {
return config;
}
35 changes: 24 additions & 11 deletions packages/eslint-remote-tester/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"exports": "./dist/index.js",
"types": "./dist/index.d.ts",
"types": "./dist/types.d.ts",
"exports": {
".": {
"types": "./dist/types.d.ts",
"import": "./dist/index.js"
},
"./github-actions": {
"types": "./dist/exports-for-compare-action.d.ts",
"import": "./dist/exports-for-compare-action.js"
}
},
"files": [
"dist"
],
Expand All @@ -22,9 +31,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,29 +53,34 @@
"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:*",
"importx": "^0.3.5",
"ink-testing-library": "^2.1.0",
"jiti": "^1.21.3",
"node-pty": "^1.0.0",
"strip-ansi": "^6.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"vitest": "^1.6.0"
},
"peerDependencies": {
"eslint": ">=7",
"ts-node": ">=9.0.0"
"eslint": ">=9",
"importx": ">=0.3.5",
"jiti": ">=1"
},
"peerDependenciesMeta": {
"ts-node": {
"importx": {
"optional": true
},
"jiti": {
"optional": true
}
},
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;
52 changes: 26 additions & 26 deletions packages/eslint-remote-tester/src/config/load.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
import { createRequire } from 'node:module';
import type { Service, RegisterOptions } from 'ts-node';

let registerer: Service | null = null;
const require = createRequire(import.meta.url);

/* istanbul ignore next */
const interopRequireDefault = (obj: any): { default: any } =>
obj && obj.__esModule ? obj : { default: obj };

/** @internal */
export const loadTSConfig = (configPath: string) => {
export const loadTSConfig = async (configPath: string) => {
let importx: typeof import('importx') | undefined = undefined;
let jiti: typeof import('jiti') | undefined = undefined;

try {
registerer ||= require('ts-node').register({
moduleTypes: { '**/*.ts': 'cjs' },
} satisfies RegisterOptions) as Service;
} catch (e: any) {
if (e.code === 'MODULE_NOT_FOUND') {
importx = await import('importx');
} catch {
try {
jiti = await import('jiti');
} catch {
throw new Error(
`'ts-node' is required for TypeScript configuration files. Make sure it is installed\nError: ${e.message}`
"'jiti' or 'importx' is required for loading TypeScript configuration files. Make sure to install one of them."
);
}

throw e;
}

registerer.enabled(true);

const configObject = interopRequireDefault(require(configPath)).default;
if (importx) {
const config = await importx.import(configPath, import.meta.url);
return config.default;
}

registerer.enabled(false);
if (jiti) {
return jiti.default(import.meta.url, {
interopDefault: true,
esmResolve: true,
})(configPath);
}

return configObject;
throw new Error(
"'jiti' or 'importx' is required for loading TypeScript configuration files. Make sure to install one of them."
);
};

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
29 changes: 17 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,37 @@ 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
overrideConfigFile: true,

// @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 +299,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 +318,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
Loading

0 comments on commit f20858c

Please sign in to comment.