@nx/eslint 19.8.0-canary.20240912-b6140d4 → 19.8.0-canary.20240914-dc821ab

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/eslint",
3
- "version": "19.8.0-canary.20240912-b6140d4",
3
+ "version": "19.8.0-canary.20240914-dc821ab",
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.8.0-canary.20240912-b6140d4",
39
- "@nx/js": "19.8.0-canary.20240912-b6140d4",
38
+ "@nx/devkit": "19.8.0-canary.20240914-dc821ab",
39
+ "@nx/js": "19.8.0-canary.20240914-dc821ab",
40
40
  "semver": "^7.5.3",
41
41
  "tslib": "^2.3.0",
42
42
  "typescript": "~5.4.2",
43
- "@nx/linter": "19.8.0-canary.20240912-b6140d4"
43
+ "@nx/linter": "19.8.0-canary.20240914-dc821ab"
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
  }
@@ -20,7 +20,7 @@ async function convertToInferred(tree, options) {
20
20
  },
21
21
  ], options.project);
22
22
  if (migratedProjects.size === 0) {
23
- throw new Error('Could not find any targets to migrate.');
23
+ throw new executor_to_plugin_migrator_1.NoTargetsToMigrateError();
24
24
  }
25
25
  if (!options.skipFormat) {
26
26
  await (0, devkit_1.formatFiles)(tree);
@@ -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,37 @@ 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
- content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatOverride)(moduleBoundariesOverride));
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));
89
+ content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatOverride)({
90
+ files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
91
+ rules: {
92
+ '@nx/enforce-module-boundaries': [
93
+ 'error',
94
+ {
95
+ enforceBuildableLibDependency: true,
96
+ allow: [
97
+ // This allows a root project to be present without causing lint errors
98
+ // since all projects will depend on this base file.
99
+ '^.*/eslint(\\.base)?\\.config\\.[cm]?js$',
100
+ ],
101
+ depConstraints: [
102
+ { sourceTag: '*', onlyDependOnLibsWithTags: ['*'] },
103
+ ],
104
+ },
105
+ ],
106
+ },
107
+ }));
95
108
  }
96
- // add ignore for .nx folder
97
- content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateAst)({
98
- ignores: ['.nx'],
109
+ content = (0, ast_utils_1.addBlockToFlatConfigExport)(content, (0, ast_utils_1.generateFlatOverride)({
110
+ files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
111
+ rules: {},
99
112
  }));
100
113
  return content;
101
114
  };
@@ -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, '.');
@@ -91,6 +91,11 @@ function migrateEslintFile(projectEslintPath, tree) {
91
91
  'plugin:@nrwl/typescript',
92
92
  'plugin:@nrwl/javascript',
93
93
  ]);
94
+ config = (0, ast_utils_1.removePredefinedConfigs)(config, '@nx/eslint-plugin', 'nx', [
95
+ 'flat/base',
96
+ 'flat/typescript',
97
+ 'flat/javascript',
98
+ ]);
94
99
  tree.write(projectEslintPath, config);
95
100
  }
96
101
  else {
@@ -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: ['{projectRoot}/eslint.config.{js,cjs,mjs}'],
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,11 +8,15 @@ 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
  */
15
15
  export declare function addImportToFlatConfig(content: string, variable: string | string[], imp: string): string;
16
+ /**
17
+ * Remove an import from flat config
18
+ */
19
+ export declare function removeImportFromFlatConfig(content: string, variable: string, imp: string): string;
16
20
  /**
17
21
  * Injects new ts.expression to the end of the module.exports array.
18
22
  */
@@ -22,6 +26,7 @@ export declare function addBlockToFlatConfigExport(content: string, config: ts.E
22
26
  }): string;
23
27
  export declare function removePlugin(content: string, pluginName: string, pluginImport: string): string;
24
28
  export declare function removeCompatExtends(content: string, compatExtends: string[]): string;
29
+ export declare function removePredefinedConfigs(content: string, moduleImport: string, moduleVariable: string, configs: string[]): string;
25
30
  /**
26
31
  * Add plugins block to the top of the export blocks
27
32
  */
@@ -33,14 +38,15 @@ export declare function addPluginsToExportsBlock(content: string, plugins: {
33
38
  /**
34
39
  * Adds compat if missing to flat config
35
40
  */
36
- export declare function addCompatToFlatConfig(content: string): string;
41
+ export declare function addFlatCompatToFlatConfig(content: string): string;
37
42
  /**
38
43
  * Generate node list representing the imports and the exports blocks
39
44
  * Optionally add flat compat initialization
40
45
  */
41
- export declare function createNodeList(importsMap: Map<string, string>, exportElements: ts.Expression[], isFlatCompatNeeded: boolean): ts.NodeArray<ts.VariableStatement | ts.Identifier | ts.ExpressionStatement | ts.SourceFile>;
46
+ export declare function createNodeList(importsMap: Map<string, string>, exportElements: ts.Expression[]): ts.NodeArray<ts.VariableStatement | ts.Identifier | ts.ExpressionStatement | ts.SourceFile>;
42
47
  export declare function generateSpreadElement(name: string): ts.SpreadElement;
43
48
  export declare function generatePluginExtendsElement(plugins: string[]): ts.SpreadElement;
49
+ export declare function generatePluginExtendsElementWithCompatFixup(plugin: string): ts.SpreadElement;
44
50
  /**
45
51
  * Stringifies TS nodes to file content string
46
52
  */
@@ -49,12 +55,20 @@ export declare function stringifyNodeList(nodes: ts.NodeArray<ts.VariableStateme
49
55
  * generates AST require statement
50
56
  */
51
57
  export declare function generateRequire(variableName: string | ts.ObjectBindingPattern, imp: string): ts.VariableStatement;
58
+ export declare function overrideNeedsCompat(override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>): string | string[] | {
59
+ [name: string]: boolean;
60
+ };
52
61
  /**
53
- * Generates AST object or spread element based on JSON override object
62
+ * Generates an AST object or spread element representing a modern flat config entry,
63
+ * based on a given legacy eslintrc JSON override object
54
64
  */
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;
65
+ export declare function generateFlatOverride(_override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>): ts.ObjectLiteralExpression | ts.SpreadElement;
66
+ export declare function generateFlatPredefinedConfig(predefinedConfigName: string, moduleName?: string, spread?: boolean): ts.ObjectLiteralExpression | ts.SpreadElement | ts.ElementAccessExpression;
67
+ export declare function mapFilePaths(_override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>): Partial<Linter.ConfigOverride<Linter.RulesRecord>>;
57
68
  /**
58
69
  * Generates an AST from a JSON-type input
59
70
  */
60
- export declare function generateAst<T>(input: unknown): T;
71
+ export declare function generateAst<T>(input: unknown, propertyAssignmentReplacer?: {
72
+ keyToMatch: RegExp | string;
73
+ replacer: (propertyAssignment: ts.PropertyAssignment, propertyName: string) => ts.PropertyAssignment;
74
+ }): T;
@@ -4,17 +4,22 @@ exports.removeOverridesFromLintConfig = removeOverridesFromLintConfig;
4
4
  exports.hasOverride = hasOverride;
5
5
  exports.replaceOverride = replaceOverride;
6
6
  exports.addImportToFlatConfig = addImportToFlatConfig;
7
+ exports.removeImportFromFlatConfig = removeImportFromFlatConfig;
7
8
  exports.addBlockToFlatConfigExport = addBlockToFlatConfigExport;
8
9
  exports.removePlugin = removePlugin;
9
10
  exports.removeCompatExtends = removeCompatExtends;
11
+ exports.removePredefinedConfigs = removePredefinedConfigs;
10
12
  exports.addPluginsToExportsBlock = addPluginsToExportsBlock;
11
- exports.addCompatToFlatConfig = addCompatToFlatConfig;
13
+ exports.addFlatCompatToFlatConfig = addFlatCompatToFlatConfig;
12
14
  exports.createNodeList = createNodeList;
13
15
  exports.generateSpreadElement = generateSpreadElement;
14
16
  exports.generatePluginExtendsElement = generatePluginExtendsElement;
17
+ exports.generatePluginExtendsElementWithCompatFixup = generatePluginExtendsElementWithCompatFixup;
15
18
  exports.stringifyNodeList = stringifyNodeList;
16
19
  exports.generateRequire = generateRequire;
20
+ exports.overrideNeedsCompat = overrideNeedsCompat;
17
21
  exports.generateFlatOverride = generateFlatOverride;
22
+ exports.generateFlatPredefinedConfig = generateFlatPredefinedConfig;
18
23
  exports.mapFilePaths = mapFilePaths;
19
24
  exports.generateAst = generateAst;
20
25
  const devkit_1 = require("@nx/devkit");
@@ -86,10 +91,7 @@ function hasOverride(content, lookup) {
86
91
  // strip any spread elements
87
92
  objSource = fullNodeText.replace(SPREAD_ELEMENTS_REGEXP, '');
88
93
  }
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": '));
94
+ const data = parseTextToJson(objSource);
93
95
  if (lookup(data)) {
94
96
  return true;
95
97
  }
@@ -101,7 +103,9 @@ function parseTextToJson(text) {
101
103
  return (0, devkit_1.parseJson)(text
102
104
  // ensure property names have double quotes so that JSON.parse works
103
105
  .replace(/'/g, '"')
104
- .replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": '));
106
+ .replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": ')
107
+ // stringify any require calls to avoid JSON parsing errors, turn them into just the string value being required
108
+ .replace(/require\(['"]([^'"]+)['"]\)/g, '"$1"'));
105
109
  }
106
110
  /**
107
111
  * Finds an override matching the lookup function and applies the update function to it
@@ -138,13 +142,18 @@ function replaceOverride(content, root, lookup, update) {
138
142
  start,
139
143
  length: end - start,
140
144
  });
141
- const updatedData = update(data);
145
+ let updatedData = update(data);
142
146
  if (updatedData) {
143
- mapFilePaths(updatedData);
147
+ updatedData = mapFilePaths(updatedData);
144
148
  changes.push({
145
149
  type: devkit_1.ChangeType.Insert,
146
150
  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
151
+ text: JSON.stringify(updatedData, null, 2)
152
+ // restore any parser require calls that were stripped during JSON parsing
153
+ .replace(/"parser": "([^"]+)"/g, (_, parser) => {
154
+ return `"parser": require('${parser}')`;
155
+ })
156
+ .slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties
148
157
  });
149
158
  }
150
159
  }
@@ -226,6 +235,32 @@ function addImportToFlatConfig(content, variable, imp) {
226
235
  },
227
236
  ]);
228
237
  }
238
+ /**
239
+ * Remove an import from flat config
240
+ */
241
+ function removeImportFromFlatConfig(content, variable, imp) {
242
+ const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
243
+ const changes = [];
244
+ ts.forEachChild(source, (node) => {
245
+ // we can only combine object binding patterns
246
+ if (ts.isVariableStatement(node) &&
247
+ ts.isVariableDeclaration(node.declarationList.declarations[0]) &&
248
+ ts.isIdentifier(node.declarationList.declarations[0].name) &&
249
+ node.declarationList.declarations[0].name.getText() === variable &&
250
+ ts.isCallExpression(node.declarationList.declarations[0].initializer) &&
251
+ node.declarationList.declarations[0].initializer.expression.getText() ===
252
+ 'require' &&
253
+ ts.isStringLiteral(node.declarationList.declarations[0].initializer.arguments[0]) &&
254
+ node.declarationList.declarations[0].initializer.arguments[0].text === imp) {
255
+ changes.push({
256
+ type: devkit_1.ChangeType.Delete,
257
+ start: node.pos,
258
+ length: node.end - node.pos,
259
+ });
260
+ }
261
+ });
262
+ return (0, devkit_1.applyChangesToString)(content, changes);
263
+ }
229
264
  /**
230
265
  * Injects new ts.expression to the end of the module.exports array.
231
266
  */
@@ -242,6 +277,11 @@ function addBlockToFlatConfigExport(content, config, options = {
242
277
  return node.expression.right.elements;
243
278
  }
244
279
  });
280
+ // The config is not in the format that we generate with, skip update.
281
+ // This could happen during `init-migration` when extracting config from the base, but
282
+ // base config was not generated by Nx.
283
+ if (!exportsArray)
284
+ return content;
245
285
  const insert = printer.printNode(ts.EmitHint.Expression, config, source);
246
286
  if (options.insertAtTheEnd) {
247
287
  const index = exportsArray.length > 0
@@ -379,7 +419,7 @@ function removePlugin(content, pluginName, pluginImport) {
379
419
  function removeCompatExtends(content, compatExtends) {
380
420
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
381
421
  const changes = [];
382
- findAllBlocks(source).forEach((node) => {
422
+ findAllBlocks(source)?.forEach((node) => {
383
423
  if (ts.isSpreadElement(node) &&
384
424
  ts.isCallExpression(node.expression) &&
385
425
  ts.isArrowFunction(node.expression.arguments[0]) &&
@@ -404,13 +444,45 @@ function removeCompatExtends(content, compatExtends) {
404
444
  type: devkit_1.ChangeType.Insert,
405
445
  index: node.pos,
406
446
  text: '\n' +
407
- body.replace(new RegExp('[ \t]s*...' + paramName + '[ \t]*,?\\s*', 'g'), ''),
447
+ body.replace(new RegExp('[ \t]s*...' + paramName + '(\\.rules)?[ \t]*,?\\s*', 'g'), ''),
408
448
  });
409
449
  }
410
450
  }
411
451
  });
412
452
  return (0, devkit_1.applyChangesToString)(content, changes);
413
453
  }
454
+ function removePredefinedConfigs(content, moduleImport, moduleVariable, configs) {
455
+ const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
456
+ const changes = [];
457
+ let removeImport = true;
458
+ findAllBlocks(source)?.forEach((node) => {
459
+ if (ts.isSpreadElement(node) &&
460
+ ts.isElementAccessExpression(node.expression) &&
461
+ ts.isPropertyAccessExpression(node.expression.expression) &&
462
+ ts.isIdentifier(node.expression.expression.expression) &&
463
+ node.expression.expression.expression.getText() === moduleVariable &&
464
+ ts.isStringLiteral(node.expression.argumentExpression)) {
465
+ const config = node.expression.argumentExpression.getText();
466
+ // Check the text without quotes
467
+ if (configs.includes(config.substring(1, config.length - 1))) {
468
+ changes.push({
469
+ type: devkit_1.ChangeType.Delete,
470
+ start: node.pos,
471
+ length: node.end - node.pos + 1, // trailing comma
472
+ });
473
+ }
474
+ else {
475
+ // If there is still a config used, do not remove import
476
+ removeImport = false;
477
+ }
478
+ }
479
+ });
480
+ let updated = (0, devkit_1.applyChangesToString)(content, changes);
481
+ if (removeImport) {
482
+ updated = removeImportFromFlatConfig(updated, moduleVariable, moduleImport);
483
+ }
484
+ return updated;
485
+ }
414
486
  /**
415
487
  * Add plugins block to the top of the export blocks
416
488
  */
@@ -427,7 +499,7 @@ function addPluginsToExportsBlock(content, plugins) {
427
499
  /**
428
500
  * Adds compat if missing to flat config
429
501
  */
430
- function addCompatToFlatConfig(content) {
502
+ function addFlatCompatToFlatConfig(content) {
431
503
  let result = content;
432
504
  result = addImportToFlatConfig(result, 'js', '@eslint/js');
433
505
  if (result.includes('const compat = new FlatCompat')) {
@@ -439,28 +511,21 @@ function addCompatToFlatConfig(content) {
439
511
  {
440
512
  type: devkit_1.ChangeType.Insert,
441
513
  index: index - 1,
442
- text: `${DEFAULT_FLAT_CONFIG}\n`,
514
+ text: `
515
+ const compat = new FlatCompat({
516
+ baseDirectory: __dirname,
517
+ recommendedConfig: js.configs.recommended,
518
+ });
519
+ `,
443
520
  },
444
521
  ]);
445
522
  }
446
- const DEFAULT_FLAT_CONFIG = `
447
- const compat = new FlatCompat({
448
- baseDirectory: __dirname,
449
- recommendedConfig: js.configs.recommended,
450
- });
451
- `;
452
523
  /**
453
524
  * Generate node list representing the imports and the exports blocks
454
525
  * Optionally add flat compat initialization
455
526
  */
456
- function createNodeList(importsMap, exportElements, isFlatCompatNeeded) {
527
+ function createNodeList(importsMap, exportElements) {
457
528
  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
529
  // generateRequire(varName, imp, ts.factory);
465
530
  Array.from(importsMap.entries()).forEach(([imp, varName]) => {
466
531
  importsList.push(generateRequire(varName, imp));
@@ -468,7 +533,7 @@ function createNodeList(importsMap, exportElements, isFlatCompatNeeded) {
468
533
  return ts.factory.createNodeArray([
469
534
  // add plugin imports
470
535
  ...importsList,
471
- ts.createSourceFile('', isFlatCompatNeeded ? DEFAULT_FLAT_CONFIG : '', ts.ScriptTarget.Latest, false, ts.ScriptKind.JS),
536
+ ts.createSourceFile('', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.JS),
472
537
  // creates:
473
538
  // module.exports = [ ... ];
474
539
  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 +545,11 @@ function generateSpreadElement(name) {
480
545
  function generatePluginExtendsElement(plugins) {
481
546
  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
547
  }
548
+ function generatePluginExtendsElementWithCompatFixup(plugin) {
549
+ return ts.factory.createSpreadElement(ts.factory.createCallExpression(ts.factory.createIdentifier('fixupConfigRules'), undefined, [
550
+ ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('compat'), ts.factory.createIdentifier('extends')), undefined, [ts.factory.createStringLiteral(plugin)]),
551
+ ]));
552
+ }
483
553
  /**
484
554
  * Stringifies TS nodes to file content string
485
555
  */
@@ -502,21 +572,95 @@ function generateRequire(variableName, imp) {
502
572
  ], ts.NodeFlags.Const));
503
573
  }
504
574
  /**
505
- * Generates AST object or spread element based on JSON override object
575
+ * FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L222
576
+ *
577
+ * Converts a glob pattern to a format that can be used in a flat config.
578
+ * @param {string} pattern The glob pattern to convert.
579
+ * @returns {string} The converted glob pattern.
580
+ */
581
+ function convertGlobPattern(pattern) {
582
+ const isNegated = pattern.startsWith('!');
583
+ const patternToTest = isNegated ? pattern.slice(1) : pattern;
584
+ // if the pattern is already in the correct format, return it
585
+ if (patternToTest === '**' || patternToTest.includes('/')) {
586
+ return pattern;
587
+ }
588
+ return `${isNegated ? '!' : ''}**/${patternToTest}`;
589
+ }
590
+ // FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L38
591
+ const keysToCopy = ['settings', 'rules', 'processor'];
592
+ function overrideNeedsCompat(override) {
593
+ return override.env || override.extends || override.plugins;
594
+ }
595
+ /**
596
+ * Generates an AST object or spread element representing a modern flat config entry,
597
+ * based on a given legacy eslintrc JSON override object
506
598
  */
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 } });
599
+ function generateFlatOverride(_override) {
600
+ const override = mapFilePaths(_override);
601
+ // We do not need the compat tooling for this override
602
+ if (!overrideNeedsCompat(override)) {
603
+ // Ensure files is an array
604
+ let files = override.files;
605
+ if (typeof files === 'string') {
606
+ files = [files];
607
+ }
608
+ const flatConfigOverride = {
609
+ files,
610
+ };
611
+ if (override.rules) {
612
+ flatConfigOverride.rules = override.rules;
516
613
  }
517
- return generateAst(override);
614
+ // Copy over everything that stays the same
615
+ keysToCopy.forEach((key) => {
616
+ if (override[key]) {
617
+ flatConfigOverride[key] = override[key];
618
+ }
619
+ });
620
+ if (override.parser || override.parserOptions) {
621
+ const languageOptions = {};
622
+ if (override.parser) {
623
+ languageOptions['parser'] = override.parser;
624
+ }
625
+ if (override.parserOptions) {
626
+ languageOptions['parserOptions'] = override.parserOptions;
627
+ }
628
+ if (Object.keys(languageOptions).length) {
629
+ flatConfigOverride.languageOptions = languageOptions;
630
+ }
631
+ }
632
+ if (override['languageOptions']) {
633
+ flatConfigOverride.languageOptions = override['languageOptions'];
634
+ }
635
+ if (override.excludedFiles) {
636
+ flatConfigOverride.ignores = (Array.isArray(override.excludedFiles)
637
+ ? override.excludedFiles
638
+ : [override.excludedFiles]).map((p) => convertGlobPattern(p));
639
+ }
640
+ return generateAst(flatConfigOverride, {
641
+ keyToMatch: /^(parser|rules)$/,
642
+ replacer: (propertyAssignment, propertyName) => {
643
+ if (propertyName === 'rules') {
644
+ // Add comment that user can override rules if there are no overrides.
645
+ if (ts.isObjectLiteralExpression(propertyAssignment.initializer) &&
646
+ propertyAssignment.initializer.properties.length === 0) {
647
+ return ts.addSyntheticLeadingComment(ts.factory.createPropertyAssignment(propertyAssignment.name, ts.factory.createObjectLiteralExpression([])), ts.SyntaxKind.SingleLineCommentTrivia, ' Override or add rules here');
648
+ }
649
+ return propertyAssignment;
650
+ }
651
+ else {
652
+ // Change parser to require statement.
653
+ return ts.factory.createPropertyAssignment('parser', ts.factory.createCallExpression(ts.factory.createIdentifier('require'), undefined, [
654
+ ts.factory.createStringLiteral(override['languageOptions']?.['parserOptions']?.parser ??
655
+ override['languageOptions']?.parser ??
656
+ override.parser),
657
+ ]));
658
+ }
659
+ },
660
+ });
518
661
  }
519
- const { files, excludedFiles, rules, parserOptions, ...rest } = override;
662
+ // At this point we are applying the flat config compat tooling to the override
663
+ const { excludedFiles, parser, parserOptions, rules, files, ...rest } = override;
520
664
  const objectLiteralElements = [
521
665
  ts.factory.createSpreadAssignment(ts.factory.createIdentifier('config')),
522
666
  ];
@@ -543,7 +687,14 @@ function generateFlatOverride(override) {
543
687
  ], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createParenthesizedExpression(ts.factory.createObjectLiteralExpression(objectLiteralElements, true))),
544
688
  ]));
545
689
  }
546
- function mapFilePaths(override) {
690
+ function generateFlatPredefinedConfig(predefinedConfigName, moduleName = 'nx', spread = true) {
691
+ const node = ts.factory.createElementAccessExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(moduleName), ts.factory.createIdentifier('configs')), ts.factory.createStringLiteral(predefinedConfigName));
692
+ return spread ? ts.factory.createSpreadElement(node) : node;
693
+ }
694
+ function mapFilePaths(_override) {
695
+ const override = {
696
+ ..._override,
697
+ };
547
698
  if (override.files) {
548
699
  override.files = Array.isArray(override.files)
549
700
  ? override.files
@@ -556,6 +707,7 @@ function mapFilePaths(override) {
556
707
  : [override.excludedFiles];
557
708
  override.excludedFiles = override.excludedFiles.map((file) => (0, path_utils_1.mapFilePath)(file));
558
709
  }
710
+ return override;
559
711
  }
560
712
  function addTSObjectProperty(elements, key, value) {
561
713
  if (value) {
@@ -565,18 +717,16 @@ function addTSObjectProperty(elements, key, value) {
565
717
  /**
566
718
  * Generates an AST from a JSON-type input
567
719
  */
568
- function generateAst(input) {
720
+ function generateAst(input, propertyAssignmentReplacer) {
569
721
  if (Array.isArray(input)) {
570
- return ts.factory.createArrayLiteralExpression(input.map((item) => generateAst(item)), input.length > 1 // multiline only if more than one item
722
+ return ts.factory.createArrayLiteralExpression(input.map((item) => generateAst(item, propertyAssignmentReplacer)), input.length > 1 // multiline only if more than one item
571
723
  );
572
724
  }
573
725
  if (input === null) {
574
726
  return ts.factory.createNull();
575
727
  }
576
728
  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
729
+ return ts.factory.createObjectLiteralExpression(generatePropertyAssignmentsFromObjectEntries(input, propertyAssignmentReplacer), Object.keys(input).length > 1 // multiline only if more than one property
580
730
  );
581
731
  }
582
732
  if (typeof input === 'string') {
@@ -591,6 +741,20 @@ function generateAst(input) {
591
741
  // since we are parsing JSON, this should never happen
592
742
  throw new Error(`Unknown type: ${typeof input} `);
593
743
  }
744
+ function generatePropertyAssignmentsFromObjectEntries(input, propertyAssignmentReplacer) {
745
+ return Object.entries(input)
746
+ .filter(([_, value]) => value !== undefined)
747
+ .map(([key, value]) => {
748
+ const original = ts.factory.createPropertyAssignment(isValidKey(key) ? key : ts.factory.createStringLiteral(key), generateAst(value, propertyAssignmentReplacer));
749
+ if (propertyAssignmentReplacer &&
750
+ (typeof propertyAssignmentReplacer.keyToMatch === 'string'
751
+ ? key === propertyAssignmentReplacer.keyToMatch
752
+ : propertyAssignmentReplacer.keyToMatch.test(key))) {
753
+ return propertyAssignmentReplacer.replacer(original, key);
754
+ }
755
+ return original;
756
+ });
757
+ }
594
758
  function isValidKey(key) {
595
759
  return /^[a-zA-Z0-9_]+$/.test(key);
596
760
  }
@@ -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) => {
@@ -96,7 +98,9 @@ const internalCreateNodes = async (configFilePath, options, context, projectsCac
96
98
  };
97
99
  const internalCreateNodesV2 = async (configFilePath, options, context, eslintConfigFiles, projectRootsByEslintRoots, lintableFilesPerProjectRoot, projectsCache) => {
98
100
  const configDir = (0, posix_1.dirname)(configFilePath);
99
- 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
+ });
100
104
  const eslintVersion = ESLint.version;
101
105
  const projects = {};
102
106
  await Promise.all(projectRootsByEslintRoots.get(configDir).map(async (projectRoot) => {
@@ -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';