@nx/eslint 19.2.3 → 19.3.0-beta.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/eslint",
3
- "version": "19.2.3",
3
+ "version": "19.3.0-beta.1",
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.2.3",
39
- "@nx/js": "19.2.3",
38
+ "@nx/devkit": "19.3.0-beta.1",
39
+ "@nx/js": "19.3.0-beta.1",
40
40
  "semver": "^7.5.3",
41
41
  "tslib": "^2.3.0",
42
42
  "typescript": "~5.4.2",
43
- "@nx/linter": "19.2.3"
43
+ "@nx/linter": "19.3.0-beta.1"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "@zkochan/js-yaml": {
@@ -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 ESLINT_CONFIG_GLOB = (0, globs_1.combineGlobPatterns)([
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 (0, node_fs_1.existsSync)(cachePath) ? (0, devkit_1.readJsonFile)(cachePath) : {};
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
- childProjectRoots.add(childProjectRoot);
72
+ hasNonIgnoredLintableFiles = true;
70
73
  break;
71
74
  }
72
75
  }
73
- const uniqueChildProjectRoots = Array.from(childProjectRoots);
74
- const projectsForRoot = getProjectsUsingESLintConfig(configFilePath, uniqueChildProjectRoots, eslintVersion, options, context);
75
- if (Object.keys(projectsForRoot).length > 0) {
76
- Object.assign(projects, projectsForRoot);
77
- // Store those projects into the cache;
78
- projectsCache[hash] = projectsForRoot;
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
- ESLINT_CONFIG_GLOB,
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) => internalCreateNodes(configFile, options, context, targetsCache), configFiles, 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
- ESLINT_CONFIG_GLOB,
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 getProjectsUsingESLintConfig(configFilePath, childProjectRoots, eslintVersion, options, context) {
107
- const projects = {};
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
- for (const projectRoot of childProjectRoots) {
115
- let standaloneSrcPath;
116
- if (projectRoot === '.' &&
117
- (0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'package.json'))) {
118
- if ((0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'src'))) {
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 (projectRoot === '.' && !standaloneSrcPath) {
126
- continue;
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
- return projects;
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
- options ??= {};
174
- options.targetName ??= 'lint';
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
- options.extensions = options.extensions.map((f) => f.replace(/^\.+/, ''));
297
+ normalizedOptions.extensions = options.extensions.map((f) => f.replace(/^\.+/, ''));
178
298
  }
179
299
  else {
180
- options.extensions = DEFAULT_EXTENSIONS;
300
+ normalizedOptions.extensions = DEFAULT_EXTENSIONS;
181
301
  }
182
- return options;
302
+ return normalizedOptions;
183
303
  }
184
304
  /**
185
305
  * Determines if `child` is a subdirectory of `parent`. This is a simplified
@@ -27,31 +27,32 @@ exports.isFlatConfig = isFlatConfig;
27
27
  // https://eslint.org/docs/latest/use/configure/configuration-files#configuration-file-resolution
28
28
  function findFlatConfigFile(directory, workspaceRoot) {
29
29
  let currentDir = (0, path_1.resolve)(workspaceRoot, directory);
30
- if (currentDir === workspaceRoot) {
31
- return getConfigFileInDirectory(currentDir, exports.ESLINT_FLAT_CONFIG_FILENAMES);
32
- }
33
- while (currentDir !== workspaceRoot) {
30
+ while (true) {
34
31
  const configFilePath = getConfigFileInDirectory(currentDir, exports.ESLINT_FLAT_CONFIG_FILENAMES);
35
32
  if (configFilePath) {
36
33
  return configFilePath;
37
34
  }
35
+ if (currentDir === workspaceRoot) {
36
+ break;
37
+ }
38
38
  currentDir = (0, path_1.dirname)(currentDir);
39
39
  }
40
40
  return null;
41
41
  }
42
42
  exports.findFlatConfigFile = findFlatConfigFile;
43
43
  function findOldConfigFile(filePathOrDirectory, workspaceRoot) {
44
- let currentDir = (0, fs_1.statSync)(filePathOrDirectory).isDirectory()
45
- ? filePathOrDirectory
46
- : (0, path_1.dirname)(filePathOrDirectory);
47
- if (currentDir === workspaceRoot) {
48
- return getConfigFileInDirectory(currentDir, exports.ESLINT_OLD_CONFIG_FILENAMES);
44
+ let currentDir = (0, path_1.resolve)(workspaceRoot, filePathOrDirectory);
45
+ if (!(0, fs_1.statSync)(currentDir).isDirectory()) {
46
+ currentDir = (0, path_1.dirname)(currentDir);
49
47
  }
50
- while (currentDir !== workspaceRoot) {
48
+ while (true) {
51
49
  const configFilePath = getConfigFileInDirectory(currentDir, exports.ESLINT_OLD_CONFIG_FILENAMES);
52
50
  if (configFilePath) {
53
51
  return configFilePath;
54
52
  }
53
+ if (currentDir === workspaceRoot) {
54
+ break;
55
+ }
55
56
  currentDir = (0, path_1.dirname)(currentDir);
56
57
  }
57
58
  return null;