fix(linter): support eslint v9 (#24632)
This commit is contained in:
parent
6e6211d072
commit
8cfc0a0c08
@ -110,7 +110,7 @@
|
||||
"@types/cytoscape": "^3.18.2",
|
||||
"@types/detect-port": "^1.3.2",
|
||||
"@types/ejs": "3.1.2",
|
||||
"@types/eslint": "~8.44.2",
|
||||
"@types/eslint": "~8.56.10",
|
||||
"@types/express": "4.17.14",
|
||||
"@types/flat": "^5.0.1",
|
||||
"@types/fs-extra": "^11.0.0",
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
import { join } from 'path';
|
||||
import { satisfies } from 'semver';
|
||||
import { NX_VERSION, normalizePath, workspaceRoot } from '@nx/devkit';
|
||||
import { findNpmDependencies } from '@nx/js/src/utils/find-npm-dependencies';
|
||||
import { ESLintUtils } from '@typescript-eslint/utils';
|
||||
import { AST } from 'jsonc-eslint-parser';
|
||||
import { type JSONLiteral } from 'jsonc-eslint-parser/lib/parser/ast';
|
||||
import { normalizePath, workspaceRoot, NX_VERSION } from '@nx/devkit';
|
||||
import { findNpmDependencies } from '@nx/js/src/utils/find-npm-dependencies';
|
||||
import { readProjectGraph } from '../utils/project-graph-utils';
|
||||
import { findProject, getSourceFilePath } from '../utils/runtime-lint-utils';
|
||||
import { join } from 'path';
|
||||
import { satisfies } from 'semver';
|
||||
import {
|
||||
getAllDependencies,
|
||||
getPackageJson,
|
||||
getProductionDependencies,
|
||||
} from '../utils/package-json-utils';
|
||||
import { ESLintUtils } from '@typescript-eslint/utils';
|
||||
import { readProjectGraph } from '../utils/project-graph-utils';
|
||||
import {
|
||||
findProject,
|
||||
getParserServices,
|
||||
getSourceFilePath,
|
||||
} from '../utils/runtime-lint-utils';
|
||||
|
||||
export type Options = [
|
||||
{
|
||||
@ -96,10 +100,10 @@ export default ESLintUtils.RuleCreator(
|
||||
},
|
||||
]
|
||||
) {
|
||||
if (!(context.parserServices as any).isJSON) {
|
||||
if (!getParserServices(context).isJSON) {
|
||||
return {};
|
||||
}
|
||||
const fileName = normalizePath(context.getFilename());
|
||||
const fileName = normalizePath(context.filename ?? context.getFilename());
|
||||
// support only package.json
|
||||
if (!fileName.endsWith('/package.json')) {
|
||||
return {};
|
||||
|
||||
@ -215,7 +215,7 @@ export default ESLintUtils.RuleCreator(
|
||||
const projectPath = normalizePath(
|
||||
(global as any).projectPath || workspaceRoot
|
||||
);
|
||||
const fileName = normalizePath(context.getFilename());
|
||||
const fileName = normalizePath(context.filename ?? context.getFilename());
|
||||
|
||||
const {
|
||||
projectGraph,
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
import type { AST } from 'jsonc-eslint-parser';
|
||||
import type { TSESLint } from '@typescript-eslint/utils';
|
||||
import { ESLintUtils } from '@typescript-eslint/utils';
|
||||
import type { AST } from 'jsonc-eslint-parser';
|
||||
|
||||
import {
|
||||
ProjectGraphProjectNode,
|
||||
readJsonFile,
|
||||
workspaceRoot,
|
||||
} from '@nx/devkit';
|
||||
import { findProject, getSourceFilePath } from '../utils/runtime-lint-utils';
|
||||
import { existsSync } from 'fs';
|
||||
import { registerTsProject } from '@nx/js/src/internal';
|
||||
import * as path from 'path';
|
||||
import { readProjectGraph } from '../utils/project-graph-utils';
|
||||
import { valid } from 'semver';
|
||||
import { getRootTsConfigPath } from '@nx/js';
|
||||
import { registerTsProject } from '@nx/js/src/internal';
|
||||
import { existsSync } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { valid } from 'semver';
|
||||
import { readProjectGraph } from '../utils/project-graph-utils';
|
||||
import {
|
||||
findProject,
|
||||
getParserServices,
|
||||
getSourceFilePath,
|
||||
} from '../utils/runtime-lint-utils';
|
||||
|
||||
type Options = [
|
||||
{
|
||||
@ -113,14 +117,14 @@ export default ESLintUtils.RuleCreator(() => ``)<Options, MessageIds>({
|
||||
defaultOptions: [DEFAULT_OPTIONS],
|
||||
create(context) {
|
||||
// jsonc-eslint-parser adds this property to parserServices where appropriate
|
||||
if (!(context.parserServices as any).isJSON) {
|
||||
if (!getParserServices(context).isJSON) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { projectGraph, projectRootMappings } = readProjectGraph(RULE_NAME);
|
||||
|
||||
const sourceFilePath = getSourceFilePath(
|
||||
context.getFilename(),
|
||||
context.filename ?? context.getFilename(),
|
||||
workspaceRoot
|
||||
);
|
||||
|
||||
@ -301,7 +305,7 @@ export function validateEntry(
|
||||
});
|
||||
} else {
|
||||
const schemaFilePath = path.join(
|
||||
path.dirname(context.getFilename()),
|
||||
path.dirname(context.filename ?? context.getFilename()),
|
||||
schemaNode.value.value
|
||||
);
|
||||
if (!existsSync(schemaFilePath)) {
|
||||
@ -399,7 +403,7 @@ export function validateImplemenationNode(
|
||||
let resolvedPath: string;
|
||||
|
||||
const modulePath = path.join(
|
||||
path.dirname(context.getFilename()),
|
||||
path.dirname(context.filename ?? context.getFilename()),
|
||||
implementationPath
|
||||
);
|
||||
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import * as path from 'path';
|
||||
import { join } from 'path';
|
||||
import {
|
||||
DependencyType,
|
||||
joinPathFragments,
|
||||
@ -11,18 +9,19 @@ import {
|
||||
ProjectGraphProjectNode,
|
||||
workspaceRoot,
|
||||
} from '@nx/devkit';
|
||||
import { getPath, pathExists } from './graph-utils';
|
||||
import { readFileIfExisting } from 'nx/src/utils/fileutils';
|
||||
import {
|
||||
findProjectForPath,
|
||||
ProjectRootMappings,
|
||||
} from 'nx/src/project-graph/utils/find-project-for-path';
|
||||
import { getRootTsConfigFileName } from '@nx/js';
|
||||
import {
|
||||
resolveModuleByImport,
|
||||
TargetProjectLocator,
|
||||
} from '@nx/js/src/internal';
|
||||
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
|
||||
import { AST_NODE_TYPES, TSESLint, TSESTree } from '@typescript-eslint/utils';
|
||||
import * as path from 'node:path';
|
||||
import {
|
||||
findProjectForPath,
|
||||
ProjectRootMappings,
|
||||
} from 'nx/src/project-graph/utils/find-project-for-path';
|
||||
import { readFileIfExisting } from 'nx/src/utils/fileutils';
|
||||
import { getPath, pathExists } from './graph-utils';
|
||||
|
||||
export type Deps = { [projectName: string]: ProjectGraphDependency[] };
|
||||
type SingleSourceTagConstraint = {
|
||||
@ -393,7 +392,7 @@ function packageExistsInPackageJson(
|
||||
projectRoot: string
|
||||
): boolean {
|
||||
const content = readFileIfExisting(
|
||||
join(workspaceRoot, projectRoot, 'package.json')
|
||||
path.join(workspaceRoot, projectRoot, 'package.json')
|
||||
);
|
||||
if (content) {
|
||||
const { dependencies, devDependencies, peerDependencies } =
|
||||
@ -499,7 +498,7 @@ export function belongsToDifferentNgEntryPoint(
|
||||
const resolvedImportFile = resolveModuleByImport(
|
||||
importExpr,
|
||||
filePath, // not strictly necessary, but speeds up resolution
|
||||
join(workspaceRoot, getRootTsConfigFileName())
|
||||
path.join(workspaceRoot, getRootTsConfigFileName())
|
||||
);
|
||||
|
||||
if (!resolvedImportFile) {
|
||||
@ -560,3 +559,22 @@ export function appIsMFERemote(project: ProjectGraphProjectNode): boolean {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* parserServices moved from the context object to the nested sourceCode object in v8,
|
||||
* and was removed from its original location in v9.
|
||||
*/
|
||||
export function getParserServices(
|
||||
context: Readonly<TSESLint.RuleContext<any, any>>
|
||||
): any {
|
||||
if (context.sourceCode && context.sourceCode.parserServices) {
|
||||
return context.sourceCode.parserServices;
|
||||
}
|
||||
const parserServices = context.parserServices;
|
||||
if (!parserServices) {
|
||||
throw new Error(
|
||||
'Parser Services are not available, please check your ESLint configuration'
|
||||
);
|
||||
}
|
||||
return parserServices;
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
"dependencies": {
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"@nx/js": "file:../js",
|
||||
"eslint": "^8.0.0",
|
||||
"eslint": "^8.0.0 || ^9.0.0",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript": "~5.4.2"
|
||||
},
|
||||
|
||||
@ -1,21 +1,7 @@
|
||||
import type { ESLint } from 'eslint';
|
||||
import { resolveESLintClass } from '../../../utils/resolve-eslint-class';
|
||||
import type { Schema } from '../schema';
|
||||
|
||||
async function resolveESLintClass(
|
||||
useFlatConfig = false
|
||||
): Promise<typeof ESLint> {
|
||||
try {
|
||||
if (!useFlatConfig) {
|
||||
return (await import('eslint')).ESLint;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { FlatESLint } = require('eslint/use-at-your-own-risk');
|
||||
return FlatESLint;
|
||||
} catch {
|
||||
throw new Error('Unable to find ESLint. Ensure ESLint is installed.');
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveAndInstantiateESLint(
|
||||
eslintConfigPath: string | undefined,
|
||||
options: Schema,
|
||||
|
||||
@ -108,6 +108,9 @@ describe('@nx/eslint/plugin', () => {
|
||||
],
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
"env": {
|
||||
"ESLINT_USE_FLAT_CONFIG": "false",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputFile}",
|
||||
@ -180,6 +183,9 @@ describe('@nx/eslint/plugin', () => {
|
||||
],
|
||||
"options": {
|
||||
"cwd": "apps/my-app",
|
||||
"env": {
|
||||
"ESLINT_USE_FLAT_CONFIG": "false",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputFile}",
|
||||
@ -221,6 +227,9 @@ describe('@nx/eslint/plugin', () => {
|
||||
],
|
||||
"options": {
|
||||
"cwd": "apps/my-app",
|
||||
"env": {
|
||||
"ESLINT_USE_FLAT_CONFIG": "false",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputFile}",
|
||||
@ -334,6 +343,9 @@ describe('@nx/eslint/plugin', () => {
|
||||
],
|
||||
"options": {
|
||||
"cwd": "apps/my-app",
|
||||
"env": {
|
||||
"ESLINT_USE_FLAT_CONFIG": "false",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputFile}",
|
||||
@ -359,6 +371,9 @@ describe('@nx/eslint/plugin', () => {
|
||||
],
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
"env": {
|
||||
"ESLINT_USE_FLAT_CONFIG": "false",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputFile}",
|
||||
@ -444,6 +459,9 @@ describe('@nx/eslint/plugin', () => {
|
||||
],
|
||||
"options": {
|
||||
"cwd": "apps/my-app",
|
||||
"env": {
|
||||
"ESLINT_USE_FLAT_CONFIG": "false",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputFile}",
|
||||
@ -470,6 +488,9 @@ describe('@nx/eslint/plugin', () => {
|
||||
],
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
"env": {
|
||||
"ESLINT_USE_FLAT_CONFIG": "false",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputFile}",
|
||||
@ -513,6 +534,9 @@ describe('@nx/eslint/plugin', () => {
|
||||
],
|
||||
"options": {
|
||||
"cwd": "apps/myapp",
|
||||
"env": {
|
||||
"ESLINT_USE_FLAT_CONFIG": "false",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputFile}",
|
||||
@ -561,6 +585,9 @@ describe('@nx/eslint/plugin', () => {
|
||||
],
|
||||
"options": {
|
||||
"cwd": "apps/myapp/nested/mylib",
|
||||
"env": {
|
||||
"ESLINT_USE_FLAT_CONFIG": "false",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputFile}",
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
CreateNodesResult,
|
||||
TargetConfiguration,
|
||||
} from '@nx/devkit';
|
||||
import type { ESLint } from 'eslint';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { dirname, join, normalize, sep } from 'node:path';
|
||||
import { combineGlobPatterns } from 'nx/src/utils/globs';
|
||||
@ -15,6 +14,7 @@ import {
|
||||
baseEsLintFlatConfigFile,
|
||||
isFlatConfig,
|
||||
} from '../utils/config-file';
|
||||
import { resolveESLintClass } from '../utils/resolve-eslint-class';
|
||||
|
||||
export interface EslintPluginOptions {
|
||||
targetName?: string;
|
||||
@ -66,7 +66,7 @@ export const createNodes: CreateNodes<EslintPluginOptions> = [
|
||||
).sort((a, b) => (a !== b && isSubDir(a, b) ? -1 : 1));
|
||||
const excludePatterns = dedupedProjectRoots.map((root) => `${root}/**/*`);
|
||||
|
||||
const ESLint = resolveESLintClass(isFlatConfig(configFilePath));
|
||||
const ESLint = await resolveESLintClass(isFlatConfig(configFilePath));
|
||||
const childProjectRoots = new Set<string>();
|
||||
|
||||
await Promise.all(
|
||||
@ -188,11 +188,12 @@ function buildEslintTargets(
|
||||
],
|
||||
outputs: ['{options.outputFile}'],
|
||||
};
|
||||
if (eslintConfigs.some((config) => isFlatConfig(config))) {
|
||||
|
||||
// Always set the environment variable to ensure that the ESLint CLI can run on eslint v8 and v9
|
||||
const useFlatConfig = eslintConfigs.some((config) => isFlatConfig(config));
|
||||
targetConfig.options.env = {
|
||||
ESLINT_USE_FLAT_CONFIG: 'true',
|
||||
ESLINT_USE_FLAT_CONFIG: useFlatConfig ? 'true' : 'false',
|
||||
};
|
||||
}
|
||||
|
||||
targets[options.targetName] = targetConfig;
|
||||
|
||||
@ -213,18 +214,6 @@ function normalizeOptions(options: EslintPluginOptions): EslintPluginOptions {
|
||||
return options;
|
||||
}
|
||||
|
||||
function resolveESLintClass(useFlatConfig = false): typeof ESLint {
|
||||
try {
|
||||
if (!useFlatConfig) {
|
||||
return require('eslint').ESLint;
|
||||
}
|
||||
|
||||
return require('eslint/use-at-your-own-risk').FlatESLint;
|
||||
} catch {
|
||||
throw new Error('Unable to find ESLint. Ensure ESLint is installed.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `child` is a subdirectory of `parent`. This is a simplified
|
||||
* version that takes into account that paths are always relative to the
|
||||
|
||||
22
packages/eslint/src/utils/resolve-eslint-class.ts
Normal file
22
packages/eslint/src/utils/resolve-eslint-class.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { ESLint } from 'eslint';
|
||||
|
||||
export async function resolveESLintClass(
|
||||
useFlatConfig = false
|
||||
): Promise<typeof ESLint> {
|
||||
try {
|
||||
// In eslint 8.57.0 (the final v8 version), a dedicated API was added for resolving the correct ESLint class.
|
||||
const eslint = await import('eslint');
|
||||
if (typeof (eslint as any).loadESLint === 'function') {
|
||||
return await (eslint as any).loadESLint({ useFlatConfig });
|
||||
}
|
||||
// If that API is not available (an older version of v8), we need to use the old way of resolving the ESLint class.
|
||||
if (!useFlatConfig) {
|
||||
return eslint.ESLint;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { FlatESLint } = require('eslint/use-at-your-own-risk');
|
||||
return FlatESLint;
|
||||
} catch {
|
||||
throw new Error('Unable to find ESLint. Ensure ESLint is installed.');
|
||||
}
|
||||
}
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@ -405,8 +405,8 @@ devDependencies:
|
||||
specifier: 3.1.2
|
||||
version: 3.1.2
|
||||
'@types/eslint':
|
||||
specifier: ~8.44.2
|
||||
version: 8.44.2
|
||||
specifier: ~8.56.10
|
||||
version: 8.56.10
|
||||
'@types/express':
|
||||
specifier: 4.17.14
|
||||
version: 4.17.14
|
||||
@ -12803,13 +12803,13 @@ packages:
|
||||
/@types/eslint-scope@3.7.4:
|
||||
resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==}
|
||||
dependencies:
|
||||
'@types/eslint': 8.44.2
|
||||
'@types/eslint': 8.56.10
|
||||
'@types/estree': 1.0.5
|
||||
|
||||
/@types/eslint@8.44.2:
|
||||
resolution: {integrity: sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==}
|
||||
/@types/eslint@8.56.10:
|
||||
resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==}
|
||||
dependencies:
|
||||
'@types/estree': 1.0.1
|
||||
'@types/estree': 1.0.5
|
||||
'@types/json-schema': 7.0.12
|
||||
|
||||
/@types/estree-jsx@1.0.3:
|
||||
|
||||
@ -24,7 +24,7 @@ export const rule = ESLintUtils.RuleCreator(() => __filename)({
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
// jsonc-eslint-parser adds this property to parserServices where appropriate
|
||||
if (!(context.parserServices as any).isJSON) {
|
||||
if (!(context.sourceCode.parserServices as any).isJSON) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user