@nx/eslint 0.0.0-pr-31222-862e973 → 0.0.0-pr-31313-387cdca

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.
Files changed (55) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -1
  3. package/migrations.json +69 -112
  4. package/package.json +6 -7
  5. package/src/executors/lint/utility/eslint-utils.js +6 -0
  6. package/src/generators/convert-to-flat-config/converters/json-converter.d.ts +1 -1
  7. package/src/generators/convert-to-flat-config/converters/json-converter.js +10 -18
  8. package/src/generators/convert-to-flat-config/generator.js +18 -17
  9. package/src/generators/convert-to-flat-config/schema.d.ts +2 -0
  10. package/src/generators/convert-to-inferred/convert-to-inferred.js +2 -1
  11. package/src/generators/init/global-eslint-config.d.ts +1 -1
  12. package/src/generators/init/global-eslint-config.js +17 -6
  13. package/src/generators/init/init-migration.d.ts +1 -1
  14. package/src/generators/init/init-migration.js +18 -13
  15. package/src/generators/init/init.d.ts +1 -0
  16. package/src/generators/init/init.js +31 -6
  17. package/src/generators/lint-project/lint-project.d.ts +1 -0
  18. package/src/generators/lint-project/lint-project.js +37 -15
  19. package/src/generators/lint-project/setup-root-eslint.d.ts +1 -0
  20. package/src/generators/lint-project/setup-root-eslint.js +2 -1
  21. package/src/generators/utils/eslint-file.d.ts +3 -2
  22. package/src/generators/utils/eslint-file.js +160 -28
  23. package/src/generators/utils/flat-config/ast-utils.d.ts +12 -4
  24. package/src/generators/utils/flat-config/ast-utils.js +412 -63
  25. package/src/generators/utils/linter.d.ts +3 -0
  26. package/src/generators/utils/linter.js +2 -2
  27. package/src/generators/workspace-rule/files/__name__.spec.ts__tmpl__ +11 -2
  28. package/src/generators/workspace-rule/workspace-rule.d.ts +2 -2
  29. package/src/generators/workspace-rule/workspace-rule.js +11 -3
  30. package/src/generators/workspace-rules-project/workspace-rules-project.js +4 -1
  31. package/src/migrations/update-20-2-0/update-typescript-eslint-v8-13-0.d.ts +2 -0
  32. package/src/migrations/update-20-2-0/update-typescript-eslint-v8-13-0.js +23 -0
  33. package/src/migrations/update-20-3-0/add-file-extensions-to-overrides.d.ts +2 -0
  34. package/src/migrations/update-20-3-0/add-file-extensions-to-overrides.js +49 -0
  35. package/src/plugins/plugin.js +21 -10
  36. package/src/utils/config-file.d.ts +3 -1
  37. package/src/utils/config-file.js +5 -2
  38. package/src/utils/flat-config.d.ts +1 -0
  39. package/src/utils/flat-config.js +9 -3
  40. package/src/utils/version-utils.d.ts +1 -0
  41. package/src/utils/version-utils.js +13 -9
  42. package/src/utils/versions.d.ts +3 -2
  43. package/src/utils/versions.js +4 -3
  44. package/src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages.d.ts +0 -2
  45. package/src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages.js +0 -9
  46. package/src/migrations/update-16-8-0-add-ignored-files/update-16-8-0-add-ignored-files.d.ts +0 -2
  47. package/src/migrations/update-16-8-0-add-ignored-files/update-16-8-0-add-ignored-files.js +0 -44
  48. package/src/migrations/update-17-0-0-rename-to-eslint/update-17-0-0-rename-to-eslint.d.ts +0 -2
  49. package/src/migrations/update-17-0-0-rename-to-eslint/update-17-0-0-rename-to-eslint.js +0 -47
  50. package/src/migrations/update-17-1-0/update-typescript-eslint.d.ts +0 -2
  51. package/src/migrations/update-17-1-0/update-typescript-eslint.js +0 -74
  52. package/src/migrations/update-17-2-0/simplify-eslint-patterns.d.ts +0 -2
  53. package/src/migrations/update-17-2-0/simplify-eslint-patterns.js +0 -46
  54. package/src/migrations/update-17-2-9/move-options-to-target-defaults.d.ts +0 -2
  55. package/src/migrations/update-17-2-9/move-options-to-target-defaults.js +0 -107
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.removeOverridesFromLintConfig = removeOverridesFromLintConfig;
4
+ exports.addPatternsToFlatConfigIgnoresBlock = addPatternsToFlatConfigIgnoresBlock;
5
+ exports.hasFlatConfigIgnoresBlock = hasFlatConfigIgnoresBlock;
4
6
  exports.hasOverride = hasOverride;
5
7
  exports.replaceOverride = replaceOverride;
6
8
  exports.addImportToFlatConfig = addImportToFlatConfig;
@@ -17,6 +19,7 @@ exports.generatePluginExtendsElement = generatePluginExtendsElement;
17
19
  exports.generatePluginExtendsElementWithCompatFixup = generatePluginExtendsElementWithCompatFixup;
18
20
  exports.stringifyNodeList = stringifyNodeList;
19
21
  exports.generateRequire = generateRequire;
22
+ exports.generateESMImport = generateESMImport;
20
23
  exports.overrideNeedsCompat = overrideNeedsCompat;
21
24
  exports.generateFlatOverride = generateFlatOverride;
22
25
  exports.generateFlatPredefinedConfig = generateFlatPredefinedConfig;
@@ -35,7 +38,8 @@ const SPREAD_ELEMENTS_REGEXP = /\s*\.\.\.[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)*,?\n?/g;
35
38
  */
36
39
  function removeOverridesFromLintConfig(content) {
37
40
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
38
- const exportsArray = findAllBlocks(source);
41
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
42
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
39
43
  if (!exportsArray) {
40
44
  return content;
41
45
  }
@@ -52,7 +56,16 @@ function removeOverridesFromLintConfig(content) {
52
56
  });
53
57
  return (0, devkit_1.applyChangesToString)(content, changes);
54
58
  }
55
- function findAllBlocks(source) {
59
+ // TODO Change name
60
+ function findExportDefault(source) {
61
+ return ts.forEachChild(source, function analyze(node) {
62
+ if (ts.isExportAssignment(node) &&
63
+ ts.isArrayLiteralExpression(node.expression)) {
64
+ return node.expression.elements;
65
+ }
66
+ });
67
+ }
68
+ function findModuleExports(source) {
56
69
  return ts.forEachChild(source, function analyze(node) {
57
70
  if (ts.isExpressionStatement(node) &&
58
71
  ts.isBinaryExpression(node.expression) &&
@@ -62,9 +75,58 @@ function findAllBlocks(source) {
62
75
  }
63
76
  });
64
77
  }
78
+ function addPatternsToFlatConfigIgnoresBlock(content, ignorePatterns) {
79
+ const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
80
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
81
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
82
+ if (!exportsArray) {
83
+ return content;
84
+ }
85
+ const changes = [];
86
+ for (const node of exportsArray) {
87
+ if (!isFlatConfigIgnoresBlock(node)) {
88
+ continue;
89
+ }
90
+ const start = node.properties.pos + 1; // keep leading line break
91
+ const data = parseTextToJson(node.getFullText());
92
+ changes.push({
93
+ type: devkit_1.ChangeType.Delete,
94
+ start,
95
+ length: node.properties.end - start,
96
+ });
97
+ data.ignores = Array.from(new Set([...(data.ignores ?? []), ...ignorePatterns]));
98
+ changes.push({
99
+ type: devkit_1.ChangeType.Insert,
100
+ index: start,
101
+ text: ' ' +
102
+ JSON.stringify(data, null, 2)
103
+ .slice(2, -2) // Remove curly braces and start/end line breaks
104
+ .replaceAll(/\n/g, '\n '), // Maintain indentation
105
+ });
106
+ break;
107
+ }
108
+ return (0, devkit_1.applyChangesToString)(content, changes);
109
+ }
110
+ function hasFlatConfigIgnoresBlock(content) {
111
+ const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
112
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
113
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
114
+ if (!exportsArray) {
115
+ return false;
116
+ }
117
+ return exportsArray.some(isFlatConfigIgnoresBlock);
118
+ }
119
+ function isFlatConfigIgnoresBlock(node) {
120
+ return (ts.isObjectLiteralExpression(node) &&
121
+ node.properties.length === 1 &&
122
+ (node.properties[0].name.getText() === 'ignores' ||
123
+ node.properties[0].name.getText() === '"ignores"') &&
124
+ ts.isPropertyAssignment(node.properties[0]) &&
125
+ ts.isArrayLiteralExpression(node.properties[0].initializer));
126
+ }
65
127
  function isOverride(node) {
66
128
  return ((ts.isObjectLiteralExpression(node) &&
67
- node.properties.some((p) => p.name.getText() === 'files')) ||
129
+ node.properties.some((p) => p.name.getText() === 'files' || p.name.getText() === '"files"')) ||
68
130
  // detect ...compat.config(...).map(...)
69
131
  (ts.isSpreadElement(node) &&
70
132
  ts.isCallExpression(node.expression) &&
@@ -74,7 +136,8 @@ function isOverride(node) {
74
136
  }
75
137
  function hasOverride(content, lookup) {
76
138
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
77
- const exportsArray = findAllBlocks(source);
139
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
140
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
78
141
  if (!exportsArray) {
79
142
  return false;
80
143
  }
@@ -105,14 +168,16 @@ function parseTextToJson(text) {
105
168
  .replace(/'/g, '"')
106
169
  .replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": ')
107
170
  // stringify any require calls to avoid JSON parsing errors, turn them into just the string value being required
108
- .replace(/require\(['"]([^'"]+)['"]\)/g, '"$1"'));
171
+ .replace(/require\(['"]([^'"]+)['"]\)/g, '"$1"')
172
+ .replace(/\(?await import\(['"]([^'"]+)['"]\)\)?/g, '"$1"'));
109
173
  }
110
174
  /**
111
175
  * Finds an override matching the lookup function and applies the update function to it
112
176
  */
113
177
  function replaceOverride(content, root, lookup, update) {
114
178
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
115
- const exportsArray = findAllBlocks(source);
179
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
180
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
116
181
  if (!exportsArray) {
117
182
  return content;
118
183
  }
@@ -145,15 +210,17 @@ function replaceOverride(content, root, lookup, update) {
145
210
  let updatedData = update(data);
146
211
  if (updatedData) {
147
212
  updatedData = mapFilePaths(updatedData);
213
+ const parserReplacement = format === 'mjs'
214
+ ? (parser) => `(await import('${parser}'))`
215
+ : (parser) => `require('${parser}')`;
148
216
  changes.push({
149
217
  type: devkit_1.ChangeType.Insert,
150
218
  index: start,
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
219
+ text: ' ' +
220
+ JSON.stringify(updatedData, null, 2)
221
+ .replace(/"parser": "([^"]+)"/g, (_, parser) => `"parser": ${parserReplacement(parser)}`)
222
+ .slice(2, -2) // Remove curly braces and start/end line breaks
223
+ .replaceAll(/\n/g, '\n '), // Maintain indentation
157
224
  });
158
225
  }
159
226
  }
@@ -162,11 +229,89 @@ function replaceOverride(content, root, lookup, update) {
162
229
  return (0, devkit_1.applyChangesToString)(content, changes);
163
230
  }
164
231
  /**
165
- * Adding require statement to the top of the file
232
+ * Adding import statement to the top of the file
233
+ * The imports are added based on a few rules:
234
+ * 1. If it's a default import and matches the variable, return content unchanged.
235
+ * 2. If it's a named import and the variables are not part of the import object, add them.
236
+ * 3. If no existing import and variable is a string, add a default import.
237
+ * 4. If no existing import and variable is an array, add it as an object import.
166
238
  */
167
239
  function addImportToFlatConfig(content, variable, imp) {
168
240
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
169
241
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
242
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
243
+ if (format === 'mjs') {
244
+ return addESMImportToFlatConfig(source, printer, content, variable, imp);
245
+ }
246
+ return addCJSImportToFlatConfig(source, printer, content, variable, imp);
247
+ }
248
+ function addESMImportToFlatConfig(source, printer, content, variable, imp) {
249
+ let existingImport;
250
+ ts.forEachChild(source, (node) => {
251
+ if (ts.isImportDeclaration(node) &&
252
+ ts.isStringLiteral(node.moduleSpecifier) &&
253
+ node.moduleSpecifier.text === imp) {
254
+ existingImport = node;
255
+ }
256
+ });
257
+ // Rule 1:
258
+ if (existingImport &&
259
+ typeof variable === 'string' &&
260
+ existingImport.importClause?.name?.getText() === variable) {
261
+ return content;
262
+ }
263
+ // Rule 2:
264
+ if (existingImport &&
265
+ existingImport.importClause?.namedBindings &&
266
+ Array.isArray(variable)) {
267
+ const namedImports = existingImport.importClause
268
+ .namedBindings;
269
+ const existingElements = namedImports.elements;
270
+ // Filter out variables that are already imported
271
+ const newVariables = variable.filter((v) => !existingElements.some((e) => e.name.getText() === v));
272
+ if (newVariables.length === 0) {
273
+ return content;
274
+ }
275
+ const newImportSpecifiers = newVariables.map((v) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(v)));
276
+ const lastElement = existingElements[existingElements.length - 1];
277
+ const insertIndex = lastElement
278
+ ? lastElement.getEnd()
279
+ : namedImports.getEnd();
280
+ const insertText = printer.printList(ts.ListFormat.NamedImportsOrExportsElements, ts.factory.createNodeArray(newImportSpecifiers), source);
281
+ return (0, devkit_1.applyChangesToString)(content, [
282
+ {
283
+ type: devkit_1.ChangeType.Insert,
284
+ index: insertIndex,
285
+ text: `, ${insertText}`,
286
+ },
287
+ ]);
288
+ }
289
+ // Rule 3:
290
+ if (!existingImport && typeof variable === 'string') {
291
+ const defaultImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, ts.factory.createIdentifier(variable), undefined), ts.factory.createStringLiteral(imp));
292
+ const insert = printer.printNode(ts.EmitHint.Unspecified, defaultImport, source);
293
+ return (0, devkit_1.applyChangesToString)(content, [
294
+ {
295
+ type: devkit_1.ChangeType.Insert,
296
+ index: 0,
297
+ text: `${insert}\n`,
298
+ },
299
+ ]);
300
+ }
301
+ // Rule 4:
302
+ if (!existingImport && Array.isArray(variable)) {
303
+ const objectImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports(variable.map((v) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(v))))), ts.factory.createStringLiteral(imp));
304
+ const insert = printer.printNode(ts.EmitHint.Unspecified, objectImport, source);
305
+ return (0, devkit_1.applyChangesToString)(content, [
306
+ {
307
+ type: devkit_1.ChangeType.Insert,
308
+ index: 0,
309
+ text: `${insert}\n`,
310
+ },
311
+ ]);
312
+ }
313
+ }
314
+ function addCJSImportToFlatConfig(source, printer, content, variable, imp) {
170
315
  const foundBindingVars = ts.forEachChild(source, function analyze(node) {
171
316
  // we can only combine object binding patterns
172
317
  if (!Array.isArray(variable)) {
@@ -235,11 +380,48 @@ function addImportToFlatConfig(content, variable, imp) {
235
380
  },
236
381
  ]);
237
382
  }
383
+ function existsAsNamedOrDefaultImport(node, variable) {
384
+ const isNamed = node.importClause.namedBindings &&
385
+ ts.isNamedImports(node.importClause.namedBindings);
386
+ if (Array.isArray(variable)) {
387
+ return isNamed || variable.includes(node.importClause?.name?.getText());
388
+ }
389
+ return ((node.importClause.namedBindings &&
390
+ ts.isNamedImports(node.importClause.namedBindings)) ||
391
+ node.importClause?.name?.getText() === variable);
392
+ }
238
393
  /**
239
394
  * Remove an import from flat config
240
395
  */
241
396
  function removeImportFromFlatConfig(content, variable, imp) {
242
397
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
398
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
399
+ if (format === 'mjs') {
400
+ return removeImportFromFlatConfigESM(source, content, variable, imp);
401
+ }
402
+ else {
403
+ return removeImportFromFlatConfigCJS(source, content, variable, imp);
404
+ }
405
+ }
406
+ function removeImportFromFlatConfigESM(source, content, variable, imp) {
407
+ const changes = [];
408
+ ts.forEachChild(source, (node) => {
409
+ // we can only combine object binding patterns
410
+ if (ts.isImportDeclaration(node) &&
411
+ ts.isStringLiteral(node.moduleSpecifier) &&
412
+ node.moduleSpecifier.text === imp &&
413
+ node.importClause &&
414
+ existsAsNamedOrDefaultImport(node, variable)) {
415
+ changes.push({
416
+ type: devkit_1.ChangeType.Delete,
417
+ start: node.pos,
418
+ length: node.end - node.pos,
419
+ });
420
+ }
421
+ });
422
+ return (0, devkit_1.applyChangesToString)(content, changes);
423
+ }
424
+ function removeImportFromFlatConfigCJS(source, content, variable, imp) {
243
425
  const changes = [];
244
426
  ts.forEachChild(source, (node) => {
245
427
  // we can only combine object binding patterns
@@ -262,13 +444,44 @@ function removeImportFromFlatConfig(content, variable, imp) {
262
444
  return (0, devkit_1.applyChangesToString)(content, changes);
263
445
  }
264
446
  /**
265
- * Injects new ts.expression to the end of the module.exports array.
447
+ * Injects new ts.expression to the end of the module.exports or export default array.
266
448
  */
267
449
  function addBlockToFlatConfigExport(content, config, options = {
268
450
  insertAtTheEnd: true,
269
451
  }) {
270
452
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
271
453
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
454
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
455
+ // find the export default array statement
456
+ if (format === 'mjs') {
457
+ return addBlockToFlatConfigExportESM(content, config, source, printer, options);
458
+ }
459
+ else {
460
+ return addBlockToFlatConfigExportCJS(content, config, source, printer, options);
461
+ }
462
+ }
463
+ function addBlockToFlatConfigExportESM(content, config, source, printer, options = {
464
+ insertAtTheEnd: true,
465
+ }) {
466
+ const exportDefaultStatement = source.statements.find((statement) => ts.isExportAssignment(statement) &&
467
+ ts.isArrayLiteralExpression(statement.expression));
468
+ if (!exportDefaultStatement)
469
+ return content;
470
+ const exportArrayLiteral = exportDefaultStatement.expression;
471
+ const updatedArrayElements = options.insertAtTheEnd
472
+ ? [...exportArrayLiteral.elements, config]
473
+ : [config, ...exportArrayLiteral.elements];
474
+ const updatedExportDefault = ts.factory.createExportAssignment(undefined, false, ts.factory.createArrayLiteralExpression(updatedArrayElements, true));
475
+ // update the existing export default array
476
+ const updatedStatements = source.statements.map((statement) => statement === exportDefaultStatement ? updatedExportDefault : statement);
477
+ const updatedSource = ts.factory.updateSourceFile(source, updatedStatements);
478
+ return printer
479
+ .printFile(updatedSource)
480
+ .replace(/export default/, '\nexport default');
481
+ }
482
+ function addBlockToFlatConfigExportCJS(content, config, source, printer, options = {
483
+ insertAtTheEnd: true,
484
+ }) {
272
485
  const exportsArray = ts.forEachChild(source, function analyze(node) {
273
486
  if (ts.isExpressionStatement(node) &&
274
487
  ts.isBinaryExpression(node.expression) &&
@@ -282,7 +495,10 @@ function addBlockToFlatConfigExport(content, config, options = {
282
495
  // base config was not generated by Nx.
283
496
  if (!exportsArray)
284
497
  return content;
285
- const insert = printer.printNode(ts.EmitHint.Expression, config, source);
498
+ const insert = ' ' +
499
+ printer
500
+ .printNode(ts.EmitHint.Expression, config, source)
501
+ .replaceAll(/\n/g, '\n ');
286
502
  if (options.insertAtTheEnd) {
287
503
  const index = exportsArray.length > 0
288
504
  ? exportsArray.at(exportsArray.length - 1).end
@@ -301,35 +517,54 @@ function addBlockToFlatConfigExport(content, config, options = {
301
517
  {
302
518
  type: devkit_1.ChangeType.Insert,
303
519
  index,
304
- text: `\n${insert},`,
520
+ text: `\n${insert},\n`,
305
521
  },
306
522
  ]);
307
523
  }
308
524
  }
309
525
  function removePlugin(content, pluginName, pluginImport) {
310
526
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
527
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
311
528
  const changes = [];
529
+ if (format === 'mjs') {
530
+ ts.forEachChild(source, function analyze(node) {
531
+ if (ts.isImportDeclaration(node) &&
532
+ ts.isStringLiteral(node.moduleSpecifier) &&
533
+ node.moduleSpecifier.text === pluginImport) {
534
+ const importClause = node.importClause;
535
+ if ((importClause && importClause.name) ||
536
+ (importClause.namedBindings &&
537
+ ts.isNamedImports(importClause.namedBindings))) {
538
+ changes.push({
539
+ type: devkit_1.ChangeType.Delete,
540
+ start: node.pos,
541
+ length: node.end - node.pos,
542
+ });
543
+ }
544
+ }
545
+ });
546
+ }
547
+ else {
548
+ ts.forEachChild(source, function analyze(node) {
549
+ if (ts.isVariableStatement(node) &&
550
+ ts.isVariableDeclaration(node.declarationList.declarations[0]) &&
551
+ ts.isCallExpression(node.declarationList.declarations[0].initializer) &&
552
+ node.declarationList.declarations[0].initializer.arguments.length &&
553
+ ts.isStringLiteral(node.declarationList.declarations[0].initializer.arguments[0]) &&
554
+ node.declarationList.declarations[0].initializer.arguments[0].text ===
555
+ pluginImport) {
556
+ changes.push({
557
+ type: devkit_1.ChangeType.Delete,
558
+ start: node.pos,
559
+ length: node.end - node.pos,
560
+ });
561
+ }
562
+ });
563
+ }
312
564
  ts.forEachChild(source, function analyze(node) {
313
- if (ts.isVariableStatement(node) &&
314
- ts.isVariableDeclaration(node.declarationList.declarations[0]) &&
315
- ts.isCallExpression(node.declarationList.declarations[0].initializer) &&
316
- node.declarationList.declarations[0].initializer.arguments.length &&
317
- ts.isStringLiteral(node.declarationList.declarations[0].initializer.arguments[0]) &&
318
- node.declarationList.declarations[0].initializer.arguments[0].text ===
319
- pluginImport) {
320
- changes.push({
321
- type: devkit_1.ChangeType.Delete,
322
- start: node.pos,
323
- length: node.end - node.pos,
324
- });
325
- }
326
- });
327
- ts.forEachChild(source, function analyze(node) {
328
- if (ts.isExpressionStatement(node) &&
329
- ts.isBinaryExpression(node.expression) &&
330
- node.expression.left.getText() === 'module.exports' &&
331
- ts.isArrayLiteralExpression(node.expression.right)) {
332
- const blockElements = node.expression.right.elements;
565
+ if (ts.isExportAssignment(node) &&
566
+ ts.isArrayLiteralExpression(node.expression)) {
567
+ const blockElements = node.expression.elements;
333
568
  blockElements.forEach((element) => {
334
569
  if (ts.isObjectLiteralExpression(element)) {
335
570
  const pluginsElem = element.properties.find((prop) => prop.name?.getText() === 'plugins');
@@ -419,7 +654,12 @@ function removePlugin(content, pluginName, pluginImport) {
419
654
  function removeCompatExtends(content, compatExtends) {
420
655
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
421
656
  const changes = [];
422
- findAllBlocks(source)?.forEach((node) => {
657
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
658
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
659
+ if (!exportsArray) {
660
+ return content;
661
+ }
662
+ exportsArray.forEach((node) => {
423
663
  if (ts.isSpreadElement(node) &&
424
664
  ts.isCallExpression(node.expression) &&
425
665
  ts.isArrowFunction(node.expression.arguments[0]) &&
@@ -453,9 +693,14 @@ function removeCompatExtends(content, compatExtends) {
453
693
  }
454
694
  function removePredefinedConfigs(content, moduleImport, moduleVariable, configs) {
455
695
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
696
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
456
697
  const changes = [];
457
698
  let removeImport = true;
458
- findAllBlocks(source)?.forEach((node) => {
699
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
700
+ if (!exportsArray) {
701
+ return content;
702
+ }
703
+ exportsArray.forEach((node) => {
459
704
  if (ts.isSpreadElement(node) &&
460
705
  ts.isElementAccessExpression(node.expression) &&
461
706
  ts.isPropertyAccessExpression(node.expression.expression) &&
@@ -500,14 +745,22 @@ function addPluginsToExportsBlock(content, plugins) {
500
745
  * Adds compat if missing to flat config
501
746
  */
502
747
  function addFlatCompatToFlatConfig(content) {
503
- let result = content;
504
- result = addImportToFlatConfig(result, 'js', '@eslint/js');
748
+ const result = addImportToFlatConfig(content, 'js', '@eslint/js');
749
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
505
750
  if (result.includes('const compat = new FlatCompat')) {
506
751
  return result;
507
752
  }
508
- result = addImportToFlatConfig(result, ['FlatCompat'], '@eslint/eslintrc');
509
- const index = result.indexOf('module.exports');
510
- return (0, devkit_1.applyChangesToString)(result, [
753
+ if (format === 'mjs') {
754
+ return addFlatCompatToFlatConfigESM(result);
755
+ }
756
+ else {
757
+ return addFlatCompatToFlatConfigCJS(result);
758
+ }
759
+ }
760
+ function addFlatCompatToFlatConfigCJS(content) {
761
+ content = addImportToFlatConfig(content, ['FlatCompat'], '@eslint/eslintrc');
762
+ const index = content.indexOf('module.exports');
763
+ return (0, devkit_1.applyChangesToString)(content, [
511
764
  {
512
765
  type: devkit_1.ChangeType.Insert,
513
766
  index: index - 1,
@@ -520,25 +773,62 @@ const compat = new FlatCompat({
520
773
  },
521
774
  ]);
522
775
  }
776
+ function addFlatCompatToFlatConfigESM(content) {
777
+ const importsToAdd = [
778
+ { variable: 'js', module: '@eslint/js' },
779
+ { variable: ['fileURLToPath'], module: 'url' },
780
+ { variable: ['dirname'], module: 'path' },
781
+ { variable: ['FlatCompat'], module: '@eslint/eslintrc' },
782
+ ];
783
+ for (const { variable, module } of importsToAdd) {
784
+ content = addImportToFlatConfig(content, variable, module);
785
+ }
786
+ const index = content.indexOf('export default');
787
+ return (0, devkit_1.applyChangesToString)(content, [
788
+ {
789
+ type: devkit_1.ChangeType.Insert,
790
+ index: index - 1,
791
+ text: `
792
+ const compat = new FlatCompat({
793
+ baseDirectory: dirname(fileURLToPath(import.meta.url)),
794
+ recommendedConfig: js.configs.recommended,
795
+ });\n
796
+ `,
797
+ },
798
+ ]);
799
+ }
523
800
  /**
524
801
  * Generate node list representing the imports and the exports blocks
525
802
  * Optionally add flat compat initialization
526
803
  */
527
- function createNodeList(importsMap, exportElements) {
804
+ function createNodeList(importsMap, exportElements, format) {
528
805
  const importsList = [];
529
- // generateRequire(varName, imp, ts.factory);
530
806
  Array.from(importsMap.entries()).forEach(([imp, varName]) => {
531
- importsList.push(generateRequire(varName, imp));
807
+ if (format === 'mjs') {
808
+ importsList.push(generateESMImport(varName, imp));
809
+ }
810
+ else {
811
+ importsList.push(generateRequire(varName, imp));
812
+ }
532
813
  });
814
+ const exports = format === 'mjs'
815
+ ? generateESMExport(exportElements)
816
+ : generateCJSExport(exportElements);
533
817
  return ts.factory.createNodeArray([
534
818
  // add plugin imports
535
819
  ...importsList,
536
820
  ts.createSourceFile('', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.JS),
537
- // creates:
538
- // module.exports = [ ... ];
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))),
821
+ exports,
540
822
  ]);
541
823
  }
824
+ function generateESMExport(elements) {
825
+ // creates: export default = [...]
826
+ return ts.factory.createExportAssignment(undefined, false, ts.factory.createArrayLiteralExpression(elements, true));
827
+ }
828
+ function generateCJSExport(elements) {
829
+ // creates: module.exports = [...]
830
+ return 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(elements, true)));
831
+ }
542
832
  function generateSpreadElement(name) {
543
833
  return ts.factory.createSpreadElement(ts.factory.createIdentifier(name));
544
834
  }
@@ -556,12 +846,17 @@ function generatePluginExtendsElementWithCompatFixup(plugin) {
556
846
  function stringifyNodeList(nodes) {
557
847
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
558
848
  const resultFile = ts.createSourceFile('', '', ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
559
- return (printer
849
+ const result = printer
560
850
  .printList(ts.ListFormat.MultiLine, nodes, resultFile)
561
851
  // add new line before compat initialization
562
- .replace(/const compat = new FlatCompat/, '\nconst compat = new FlatCompat')
563
- // add new line before module.exports = ...
564
- .replace(/module\.exports/, '\nmodule.exports'));
852
+ .replace(/const compat = new FlatCompat/, '\nconst compat = new FlatCompat');
853
+ if (result.includes('export default')) {
854
+ return result // add new line before export default = ...
855
+ .replace(/export default/, '\nexport default');
856
+ }
857
+ else {
858
+ return result.replace(/module.exports/, '\nmodule.exports');
859
+ }
565
860
  }
566
861
  /**
567
862
  * generates AST require statement
@@ -571,6 +866,26 @@ function generateRequire(variableName, imp) {
571
866
  ts.factory.createVariableDeclaration(variableName, undefined, undefined, ts.factory.createCallExpression(ts.factory.createIdentifier('require'), undefined, [ts.factory.createStringLiteral(imp)])),
572
867
  ], ts.NodeFlags.Const));
573
868
  }
869
+ // Top level imports
870
+ function generateESMImport(variableName, imp) {
871
+ let importClause;
872
+ if (typeof variableName === 'string') {
873
+ // For single variable import e.g import foo from 'module';
874
+ importClause = ts.factory.createImportClause(false, ts.factory.createIdentifier(variableName), undefined);
875
+ }
876
+ else {
877
+ // For object binding pattern import e.g import { a, b, c } from 'module';
878
+ importClause = ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports(variableName.elements.map((element) => {
879
+ const propertyName = element.propertyName
880
+ ? ts.isIdentifier(element.propertyName)
881
+ ? element.propertyName
882
+ : ts.factory.createIdentifier(element.propertyName.getText())
883
+ : undefined;
884
+ return ts.factory.createImportSpecifier(false, propertyName, element.name);
885
+ })));
886
+ }
887
+ return ts.factory.createImportDeclaration(undefined, importClause, ts.factory.createStringLiteral(imp));
888
+ }
574
889
  /**
575
890
  * FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L222
576
891
  *
@@ -596,7 +911,7 @@ function overrideNeedsCompat(override) {
596
911
  * Generates an AST object or spread element representing a modern flat config entry,
597
912
  * based on a given legacy eslintrc JSON override object
598
913
  */
599
- function generateFlatOverride(_override) {
914
+ function generateFlatOverride(_override, format) {
600
915
  const override = mapFilePaths(_override);
601
916
  // We do not need the compat tooling for this override
602
917
  if (!overrideNeedsCompat(override)) {
@@ -652,21 +967,41 @@ function generateFlatOverride(_override) {
652
967
  return propertyAssignment;
653
968
  }
654
969
  else {
655
- // Change parser to require statement.
656
- return ts.factory.createPropertyAssignment('parser', ts.factory.createCallExpression(ts.factory.createIdentifier('require'), undefined, [
657
- ts.factory.createStringLiteral(override['languageOptions']?.['parserOptions']?.parser ??
658
- override['languageOptions']?.parser ??
659
- override.parser),
660
- ]));
970
+ // Change parser to import statement.
971
+ return format === 'mjs'
972
+ ? generateESMParserImport(override)
973
+ : generateCJSParserImport(override);
661
974
  }
662
975
  },
663
976
  });
664
977
  }
665
978
  // At this point we are applying the flat config compat tooling to the override
666
- const { excludedFiles, parser, parserOptions, rules, files, ...rest } = override;
979
+ let { excludedFiles, parser, parserOptions, rules, files, ...rest } = override;
667
980
  const objectLiteralElements = [
668
981
  ts.factory.createSpreadAssignment(ts.factory.createIdentifier('config')),
669
982
  ];
983
+ // If converting the JS rule, then we need to match ESLint default and also include .cjs and .mjs files.
984
+ if ((Array.isArray(rest.extends) &&
985
+ rest.extends.includes('plugin:@nx/javascript')) ||
986
+ rest.extends === 'plugin:@nx/javascript') {
987
+ const newFiles = new Set(files);
988
+ newFiles.add('**/*.js');
989
+ newFiles.add('**/*.jsx');
990
+ newFiles.add('**/*.cjs');
991
+ newFiles.add('**/*.mjs');
992
+ files = Array.from(newFiles);
993
+ }
994
+ // If converting the TS rule, then we need to match ESLint default and also include .cts and .mts files.
995
+ if ((Array.isArray(rest.extends) &&
996
+ rest.extends.includes('plugin:@nx/typescript')) ||
997
+ rest.extends === 'plugin:@nx/typescript') {
998
+ const newFiles = new Set(files);
999
+ newFiles.add('**/*.ts');
1000
+ newFiles.add('**/*.tsx');
1001
+ newFiles.add('**/*.cts');
1002
+ newFiles.add('**/*.mts');
1003
+ files = Array.from(newFiles);
1004
+ }
670
1005
  addTSObjectProperty(objectLiteralElements, 'files', files);
671
1006
  addTSObjectProperty(objectLiteralElements, 'excludedFiles', excludedFiles);
672
1007
  // Apply rules (and spread ...config.rules into it as the first assignment)
@@ -690,6 +1025,20 @@ function generateFlatOverride(_override) {
690
1025
  ], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createParenthesizedExpression(ts.factory.createObjectLiteralExpression(objectLiteralElements, true))),
691
1026
  ]));
692
1027
  }
1028
+ function generateESMParserImport(override) {
1029
+ return ts.factory.createPropertyAssignment('parser', ts.factory.createAwaitExpression(ts.factory.createCallExpression(ts.factory.createIdentifier('import'), undefined, [
1030
+ ts.factory.createStringLiteral(override['languageOptions']?.['parserOptions']?.parser ??
1031
+ override['languageOptions']?.parser ??
1032
+ override.parser),
1033
+ ])));
1034
+ }
1035
+ function generateCJSParserImport(override) {
1036
+ return ts.factory.createPropertyAssignment('parser', ts.factory.createCallExpression(ts.factory.createIdentifier('require'), undefined, [
1037
+ ts.factory.createStringLiteral(override['languageOptions']?.['parserOptions']?.parser ??
1038
+ override['languageOptions']?.parser ??
1039
+ override.parser),
1040
+ ]));
1041
+ }
693
1042
  function generateFlatPredefinedConfig(predefinedConfigName, moduleName = 'nx', spread = true) {
694
1043
  const node = ts.factory.createElementAccessExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(moduleName), ts.factory.createIdentifier('configs')), ts.factory.createStringLiteral(predefinedConfigName));
695
1044
  return spread ? ts.factory.createSpreadElement(node) : node;
@@ -722,14 +1071,14 @@ function addTSObjectProperty(elements, key, value) {
722
1071
  */
723
1072
  function generateAst(input, propertyAssignmentReplacer) {
724
1073
  if (Array.isArray(input)) {
725
- return ts.factory.createArrayLiteralExpression(input.map((item) => generateAst(item, propertyAssignmentReplacer)), input.length > 1 // multiline only if more than one item
1074
+ return ts.factory.createArrayLiteralExpression(input.map((item) => generateAst(item, propertyAssignmentReplacer)), true // Always treat as multiline, using item.length does not work in all cases
726
1075
  );
727
1076
  }
728
1077
  if (input === null) {
729
1078
  return ts.factory.createNull();
730
1079
  }
731
1080
  if (typeof input === 'object') {
732
- return ts.factory.createObjectLiteralExpression(generatePropertyAssignmentsFromObjectEntries(input, propertyAssignmentReplacer), Object.keys(input).length > 1 // multiline only if more than one property
1081
+ return ts.factory.createObjectLiteralExpression(generatePropertyAssignmentsFromObjectEntries(input, propertyAssignmentReplacer), true // Always treat as multiline, using Object.keys(input).length > 1 does not work in all cases
733
1082
  );
734
1083
  }
735
1084
  if (typeof input === 'string') {