@nx/eslint 19.3.0-beta.0 → 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.3.0-beta.0",
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.3.0-beta.0",
39
- "@nx/js": "19.3.0-beta.0",
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.3.0-beta.0"
43
+ "@nx/linter": "19.3.0-beta.1"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "@zkochan/js-yaml": {
@@ -77,15 +77,24 @@ async function run(options, context) {
77
77
  catch (err) {
78
78
  if (err.message.includes('You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser')) {
79
79
  const ruleName = err.message.match(/rule '([^']+)':/)?.[1];
80
- let eslintConfigPathForError = `for ${projectName}`;
81
- if (context.projectsConfigurations?.projects?.[projectName]?.root) {
82
- const { root } = context.projectsConfigurations.projects[projectName];
83
- eslintConfigPathForError = `\`${root}/.eslintrc.json\``;
80
+ const reportedFile = err.message.match(/Occurred while linting (.+)$/)?.[1];
81
+ let eslintConfigPathForError = `for the project "${projectName}"`;
82
+ if (eslintConfigPath) {
83
+ eslintConfigPathForError = `"${path_1.posix.relative(context.root, eslintConfigPath)}"`;
84
+ }
85
+ else {
86
+ const configPathForfile = hasFlatConfig
87
+ ? (0, config_file_1.findFlatConfigFile)(projectRoot, context.root)
88
+ : (0, config_file_1.findOldConfigFile)(reportedFile ?? projectRoot, context.root);
89
+ if (configPathForfile) {
90
+ eslintConfigPathForError = `"${path_1.posix.relative(context.root, configPathForfile)}"`;
91
+ }
84
92
  }
85
93
  console.error(`
86
- Error: You have attempted to use ${ruleName ? `the lint rule ${ruleName}` : 'a lint rule'} which requires the full TypeScript type-checker to be available, but you do not have \`parserOptions.project\` configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your project ESLint config ${eslintConfigPath || eslintConfigPathForError}
94
+ Error: You have attempted to use ${ruleName ? `the lint rule "${ruleName}"` : 'a lint rule'} which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config ${eslintConfigPathForError}
95
+ ${reportedFile ? `Occurred while linting ${reportedFile}` : ''}
87
96
 
88
- Please see https://nx.dev/guides/eslint for full guidance on how to resolve this issue.
97
+ Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how to resolve this issue.
89
98
  `);
90
99
  return {
91
100
  success: false,
@@ -2,24 +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}`),
18
- config_file_1.baseEsLintConfigFile,
19
- config_file_1.baseEsLintFlatConfigFile,
20
+ ...PROJECT_CONFIG_FILENAMES.map((f) => `**/${f}`),
20
21
  ]);
21
22
  function readTargetsCache(cachePath) {
22
- 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
+ : {};
23
26
  }
24
27
  function writeTargetsToCache(cachePath, results) {
25
28
  (0, devkit_1.writeJsonFile)(cachePath, results);
@@ -32,11 +35,9 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
32
35
  context.configFiles = context.configFiles ?? [];
33
36
  // Create a Set of all the directories containing eslint configs, and a
34
37
  // list of globs to exclude from child projects
35
- const eslintRoots = new Set();
36
38
  const nestedEslintRootPatterns = [];
37
39
  for (const configFile of context.configFiles) {
38
40
  const eslintRootDir = (0, node_path_1.dirname)(configFile);
39
- eslintRoots.add(eslintRootDir);
40
41
  if (eslintRootDir !== configDir && isSubDir(configDir, eslintRootDir)) {
41
42
  nestedEslintRootPatterns.push(`${eslintRootDir}/**/*`);
42
43
  }
@@ -47,7 +48,6 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
47
48
  const excludePatterns = dedupedProjectRoots.map((root) => `${root}/**/*`);
48
49
  const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)((0, config_file_1.isFlatConfig)(configFilePath));
49
50
  const eslintVersion = ESLint.version;
50
- const childProjectRoots = new Set();
51
51
  const projects = {};
52
52
  await Promise.all(dedupedProjectRoots.map(async (childProjectRoot, index) => {
53
53
  // anything after is either a nested project or a sibling project, can be excluded
@@ -66,18 +66,80 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
66
66
  const eslint = new ESLint({
67
67
  cwd: (0, node_path_1.join)(context.workspaceRoot, childProjectRoot),
68
68
  });
69
+ let hasNonIgnoredLintableFiles = false;
69
70
  for (const file of lintableFiles) {
70
71
  if (!(await eslint.isPathIgnored((0, node_path_1.join)(context.workspaceRoot, file)))) {
71
- childProjectRoots.add(childProjectRoot);
72
+ hasNonIgnoredLintableFiles = true;
72
73
  break;
73
74
  }
74
75
  }
75
- const uniqueChildProjectRoots = Array.from(childProjectRoots);
76
- const projectsForRoot = getProjectsUsingESLintConfig(configFilePath, uniqueChildProjectRoots, eslintVersion, options, context);
77
- if (Object.keys(projectsForRoot).length > 0) {
78
- Object.assign(projects, projectsForRoot);
79
- // Store those projects into the cache;
80
- 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] = {};
81
143
  }
82
144
  }));
83
145
  return {
@@ -85,13 +147,16 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
85
147
  };
86
148
  };
87
149
  exports.createNodesV2 = [
88
- ESLINT_CONFIG_GLOB,
150
+ ESLINT_CONFIG_GLOB_V2,
89
151
  async (configFiles, options, context) => {
152
+ options = normalizeOptions(options);
90
153
  const optionsHash = (0, file_hasher_1.hashObject)(options);
91
154
  const cachePath = (0, node_path_1.join)(cache_directory_1.workspaceDataDirectory, `eslint-${optionsHash}.hash`);
92
155
  const targetsCache = readTargetsCache(cachePath);
156
+ const { eslintConfigFiles, projectRoots, projectRootsByEslintRoots } = splitConfigFiles(configFiles);
157
+ const lintableFilesPerProjectRoot = new Map();
93
158
  try {
94
- 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);
95
160
  }
96
161
  finally {
97
162
  writeTargetsToCache(cachePath, targetsCache);
@@ -99,43 +164,95 @@ exports.createNodesV2 = [
99
164
  },
100
165
  ];
101
166
  exports.createNodes = [
102
- ESLINT_CONFIG_GLOB,
167
+ ESLINT_CONFIG_GLOB_V1,
103
168
  (configFilePath, options, context) => {
104
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.');
105
170
  return internalCreateNodes(configFilePath, options, context, {});
106
171
  },
107
172
  ];
108
- function getProjectsUsingESLintConfig(configFilePath, childProjectRoots, eslintVersion, options, context) {
109
- 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) {
110
230
  const rootEslintConfig = [
111
231
  config_file_1.baseEsLintConfigFile,
112
232
  config_file_1.baseEsLintFlatConfigFile,
113
233
  ...config_file_1.ESLINT_CONFIG_FILENAMES,
114
234
  ].find((f) => (0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, f)));
115
235
  // Add a lint target for each child project without an eslint config, with the root level config as an input
116
- for (const projectRoot of childProjectRoots) {
117
- let standaloneSrcPath;
118
- if (projectRoot === '.' &&
119
- (0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'package.json'))) {
120
- if ((0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'src'))) {
121
- standaloneSrcPath = 'src';
122
- }
123
- else if ((0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'lib'))) {
124
- standaloneSrcPath = 'lib';
125
- }
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';
126
241
  }
127
- if (projectRoot === '.' && !standaloneSrcPath) {
128
- continue;
242
+ else if ((0, node_fs_1.existsSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot, 'lib'))) {
243
+ standaloneSrcPath = 'lib';
129
244
  }
130
- const eslintConfigs = [configFilePath];
131
- if (rootEslintConfig && !eslintConfigs.includes(rootEslintConfig)) {
132
- eslintConfigs.unshift(rootEslintConfig);
133
- }
134
- projects[projectRoot] = {
135
- targets: buildEslintTargets(eslintConfigs, eslintVersion, projectRoot, context.workspaceRoot, options, standaloneSrcPath),
136
- };
137
245
  }
138
- 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
+ };
139
256
  }
140
257
  function buildEslintTargets(eslintConfigs, eslintVersion, projectRoot, workspaceRoot, options, standaloneSrcPath) {
141
258
  const isRootProject = projectRoot === '.';
@@ -172,16 +289,17 @@ function buildEslintTargets(eslintConfigs, eslintVersion, projectRoot, workspace
172
289
  return targets;
173
290
  }
174
291
  function normalizeOptions(options) {
175
- options ??= {};
176
- options.targetName ??= 'lint';
292
+ const normalizedOptions = {
293
+ targetName: options.targetName ?? 'lint',
294
+ };
177
295
  // Normalize user input for extensions (strip leading . characters)
178
296
  if (Array.isArray(options.extensions)) {
179
- options.extensions = options.extensions.map((f) => f.replace(/^\.+/, ''));
297
+ normalizedOptions.extensions = options.extensions.map((f) => f.replace(/^\.+/, ''));
180
298
  }
181
299
  else {
182
- options.extensions = DEFAULT_EXTENSIONS;
300
+ normalizedOptions.extensions = DEFAULT_EXTENSIONS;
183
301
  }
184
- return options;
302
+ return normalizedOptions;
185
303
  }
186
304
  /**
187
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;