@nx/eslint 19.3.0-canary.20240612-4a5eb23 → 19.3.0
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/package.json +4 -4
- package/src/plugins/plugin.js +166 -46
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@nx/eslint",
|
3
|
-
"version": "19.3.0
|
3
|
+
"version": "19.3.0",
|
4
4
|
"private": false,
|
5
5
|
"description": "The ESLint plugin for Nx contains executors, generators and utilities used for linting JavaScript/TypeScript projects within an Nx workspace.",
|
6
6
|
"repository": {
|
@@ -35,12 +35,12 @@
|
|
35
35
|
"eslint": "^8.0.0 || ^9.0.0"
|
36
36
|
},
|
37
37
|
"dependencies": {
|
38
|
-
"@nx/devkit": "19.3.0
|
39
|
-
"@nx/js": "19.3.0
|
38
|
+
"@nx/devkit": "19.3.0",
|
39
|
+
"@nx/js": "19.3.0",
|
40
40
|
"semver": "^7.5.3",
|
41
41
|
"tslib": "^2.3.0",
|
42
42
|
"typescript": "~5.4.2",
|
43
|
-
"@nx/linter": "19.3.0
|
43
|
+
"@nx/linter": "19.3.0"
|
44
44
|
},
|
45
45
|
"peerDependenciesMeta": {
|
46
46
|
"@zkochan/js-yaml": {
|
package/src/plugins/plugin.js
CHANGED
@@ -2,22 +2,27 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.createNodes = exports.createNodesV2 = void 0;
|
4
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");
|
5
6
|
const node_fs_1 = require("node:fs");
|
6
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");
|
7
10
|
const globs_1 = require("nx/src/utils/globs");
|
8
11
|
const workspace_context_1 = require("nx/src/utils/workspace-context");
|
12
|
+
const semver_1 = require("semver");
|
9
13
|
const config_file_1 = require("../utils/config-file");
|
10
14
|
const resolve_eslint_class_1 = require("../utils/resolve-eslint-class");
|
11
|
-
const semver_1 = require("semver");
|
12
|
-
const cache_directory_1 = require("nx/src/utils/cache-directory");
|
13
|
-
const file_hasher_1 = require("nx/src/hasher/file-hasher");
|
14
|
-
const calculate_hash_for_create_nodes_1 = require("@nx/devkit/src/utils/calculate-hash-for-create-nodes");
|
15
15
|
const DEFAULT_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'html', 'vue'];
|
16
|
-
const
|
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)([
|
17
19
|
...config_file_1.ESLINT_CONFIG_FILENAMES.map((f) => `**/${f}`),
|
20
|
+
...PROJECT_CONFIG_FILENAMES.map((f) => `**/${f}`),
|
18
21
|
]);
|
19
22
|
function readTargetsCache(cachePath) {
|
20
|
-
return
|
23
|
+
return process.env.NX_CACHE_PROJECT_GRAPH !== 'false' && (0, node_fs_1.existsSync)(cachePath)
|
24
|
+
? (0, devkit_1.readJsonFile)(cachePath)
|
25
|
+
: {};
|
21
26
|
}
|
22
27
|
function writeTargetsToCache(cachePath, results) {
|
23
28
|
(0, devkit_1.writeJsonFile)(cachePath, results);
|
@@ -30,11 +35,9 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
|
|
30
35
|
context.configFiles = context.configFiles ?? [];
|
31
36
|
// Create a Set of all the directories containing eslint configs, and a
|
32
37
|
// list of globs to exclude from child projects
|
33
|
-
const eslintRoots = new Set();
|
34
38
|
const nestedEslintRootPatterns = [];
|
35
39
|
for (const configFile of context.configFiles) {
|
36
40
|
const eslintRootDir = (0, node_path_1.dirname)(configFile);
|
37
|
-
eslintRoots.add(eslintRootDir);
|
38
41
|
if (eslintRootDir !== configDir && isSubDir(configDir, eslintRootDir)) {
|
39
42
|
nestedEslintRootPatterns.push(`${eslintRootDir}/**/*`);
|
40
43
|
}
|
@@ -45,7 +48,6 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
|
|
45
48
|
const excludePatterns = dedupedProjectRoots.map((root) => `${root}/**/*`);
|
46
49
|
const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)((0, config_file_1.isFlatConfig)(configFilePath));
|
47
50
|
const eslintVersion = ESLint.version;
|
48
|
-
const childProjectRoots = new Set();
|
49
51
|
const projects = {};
|
50
52
|
await Promise.all(dedupedProjectRoots.map(async (childProjectRoot, index) => {
|
51
53
|
// anything after is either a nested project or a sibling project, can be excluded
|
@@ -64,18 +66,80 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
|
|
64
66
|
const eslint = new ESLint({
|
65
67
|
cwd: (0, node_path_1.join)(context.workspaceRoot, childProjectRoot),
|
66
68
|
});
|
69
|
+
let hasNonIgnoredLintableFiles = false;
|
67
70
|
for (const file of lintableFiles) {
|
68
71
|
if (!(await eslint.isPathIgnored((0, node_path_1.join)(context.workspaceRoot, file)))) {
|
69
|
-
|
72
|
+
hasNonIgnoredLintableFiles = true;
|
70
73
|
break;
|
71
74
|
}
|
72
75
|
}
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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] = {};
|
79
143
|
}
|
80
144
|
}));
|
81
145
|
return {
|
@@ -83,13 +147,16 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
|
|
83
147
|
};
|
84
148
|
};
|
85
149
|
exports.createNodesV2 = [
|
86
|
-
|
150
|
+
ESLINT_CONFIG_GLOB_V2,
|
87
151
|
async (configFiles, options, context) => {
|
152
|
+
options = normalizeOptions(options);
|
88
153
|
const optionsHash = (0, file_hasher_1.hashObject)(options);
|
89
154
|
const cachePath = (0, node_path_1.join)(cache_directory_1.workspaceDataDirectory, `eslint-${optionsHash}.hash`);
|
90
155
|
const targetsCache = readTargetsCache(cachePath);
|
156
|
+
const { eslintConfigFiles, projectRoots, projectRootsByEslintRoots } = splitConfigFiles(configFiles);
|
157
|
+
const lintableFilesPerProjectRoot = new Map();
|
91
158
|
try {
|
92
|
-
return await (0, devkit_1.createNodesFromFiles)((configFile, options, context) =>
|
159
|
+
return await (0, devkit_1.createNodesFromFiles)((configFile, options, context) => internalCreateNodesV2(configFile, options, context, eslintConfigFiles, projectRoots, projectRootsByEslintRoots, lintableFilesPerProjectRoot, targetsCache), eslintConfigFiles, options, context);
|
93
160
|
}
|
94
161
|
finally {
|
95
162
|
writeTargetsToCache(cachePath, targetsCache);
|
@@ -97,43 +164,95 @@ exports.createNodesV2 = [
|
|
97
164
|
},
|
98
165
|
];
|
99
166
|
exports.createNodes = [
|
100
|
-
|
167
|
+
ESLINT_CONFIG_GLOB_V1,
|
101
168
|
(configFilePath, options, context) => {
|
102
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.');
|
103
170
|
return internalCreateNodes(configFilePath, options, context, {});
|
104
171
|
},
|
105
172
|
];
|
106
|
-
function
|
107
|
-
const
|
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);
|
226
|
+
}
|
227
|
+
return roots.has(currentPath) ? currentPath : null;
|
228
|
+
}
|
229
|
+
function getProjectUsingESLintConfig(configFilePath, projectRoot, eslintVersion, options, context) {
|
108
230
|
const rootEslintConfig = [
|
109
231
|
config_file_1.baseEsLintConfigFile,
|
110
232
|
config_file_1.baseEsLintFlatConfigFile,
|
111
233
|
...config_file_1.ESLINT_CONFIG_FILENAMES,
|
112
234
|
].find((f) => (0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, f)));
|
113
235
|
// Add a lint target for each child project without an eslint config, with the root level config as an input
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
standaloneSrcPath = 'src';
|
120
|
-
}
|
121
|
-
else if ((0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'lib'))) {
|
122
|
-
standaloneSrcPath = 'lib';
|
123
|
-
}
|
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';
|
124
241
|
}
|
125
|
-
if (
|
126
|
-
|
242
|
+
else if ((0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'lib'))) {
|
243
|
+
standaloneSrcPath = 'lib';
|
127
244
|
}
|
128
|
-
const eslintConfigs = [configFilePath];
|
129
|
-
if (rootEslintConfig && !eslintConfigs.includes(rootEslintConfig)) {
|
130
|
-
eslintConfigs.unshift(rootEslintConfig);
|
131
|
-
}
|
132
|
-
projects[projectRoot] = {
|
133
|
-
targets: buildEslintTargets(eslintConfigs, eslintVersion, projectRoot, context.workspaceRoot, options, standaloneSrcPath),
|
134
|
-
};
|
135
245
|
}
|
136
|
-
|
246
|
+
if (projectRoot === '.' && !standaloneSrcPath) {
|
247
|
+
return null;
|
248
|
+
}
|
249
|
+
const eslintConfigs = [configFilePath];
|
250
|
+
if (rootEslintConfig && !eslintConfigs.includes(rootEslintConfig)) {
|
251
|
+
eslintConfigs.unshift(rootEslintConfig);
|
252
|
+
}
|
253
|
+
return {
|
254
|
+
targets: buildEslintTargets(eslintConfigs, eslintVersion, projectRoot, context.workspaceRoot, options, standaloneSrcPath),
|
255
|
+
};
|
137
256
|
}
|
138
257
|
function buildEslintTargets(eslintConfigs, eslintVersion, projectRoot, workspaceRoot, options, standaloneSrcPath) {
|
139
258
|
const isRootProject = projectRoot === '.';
|
@@ -170,16 +289,17 @@ function buildEslintTargets(eslintConfigs, eslintVersion, projectRoot, workspace
|
|
170
289
|
return targets;
|
171
290
|
}
|
172
291
|
function normalizeOptions(options) {
|
173
|
-
|
174
|
-
|
292
|
+
const normalizedOptions = {
|
293
|
+
targetName: options.targetName ?? 'lint',
|
294
|
+
};
|
175
295
|
// Normalize user input for extensions (strip leading . characters)
|
176
296
|
if (Array.isArray(options.extensions)) {
|
177
|
-
|
297
|
+
normalizedOptions.extensions = options.extensions.map((f) => f.replace(/^\.+/, ''));
|
178
298
|
}
|
179
299
|
else {
|
180
|
-
|
300
|
+
normalizedOptions.extensions = DEFAULT_EXTENSIONS;
|
181
301
|
}
|
182
|
-
return
|
302
|
+
return normalizedOptions;
|
183
303
|
}
|
184
304
|
/**
|
185
305
|
* Determines if `child` is a subdirectory of `parent`. This is a simplified
|