@nx/eslint 0.0.0-pr-27696-37f7ce3 → 0.0.0-pr-27404-f7ba497

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": "0.0.0-pr-27696-37f7ce3",
3
+ "version": "0.0.0-pr-27404-f7ba497",
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": "0.0.0-pr-27696-37f7ce3",
39
- "@nx/js": "0.0.0-pr-27696-37f7ce3",
38
+ "@nx/devkit": "0.0.0-pr-27404-f7ba497",
39
+ "@nx/js": "0.0.0-pr-27404-f7ba497",
40
40
  "semver": "^7.5.3",
41
41
  "tslib": "^2.3.0",
42
42
  "typescript": "~5.4.2",
43
- "@nx/linter": "0.0.0-pr-27696-37f7ce3"
43
+ "@nx/linter": "0.0.0-pr-27404-f7ba497"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "@zkochan/js-yaml": {
@@ -9,7 +9,9 @@ async function resolveAndInstantiateESLint(eslintConfigPath, options, useFlatCon
9
9
  // todo: add support for eslint.config.mjs,
10
10
  'When using the new Flat Config with ESLint, all configs must be named eslint.config.js or eslint.config.cjs and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files');
11
11
  }
12
- const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)(useFlatConfig);
12
+ const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)({
13
+ useFlatConfigOverrideVal: useFlatConfig,
14
+ });
13
15
  const eslintOptions = {
14
16
  overrideConfigFile: eslintConfigPath,
15
17
  fix: !!options.fix,
@@ -71,6 +71,18 @@ function convertEslintJsonToFlatConfig(tree, root, config, ignorePaths) {
71
71
  isFlatCompatNeeded = true;
72
72
  }
73
73
  exportElements.push((0, ast_utils_1.generateFlatOverride)(override));
74
+ // eslint-plugin-import cannot be used with ESLint v9 yet
75
+ // TODO(jack): Once v9 support is released, remove this block.
76
+ // See: https://github.com/import-js/eslint-plugin-import/pull/2996
77
+ if (override.extends === 'plugin:@nx/react') {
78
+ exportElements.push((0, ast_utils_1.generateFlatOverride)({
79
+ rules: {
80
+ 'import/first': 'off',
81
+ 'import/no-amd': 'off',
82
+ 'import/no-webpack-loader-syntax': 'off',
83
+ },
84
+ }));
85
+ }
74
86
  });
75
87
  }
76
88
  if (config.ignorePatterns) {
@@ -96,9 +108,13 @@ function convertEslintJsonToFlatConfig(tree, root, config, ignorePaths) {
96
108
  }
97
109
  }
98
110
  // create the node list and print it to new file
99
- const nodeList = (0, ast_utils_1.createNodeList)(importsMap, exportElements, isFlatCompatNeeded);
111
+ const nodeList = (0, ast_utils_1.createNodeList)(importsMap, exportElements);
112
+ let content = (0, ast_utils_1.stringifyNodeList)(nodeList);
113
+ if (isFlatCompatNeeded) {
114
+ content = (0, ast_utils_1.addFlatCompatToFlatConfig)(content);
115
+ }
100
116
  return {
101
- content: (0, ast_utils_1.stringifyNodeList)(nodeList),
117
+ content,
102
118
  addESLintRC: isFlatCompatNeeded,
103
119
  addESLintJS: isESLintJSNeeded,
104
120
  };
@@ -6,7 +6,6 @@ const eslint_file_1 = require("../utils/eslint-file");
6
6
  const path_1 = require("path");
7
7
  const versions_1 = require("../../utils/versions");
8
8
  const json_converter_1 = require("./converters/json-converter");
9
- let shouldInstallDeps = false;
10
9
  async function convertToFlatConfigGenerator(tree, options) {
11
10
  const eslintFile = (0, eslint_file_1.findEslintFile)(tree);
12
11
  if (!eslintFile) {
@@ -33,9 +32,7 @@ async function convertToFlatConfigGenerator(tree, options) {
33
32
  if (!options.skipFormat) {
34
33
  await (0, devkit_1.formatFiles)(tree);
35
34
  }
36
- if (shouldInstallDeps) {
37
- return () => (0, devkit_1.installPackagesTask)(tree);
38
- }
35
+ return () => (0, devkit_1.installPackagesTask)(tree);
39
36
  }
40
37
  exports.default = convertToFlatConfigGenerator;
41
38
  function convertRootToFlatConfig(tree, eslintFile) {
@@ -128,17 +125,18 @@ function processConvertedConfig(tree, root, source, target, { content, addESLint
128
125
  tree.delete((0, path_1.join)(root, source));
129
126
  // save new
130
127
  tree.write((0, path_1.join)(root, target), content);
128
+ // These dependencies are required for flat configs that are generated by subsequent app/lib generators.
129
+ const devDependencies = {
130
+ eslint: versions_1.eslint9__eslintVersion,
131
+ 'eslint-config-prettier': versions_1.eslintConfigPrettierVersion,
132
+ 'typescript-eslint': versions_1.eslint9__typescriptESLintVersion,
133
+ };
131
134
  // add missing packages
132
135
  if (addESLintRC) {
133
- shouldInstallDeps = true;
134
- (0, devkit_1.addDependenciesToPackageJson)(tree, {}, {
135
- '@eslint/eslintrc': versions_1.eslintrcVersion,
136
- });
136
+ devDependencies['@eslint/eslintrc'] = versions_1.eslintrcVersion;
137
137
  }
138
138
  if (addESLintJS) {
139
- shouldInstallDeps = true;
140
- (0, devkit_1.addDependenciesToPackageJson)(tree, {}, {
141
- '@eslint/js': versions_1.eslintVersion,
142
- });
139
+ devDependencies['@eslint/js'] = versions_1.eslintVersion;
143
140
  }
141
+ (0, devkit_1.addDependenciesToPackageJson)(tree, {}, devDependencies);
144
142
  }
@@ -26,4 +26,4 @@ export declare const javaScriptOverride: {
26
26
  rules: {};
27
27
  };
28
28
  export declare const getGlobalEsLintConfiguration: (unitTestRunner?: string, rootProject?: boolean) => Linter.Config;
29
- export declare const getGlobalFlatEslintConfiguration: (unitTestRunner?: string, rootProject?: boolean) => string;
29
+ export declare const getGlobalFlatEslintConfiguration: (rootProject?: boolean) => string;
@@ -78,24 +78,19 @@ const getGlobalEsLintConfiguration = (unitTestRunner, rootProject) => {
78
78
  return config;
79
79
  };
80
80
  exports.getGlobalEsLintConfiguration = getGlobalEsLintConfiguration;
81
- const getGlobalFlatEslintConfiguration = (unitTestRunner, rootProject) => {
82
- const nodeList = (0, ast_utils_1.createNodeList)(new Map(), [], true);
81
+ const getGlobalFlatEslintConfiguration = (rootProject) => {
82
+ const nodeList = (0, ast_utils_1.createNodeList)(new Map(), []);
83
83
  let content = (0, ast_utils_1.stringifyNodeList)(nodeList);
84
- content = (0, ast_utils_1.addImportToFlatConfig)(content, 'nxPlugin', '@nx/eslint-plugin');
85
- content = (0, ast_utils_1.addPluginsToExportsBlock)(content, [
86
- { name: '@nx', varName: 'nxPlugin', imp: '@nx/eslint-plugin' },
87
- ]);
84
+ content = (0, ast_utils_1.addImportToFlatConfig)(content, 'nx', '@nx/eslint-plugin');
85
+ content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatPredefinedConfig)('flat/base'), { insertAtTheEnd: false });
86
+ content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatPredefinedConfig)('flat/typescript'));
87
+ content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatPredefinedConfig)('flat/javascript'));
88
88
  if (!rootProject) {
89
89
  content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatOverride)(moduleBoundariesOverride));
90
90
  }
91
- content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatOverride)(exports.typeScriptOverride));
92
- content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatOverride)(exports.javaScriptOverride));
93
- if (unitTestRunner === 'jest') {
94
- content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatOverride)(jestOverride));
95
- }
96
- // add ignore for .nx folder
97
- content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateAst)({
98
- ignores: ['.nx'],
91
+ content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatOverride)({
92
+ files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
93
+ rules: {},
99
94
  }));
100
95
  return content;
101
96
  };
@@ -29,7 +29,7 @@ function migrateConfigToMonorepoStyle(projects, tree, unitTestRunner, keepExisti
29
29
  }, undefined, keepExistingVersions);
30
30
  tree.write(tree.exists('eslint.config.js')
31
31
  ? 'eslint.base.config.js'
32
- : 'eslint.config.js', (0, global_eslint_config_1.getGlobalFlatEslintConfiguration)(unitTestRunner));
32
+ : 'eslint.config.js', (0, global_eslint_config_1.getGlobalFlatEslintConfiguration)());
33
33
  }
34
34
  else {
35
35
  const eslintFile = (0, eslint_file_1.findEslintFile)(tree, '.');
@@ -114,55 +114,63 @@ function createEsLintConfiguration(tree, options, projectConfig, setParserOption
114
114
  const pathToRootConfig = extendedRootConfig
115
115
  ? `${(0, devkit_1.offsetFromRoot)(projectConfig.root)}${extendedRootConfig}`
116
116
  : undefined;
117
- const addDependencyChecks = isBuildableLibraryProject(projectConfig);
118
- const overrides = [
119
- {
120
- files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
121
- /**
122
- * NOTE: We no longer set parserOptions.project by default when creating new projects.
123
- *
124
- * We have observed that users rarely add rules requiring type-checking to their Nx workspaces, and therefore
125
- * do not actually need the capabilites which parserOptions.project provides. When specifying parserOptions.project,
126
- * typescript-eslint needs to create full TypeScript Programs for you. When omitting it, it can perform a simple
127
- * parse (and AST tranformation) of the source files it encounters during a lint run, which is much faster and much
128
- * less memory intensive.
129
- *
130
- * In the rare case that users attempt to add rules requiring type-checking to their setup later on (and haven't set
131
- * parserOptions.project), the executor will attempt to look for the particular error typescript-eslint gives you
132
- * and provide feedback to the user.
133
- */
134
- parserOptions: !setParserOptionsProject
135
- ? undefined
136
- : {
137
- project: [`${projectConfig.root}/tsconfig.*?.json`],
138
- },
139
- /**
140
- * Having an empty rules object present makes it more obvious to the user where they would
141
- * extend things from if they needed to
142
- */
143
- rules: {},
144
- },
145
- {
146
- files: ['*.ts', '*.tsx'],
147
- rules: {},
148
- },
149
- {
150
- files: ['*.js', '*.jsx'],
151
- rules: {},
152
- },
153
- ];
154
- if (options.addPackageJsonDependencyChecks ||
155
- isBuildableLibraryProject(projectConfig)) {
117
+ const addDependencyChecks = options.addPackageJsonDependencyChecks ||
118
+ isBuildableLibraryProject(projectConfig);
119
+ const overrides = (0, flat_config_1.useFlatConfig)(tree)
120
+ ? // For flat configs, we don't need to generate different overrides for each file. Users should add their own overrides as needed.
121
+ []
122
+ : [
123
+ {
124
+ files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
125
+ /**
126
+ * NOTE: We no longer set parserOptions.project by default when creating new projects.
127
+ *
128
+ * We have observed that users rarely add rules requiring type-checking to their Nx workspaces, and therefore
129
+ * do not actually need the capabilites which parserOptions.project provides. When specifying parserOptions.project,
130
+ * typescript-eslint needs to create full TypeScript Programs for you. When omitting it, it can perform a simple
131
+ * parse (and AST tranformation) of the source files it encounters during a lint run, which is much faster and much
132
+ * less memory intensive.
133
+ *
134
+ * In the rare case that users attempt to add rules requiring type-checking to their setup later on (and haven't set
135
+ * parserOptions.project), the executor will attempt to look for the particular error typescript-eslint gives you
136
+ * and provide feedback to the user.
137
+ */
138
+ parserOptions: !setParserOptionsProject
139
+ ? undefined
140
+ : {
141
+ project: [`${projectConfig.root}/tsconfig.*?.json`],
142
+ },
143
+ /**
144
+ * Having an empty rules object present makes it more obvious to the user where they would
145
+ * extend things from if they needed to
146
+ */
147
+ rules: {},
148
+ },
149
+ {
150
+ files: ['*.ts', '*.tsx'],
151
+ rules: {},
152
+ },
153
+ {
154
+ files: ['*.js', '*.jsx'],
155
+ rules: {},
156
+ },
157
+ ];
158
+ if (addDependencyChecks) {
156
159
  overrides.push({
157
160
  files: ['*.json'],
158
161
  parser: 'jsonc-eslint-parser',
159
162
  rules: {
160
- '@nx/dependency-checks': 'error',
163
+ '@nx/dependency-checks': [
164
+ 'error',
165
+ {
166
+ // With flat configs, we don't want to include imports in the eslint js/cjs/mjs files to be checked
167
+ ignoredFiles: ['**/*/*eslint*'],
168
+ },
169
+ ],
161
170
  },
162
171
  });
163
172
  }
164
173
  if ((0, flat_config_1.useFlatConfig)(tree)) {
165
- const isCompatNeeded = addDependencyChecks;
166
174
  const nodes = [];
167
175
  const importMap = new Map();
168
176
  if (extendedRootConfig) {
@@ -172,7 +180,7 @@ function createEsLintConfiguration(tree, options, projectConfig, setParserOption
172
180
  overrides.forEach((override) => {
173
181
  nodes.push((0, ast_utils_1.generateFlatOverride)(override));
174
182
  });
175
- const nodeList = (0, ast_utils_1.createNodeList)(importMap, nodes, isCompatNeeded);
183
+ const nodeList = (0, ast_utils_1.createNodeList)(importMap, nodes);
176
184
  const content = (0, ast_utils_1.stringifyNodeList)(nodeList);
177
185
  tree.write((0, path_1.join)(projectConfig.root, 'eslint.config.js'), content);
178
186
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setupRootEsLint = setupRootEsLint;
4
4
  const devkit_1 = require("@nx/devkit");
5
+ const flat_config_1 = require("../../utils/flat-config");
5
6
  const versions_1 = require("../../utils/versions");
6
7
  const global_eslint_config_1 = require("../init/global-eslint-config");
7
8
  const eslint_file_1 = require("../utils/eslint-file");
@@ -10,6 +11,12 @@ function setupRootEsLint(tree, options) {
10
11
  if (rootEslintFile) {
11
12
  return () => { };
12
13
  }
14
+ if (!(0, flat_config_1.useFlatConfig)(tree)) {
15
+ return setUpLegacyRootEslintRc(tree, options);
16
+ }
17
+ return setUpRootFlatConfig(tree, options);
18
+ }
19
+ function setUpLegacyRootEslintRc(tree, options) {
13
20
  (0, devkit_1.writeJson)(tree, '.eslintrc.json', (0, global_eslint_config_1.getGlobalEsLintConfiguration)(options.unitTestRunner, options.rootProject));
14
21
  if (tree.exists('.eslintignore')) {
15
22
  let content = tree.read('.eslintignore', 'utf-8');
@@ -30,3 +37,15 @@ function setupRootEsLint(tree, options) {
30
37
  })
31
38
  : () => { };
32
39
  }
40
+ function setUpRootFlatConfig(tree, options) {
41
+ tree.write('eslint.config.js', (0, global_eslint_config_1.getGlobalFlatEslintConfiguration)(options.rootProject));
42
+ return !options.skipPackageJson
43
+ ? (0, devkit_1.addDependenciesToPackageJson)(tree, {}, {
44
+ '@eslint/js': versions_1.eslint9__eslintVersion,
45
+ '@nx/eslint-plugin': versions_1.nxVersion,
46
+ eslint: versions_1.eslint9__eslintVersion,
47
+ 'eslint-config-prettier': versions_1.eslintConfigPrettierVersion,
48
+ 'typescript-eslint': versions_1.eslint9__typescriptESLintVersion,
49
+ })
50
+ : () => { };
51
+ }
@@ -1,16 +1,23 @@
1
- import type { Tree } from '@nx/devkit';
1
+ import { type GeneratorCallback, type Tree } from '@nx/devkit';
2
2
  import type { Linter } from 'eslint';
3
3
  export declare function findEslintFile(tree: Tree, projectRoot?: string): string | null;
4
4
  export declare function isEslintConfigSupported(tree: Tree, projectRoot?: string): boolean;
5
5
  export declare function updateRelativePathsInConfig(tree: Tree, sourcePath: string, destinationPath: string): void;
6
- export declare function addOverrideToLintConfig(tree: Tree, root: string, override: Linter.ConfigOverride<Linter.RulesRecord>, options?: {
6
+ export declare function addOverrideToLintConfig(tree: Tree, root: string, override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>, options?: {
7
7
  insertAtTheEnd?: boolean;
8
8
  checkBaseConfig?: boolean;
9
9
  }): void;
10
10
  export declare function updateOverrideInLintConfig(tree: Tree, root: string, lookup: (override: Linter.ConfigOverride<Linter.RulesRecord>) => boolean, update: (override: Linter.ConfigOverride<Linter.RulesRecord>) => Linter.ConfigOverride<Linter.RulesRecord>): void;
11
11
  export declare function lintConfigHasOverride(tree: Tree, root: string, lookup: (override: Linter.ConfigOverride<Linter.RulesRecord>) => boolean, checkBaseConfig?: boolean): boolean;
12
12
  export declare function replaceOverridesInLintConfig(tree: Tree, root: string, overrides: Linter.ConfigOverride<Linter.RulesRecord>[]): void;
13
- export declare function addExtendsToLintConfig(tree: Tree, root: string, plugin: string | string[]): void;
13
+ export declare function addExtendsToLintConfig(tree: Tree, root: string, plugin: string | {
14
+ name: string;
15
+ needCompatFixup: boolean;
16
+ } | Array<string | {
17
+ name: string;
18
+ needCompatFixup: boolean;
19
+ }>, insertAtTheEnd?: boolean): GeneratorCallback;
20
+ export declare function addPredefinedConfigToFlatLintConfig(tree: Tree, root: string, predefinedConfigName: string, moduleName?: string, moduleImportPath?: string, spread?: boolean, insertAtTheEnd?: boolean): void;
14
21
  export declare function addPluginsToLintConfig(tree: Tree, root: string, plugin: string | string[]): void;
15
22
  export declare function addIgnoresToLintConfig(tree: Tree, root: string, ignorePatterns: string[]): void;
16
23
  export declare function getPluginImport(pluginName: string): string;
@@ -8,14 +8,18 @@ exports.updateOverrideInLintConfig = updateOverrideInLintConfig;
8
8
  exports.lintConfigHasOverride = lintConfigHasOverride;
9
9
  exports.replaceOverridesInLintConfig = replaceOverridesInLintConfig;
10
10
  exports.addExtendsToLintConfig = addExtendsToLintConfig;
11
+ exports.addPredefinedConfigToFlatLintConfig = addPredefinedConfigToFlatLintConfig;
11
12
  exports.addPluginsToLintConfig = addPluginsToLintConfig;
12
13
  exports.addIgnoresToLintConfig = addIgnoresToLintConfig;
13
14
  exports.getPluginImport = getPluginImport;
14
15
  const devkit_1 = require("@nx/devkit");
16
+ const semver_1 = require("semver");
17
+ const config_file_1 = require("../../utils/config-file");
15
18
  const flat_config_1 = require("../../utils/flat-config");
19
+ const version_utils_1 = require("../../utils/version-utils");
20
+ const versions_1 = require("../../utils/versions");
16
21
  const ast_utils_1 = require("./flat-config/ast-utils");
17
22
  const path_utils_1 = require("./flat-config/path-utils");
18
- const config_file_1 = require("../../utils/config-file");
19
23
  function findEslintFile(tree, projectRoot) {
20
24
  if (projectRoot === undefined && tree.exists(config_file_1.baseEsLintConfigFile)) {
21
25
  return config_file_1.baseEsLintConfigFile;
@@ -110,12 +114,12 @@ function addOverrideToLintConfig(tree, root, override, options = {
110
114
  }) {
111
115
  const isBase = options.checkBaseConfig && findEslintFile(tree, root).includes('.base');
112
116
  if ((0, flat_config_1.useFlatConfig)(tree)) {
113
- const fileName = (0, devkit_1.joinPathFragments)(root, isBase ? config_file_1.baseEsLintFlatConfigFile : (0, flat_config_1.flatConfigEslintFilename)(tree));
117
+ const fileName = (0, devkit_1.joinPathFragments)(root, isBase ? config_file_1.baseEsLintFlatConfigFile : (0, flat_config_1.getRootESLintFlatConfigFilename)(tree));
114
118
  const flatOverride = (0, ast_utils_1.generateFlatOverride)(override);
115
119
  let content = tree.read(fileName, 'utf8');
116
- // we will be using compat here so we need to make sure it's added
117
- if (overrideNeedsCompat(override)) {
118
- content = (0, ast_utils_1.addCompatToFlatConfig)(content);
120
+ // Check if the provided override using legacy eslintrc properties or plugins, if so we need to add compat
121
+ if ((0, ast_utils_1.overrideNeedsCompat)(override)) {
122
+ content = (0, ast_utils_1.addFlatCompatToFlatConfig)(content);
119
123
  }
120
124
  tree.write(fileName, (0, ast_utils_1.addBlockToFlatConfigExport)(content, flatOverride, options));
121
125
  }
@@ -133,12 +137,9 @@ function addOverrideToLintConfig(tree, root, override, options = {
133
137
  });
134
138
  }
135
139
  }
136
- function overrideNeedsCompat(override) {
137
- return (override.env || override.extends || override.plugins || override.parser);
138
- }
139
140
  function updateOverrideInLintConfig(tree, root, lookup, update) {
140
141
  if ((0, flat_config_1.useFlatConfig)(tree)) {
141
- const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.flatConfigEslintFilename)(tree));
142
+ const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.getRootESLintFlatConfigFilename)(tree));
142
143
  let content = tree.read(fileName, 'utf8');
143
144
  content = (0, ast_utils_1.replaceOverride)(content, root, lookup, update);
144
145
  tree.write(fileName, content);
@@ -173,7 +174,7 @@ function lintConfigHasOverride(tree, root, lookup, checkBaseConfig = false) {
173
174
  }
174
175
  const isBase = checkBaseConfig && findEslintFile(tree, root).includes('.base');
175
176
  if ((0, flat_config_1.useFlatConfig)(tree)) {
176
- const fileName = (0, devkit_1.joinPathFragments)(root, isBase ? config_file_1.baseEsLintFlatConfigFile : (0, flat_config_1.flatConfigEslintFilename)(tree));
177
+ const fileName = (0, devkit_1.joinPathFragments)(root, isBase ? config_file_1.baseEsLintFlatConfigFile : (0, flat_config_1.getRootESLintFlatConfigFilename)(tree));
177
178
  const content = tree.read(fileName, 'utf8');
178
179
  return (0, ast_utils_1.hasOverride)(content, lookup);
179
180
  }
@@ -184,11 +185,11 @@ function lintConfigHasOverride(tree, root, lookup, checkBaseConfig = false) {
184
185
  }
185
186
  function replaceOverridesInLintConfig(tree, root, overrides) {
186
187
  if ((0, flat_config_1.useFlatConfig)(tree)) {
187
- const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.flatConfigEslintFilename)(tree));
188
+ const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.getRootESLintFlatConfigFilename)(tree));
188
189
  let content = tree.read(fileName, 'utf8');
189
- // we will be using compat here so we need to make sure it's added
190
- if (overrides.some(overrideNeedsCompat)) {
191
- content = (0, ast_utils_1.addCompatToFlatConfig)(content);
190
+ // Check if any of the provided overrides using legacy eslintrc properties or plugins, if so we need to add compat
191
+ if (overrides.some(ast_utils_1.overrideNeedsCompat)) {
192
+ content = (0, ast_utils_1.addFlatCompatToFlatConfig)(content);
192
193
  }
193
194
  content = (0, ast_utils_1.removeOverridesFromLintConfig)(content);
194
195
  overrides.forEach((override) => {
@@ -205,18 +206,62 @@ function replaceOverridesInLintConfig(tree, root, overrides) {
205
206
  });
206
207
  }
207
208
  }
208
- function addExtendsToLintConfig(tree, root, plugin) {
209
- const plugins = Array.isArray(plugin) ? plugin : [plugin];
209
+ function addExtendsToLintConfig(tree, root, plugin, insertAtTheEnd = false) {
210
210
  if ((0, flat_config_1.useFlatConfig)(tree)) {
211
- const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.flatConfigEslintFilename)(tree));
212
- const pluginExtends = (0, ast_utils_1.generatePluginExtendsElement)(plugins);
211
+ const pluginExtends = [];
212
+ const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.getRootESLintFlatConfigFilename)(tree));
213
+ let shouldImportEslintCompat = false;
214
+ // assume eslint version is 9 if not found, as it's what we'd be generating by default
215
+ const eslintVersion = (0, version_utils_1.getInstalledEslintVersion)(tree) ?? versions_1.eslint9__eslintVersion;
216
+ if ((0, semver_1.gte)(eslintVersion, '9.0.0')) {
217
+ // eslint v9 requires the incompatible plugins to be wrapped with a helper from @eslint/compat
218
+ const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => typeof p === 'string' ? { name: p, needCompatFixup: false } : p);
219
+ let compatiblePluginsBatch = [];
220
+ plugins.forEach(({ name, needCompatFixup }) => {
221
+ if (needCompatFixup) {
222
+ if (compatiblePluginsBatch.length > 0) {
223
+ // flush the current batch of compatible plugins and reset it
224
+ pluginExtends.push((0, ast_utils_1.generatePluginExtendsElement)(compatiblePluginsBatch));
225
+ compatiblePluginsBatch = [];
226
+ }
227
+ // generate the extends for the incompatible plugin
228
+ pluginExtends.push((0, ast_utils_1.generatePluginExtendsElementWithCompatFixup)(name));
229
+ shouldImportEslintCompat = true;
230
+ }
231
+ else {
232
+ // add the compatible plugin to the current batch
233
+ compatiblePluginsBatch.push(name);
234
+ }
235
+ });
236
+ if (compatiblePluginsBatch.length > 0) {
237
+ // flush the batch of compatible plugins
238
+ pluginExtends.push((0, ast_utils_1.generatePluginExtendsElement)(compatiblePluginsBatch));
239
+ }
240
+ }
241
+ else {
242
+ const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => typeof p === 'string' ? p : p.name);
243
+ pluginExtends.push((0, ast_utils_1.generatePluginExtendsElement)(plugins));
244
+ }
213
245
  let content = tree.read(fileName, 'utf8');
214
- content = (0, ast_utils_1.addCompatToFlatConfig)(content);
215
- tree.write(fileName, (0, ast_utils_1.addBlockToFlatConfigExport)(content, pluginExtends, {
216
- insertAtTheEnd: false,
217
- }));
246
+ if (shouldImportEslintCompat) {
247
+ content = (0, ast_utils_1.addImportToFlatConfig)(content, ['fixupConfigRules'], '@eslint/compat');
248
+ }
249
+ content = (0, ast_utils_1.addFlatCompatToFlatConfig)(content);
250
+ // reverse the order to ensure they are added in the correct order at the
251
+ // start of the `extends` array
252
+ for (const pluginExtend of pluginExtends.reverse()) {
253
+ content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, pluginExtend, {
254
+ insertAtTheEnd,
255
+ });
256
+ }
257
+ tree.write(fileName, content);
258
+ if (shouldImportEslintCompat) {
259
+ return (0, devkit_1.addDependenciesToPackageJson)(tree, {}, { '@eslint/compat': versions_1.eslintCompat }, undefined, true);
260
+ }
261
+ return () => { };
218
262
  }
219
263
  else {
264
+ const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => typeof p === 'string' ? p : p.name);
220
265
  const fileName = (0, devkit_1.joinPathFragments)(root, '.eslintrc.json');
221
266
  (0, devkit_1.updateJson)(tree, fileName, (json) => {
222
267
  json.extends ??= [];
@@ -226,12 +271,22 @@ function addExtendsToLintConfig(tree, root, plugin) {
226
271
  ];
227
272
  return json;
228
273
  });
274
+ return () => { };
229
275
  }
230
276
  }
277
+ function addPredefinedConfigToFlatLintConfig(tree, root, predefinedConfigName, moduleName = 'nx', moduleImportPath = '@nx/eslint-plugin', spread = true, insertAtTheEnd = true) {
278
+ if (!(0, flat_config_1.useFlatConfig)(tree))
279
+ throw new Error('Predefined configs can only be used with flat configs');
280
+ const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.getRootESLintFlatConfigFilename)(tree));
281
+ let content = tree.read(fileName, 'utf8');
282
+ content = (0, ast_utils_1.addImportToFlatConfig)(content, moduleName, moduleImportPath);
283
+ content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatPredefinedConfig)(predefinedConfigName, moduleName, spread), { insertAtTheEnd });
284
+ tree.write(fileName, content);
285
+ }
231
286
  function addPluginsToLintConfig(tree, root, plugin) {
232
287
  const plugins = Array.isArray(plugin) ? plugin : [plugin];
233
288
  if ((0, flat_config_1.useFlatConfig)(tree)) {
234
- const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.flatConfigEslintFilename)(tree));
289
+ const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.getRootESLintFlatConfigFilename)(tree));
235
290
  let content = tree.read(fileName, 'utf8');
236
291
  const mappedPlugins = [];
237
292
  plugins.forEach((name) => {
@@ -255,7 +310,7 @@ function addPluginsToLintConfig(tree, root, plugin) {
255
310
  }
256
311
  function addIgnoresToLintConfig(tree, root, ignorePatterns) {
257
312
  if ((0, flat_config_1.useFlatConfig)(tree)) {
258
- const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.flatConfigEslintFilename)(tree));
313
+ const fileName = (0, devkit_1.joinPathFragments)(root, (0, flat_config_1.getRootESLintFlatConfigFilename)(tree));
259
314
  const block = (0, ast_utils_1.generateAst)({
260
315
  ignores: ignorePatterns.map((path) => (0, path_utils_1.mapFilePath)(path)),
261
316
  });
@@ -8,7 +8,7 @@ export declare function hasOverride(content: string, lookup: (override: Linter.C
8
8
  /**
9
9
  * Finds an override matching the lookup function and applies the update function to it
10
10
  */
11
- export declare function replaceOverride(content: string, root: string, lookup: (override: Linter.ConfigOverride<Linter.RulesRecord>) => boolean, update?: (override: Linter.ConfigOverride<Linter.RulesRecord>) => Linter.ConfigOverride<Linter.RulesRecord>): string;
11
+ export declare function replaceOverride(content: string, root: string, lookup: (override: Linter.ConfigOverride<Linter.RulesRecord>) => boolean, update?: (override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>) => Partial<Linter.ConfigOverride<Linter.RulesRecord>>): string;
12
12
  /**
13
13
  * Adding require statement to the top of the file
14
14
  */
@@ -33,14 +33,15 @@ export declare function addPluginsToExportsBlock(content: string, plugins: {
33
33
  /**
34
34
  * Adds compat if missing to flat config
35
35
  */
36
- export declare function addCompatToFlatConfig(content: string): string;
36
+ export declare function addFlatCompatToFlatConfig(content: string): string;
37
37
  /**
38
38
  * Generate node list representing the imports and the exports blocks
39
39
  * Optionally add flat compat initialization
40
40
  */
41
- export declare function createNodeList(importsMap: Map<string, string>, exportElements: ts.Expression[], isFlatCompatNeeded: boolean): ts.NodeArray<ts.VariableStatement | ts.Identifier | ts.ExpressionStatement | ts.SourceFile>;
41
+ export declare function createNodeList(importsMap: Map<string, string>, exportElements: ts.Expression[]): ts.NodeArray<ts.VariableStatement | ts.Identifier | ts.ExpressionStatement | ts.SourceFile>;
42
42
  export declare function generateSpreadElement(name: string): ts.SpreadElement;
43
43
  export declare function generatePluginExtendsElement(plugins: string[]): ts.SpreadElement;
44
+ export declare function generatePluginExtendsElementWithCompatFixup(plugin: string): ts.SpreadElement;
44
45
  /**
45
46
  * Stringifies TS nodes to file content string
46
47
  */
@@ -49,12 +50,20 @@ export declare function stringifyNodeList(nodes: ts.NodeArray<ts.VariableStateme
49
50
  * generates AST require statement
50
51
  */
51
52
  export declare function generateRequire(variableName: string | ts.ObjectBindingPattern, imp: string): ts.VariableStatement;
53
+ export declare function overrideNeedsCompat(override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>): string | string[] | {
54
+ [name: string]: boolean;
55
+ };
52
56
  /**
53
- * Generates AST object or spread element based on JSON override object
57
+ * Generates an AST object or spread element representing a modern flat config entry,
58
+ * based on a given legacy eslintrc JSON override object
54
59
  */
55
- export declare function generateFlatOverride(override: Linter.ConfigOverride<Linter.RulesRecord>): ts.ObjectLiteralExpression | ts.SpreadElement;
56
- export declare function mapFilePaths(override: Linter.ConfigOverride<Linter.RulesRecord>): void;
60
+ export declare function generateFlatOverride(_override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>): ts.ObjectLiteralExpression | ts.SpreadElement;
61
+ export declare function generateFlatPredefinedConfig(predefinedConfigName: string, moduleName?: string, spread?: boolean): ts.ObjectLiteralExpression | ts.SpreadElement | ts.ElementAccessExpression;
62
+ export declare function mapFilePaths(_override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>): Partial<Linter.ConfigOverride<Linter.RulesRecord>>;
57
63
  /**
58
64
  * Generates an AST from a JSON-type input
59
65
  */
60
- export declare function generateAst<T>(input: unknown): T;
66
+ export declare function generateAst<T>(input: unknown, propertyAssignmentReplacer?: {
67
+ keyToMatch: RegExp | string;
68
+ replacer: (propertyAssignment: ts.PropertyAssignment, propertyName: string) => ts.PropertyAssignment;
69
+ }): T;
@@ -8,13 +8,16 @@ exports.addBlockToFlatConfigExport = addBlockToFlatConfigExport;
8
8
  exports.removePlugin = removePlugin;
9
9
  exports.removeCompatExtends = removeCompatExtends;
10
10
  exports.addPluginsToExportsBlock = addPluginsToExportsBlock;
11
- exports.addCompatToFlatConfig = addCompatToFlatConfig;
11
+ exports.addFlatCompatToFlatConfig = addFlatCompatToFlatConfig;
12
12
  exports.createNodeList = createNodeList;
13
13
  exports.generateSpreadElement = generateSpreadElement;
14
14
  exports.generatePluginExtendsElement = generatePluginExtendsElement;
15
+ exports.generatePluginExtendsElementWithCompatFixup = generatePluginExtendsElementWithCompatFixup;
15
16
  exports.stringifyNodeList = stringifyNodeList;
16
17
  exports.generateRequire = generateRequire;
18
+ exports.overrideNeedsCompat = overrideNeedsCompat;
17
19
  exports.generateFlatOverride = generateFlatOverride;
20
+ exports.generateFlatPredefinedConfig = generateFlatPredefinedConfig;
18
21
  exports.mapFilePaths = mapFilePaths;
19
22
  exports.generateAst = generateAst;
20
23
  const devkit_1 = require("@nx/devkit");
@@ -86,10 +89,7 @@ function hasOverride(content, lookup) {
86
89
  // strip any spread elements
87
90
  objSource = fullNodeText.replace(SPREAD_ELEMENTS_REGEXP, '');
88
91
  }
89
- const data = (0, devkit_1.parseJson)(objSource
90
- // ensure property names have double quotes so that JSON.parse works
91
- .replace(/'/g, '"')
92
- .replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": '));
92
+ const data = parseTextToJson(objSource);
93
93
  if (lookup(data)) {
94
94
  return true;
95
95
  }
@@ -101,7 +101,9 @@ function parseTextToJson(text) {
101
101
  return (0, devkit_1.parseJson)(text
102
102
  // ensure property names have double quotes so that JSON.parse works
103
103
  .replace(/'/g, '"')
104
- .replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": '));
104
+ .replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": ')
105
+ // stringify any require calls to avoid JSON parsing errors, turn them into just the string value being required
106
+ .replace(/require\(['"]([^'"]+)['"]\)/g, '"$1"'));
105
107
  }
106
108
  /**
107
109
  * Finds an override matching the lookup function and applies the update function to it
@@ -138,13 +140,18 @@ function replaceOverride(content, root, lookup, update) {
138
140
  start,
139
141
  length: end - start,
140
142
  });
141
- const updatedData = update(data);
143
+ let updatedData = update(data);
142
144
  if (updatedData) {
143
- mapFilePaths(updatedData);
145
+ updatedData = mapFilePaths(updatedData);
144
146
  changes.push({
145
147
  type: devkit_1.ChangeType.Insert,
146
148
  index: start,
147
- text: JSON.stringify(updatedData, null, 2).slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties
149
+ text: JSON.stringify(updatedData, null, 2)
150
+ // restore any parser require calls that were stripped during JSON parsing
151
+ .replace(/"parser": "([^"]+)"/g, (_, parser) => {
152
+ return `"parser": require('${parser}')`;
153
+ })
154
+ .slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties
148
155
  });
149
156
  }
150
157
  }
@@ -404,7 +411,7 @@ function removeCompatExtends(content, compatExtends) {
404
411
  type: devkit_1.ChangeType.Insert,
405
412
  index: node.pos,
406
413
  text: '\n' +
407
- body.replace(new RegExp('[ \t]s*...' + paramName + '[ \t]*,?\\s*', 'g'), ''),
414
+ body.replace(new RegExp('[ \t]s*...' + paramName + '(\\.rules)?[ \t]*,?\\s*', 'g'), ''),
408
415
  });
409
416
  }
410
417
  }
@@ -427,7 +434,7 @@ function addPluginsToExportsBlock(content, plugins) {
427
434
  /**
428
435
  * Adds compat if missing to flat config
429
436
  */
430
- function addCompatToFlatConfig(content) {
437
+ function addFlatCompatToFlatConfig(content) {
431
438
  let result = content;
432
439
  result = addImportToFlatConfig(result, 'js', '@eslint/js');
433
440
  if (result.includes('const compat = new FlatCompat')) {
@@ -439,28 +446,21 @@ function addCompatToFlatConfig(content) {
439
446
  {
440
447
  type: devkit_1.ChangeType.Insert,
441
448
  index: index - 1,
442
- text: `${DEFAULT_FLAT_CONFIG}\n`,
449
+ text: `
450
+ const compat = new FlatCompat({
451
+ baseDirectory: __dirname,
452
+ recommendedConfig: js.configs.recommended,
453
+ });
454
+ `,
443
455
  },
444
456
  ]);
445
457
  }
446
- const DEFAULT_FLAT_CONFIG = `
447
- const compat = new FlatCompat({
448
- baseDirectory: __dirname,
449
- recommendedConfig: js.configs.recommended,
450
- });
451
- `;
452
458
  /**
453
459
  * Generate node list representing the imports and the exports blocks
454
460
  * Optionally add flat compat initialization
455
461
  */
456
- function createNodeList(importsMap, exportElements, isFlatCompatNeeded) {
462
+ function createNodeList(importsMap, exportElements) {
457
463
  const importsList = [];
458
- if (isFlatCompatNeeded) {
459
- importsMap.set('@eslint/js', 'js');
460
- importsList.push(generateRequire(ts.factory.createObjectBindingPattern([
461
- ts.factory.createBindingElement(undefined, undefined, 'FlatCompat'),
462
- ]), '@eslint/eslintrc'));
463
- }
464
464
  // generateRequire(varName, imp, ts.factory);
465
465
  Array.from(importsMap.entries()).forEach(([imp, varName]) => {
466
466
  importsList.push(generateRequire(varName, imp));
@@ -468,7 +468,7 @@ function createNodeList(importsMap, exportElements, isFlatCompatNeeded) {
468
468
  return ts.factory.createNodeArray([
469
469
  // add plugin imports
470
470
  ...importsList,
471
- ts.createSourceFile('', isFlatCompatNeeded ? DEFAULT_FLAT_CONFIG : '', ts.ScriptTarget.Latest, false, ts.ScriptKind.JS),
471
+ ts.createSourceFile('', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.JS),
472
472
  // creates:
473
473
  // module.exports = [ ... ];
474
474
  ts.factory.createExpressionStatement(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('module'), ts.factory.createIdentifier('exports')), ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createArrayLiteralExpression(exportElements, true))),
@@ -480,6 +480,11 @@ function generateSpreadElement(name) {
480
480
  function generatePluginExtendsElement(plugins) {
481
481
  return ts.factory.createSpreadElement(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('compat'), ts.factory.createIdentifier('extends')), undefined, plugins.map((plugin) => ts.factory.createStringLiteral(plugin))));
482
482
  }
483
+ function generatePluginExtendsElementWithCompatFixup(plugin) {
484
+ return ts.factory.createSpreadElement(ts.factory.createCallExpression(ts.factory.createIdentifier('fixupConfigRules'), undefined, [
485
+ ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('compat'), ts.factory.createIdentifier('extends')), undefined, [ts.factory.createStringLiteral(plugin)]),
486
+ ]));
487
+ }
483
488
  /**
484
489
  * Stringifies TS nodes to file content string
485
490
  */
@@ -502,21 +507,95 @@ function generateRequire(variableName, imp) {
502
507
  ], ts.NodeFlags.Const));
503
508
  }
504
509
  /**
505
- * Generates AST object or spread element based on JSON override object
510
+ * FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L222
511
+ *
512
+ * Converts a glob pattern to a format that can be used in a flat config.
513
+ * @param {string} pattern The glob pattern to convert.
514
+ * @returns {string} The converted glob pattern.
506
515
  */
507
- function generateFlatOverride(override) {
508
- mapFilePaths(override);
509
- if (!override.env &&
510
- !override.extends &&
511
- !override.plugins &&
512
- !override.parser) {
513
- if (override.parserOptions) {
514
- const { parserOptions, ...rest } = override;
515
- return generateAst({ ...rest, languageOptions: { parserOptions } });
516
+ function convertGlobPattern(pattern) {
517
+ const isNegated = pattern.startsWith('!');
518
+ const patternToTest = isNegated ? pattern.slice(1) : pattern;
519
+ // if the pattern is already in the correct format, return it
520
+ if (patternToTest === '**' || patternToTest.includes('/')) {
521
+ return pattern;
522
+ }
523
+ return `${isNegated ? '!' : ''}**/${patternToTest}`;
524
+ }
525
+ // FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L38
526
+ const keysToCopy = ['settings', 'rules', 'processor'];
527
+ function overrideNeedsCompat(override) {
528
+ return override.env || override.extends || override.plugins;
529
+ }
530
+ /**
531
+ * Generates an AST object or spread element representing a modern flat config entry,
532
+ * based on a given legacy eslintrc JSON override object
533
+ */
534
+ function generateFlatOverride(_override) {
535
+ const override = mapFilePaths(_override);
536
+ // We do not need the compat tooling for this override
537
+ if (!overrideNeedsCompat(override)) {
538
+ // Ensure files is an array
539
+ let files = override.files;
540
+ if (typeof files === 'string') {
541
+ files = [files];
542
+ }
543
+ const flatConfigOverride = {
544
+ files,
545
+ };
546
+ if (override.rules) {
547
+ flatConfigOverride.rules = override.rules;
548
+ }
549
+ // Copy over everything that stays the same
550
+ keysToCopy.forEach((key) => {
551
+ if (override[key]) {
552
+ flatConfigOverride[key] = override[key];
553
+ }
554
+ });
555
+ if (override.parser || override.parserOptions) {
556
+ const languageOptions = {};
557
+ if (override.parser) {
558
+ languageOptions['parser'] = override.parser;
559
+ }
560
+ if (override.parserOptions) {
561
+ languageOptions['parserOptions'] = override.parserOptions;
562
+ }
563
+ if (Object.keys(languageOptions).length) {
564
+ flatConfigOverride.languageOptions = languageOptions;
565
+ }
516
566
  }
517
- return generateAst(override);
567
+ if (override['languageOptions']) {
568
+ flatConfigOverride.languageOptions = override['languageOptions'];
569
+ }
570
+ if (override.excludedFiles) {
571
+ flatConfigOverride.ignores = (Array.isArray(override.excludedFiles)
572
+ ? override.excludedFiles
573
+ : [override.excludedFiles]).map((p) => convertGlobPattern(p));
574
+ }
575
+ return generateAst(flatConfigOverride, {
576
+ keyToMatch: /^(parser|rules)$/,
577
+ replacer: (propertyAssignment, propertyName) => {
578
+ if (propertyName === 'rules') {
579
+ // Add comment that user can override rules if there are no overrides.
580
+ if (ts.isObjectLiteralExpression(propertyAssignment.initializer) &&
581
+ propertyAssignment.initializer.properties.length === 0) {
582
+ return ts.addSyntheticLeadingComment(ts.factory.createPropertyAssignment(propertyAssignment.name, ts.factory.createObjectLiteralExpression([])), ts.SyntaxKind.SingleLineCommentTrivia, ' Override or add rules here');
583
+ }
584
+ return propertyAssignment;
585
+ }
586
+ else {
587
+ // Change parser to require statement.
588
+ return ts.factory.createPropertyAssignment('parser', ts.factory.createCallExpression(ts.factory.createIdentifier('require'), undefined, [
589
+ ts.factory.createStringLiteral(override['languageOptions']?.['parserOptions']?.parser ??
590
+ override['languageOptions']?.parser ??
591
+ override.parser),
592
+ ]));
593
+ }
594
+ },
595
+ });
518
596
  }
519
- const { files, excludedFiles, rules, parserOptions, ...rest } = override;
597
+ // At this point we are applying the flat config compat tooling to the override
598
+ const { excludedFiles, parser, parserOptions, rules, files, ...rest } = override;
520
599
  const objectLiteralElements = [
521
600
  ts.factory.createSpreadAssignment(ts.factory.createIdentifier('config')),
522
601
  ];
@@ -543,7 +622,14 @@ function generateFlatOverride(override) {
543
622
  ], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createParenthesizedExpression(ts.factory.createObjectLiteralExpression(objectLiteralElements, true))),
544
623
  ]));
545
624
  }
546
- function mapFilePaths(override) {
625
+ function generateFlatPredefinedConfig(predefinedConfigName, moduleName = 'nx', spread = true) {
626
+ const node = ts.factory.createElementAccessExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(moduleName), ts.factory.createIdentifier('configs')), ts.factory.createStringLiteral(predefinedConfigName));
627
+ return spread ? ts.factory.createSpreadElement(node) : node;
628
+ }
629
+ function mapFilePaths(_override) {
630
+ const override = {
631
+ ..._override,
632
+ };
547
633
  if (override.files) {
548
634
  override.files = Array.isArray(override.files)
549
635
  ? override.files
@@ -556,6 +642,7 @@ function mapFilePaths(override) {
556
642
  : [override.excludedFiles];
557
643
  override.excludedFiles = override.excludedFiles.map((file) => (0, path_utils_1.mapFilePath)(file));
558
644
  }
645
+ return override;
559
646
  }
560
647
  function addTSObjectProperty(elements, key, value) {
561
648
  if (value) {
@@ -565,18 +652,16 @@ function addTSObjectProperty(elements, key, value) {
565
652
  /**
566
653
  * Generates an AST from a JSON-type input
567
654
  */
568
- function generateAst(input) {
655
+ function generateAst(input, propertyAssignmentReplacer) {
569
656
  if (Array.isArray(input)) {
570
- return ts.factory.createArrayLiteralExpression(input.map((item) => generateAst(item)), input.length > 1 // multiline only if more than one item
657
+ return ts.factory.createArrayLiteralExpression(input.map((item) => generateAst(item, propertyAssignmentReplacer)), input.length > 1 // multiline only if more than one item
571
658
  );
572
659
  }
573
660
  if (input === null) {
574
661
  return ts.factory.createNull();
575
662
  }
576
663
  if (typeof input === 'object') {
577
- return ts.factory.createObjectLiteralExpression(Object.entries(input)
578
- .filter(([_, value]) => value !== undefined)
579
- .map(([key, value]) => ts.factory.createPropertyAssignment(isValidKey(key) ? key : ts.factory.createStringLiteral(key), generateAst(value))), Object.keys(input).length > 1 // multiline only if more than one property
664
+ return ts.factory.createObjectLiteralExpression(generatePropertyAssignmentsFromObjectEntries(input, propertyAssignmentReplacer), Object.keys(input).length > 1 // multiline only if more than one property
580
665
  );
581
666
  }
582
667
  if (typeof input === 'string') {
@@ -591,6 +676,20 @@ function generateAst(input) {
591
676
  // since we are parsing JSON, this should never happen
592
677
  throw new Error(`Unknown type: ${typeof input} `);
593
678
  }
679
+ function generatePropertyAssignmentsFromObjectEntries(input, propertyAssignmentReplacer) {
680
+ return Object.entries(input)
681
+ .filter(([_, value]) => value !== undefined)
682
+ .map(([key, value]) => {
683
+ const original = ts.factory.createPropertyAssignment(isValidKey(key) ? key : ts.factory.createStringLiteral(key), generateAst(value, propertyAssignmentReplacer));
684
+ if (propertyAssignmentReplacer &&
685
+ (typeof propertyAssignmentReplacer.keyToMatch === 'string'
686
+ ? key === propertyAssignmentReplacer.keyToMatch
687
+ : propertyAssignmentReplacer.keyToMatch.test(key))) {
688
+ return propertyAssignmentReplacer.replacer(original, key);
689
+ }
690
+ return original;
691
+ });
692
+ }
594
693
  function isValidKey(key) {
595
694
  return /^[a-zA-Z0-9_]+$/.test(key);
596
695
  }
@@ -47,7 +47,9 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
47
47
  // dedupe and sort project roots by depth for more efficient traversal
48
48
  const dedupedProjectRoots = Array.from(new Set(projectFiles.map((f) => (0, posix_1.dirname)(f)))).sort((a, b) => (a !== b && isSubDir(a, b) ? -1 : 1));
49
49
  const excludePatterns = dedupedProjectRoots.map((root) => `${root}/**/*`);
50
- const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)((0, config_file_1.isFlatConfig)(configFilePath));
50
+ const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)({
51
+ useFlatConfigOverrideVal: (0, config_file_1.isFlatConfig)(configFilePath),
52
+ });
51
53
  const eslintVersion = ESLint.version;
52
54
  const projects = {};
53
55
  await Promise.all(dedupedProjectRoots.map(async (childProjectRoot, index) => {
@@ -94,10 +96,11 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
94
96
  projects,
95
97
  };
96
98
  };
97
- let collectingLintableFilesPromise;
98
- const internalCreateNodesV2 = async (configFilePath, options, context, eslintConfigFiles, allProjectRoots, projectRootsByEslintRoots, lintableFilesPerProjectRoot, projectsCache) => {
99
+ const internalCreateNodesV2 = async (configFilePath, options, context, eslintConfigFiles, projectRootsByEslintRoots, lintableFilesPerProjectRoot, projectsCache) => {
99
100
  const configDir = (0, posix_1.dirname)(configFilePath);
100
- const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)((0, config_file_1.isFlatConfig)(configFilePath));
101
+ const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)({
102
+ useFlatConfigOverrideVal: (0, config_file_1.isFlatConfig)(configFilePath),
103
+ });
101
104
  const eslintVersion = ESLint.version;
102
105
  const projects = {};
103
106
  await Promise.all(projectRootsByEslintRoots.get(configDir).map(async (projectRoot) => {
@@ -112,11 +115,6 @@ const internalCreateNodesV2 = async (configFilePath, options, context, eslintCon
112
115
  Object.assign(projects, projectsCache[hash]);
113
116
  return;
114
117
  }
115
- if (!lintableFilesPerProjectRoot.size) {
116
- collectingLintableFilesPromise ??= collectLintableFilesByProjectRoot(lintableFilesPerProjectRoot, allProjectRoots, options, context);
117
- await collectingLintableFilesPromise;
118
- collectingLintableFilesPromise = null;
119
- }
120
118
  const eslint = new ESLint({
121
119
  cwd: (0, posix_1.join)(context.workspaceRoot, projectRoot),
122
120
  });
@@ -155,9 +153,9 @@ exports.createNodesV2 = [
155
153
  const cachePath = (0, posix_1.join)(cache_directory_1.workspaceDataDirectory, `eslint-${optionsHash}.hash`);
156
154
  const targetsCache = readTargetsCache(cachePath);
157
155
  const { eslintConfigFiles, projectRoots, projectRootsByEslintRoots } = splitConfigFiles(configFiles);
158
- const lintableFilesPerProjectRoot = new Map();
156
+ const lintableFilesPerProjectRoot = await collectLintableFilesByProjectRoot(projectRoots, options, context);
159
157
  try {
160
- return await (0, devkit_1.createNodesFromFiles)((configFile, options, context) => internalCreateNodesV2(configFile, options, context, eslintConfigFiles, projectRoots, projectRootsByEslintRoots, lintableFilesPerProjectRoot, targetsCache), eslintConfigFiles, options, context);
158
+ return await (0, devkit_1.createNodesFromFiles)((configFile, options, context) => internalCreateNodesV2(configFile, options, context, eslintConfigFiles, projectRootsByEslintRoots, lintableFilesPerProjectRoot, targetsCache), eslintConfigFiles, options, context);
161
159
  }
162
160
  finally {
163
161
  writeTargetsToCache(cachePath, targetsCache);
@@ -203,7 +201,8 @@ function groupProjectRootsByEslintRoots(eslintConfigFiles, projectRoots) {
203
201
  }
204
202
  return projectRootsByEslintRoots;
205
203
  }
206
- async function collectLintableFilesByProjectRoot(lintableFilesPerProjectRoot, projectRoots, options, context) {
204
+ async function collectLintableFilesByProjectRoot(projectRoots, options, context) {
205
+ const lintableFilesPerProjectRoot = new Map();
207
206
  const lintableFiles = await (0, workspace_context_1.globWithWorkspaceContext)(context.workspaceRoot, [
208
207
  `**/*.{${options.extensions.join(',')}}`,
209
208
  ]);
@@ -216,6 +215,7 @@ async function collectLintableFilesByProjectRoot(lintableFilesPerProjectRoot, pr
216
215
  lintableFilesPerProjectRoot.get(projectRoot).push(file);
217
216
  }
218
217
  }
218
+ return lintableFilesPerProjectRoot;
219
219
  }
220
220
  function getRootForDirectory(directory, roots) {
221
221
  let currentPath = (0, posix_1.normalize)(directory);
@@ -1,4 +1,4 @@
1
1
  import { Tree } from '@nx/devkit';
2
2
  export declare const eslintFlatConfigFilenames: string[];
3
- export declare function flatConfigEslintFilename(tree: Tree): string;
4
- export declare function useFlatConfig(tree: Tree): boolean;
3
+ export declare function getRootESLintFlatConfigFilename(tree: Tree): string;
4
+ export declare function useFlatConfig(tree?: Tree): boolean;
@@ -1,26 +1,47 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.eslintFlatConfigFilenames = void 0;
4
- exports.flatConfigEslintFilename = flatConfigEslintFilename;
4
+ exports.getRootESLintFlatConfigFilename = getRootESLintFlatConfigFilename;
5
5
  exports.useFlatConfig = useFlatConfig;
6
+ const semver_1 = require("semver");
6
7
  // todo: add support for eslint.config.mjs,
7
8
  exports.eslintFlatConfigFilenames = [
8
9
  'eslint.config.js',
9
10
  'eslint.config.cjs',
10
11
  ];
11
- function flatConfigEslintFilename(tree) {
12
+ function getRootESLintFlatConfigFilename(tree) {
12
13
  for (const file of exports.eslintFlatConfigFilenames) {
13
14
  if (tree.exists(file)) {
14
15
  return file;
15
16
  }
16
17
  }
17
- throw new Error('Could not find flat config file');
18
+ throw new Error('Could not find root flat config file');
18
19
  }
19
20
  function useFlatConfig(tree) {
21
+ // Prioritize taking ESLint's own environment variable into account when determining if we should use flat config
22
+ // If it is not defined, then default to true.
23
+ if (process.env.ESLINT_USE_FLAT_CONFIG === 'true') {
24
+ return true;
25
+ }
26
+ else if (process.env.ESLINT_USE_FLAT_CONFIG === 'false') {
27
+ return false;
28
+ }
29
+ // If we find an existing flat config file in the root of the provided tree, we should use flat config
30
+ if (tree) {
31
+ const hasRootFlatConfig = exports.eslintFlatConfigFilenames.some((filename) => tree.exists(filename));
32
+ if (hasRootFlatConfig) {
33
+ return true;
34
+ }
35
+ }
36
+ // Otherwise fallback to checking the installed eslint version
20
37
  try {
21
- return !!flatConfigEslintFilename(tree);
38
+ const { ESLint } = require('eslint');
39
+ // Default to any v8 version to compare against in this case as it implies a much older version of ESLint was found (and gte() requires a valid version)
40
+ const eslintVersion = ESLint.version || '8.0.0';
41
+ return (0, semver_1.gte)(eslintVersion, '9.0.0');
22
42
  }
23
43
  catch {
24
- return false;
44
+ // Default to assuming flat config in case ESLint is not yet installed
45
+ return true;
25
46
  }
26
47
  }
@@ -1,2 +1,4 @@
1
1
  import type { ESLint } from 'eslint';
2
- export declare function resolveESLintClass(useFlatConfig?: boolean): Promise<typeof ESLint>;
2
+ export declare function resolveESLintClass(opts?: {
3
+ useFlatConfigOverrideVal: boolean;
4
+ }): Promise<typeof ESLint>;
@@ -1,22 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveESLintClass = resolveESLintClass;
4
- async function resolveESLintClass(useFlatConfig = false) {
4
+ const flat_config_1 = require("../utils/flat-config");
5
+ async function resolveESLintClass(opts) {
5
6
  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;
7
+ // Explicitly use the FlatESLint and LegacyESLint classes here because the ESLint class points at a different one based on ESLint v8 vs ESLint v9
8
+ // But the decision on which one to use is not just based on the major version of ESLint.
9
+ // @ts-expect-error The may be wrong based on our installed eslint version
10
+ const { LegacyESLint, FlatESLint } = await Promise.resolve().then(() => require('eslint/use-at-your-own-risk'));
11
+ const shouldESLintUseFlatConfig = typeof opts?.useFlatConfigOverrideVal === 'boolean'
12
+ ? opts.useFlatConfigOverrideVal
13
+ : (0, flat_config_1.useFlatConfig)();
14
+ return shouldESLintUseFlatConfig ? FlatESLint : LegacyESLint;
18
15
  }
19
16
  catch {
20
- throw new Error('Unable to find ESLint. Ensure ESLint is installed.');
17
+ throw new Error('Unable to find `eslint`. Ensure a valid `eslint` version is installed.');
21
18
  }
22
19
  }
@@ -0,0 +1,2 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export declare function getInstalledEslintVersion(tree?: Tree): string | null;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getInstalledEslintVersion = getInstalledEslintVersion;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const semver_1 = require("@nx/devkit/src/utils/semver");
6
+ const devkit_internals_1 = require("nx/src/devkit-internals");
7
+ function getInstalledEslintVersion(tree) {
8
+ try {
9
+ const eslintPackageJson = (0, devkit_internals_1.readModulePackageJson)('eslint').packageJson;
10
+ return eslintPackageJson.version;
11
+ }
12
+ catch { }
13
+ // eslint is not installed on disk, it could be in the package.json
14
+ // but waiting to be installed
15
+ const rootPackageJson = tree
16
+ ? (0, devkit_1.readJson)(tree, 'package.json')
17
+ : (0, devkit_1.readJsonFile)('package.json');
18
+ const eslintVersionInRootPackageJson = rootPackageJson.devDependencies?.['eslint'] ??
19
+ rootPackageJson.dependencies?.['eslint'];
20
+ if (!eslintVersionInRootPackageJson) {
21
+ // eslint is not installed
22
+ return null;
23
+ }
24
+ try {
25
+ // try to parse and return the version
26
+ return (0, semver_1.checkAndCleanWithSemver)('eslint', eslintVersionInRootPackageJson);
27
+ }
28
+ catch { }
29
+ // we could not resolve the version
30
+ return null;
31
+ }
@@ -3,3 +3,6 @@ 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
5
  export declare const typescriptESLintVersion = "^7.16.0";
6
+ export declare const eslint9__typescriptESLintVersion = "^8.0.0";
7
+ export declare const eslint9__eslintVersion = "^9.8.0";
8
+ export declare const eslintCompat = "^1.1.1";
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.typescriptESLintVersion = exports.eslintConfigPrettierVersion = exports.eslintrcVersion = exports.eslintVersion = exports.nxVersion = void 0;
3
+ exports.eslintCompat = exports.eslint9__eslintVersion = exports.eslint9__typescriptESLintVersion = exports.typescriptESLintVersion = exports.eslintConfigPrettierVersion = exports.eslintrcVersion = exports.eslintVersion = exports.nxVersion = void 0;
4
4
  exports.nxVersion = require('../../package.json').version;
5
5
  exports.eslintVersion = '~8.57.0';
6
6
  exports.eslintrcVersion = '^2.1.1';
7
7
  exports.eslintConfigPrettierVersion = '^9.0.0';
8
8
  exports.typescriptESLintVersion = '^7.16.0';
9
+ // Updated linting stack for ESLint v9, typescript-eslint v8
10
+ exports.eslint9__typescriptESLintVersion = '^8.0.0';
11
+ exports.eslint9__eslintVersion = '^9.8.0';
12
+ exports.eslintCompat = '^1.1.1';