@nx/eslint 0.0.0-pr-22179-271588f → 0.0.0-pr-26482-ebe2dba
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/generators.json +5 -0
- package/migrations.json +17 -12
- package/package.json +12 -10
- package/plugin.d.ts +1 -1
- package/plugin.js +2 -1
- package/src/executors/lint/lint.impl.d.ts +1 -1
- package/src/executors/lint/lint.impl.js +23 -18
- package/src/executors/lint/utility/eslint-utils.js +4 -17
- package/src/generators/convert-to-flat-config/generator.d.ts +2 -2
- package/src/generators/convert-to-flat-config/generator.js +8 -2
- package/src/generators/convert-to-inferred/convert-to-inferred.d.ts +7 -0
- package/src/generators/convert-to-inferred/convert-to-inferred.js +80 -0
- package/src/generators/convert-to-inferred/lib/target-options-map.d.ts +16 -0
- package/src/generators/convert-to-inferred/lib/target-options-map.js +19 -0
- package/src/generators/convert-to-inferred/schema.json +19 -0
- package/src/generators/init/init-migration.js +4 -0
- package/src/generators/init/init.d.ts +1 -1
- package/src/generators/init/init.js +16 -27
- package/src/generators/lint-project/lint-project.d.ts +2 -1
- package/src/generators/lint-project/lint-project.js +16 -20
- package/src/generators/utils/eslint-file.js +4 -2
- package/src/generators/utils/flat-config/ast-utils.js +31 -7
- package/src/plugins/plugin.d.ts +3 -1
- package/src/plugins/plugin.js +285 -58
- package/src/utils/config-file.d.ts +4 -1
- package/src/utils/config-file.js +50 -16
- package/src/utils/resolve-eslint-class.d.ts +2 -0
- package/src/utils/resolve-eslint-class.js +23 -0
- package/src/utils/versions.d.ts +2 -2
- package/src/utils/versions.js +2 -2
- package/src/migrations/update-15-0-0/add-eslint-inputs.d.ts +0 -2
- package/src/migrations/update-15-0-0/add-eslint-inputs.js +0 -27
- package/src/migrations/update-15-7-1/add-eslint-ignore.d.ts +0 -2
- package/src/migrations/update-15-7-1/add-eslint-ignore.js +0 -36
|
@@ -4,6 +4,11 @@ exports.generateAst = exports.mapFilePaths = exports.generateFlatOverride = expo
|
|
|
4
4
|
const devkit_1 = require("@nx/devkit");
|
|
5
5
|
const ts = require("typescript");
|
|
6
6
|
const path_utils_1 = require("./path-utils");
|
|
7
|
+
/**
|
|
8
|
+
* Supports direct identifiers, and those nested within an object (of arbitrary depth)
|
|
9
|
+
* E.g. `...foo` and `...foo.bar.baz.qux` etc
|
|
10
|
+
*/
|
|
11
|
+
const SPREAD_ELEMENTS_REGEXP = /\s*\.\.\.[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)*,?\n?/g;
|
|
7
12
|
/**
|
|
8
13
|
* Remove all overrides from the config file
|
|
9
14
|
*/
|
|
@@ -58,11 +63,13 @@ function hasOverride(content, lookup) {
|
|
|
58
63
|
let objSource;
|
|
59
64
|
if (ts.isObjectLiteralExpression(node)) {
|
|
60
65
|
objSource = node.getFullText();
|
|
66
|
+
// strip any spread elements
|
|
67
|
+
objSource = objSource.replace(SPREAD_ELEMENTS_REGEXP, '');
|
|
61
68
|
}
|
|
62
69
|
else {
|
|
63
70
|
const fullNodeText = node['expression'].arguments[0].body.expression.getFullText();
|
|
64
71
|
// strip any spread elements
|
|
65
|
-
objSource = fullNodeText.replace(
|
|
72
|
+
objSource = fullNodeText.replace(SPREAD_ELEMENTS_REGEXP, '');
|
|
66
73
|
}
|
|
67
74
|
const data = (0, devkit_1.parseJson)(objSource
|
|
68
75
|
// ensure property names have double quotes so that JSON.parse works
|
|
@@ -76,7 +83,6 @@ function hasOverride(content, lookup) {
|
|
|
76
83
|
return false;
|
|
77
84
|
}
|
|
78
85
|
exports.hasOverride = hasOverride;
|
|
79
|
-
const STRIP_SPREAD_ELEMENTS = /\s*\.\.\.[a-zA-Z0-9_]+,?\n?/g;
|
|
80
86
|
function parseTextToJson(text) {
|
|
81
87
|
return (0, devkit_1.parseJson)(text
|
|
82
88
|
// ensure property names have double quotes so that JSON.parse works
|
|
@@ -105,7 +111,7 @@ function replaceOverride(content, root, lookup, update) {
|
|
|
105
111
|
else {
|
|
106
112
|
const fullNodeText = node['expression'].arguments[0].body.expression.getFullText();
|
|
107
113
|
// strip any spread elements
|
|
108
|
-
objSource = fullNodeText.replace(
|
|
114
|
+
objSource = fullNodeText.replace(SPREAD_ELEMENTS_REGEXP, '');
|
|
109
115
|
start =
|
|
110
116
|
node['expression'].arguments[0].body.expression.properties.pos +
|
|
111
117
|
(fullNodeText.length - objSource.length);
|
|
@@ -283,7 +289,7 @@ function removePlugin(content, pluginName, pluginImport) {
|
|
|
283
289
|
const pluginsArray = pluginsElem.initializer;
|
|
284
290
|
const plugins = parseTextToJson(pluginsElem.initializer
|
|
285
291
|
.getText()
|
|
286
|
-
.replace(
|
|
292
|
+
.replace(SPREAD_ELEMENTS_REGEXP, ''));
|
|
287
293
|
if (plugins.length > 1) {
|
|
288
294
|
changes.push({
|
|
289
295
|
type: devkit_1.ChangeType.Delete,
|
|
@@ -419,7 +425,7 @@ function addCompatToFlatConfig(content) {
|
|
|
419
425
|
if (result.includes('const compat = new FlatCompat')) {
|
|
420
426
|
return result;
|
|
421
427
|
}
|
|
422
|
-
result = addImportToFlatConfig(result, 'FlatCompat', '@eslint/eslintrc');
|
|
428
|
+
result = addImportToFlatConfig(result, ['FlatCompat'], '@eslint/eslintrc');
|
|
423
429
|
const index = result.indexOf('module.exports');
|
|
424
430
|
return (0, devkit_1.applyChangesToString)(result, [
|
|
425
431
|
{
|
|
@@ -502,15 +508,33 @@ function generateFlatOverride(override) {
|
|
|
502
508
|
!override.extends &&
|
|
503
509
|
!override.plugins &&
|
|
504
510
|
!override.parser) {
|
|
511
|
+
if (override.parserOptions) {
|
|
512
|
+
const { parserOptions, ...rest } = override;
|
|
513
|
+
return generateAst({ ...rest, languageOptions: { parserOptions } });
|
|
514
|
+
}
|
|
505
515
|
return generateAst(override);
|
|
506
516
|
}
|
|
507
|
-
const { files, excludedFiles, rules, ...rest } = override;
|
|
517
|
+
const { files, excludedFiles, rules, parserOptions, ...rest } = override;
|
|
508
518
|
const objectLiteralElements = [
|
|
509
519
|
ts.factory.createSpreadAssignment(ts.factory.createIdentifier('config')),
|
|
510
520
|
];
|
|
511
521
|
addTSObjectProperty(objectLiteralElements, 'files', files);
|
|
512
522
|
addTSObjectProperty(objectLiteralElements, 'excludedFiles', excludedFiles);
|
|
513
|
-
|
|
523
|
+
// Apply rules (and spread ...config.rules into it as the first assignment)
|
|
524
|
+
addTSObjectProperty(objectLiteralElements, 'rules', rules || {});
|
|
525
|
+
const rulesObjectAST = objectLiteralElements.pop();
|
|
526
|
+
const rulesObjectInitializer = rulesObjectAST.initializer;
|
|
527
|
+
const spreadAssignment = ts.factory.createSpreadAssignment(ts.factory.createIdentifier('config.rules'));
|
|
528
|
+
const updatedRulesProperties = [
|
|
529
|
+
spreadAssignment,
|
|
530
|
+
...rulesObjectInitializer.properties,
|
|
531
|
+
];
|
|
532
|
+
objectLiteralElements.push(ts.factory.createPropertyAssignment('rules', ts.factory.createObjectLiteralExpression(updatedRulesProperties, true)));
|
|
533
|
+
if (parserOptions) {
|
|
534
|
+
addTSObjectProperty(objectLiteralElements, 'languageOptions', {
|
|
535
|
+
parserOptions,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
514
538
|
return ts.factory.createSpreadElement(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('compat'), ts.factory.createIdentifier('config')), undefined, [generateAst(rest)]), ts.factory.createIdentifier('map')), undefined, [
|
|
515
539
|
ts.factory.createArrowFunction(undefined, undefined, [
|
|
516
540
|
ts.factory.createParameterDeclaration(undefined, undefined, 'config'),
|
package/src/plugins/plugin.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { CreateNodes } from '@nx/devkit';
|
|
1
|
+
import { CreateNodes, CreateNodesV2 } from '@nx/devkit';
|
|
2
2
|
export interface EslintPluginOptions {
|
|
3
3
|
targetName?: string;
|
|
4
|
+
extensions?: string[];
|
|
4
5
|
}
|
|
6
|
+
export declare const createNodesV2: CreateNodesV2<EslintPluginOptions>;
|
|
5
7
|
export declare const createNodes: CreateNodes<EslintPluginOptions>;
|
package/src/plugins/plugin.js
CHANGED
|
@@ -1,92 +1,319 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createNodes = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
3
|
+
exports.createNodes = exports.createNodesV2 = void 0;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const calculate_hash_for_create_nodes_1 = require("@nx/devkit/src/utils/calculate-hash-for-create-nodes");
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const file_hasher_1 = require("nx/src/hasher/file-hasher");
|
|
9
|
+
const cache_directory_1 = require("nx/src/utils/cache-directory");
|
|
6
10
|
const globs_1 = require("nx/src/utils/globs");
|
|
11
|
+
const workspace_context_1 = require("nx/src/utils/workspace-context");
|
|
12
|
+
const semver_1 = require("semver");
|
|
7
13
|
const config_file_1 = require("../utils/config-file");
|
|
14
|
+
const resolve_eslint_class_1 = require("../utils/resolve-eslint-class");
|
|
15
|
+
const DEFAULT_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'html', 'vue'];
|
|
16
|
+
const PROJECT_CONFIG_FILENAMES = ['project.json', 'package.json'];
|
|
17
|
+
const ESLINT_CONFIG_GLOB_V1 = (0, globs_1.combineGlobPatterns)(config_file_1.ESLINT_CONFIG_FILENAMES.map((f) => `**/${f}`));
|
|
18
|
+
const ESLINT_CONFIG_GLOB_V2 = (0, globs_1.combineGlobPatterns)([
|
|
19
|
+
...config_file_1.ESLINT_CONFIG_FILENAMES.map((f) => `**/${f}`),
|
|
20
|
+
...PROJECT_CONFIG_FILENAMES.map((f) => `**/${f}`),
|
|
21
|
+
]);
|
|
22
|
+
function readTargetsCache(cachePath) {
|
|
23
|
+
return process.env.NX_CACHE_PROJECT_GRAPH !== 'false' && (0, node_fs_1.existsSync)(cachePath)
|
|
24
|
+
? (0, devkit_1.readJsonFile)(cachePath)
|
|
25
|
+
: {};
|
|
26
|
+
}
|
|
27
|
+
function writeTargetsToCache(cachePath, results) {
|
|
28
|
+
(0, devkit_1.writeJsonFile)(cachePath, results);
|
|
29
|
+
}
|
|
30
|
+
const internalCreateNodes = async (configFilePath, options, context, projectsCache) => {
|
|
31
|
+
options = normalizeOptions(options);
|
|
32
|
+
const configDir = (0, node_path_1.dirname)(configFilePath);
|
|
33
|
+
// Ensure that configFiles are set, e2e-run fails due to them being undefined in CI (does not occur locally)
|
|
34
|
+
// TODO(JamesHenry): Further troubleshoot this in CI
|
|
35
|
+
context.configFiles = context.configFiles ?? [];
|
|
36
|
+
// Create a Set of all the directories containing eslint configs, and a
|
|
37
|
+
// list of globs to exclude from child projects
|
|
38
|
+
const nestedEslintRootPatterns = [];
|
|
39
|
+
for (const configFile of context.configFiles) {
|
|
40
|
+
const eslintRootDir = (0, node_path_1.dirname)(configFile);
|
|
41
|
+
if (eslintRootDir !== configDir && isSubDir(configDir, eslintRootDir)) {
|
|
42
|
+
nestedEslintRootPatterns.push(`${eslintRootDir}/**/*`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const projectFiles = await (0, workspace_context_1.globWithWorkspaceContext)(context.workspaceRoot, ['project.json', 'package.json', '**/project.json', '**/package.json'].map((f) => (0, node_path_1.join)(configDir, f)), nestedEslintRootPatterns.length ? nestedEslintRootPatterns : undefined);
|
|
46
|
+
// dedupe and sort project roots by depth for more efficient traversal
|
|
47
|
+
const dedupedProjectRoots = Array.from(new Set(projectFiles.map((f) => (0, node_path_1.dirname)(f)))).sort((a, b) => (a !== b && isSubDir(a, b) ? -1 : 1));
|
|
48
|
+
const excludePatterns = dedupedProjectRoots.map((root) => `${root}/**/*`);
|
|
49
|
+
const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)((0, config_file_1.isFlatConfig)(configFilePath));
|
|
50
|
+
const eslintVersion = ESLint.version;
|
|
51
|
+
const projects = {};
|
|
52
|
+
await Promise.all(dedupedProjectRoots.map(async (childProjectRoot, index) => {
|
|
53
|
+
// anything after is either a nested project or a sibling project, can be excluded
|
|
54
|
+
const nestedProjectRootPatterns = excludePatterns.slice(index + 1);
|
|
55
|
+
// Ignore project roots where the project does not contain any lintable files
|
|
56
|
+
const lintableFiles = await (0, workspace_context_1.globWithWorkspaceContext)(context.workspaceRoot, [(0, node_path_1.join)(childProjectRoot, `**/*.{${options.extensions.join(',')}}`)],
|
|
57
|
+
// exclude nested eslint roots and nested project roots
|
|
58
|
+
[...nestedEslintRootPatterns, ...nestedProjectRootPatterns]);
|
|
59
|
+
const parentConfigs = context.configFiles.filter((eslintConfig) => isSubDir(childProjectRoot, (0, node_path_1.dirname)(eslintConfig)));
|
|
60
|
+
const hash = await (0, calculate_hash_for_create_nodes_1.calculateHashForCreateNodes)(childProjectRoot, options, context, [...parentConfigs, (0, node_path_1.join)(childProjectRoot, '.eslintignore')]);
|
|
61
|
+
if (projectsCache[hash]) {
|
|
62
|
+
// We can reuse the projects in the cache.
|
|
63
|
+
Object.assign(projects, projectsCache[hash]);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const eslint = new ESLint({
|
|
67
|
+
cwd: (0, node_path_1.join)(context.workspaceRoot, childProjectRoot),
|
|
68
|
+
});
|
|
69
|
+
let hasNonIgnoredLintableFiles = false;
|
|
70
|
+
for (const file of lintableFiles) {
|
|
71
|
+
if (!(await eslint.isPathIgnored((0, node_path_1.join)(context.workspaceRoot, file)))) {
|
|
72
|
+
hasNonIgnoredLintableFiles = true;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!hasNonIgnoredLintableFiles) {
|
|
77
|
+
// No lintable files in the project, store in the cache and skip further processing
|
|
78
|
+
projectsCache[hash] = {};
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const project = getProjectUsingESLintConfig(configFilePath, childProjectRoot, eslintVersion, options, context);
|
|
82
|
+
if (project) {
|
|
83
|
+
projects[childProjectRoot] = project;
|
|
84
|
+
// Store project into the cache
|
|
85
|
+
projectsCache[hash] = { [childProjectRoot]: project };
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// No project found, store in the cache
|
|
89
|
+
projectsCache[hash] = {};
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
return {
|
|
93
|
+
projects,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
let collectingLintableFilesPromise;
|
|
97
|
+
const internalCreateNodesV2 = async (configFilePath, options, context, eslintConfigFiles, allProjectRoots, projectRootsByEslintRoots, lintableFilesPerProjectRoot, projectsCache) => {
|
|
98
|
+
const configDir = (0, node_path_1.dirname)(configFilePath);
|
|
99
|
+
const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)((0, config_file_1.isFlatConfig)(configFilePath));
|
|
100
|
+
const eslintVersion = ESLint.version;
|
|
101
|
+
const projects = {};
|
|
102
|
+
await Promise.all(projectRootsByEslintRoots.get(configDir).map(async (projectRoot) => {
|
|
103
|
+
const parentConfigs = eslintConfigFiles.filter((eslintConfig) => isSubDir(projectRoot, (0, node_path_1.dirname)(eslintConfig)));
|
|
104
|
+
const hash = await (0, calculate_hash_for_create_nodes_1.calculateHashForCreateNodes)(projectRoot, options, {
|
|
105
|
+
configFiles: eslintConfigFiles,
|
|
106
|
+
nxJsonConfiguration: context.nxJsonConfiguration,
|
|
107
|
+
workspaceRoot: context.workspaceRoot,
|
|
108
|
+
}, [...parentConfigs, (0, node_path_1.join)(projectRoot, '.eslintignore')]);
|
|
109
|
+
if (projectsCache[hash]) {
|
|
110
|
+
// We can reuse the projects in the cache.
|
|
111
|
+
Object.assign(projects, projectsCache[hash]);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!lintableFilesPerProjectRoot.size) {
|
|
115
|
+
collectingLintableFilesPromise ??= collectLintableFilesByProjectRoot(lintableFilesPerProjectRoot, allProjectRoots, options, context);
|
|
116
|
+
await collectingLintableFilesPromise;
|
|
117
|
+
collectingLintableFilesPromise = null;
|
|
118
|
+
}
|
|
119
|
+
const eslint = new ESLint({
|
|
120
|
+
cwd: (0, node_path_1.join)(context.workspaceRoot, projectRoot),
|
|
121
|
+
});
|
|
122
|
+
let hasNonIgnoredLintableFiles = false;
|
|
123
|
+
for (const file of lintableFilesPerProjectRoot.get(projectRoot) ?? []) {
|
|
124
|
+
if (!(await eslint.isPathIgnored((0, node_path_1.join)(context.workspaceRoot, file)))) {
|
|
125
|
+
hasNonIgnoredLintableFiles = true;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!hasNonIgnoredLintableFiles) {
|
|
130
|
+
// No lintable files in the project, store in the cache and skip further processing
|
|
131
|
+
projectsCache[hash] = {};
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const project = getProjectUsingESLintConfig(configFilePath, projectRoot, eslintVersion, options, context);
|
|
135
|
+
if (project) {
|
|
136
|
+
projects[projectRoot] = project;
|
|
137
|
+
// Store project into the cache
|
|
138
|
+
projectsCache[hash] = { [projectRoot]: project };
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// No project found, store in the cache
|
|
142
|
+
projectsCache[hash] = {};
|
|
143
|
+
}
|
|
144
|
+
}));
|
|
145
|
+
return {
|
|
146
|
+
projects,
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
exports.createNodesV2 = [
|
|
150
|
+
ESLINT_CONFIG_GLOB_V2,
|
|
151
|
+
async (configFiles, options, context) => {
|
|
152
|
+
options = normalizeOptions(options);
|
|
153
|
+
const optionsHash = (0, file_hasher_1.hashObject)(options);
|
|
154
|
+
const cachePath = (0, node_path_1.join)(cache_directory_1.workspaceDataDirectory, `eslint-${optionsHash}.hash`);
|
|
155
|
+
const targetsCache = readTargetsCache(cachePath);
|
|
156
|
+
const { eslintConfigFiles, projectRoots, projectRootsByEslintRoots } = splitConfigFiles(configFiles);
|
|
157
|
+
const lintableFilesPerProjectRoot = new Map();
|
|
158
|
+
try {
|
|
159
|
+
return await (0, devkit_1.createNodesFromFiles)((configFile, options, context) => internalCreateNodesV2(configFile, options, context, eslintConfigFiles, projectRoots, projectRootsByEslintRoots, lintableFilesPerProjectRoot, targetsCache), eslintConfigFiles, options, context);
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
writeTargetsToCache(cachePath, targetsCache);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
];
|
|
8
166
|
exports.createNodes = [
|
|
9
|
-
|
|
167
|
+
ESLINT_CONFIG_GLOB_V1,
|
|
10
168
|
(configFilePath, options, context) => {
|
|
11
|
-
|
|
12
|
-
options
|
|
13
|
-
const eslintConfigs = getEslintConfigsForProject(projectRoot, context.workspaceRoot);
|
|
14
|
-
if (!eslintConfigs.length) {
|
|
15
|
-
return {};
|
|
16
|
-
}
|
|
17
|
-
return {
|
|
18
|
-
projects: {
|
|
19
|
-
[projectRoot]: {
|
|
20
|
-
targets: buildEslintTargets(eslintConfigs, projectRoot, options),
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
};
|
|
169
|
+
devkit_1.logger.warn('`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.');
|
|
170
|
+
return internalCreateNodes(configFilePath, options, context, {});
|
|
24
171
|
},
|
|
25
172
|
];
|
|
26
|
-
function
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
173
|
+
function splitConfigFiles(configFiles) {
|
|
174
|
+
const eslintConfigFiles = [];
|
|
175
|
+
const projectRoots = new Set();
|
|
176
|
+
for (const configFile of configFiles) {
|
|
177
|
+
if (PROJECT_CONFIG_FILENAMES.includes((0, node_path_1.basename)(configFile))) {
|
|
178
|
+
projectRoots.add((0, node_path_1.dirname)(configFile));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
eslintConfigFiles.push(configFile);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const uniqueProjectRoots = Array.from(projectRoots);
|
|
185
|
+
const projectRootsByEslintRoots = groupProjectRootsByEslintRoots(eslintConfigFiles, uniqueProjectRoots);
|
|
186
|
+
return {
|
|
187
|
+
eslintConfigFiles,
|
|
188
|
+
projectRoots: uniqueProjectRoots,
|
|
189
|
+
projectRootsByEslintRoots,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function groupProjectRootsByEslintRoots(eslintConfigFiles, projectRoots) {
|
|
193
|
+
const projectRootsByEslintRoots = new Map();
|
|
194
|
+
for (const eslintConfig of eslintConfigFiles) {
|
|
195
|
+
projectRootsByEslintRoots.set((0, node_path_1.dirname)(eslintConfig), []);
|
|
196
|
+
}
|
|
197
|
+
for (const projectRoot of projectRoots) {
|
|
198
|
+
const eslintRoot = getRootForDirectory(projectRoot, projectRootsByEslintRoots);
|
|
199
|
+
if (eslintRoot) {
|
|
200
|
+
projectRootsByEslintRoots.get(eslintRoot).push(projectRoot);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return projectRootsByEslintRoots;
|
|
204
|
+
}
|
|
205
|
+
async function collectLintableFilesByProjectRoot(lintableFilesPerProjectRoot, projectRoots, options, context) {
|
|
206
|
+
const lintableFiles = await (0, workspace_context_1.globWithWorkspaceContext)(context.workspaceRoot, [
|
|
207
|
+
`**/*.{${options.extensions.join(',')}}`,
|
|
208
|
+
]);
|
|
209
|
+
for (const projectRoot of projectRoots) {
|
|
210
|
+
lintableFilesPerProjectRoot.set(projectRoot, []);
|
|
211
|
+
}
|
|
212
|
+
for (const file of lintableFiles) {
|
|
213
|
+
const projectRoot = getRootForDirectory((0, node_path_1.dirname)(file), lintableFilesPerProjectRoot);
|
|
214
|
+
if (projectRoot) {
|
|
215
|
+
lintableFilesPerProjectRoot.get(projectRoot).push(file);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function getRootForDirectory(directory, roots) {
|
|
220
|
+
let currentPath = (0, node_path_1.normalize)(directory);
|
|
221
|
+
while (currentPath !== (0, node_path_1.dirname)(currentPath)) {
|
|
222
|
+
if (roots.has(currentPath)) {
|
|
223
|
+
return currentPath;
|
|
224
|
+
}
|
|
225
|
+
currentPath = (0, node_path_1.dirname)(currentPath);
|
|
31
226
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
227
|
+
return roots.has(currentPath) ? currentPath : null;
|
|
228
|
+
}
|
|
229
|
+
function getProjectUsingESLintConfig(configFilePath, projectRoot, eslintVersion, options, context) {
|
|
230
|
+
const rootEslintConfig = [
|
|
231
|
+
config_file_1.baseEsLintConfigFile,
|
|
232
|
+
config_file_1.baseEsLintFlatConfigFile,
|
|
233
|
+
...config_file_1.ESLINT_CONFIG_FILENAMES,
|
|
234
|
+
].find((f) => (0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, f)));
|
|
235
|
+
// Add a lint target for each child project without an eslint config, with the root level config as an input
|
|
236
|
+
let standaloneSrcPath;
|
|
237
|
+
if (projectRoot === '.' &&
|
|
238
|
+
(0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'package.json'))) {
|
|
239
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'src'))) {
|
|
240
|
+
standaloneSrcPath = 'src';
|
|
241
|
+
}
|
|
242
|
+
else if ((0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'lib'))) {
|
|
243
|
+
standaloneSrcPath = 'lib';
|
|
244
|
+
}
|
|
45
245
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const config = siblingFiles.find((f) => config_file_1.ESLINT_CONFIG_FILENAMES.includes(f));
|
|
49
|
-
if (config) {
|
|
50
|
-
detectedConfigs.add(`${projectRoot}/${config}`);
|
|
51
|
-
return Array.from(detectedConfigs);
|
|
52
|
-
}
|
|
53
|
-
projectRoot = (0, path_1.dirname)(projectRoot);
|
|
54
|
-
siblingFiles = (0, fs_1.readdirSync)((0, path_1.join)(workspaceRoot, projectRoot));
|
|
246
|
+
if (projectRoot === '.' && !standaloneSrcPath) {
|
|
247
|
+
return null;
|
|
55
248
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
detectedConfigs.add(config);
|
|
60
|
-
return Array.from(detectedConfigs);
|
|
249
|
+
const eslintConfigs = [configFilePath];
|
|
250
|
+
if (rootEslintConfig && !eslintConfigs.includes(rootEslintConfig)) {
|
|
251
|
+
eslintConfigs.unshift(rootEslintConfig);
|
|
61
252
|
}
|
|
62
|
-
return
|
|
253
|
+
return {
|
|
254
|
+
targets: buildEslintTargets(eslintConfigs, eslintVersion, projectRoot, context.workspaceRoot, options, standaloneSrcPath),
|
|
255
|
+
};
|
|
63
256
|
}
|
|
64
|
-
function buildEslintTargets(eslintConfigs, projectRoot, options) {
|
|
257
|
+
function buildEslintTargets(eslintConfigs, eslintVersion, projectRoot, workspaceRoot, options, standaloneSrcPath) {
|
|
65
258
|
const isRootProject = projectRoot === '.';
|
|
66
259
|
const targets = {};
|
|
67
260
|
const targetConfig = {
|
|
68
|
-
command: `eslint ${isRootProject ?
|
|
261
|
+
command: `eslint ${isRootProject && standaloneSrcPath ? `./${standaloneSrcPath}` : '.'}`,
|
|
69
262
|
cache: true,
|
|
70
263
|
options: {
|
|
71
264
|
cwd: projectRoot,
|
|
72
265
|
},
|
|
73
266
|
inputs: [
|
|
74
267
|
'default',
|
|
75
|
-
|
|
268
|
+
// Certain lint rules can be impacted by changes to dependencies
|
|
269
|
+
'^default',
|
|
270
|
+
...eslintConfigs.map((config) => `{workspaceRoot}/${config}`.replace(`{workspaceRoot}/${projectRoot}`, isRootProject ? '{projectRoot}/' : '{projectRoot}')),
|
|
271
|
+
...((0, node_fs_1.existsSync)((0, node_path_1.join)(workspaceRoot, projectRoot, '.eslintignore'))
|
|
272
|
+
? ['{projectRoot}/.eslintignore']
|
|
273
|
+
: []),
|
|
76
274
|
'{workspaceRoot}/tools/eslint-rules/**/*',
|
|
77
275
|
{ externalDependencies: ['eslint'] },
|
|
78
276
|
],
|
|
277
|
+
outputs: ['{options.outputFile}'],
|
|
79
278
|
};
|
|
80
|
-
|
|
279
|
+
// Always set the environment variable to ensure that the ESLint CLI can run on eslint v8 and v9
|
|
280
|
+
const useFlatConfig = eslintConfigs.some((config) => (0, config_file_1.isFlatConfig)(config));
|
|
281
|
+
// Flat config is default for 9.0.0+
|
|
282
|
+
const defaultSetting = (0, semver_1.gte)(eslintVersion, '9.0.0');
|
|
283
|
+
if (useFlatConfig !== defaultSetting) {
|
|
81
284
|
targetConfig.options.env = {
|
|
82
|
-
ESLINT_USE_FLAT_CONFIG: 'true',
|
|
285
|
+
ESLINT_USE_FLAT_CONFIG: useFlatConfig ? 'true' : 'false',
|
|
83
286
|
};
|
|
84
287
|
}
|
|
85
288
|
targets[options.targetName] = targetConfig;
|
|
86
289
|
return targets;
|
|
87
290
|
}
|
|
88
291
|
function normalizeOptions(options) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
292
|
+
const normalizedOptions = {
|
|
293
|
+
targetName: options.targetName ?? 'lint',
|
|
294
|
+
};
|
|
295
|
+
// Normalize user input for extensions (strip leading . characters)
|
|
296
|
+
if (Array.isArray(options.extensions)) {
|
|
297
|
+
normalizedOptions.extensions = options.extensions.map((f) => f.replace(/^\.+/, ''));
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
normalizedOptions.extensions = DEFAULT_EXTENSIONS;
|
|
301
|
+
}
|
|
302
|
+
return normalizedOptions;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Determines if `child` is a subdirectory of `parent`. This is a simplified
|
|
306
|
+
* version that takes into account that paths are always relative to the
|
|
307
|
+
* workspace root.
|
|
308
|
+
*/
|
|
309
|
+
function isSubDir(parent, child) {
|
|
310
|
+
if (parent === '.') {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
parent = (0, node_path_1.normalize)(parent);
|
|
314
|
+
child = (0, node_path_1.normalize)(child);
|
|
315
|
+
if (!parent.endsWith(node_path_1.sep)) {
|
|
316
|
+
parent += node_path_1.sep;
|
|
317
|
+
}
|
|
318
|
+
return child.startsWith(parent);
|
|
92
319
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
export declare const ESLINT_FLAT_CONFIG_FILENAMES: string[];
|
|
2
|
+
export declare const ESLINT_OLD_CONFIG_FILENAMES: string[];
|
|
1
3
|
export declare const ESLINT_CONFIG_FILENAMES: string[];
|
|
2
4
|
export declare const baseEsLintConfigFile = ".eslintrc.base.json";
|
|
3
5
|
export declare const baseEsLintFlatConfigFile = "eslint.base.config.js";
|
|
4
|
-
export declare function findBaseEslintFile(workspaceRoot?: string): string | null;
|
|
5
6
|
export declare function isFlatConfig(configFilePath: string): boolean;
|
|
7
|
+
export declare function findFlatConfigFile(directory: string, workspaceRoot: string): string | null;
|
|
8
|
+
export declare function findOldConfigFile(filePathOrDirectory: string, workspaceRoot: string): string | null;
|
package/src/utils/config-file.js
CHANGED
|
@@ -1,35 +1,69 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
const devkit_1 = require("@nx/devkit");
|
|
3
|
+
exports.findOldConfigFile = exports.findFlatConfigFile = exports.isFlatConfig = exports.baseEsLintFlatConfigFile = exports.baseEsLintConfigFile = exports.ESLINT_CONFIG_FILENAMES = exports.ESLINT_OLD_CONFIG_FILENAMES = exports.ESLINT_FLAT_CONFIG_FILENAMES = void 0;
|
|
5
4
|
const fs_1 = require("fs");
|
|
6
|
-
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
// TODO(leo): add support for eslint.config.mjs and eslint.config.cjs
|
|
7
|
+
exports.ESLINT_FLAT_CONFIG_FILENAMES = ['eslint.config.js'];
|
|
8
|
+
exports.ESLINT_OLD_CONFIG_FILENAMES = [
|
|
7
9
|
'.eslintrc',
|
|
8
10
|
'.eslintrc.js',
|
|
9
11
|
'.eslintrc.cjs',
|
|
10
12
|
'.eslintrc.yaml',
|
|
11
13
|
'.eslintrc.yml',
|
|
12
14
|
'.eslintrc.json',
|
|
13
|
-
|
|
15
|
+
];
|
|
16
|
+
exports.ESLINT_CONFIG_FILENAMES = [
|
|
17
|
+
...exports.ESLINT_OLD_CONFIG_FILENAMES,
|
|
18
|
+
...exports.ESLINT_FLAT_CONFIG_FILENAMES,
|
|
14
19
|
];
|
|
15
20
|
exports.baseEsLintConfigFile = '.eslintrc.base.json';
|
|
16
21
|
exports.baseEsLintFlatConfigFile = 'eslint.base.config.js';
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
function isFlatConfig(configFilePath) {
|
|
23
|
+
const configFileName = (0, path_1.basename)(configFilePath);
|
|
24
|
+
return exports.ESLINT_FLAT_CONFIG_FILENAMES.includes(configFileName);
|
|
25
|
+
}
|
|
26
|
+
exports.isFlatConfig = isFlatConfig;
|
|
27
|
+
// https://eslint.org/docs/latest/use/configure/configuration-files#configuration-file-resolution
|
|
28
|
+
function findFlatConfigFile(directory, workspaceRoot) {
|
|
29
|
+
let currentDir = (0, path_1.resolve)(workspaceRoot, directory);
|
|
30
|
+
while (true) {
|
|
31
|
+
const configFilePath = getConfigFileInDirectory(currentDir, exports.ESLINT_FLAT_CONFIG_FILENAMES);
|
|
32
|
+
if (configFilePath) {
|
|
33
|
+
return configFilePath;
|
|
34
|
+
}
|
|
35
|
+
if (currentDir === workspaceRoot) {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
currentDir = (0, path_1.dirname)(currentDir);
|
|
20
39
|
}
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
exports.findFlatConfigFile = findFlatConfigFile;
|
|
43
|
+
function findOldConfigFile(filePathOrDirectory, workspaceRoot) {
|
|
44
|
+
let currentDir = (0, path_1.resolve)(workspaceRoot, filePathOrDirectory);
|
|
45
|
+
if (!(0, fs_1.statSync)(currentDir).isDirectory()) {
|
|
46
|
+
currentDir = (0, path_1.dirname)(currentDir);
|
|
23
47
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
48
|
+
while (true) {
|
|
49
|
+
const configFilePath = getConfigFileInDirectory(currentDir, exports.ESLINT_OLD_CONFIG_FILENAMES);
|
|
50
|
+
if (configFilePath) {
|
|
51
|
+
return configFilePath;
|
|
27
52
|
}
|
|
53
|
+
if (currentDir === workspaceRoot) {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
currentDir = (0, path_1.dirname)(currentDir);
|
|
28
57
|
}
|
|
29
58
|
return null;
|
|
30
59
|
}
|
|
31
|
-
exports.
|
|
32
|
-
function
|
|
33
|
-
|
|
60
|
+
exports.findOldConfigFile = findOldConfigFile;
|
|
61
|
+
function getConfigFileInDirectory(directory, candidateFileNames) {
|
|
62
|
+
for (const filename of candidateFileNames) {
|
|
63
|
+
const filePath = (0, path_1.join)(directory, filename);
|
|
64
|
+
if ((0, fs_1.existsSync)(filePath)) {
|
|
65
|
+
return filePath;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
34
69
|
}
|
|
35
|
-
exports.isFlatConfig = isFlatConfig;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveESLintClass = void 0;
|
|
4
|
+
async function resolveESLintClass(useFlatConfig = false) {
|
|
5
|
+
try {
|
|
6
|
+
// In eslint 8.57.0 (the final v8 version), a dedicated API was added for resolving the correct ESLint class.
|
|
7
|
+
const eslint = await Promise.resolve().then(() => require('eslint'));
|
|
8
|
+
if (typeof eslint.loadESLint === 'function') {
|
|
9
|
+
return await eslint.loadESLint({ useFlatConfig });
|
|
10
|
+
}
|
|
11
|
+
// If that API is not available (an older version of v8), we need to use the old way of resolving the ESLint class.
|
|
12
|
+
if (!useFlatConfig) {
|
|
13
|
+
return eslint.ESLint;
|
|
14
|
+
}
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
16
|
+
const { FlatESLint } = require('eslint/use-at-your-own-risk');
|
|
17
|
+
return FlatESLint;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new Error('Unable to find ESLint. Ensure ESLint is installed.');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.resolveESLintClass = resolveESLintClass;
|
package/src/utils/versions.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const nxVersion: any;
|
|
2
|
-
export declare const eslintVersion = "~8.
|
|
2
|
+
export declare const eslintVersion = "~8.57.0";
|
|
3
3
|
export declare const eslintrcVersion = "^2.1.1";
|
|
4
4
|
export declare const eslintConfigPrettierVersion = "^9.0.0";
|
|
5
|
-
export declare const typescriptESLintVersion = "^
|
|
5
|
+
export declare const typescriptESLintVersion = "^7.3.0";
|
package/src/utils/versions.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.typescriptESLintVersion = exports.eslintConfigPrettierVersion = exports.eslintrcVersion = exports.eslintVersion = exports.nxVersion = void 0;
|
|
4
4
|
exports.nxVersion = require('../../package.json').version;
|
|
5
|
-
exports.eslintVersion = '~8.
|
|
5
|
+
exports.eslintVersion = '~8.57.0';
|
|
6
6
|
exports.eslintrcVersion = '^2.1.1';
|
|
7
7
|
exports.eslintConfigPrettierVersion = '^9.0.0';
|
|
8
|
-
exports.typescriptESLintVersion = '^
|
|
8
|
+
exports.typescriptESLintVersion = '^7.3.0';
|