@nx/eslint 20.4.0-canary.20250116-a127177 → 20.4.0-canary.20250118-ee135b2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. package/migrations.json +40 -0
  2. package/package.json +4 -4
  3. package/src/generators/convert-to-flat-config/converters/json-converter.d.ts +1 -1
  4. package/src/generators/convert-to-flat-config/converters/json-converter.js +6 -6
  5. package/src/generators/convert-to-flat-config/generator.js +17 -16
  6. package/src/generators/convert-to-flat-config/schema.d.ts +2 -0
  7. package/src/generators/convert-to-inferred/convert-to-inferred.js +1 -0
  8. package/src/generators/init/global-eslint-config.d.ts +1 -1
  9. package/src/generators/init/global-eslint-config.js +5 -5
  10. package/src/generators/init/init-migration.d.ts +1 -1
  11. package/src/generators/init/init-migration.js +15 -5
  12. package/src/generators/init/init.d.ts +1 -0
  13. package/src/generators/init/init.js +17 -6
  14. package/src/generators/lint-project/lint-project.d.ts +1 -0
  15. package/src/generators/lint-project/lint-project.js +20 -6
  16. package/src/generators/lint-project/setup-root-eslint.d.ts +1 -0
  17. package/src/generators/lint-project/setup-root-eslint.js +2 -1
  18. package/src/generators/utils/eslint-file.d.ts +1 -0
  19. package/src/generators/utils/eslint-file.js +54 -14
  20. package/src/generators/utils/flat-config/ast-utils.d.ts +10 -4
  21. package/src/generators/utils/flat-config/ast-utils.js +328 -59
  22. package/src/plugins/plugin.js +1 -1
  23. package/src/utils/config-file.d.ts +2 -1
  24. package/src/utils/config-file.js +3 -2
  25. package/src/utils/flat-config.d.ts +1 -0
  26. package/src/utils/flat-config.js +8 -2
  27. package/src/utils/versions.d.ts +1 -1
  28. package/src/utils/versions.js +1 -1
@@ -17,6 +17,7 @@ exports.generatePluginExtendsElement = generatePluginExtendsElement;
17
17
  exports.generatePluginExtendsElementWithCompatFixup = generatePluginExtendsElementWithCompatFixup;
18
18
  exports.stringifyNodeList = stringifyNodeList;
19
19
  exports.generateRequire = generateRequire;
20
+ exports.generateESMImport = generateESMImport;
20
21
  exports.overrideNeedsCompat = overrideNeedsCompat;
21
22
  exports.generateFlatOverride = generateFlatOverride;
22
23
  exports.generateFlatPredefinedConfig = generateFlatPredefinedConfig;
@@ -35,7 +36,8 @@ const SPREAD_ELEMENTS_REGEXP = /\s*\.\.\.[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)*,?\n?/g;
35
36
  */
36
37
  function removeOverridesFromLintConfig(content) {
37
38
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
38
- const exportsArray = findAllBlocks(source);
39
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
40
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
39
41
  if (!exportsArray) {
40
42
  return content;
41
43
  }
@@ -52,7 +54,16 @@ function removeOverridesFromLintConfig(content) {
52
54
  });
53
55
  return (0, devkit_1.applyChangesToString)(content, changes);
54
56
  }
55
- function findAllBlocks(source) {
57
+ // TODO Change name
58
+ function findExportDefault(source) {
59
+ return ts.forEachChild(source, function analyze(node) {
60
+ if (ts.isExportAssignment(node) &&
61
+ ts.isArrayLiteralExpression(node.expression)) {
62
+ return node.expression.elements;
63
+ }
64
+ });
65
+ }
66
+ function findModuleExports(source) {
56
67
  return ts.forEachChild(source, function analyze(node) {
57
68
  if (ts.isExpressionStatement(node) &&
58
69
  ts.isBinaryExpression(node.expression) &&
@@ -74,7 +85,8 @@ function isOverride(node) {
74
85
  }
75
86
  function hasOverride(content, lookup) {
76
87
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
77
- const exportsArray = findAllBlocks(source);
88
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
89
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
78
90
  if (!exportsArray) {
79
91
  return false;
80
92
  }
@@ -105,14 +117,16 @@ function parseTextToJson(text) {
105
117
  .replace(/'/g, '"')
106
118
  .replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": ')
107
119
  // stringify any require calls to avoid JSON parsing errors, turn them into just the string value being required
108
- .replace(/require\(['"]([^'"]+)['"]\)/g, '"$1"'));
120
+ .replace(/require\(['"]([^'"]+)['"]\)/g, '"$1"')
121
+ .replace(/\(?await import\(['"]([^'"]+)['"]\)\)?/g, '"$1"'));
109
122
  }
110
123
  /**
111
124
  * Finds an override matching the lookup function and applies the update function to it
112
125
  */
113
126
  function replaceOverride(content, root, lookup, update) {
114
127
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
115
- const exportsArray = findAllBlocks(source);
128
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
129
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
116
130
  if (!exportsArray) {
117
131
  return content;
118
132
  }
@@ -145,19 +159,17 @@ function replaceOverride(content, root, lookup, update) {
145
159
  let updatedData = update(data);
146
160
  if (updatedData) {
147
161
  updatedData = mapFilePaths(updatedData);
162
+ const parserReplacement = format === 'mjs'
163
+ ? (parser) => `(await import('${parser}'))`
164
+ : (parser) => `require('${parser}')`;
148
165
  changes.push({
149
166
  type: devkit_1.ChangeType.Insert,
150
167
  index: start,
151
- // NOTE: Indentation added to format without formatting tools like Prettier.
152
168
  text: ' ' +
153
169
  JSON.stringify(updatedData, null, 2)
154
- // restore any parser require calls that were stripped during JSON parsing
155
- .replace(/"parser": "([^"]+)"/g, (_, parser) => {
156
- return `"parser": require('${parser}')`;
157
- })
158
- .slice(2, -2) // remove curly braces and start/end line breaks since we are injecting just properties
159
- // Append indentation so file is formatted without Prettier
160
- .replaceAll(/\n/g, '\n '),
170
+ .replace(/"parser": "([^"]+)"/g, (_, parser) => `"parser": ${parserReplacement(parser)}`)
171
+ .slice(2, -2) // Remove curly braces and start/end line breaks
172
+ .replaceAll(/\n/g, '\n '), // Maintain indentation
161
173
  });
162
174
  }
163
175
  }
@@ -166,11 +178,89 @@ function replaceOverride(content, root, lookup, update) {
166
178
  return (0, devkit_1.applyChangesToString)(content, changes);
167
179
  }
168
180
  /**
169
- * Adding require statement to the top of the file
181
+ * Adding import statement to the top of the file
182
+ * The imports are added based on a few rules:
183
+ * 1. If it's a default import and matches the variable, return content unchanged.
184
+ * 2. If it's a named import and the variables are not part of the import object, add them.
185
+ * 3. If no existing import and variable is a string, add a default import.
186
+ * 4. If no existing import and variable is an array, add it as an object import.
170
187
  */
171
188
  function addImportToFlatConfig(content, variable, imp) {
172
189
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
173
190
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
191
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
192
+ if (format === 'mjs') {
193
+ return addESMImportToFlatConfig(source, printer, content, variable, imp);
194
+ }
195
+ return addCJSImportToFlatConfig(source, printer, content, variable, imp);
196
+ }
197
+ function addESMImportToFlatConfig(source, printer, content, variable, imp) {
198
+ let existingImport;
199
+ ts.forEachChild(source, (node) => {
200
+ if (ts.isImportDeclaration(node) &&
201
+ ts.isStringLiteral(node.moduleSpecifier) &&
202
+ node.moduleSpecifier.text === imp) {
203
+ existingImport = node;
204
+ }
205
+ });
206
+ // Rule 1:
207
+ if (existingImport &&
208
+ typeof variable === 'string' &&
209
+ existingImport.importClause?.name?.getText() === variable) {
210
+ return content;
211
+ }
212
+ // Rule 2:
213
+ if (existingImport &&
214
+ existingImport.importClause?.namedBindings &&
215
+ Array.isArray(variable)) {
216
+ const namedImports = existingImport.importClause
217
+ .namedBindings;
218
+ const existingElements = namedImports.elements;
219
+ // Filter out variables that are already imported
220
+ const newVariables = variable.filter((v) => !existingElements.some((e) => e.name.getText() === v));
221
+ if (newVariables.length === 0) {
222
+ return content;
223
+ }
224
+ const newImportSpecifiers = newVariables.map((v) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(v)));
225
+ const lastElement = existingElements[existingElements.length - 1];
226
+ const insertIndex = lastElement
227
+ ? lastElement.getEnd()
228
+ : namedImports.getEnd();
229
+ const insertText = printer.printList(ts.ListFormat.NamedImportsOrExportsElements, ts.factory.createNodeArray(newImportSpecifiers), source);
230
+ return (0, devkit_1.applyChangesToString)(content, [
231
+ {
232
+ type: devkit_1.ChangeType.Insert,
233
+ index: insertIndex,
234
+ text: `, ${insertText}`,
235
+ },
236
+ ]);
237
+ }
238
+ // Rule 3:
239
+ if (!existingImport && typeof variable === 'string') {
240
+ const defaultImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, ts.factory.createIdentifier(variable), undefined), ts.factory.createStringLiteral(imp));
241
+ const insert = printer.printNode(ts.EmitHint.Unspecified, defaultImport, source);
242
+ return (0, devkit_1.applyChangesToString)(content, [
243
+ {
244
+ type: devkit_1.ChangeType.Insert,
245
+ index: 0,
246
+ text: `${insert}\n`,
247
+ },
248
+ ]);
249
+ }
250
+ // Rule 4:
251
+ if (!existingImport && Array.isArray(variable)) {
252
+ 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));
253
+ const insert = printer.printNode(ts.EmitHint.Unspecified, objectImport, source);
254
+ return (0, devkit_1.applyChangesToString)(content, [
255
+ {
256
+ type: devkit_1.ChangeType.Insert,
257
+ index: 0,
258
+ text: `${insert}\n`,
259
+ },
260
+ ]);
261
+ }
262
+ }
263
+ function addCJSImportToFlatConfig(source, printer, content, variable, imp) {
174
264
  const foundBindingVars = ts.forEachChild(source, function analyze(node) {
175
265
  // we can only combine object binding patterns
176
266
  if (!Array.isArray(variable)) {
@@ -239,11 +329,48 @@ function addImportToFlatConfig(content, variable, imp) {
239
329
  },
240
330
  ]);
241
331
  }
332
+ function existsAsNamedOrDefaultImport(node, variable) {
333
+ const isNamed = node.importClause.namedBindings &&
334
+ ts.isNamedImports(node.importClause.namedBindings);
335
+ if (Array.isArray(variable)) {
336
+ return isNamed || variable.includes(node.importClause?.name?.getText());
337
+ }
338
+ return ((node.importClause.namedBindings &&
339
+ ts.isNamedImports(node.importClause.namedBindings)) ||
340
+ node.importClause?.name?.getText() === variable);
341
+ }
242
342
  /**
243
343
  * Remove an import from flat config
244
344
  */
245
345
  function removeImportFromFlatConfig(content, variable, imp) {
246
346
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
347
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
348
+ if (format === 'mjs') {
349
+ return removeImportFromFlatConfigESM(source, content, variable, imp);
350
+ }
351
+ else {
352
+ return removeImportFromFlatConfigCJS(source, content, variable, imp);
353
+ }
354
+ }
355
+ function removeImportFromFlatConfigESM(source, content, variable, imp) {
356
+ const changes = [];
357
+ ts.forEachChild(source, (node) => {
358
+ // we can only combine object binding patterns
359
+ if (ts.isImportDeclaration(node) &&
360
+ ts.isStringLiteral(node.moduleSpecifier) &&
361
+ node.moduleSpecifier.text === imp &&
362
+ node.importClause &&
363
+ existsAsNamedOrDefaultImport(node, variable)) {
364
+ changes.push({
365
+ type: devkit_1.ChangeType.Delete,
366
+ start: node.pos,
367
+ length: node.end - node.pos,
368
+ });
369
+ }
370
+ });
371
+ return (0, devkit_1.applyChangesToString)(content, changes);
372
+ }
373
+ function removeImportFromFlatConfigCJS(source, content, variable, imp) {
247
374
  const changes = [];
248
375
  ts.forEachChild(source, (node) => {
249
376
  // we can only combine object binding patterns
@@ -266,13 +393,44 @@ function removeImportFromFlatConfig(content, variable, imp) {
266
393
  return (0, devkit_1.applyChangesToString)(content, changes);
267
394
  }
268
395
  /**
269
- * Injects new ts.expression to the end of the module.exports array.
396
+ * Injects new ts.expression to the end of the module.exports or export default array.
270
397
  */
271
398
  function addBlockToFlatConfigExport(content, config, options = {
272
399
  insertAtTheEnd: true,
273
400
  }) {
274
401
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
275
402
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
403
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
404
+ // find the export default array statement
405
+ if (format === 'mjs') {
406
+ return addBlockToFlatConfigExportESM(content, config, source, printer, options);
407
+ }
408
+ else {
409
+ return addBlockToFlatConfigExportCJS(content, config, source, printer, options);
410
+ }
411
+ }
412
+ function addBlockToFlatConfigExportESM(content, config, source, printer, options = {
413
+ insertAtTheEnd: true,
414
+ }) {
415
+ const exportDefaultStatement = source.statements.find((statement) => ts.isExportAssignment(statement) &&
416
+ ts.isArrayLiteralExpression(statement.expression));
417
+ if (!exportDefaultStatement)
418
+ return content;
419
+ const exportArrayLiteral = exportDefaultStatement.expression;
420
+ const updatedArrayElements = options.insertAtTheEnd
421
+ ? [...exportArrayLiteral.elements, config]
422
+ : [config, ...exportArrayLiteral.elements];
423
+ const updatedExportDefault = ts.factory.createExportAssignment(undefined, false, ts.factory.createArrayLiteralExpression(updatedArrayElements, true));
424
+ // update the existing export default array
425
+ const updatedStatements = source.statements.map((statement) => statement === exportDefaultStatement ? updatedExportDefault : statement);
426
+ const updatedSource = ts.factory.updateSourceFile(source, updatedStatements);
427
+ return printer
428
+ .printFile(updatedSource)
429
+ .replace(/export default/, '\nexport default');
430
+ }
431
+ function addBlockToFlatConfigExportCJS(content, config, source, printer, options = {
432
+ insertAtTheEnd: true,
433
+ }) {
276
434
  const exportsArray = ts.forEachChild(source, function analyze(node) {
277
435
  if (ts.isExpressionStatement(node) &&
278
436
  ts.isBinaryExpression(node.expression) &&
@@ -315,28 +473,47 @@ function addBlockToFlatConfigExport(content, config, options = {
315
473
  }
316
474
  function removePlugin(content, pluginName, pluginImport) {
317
475
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
476
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
318
477
  const changes = [];
478
+ if (format === 'mjs') {
479
+ ts.forEachChild(source, function analyze(node) {
480
+ if (ts.isImportDeclaration(node) &&
481
+ ts.isStringLiteral(node.moduleSpecifier) &&
482
+ node.moduleSpecifier.text === pluginImport) {
483
+ const importClause = node.importClause;
484
+ if ((importClause && importClause.name) ||
485
+ (importClause.namedBindings &&
486
+ ts.isNamedImports(importClause.namedBindings))) {
487
+ changes.push({
488
+ type: devkit_1.ChangeType.Delete,
489
+ start: node.pos,
490
+ length: node.end - node.pos,
491
+ });
492
+ }
493
+ }
494
+ });
495
+ }
496
+ else {
497
+ ts.forEachChild(source, function analyze(node) {
498
+ if (ts.isVariableStatement(node) &&
499
+ ts.isVariableDeclaration(node.declarationList.declarations[0]) &&
500
+ ts.isCallExpression(node.declarationList.declarations[0].initializer) &&
501
+ node.declarationList.declarations[0].initializer.arguments.length &&
502
+ ts.isStringLiteral(node.declarationList.declarations[0].initializer.arguments[0]) &&
503
+ node.declarationList.declarations[0].initializer.arguments[0].text ===
504
+ pluginImport) {
505
+ changes.push({
506
+ type: devkit_1.ChangeType.Delete,
507
+ start: node.pos,
508
+ length: node.end - node.pos,
509
+ });
510
+ }
511
+ });
512
+ }
319
513
  ts.forEachChild(source, function analyze(node) {
320
- if (ts.isVariableStatement(node) &&
321
- ts.isVariableDeclaration(node.declarationList.declarations[0]) &&
322
- ts.isCallExpression(node.declarationList.declarations[0].initializer) &&
323
- node.declarationList.declarations[0].initializer.arguments.length &&
324
- ts.isStringLiteral(node.declarationList.declarations[0].initializer.arguments[0]) &&
325
- node.declarationList.declarations[0].initializer.arguments[0].text ===
326
- pluginImport) {
327
- changes.push({
328
- type: devkit_1.ChangeType.Delete,
329
- start: node.pos,
330
- length: node.end - node.pos,
331
- });
332
- }
333
- });
334
- ts.forEachChild(source, function analyze(node) {
335
- if (ts.isExpressionStatement(node) &&
336
- ts.isBinaryExpression(node.expression) &&
337
- node.expression.left.getText() === 'module.exports' &&
338
- ts.isArrayLiteralExpression(node.expression.right)) {
339
- const blockElements = node.expression.right.elements;
514
+ if (ts.isExportAssignment(node) &&
515
+ ts.isArrayLiteralExpression(node.expression)) {
516
+ const blockElements = node.expression.elements;
340
517
  blockElements.forEach((element) => {
341
518
  if (ts.isObjectLiteralExpression(element)) {
342
519
  const pluginsElem = element.properties.find((prop) => prop.name?.getText() === 'plugins');
@@ -426,7 +603,12 @@ function removePlugin(content, pluginName, pluginImport) {
426
603
  function removeCompatExtends(content, compatExtends) {
427
604
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
428
605
  const changes = [];
429
- findAllBlocks(source)?.forEach((node) => {
606
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
607
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
608
+ if (!exportsArray) {
609
+ return content;
610
+ }
611
+ exportsArray.forEach((node) => {
430
612
  if (ts.isSpreadElement(node) &&
431
613
  ts.isCallExpression(node.expression) &&
432
614
  ts.isArrowFunction(node.expression.arguments[0]) &&
@@ -460,9 +642,14 @@ function removeCompatExtends(content, compatExtends) {
460
642
  }
461
643
  function removePredefinedConfigs(content, moduleImport, moduleVariable, configs) {
462
644
  const source = ts.createSourceFile('', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
645
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
463
646
  const changes = [];
464
647
  let removeImport = true;
465
- findAllBlocks(source)?.forEach((node) => {
648
+ const exportsArray = format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
649
+ if (!exportsArray) {
650
+ return content;
651
+ }
652
+ exportsArray.forEach((node) => {
466
653
  if (ts.isSpreadElement(node) &&
467
654
  ts.isElementAccessExpression(node.expression) &&
468
655
  ts.isPropertyAccessExpression(node.expression.expression) &&
@@ -507,14 +694,22 @@ function addPluginsToExportsBlock(content, plugins) {
507
694
  * Adds compat if missing to flat config
508
695
  */
509
696
  function addFlatCompatToFlatConfig(content) {
510
- let result = content;
511
- result = addImportToFlatConfig(result, 'js', '@eslint/js');
697
+ const result = addImportToFlatConfig(content, 'js', '@eslint/js');
698
+ const format = content.includes('export default') ? 'mjs' : 'cjs';
512
699
  if (result.includes('const compat = new FlatCompat')) {
513
700
  return result;
514
701
  }
515
- result = addImportToFlatConfig(result, ['FlatCompat'], '@eslint/eslintrc');
516
- const index = result.indexOf('module.exports');
517
- return (0, devkit_1.applyChangesToString)(result, [
702
+ if (format === 'mjs') {
703
+ return addFlatCompatToFlatConfigESM(result);
704
+ }
705
+ else {
706
+ return addFlatCompatToFlatConfigCJS(result);
707
+ }
708
+ }
709
+ function addFlatCompatToFlatConfigCJS(content) {
710
+ content = addImportToFlatConfig(content, ['FlatCompat'], '@eslint/eslintrc');
711
+ const index = content.indexOf('module.exports');
712
+ return (0, devkit_1.applyChangesToString)(content, [
518
713
  {
519
714
  type: devkit_1.ChangeType.Insert,
520
715
  index: index - 1,
@@ -527,25 +722,62 @@ const compat = new FlatCompat({
527
722
  },
528
723
  ]);
529
724
  }
725
+ function addFlatCompatToFlatConfigESM(content) {
726
+ const importsToAdd = [
727
+ { variable: 'js', module: '@eslint/js' },
728
+ { variable: ['fileURLToPath'], module: 'url' },
729
+ { variable: ['dirname'], module: 'path' },
730
+ { variable: ['FlatCompat'], module: '@eslint/eslintrc' },
731
+ ];
732
+ for (const { variable, module } of importsToAdd) {
733
+ content = addImportToFlatConfig(content, variable, module);
734
+ }
735
+ const index = content.indexOf('export default');
736
+ return (0, devkit_1.applyChangesToString)(content, [
737
+ {
738
+ type: devkit_1.ChangeType.Insert,
739
+ index: index - 1,
740
+ text: `
741
+ const compat = new FlatCompat({
742
+ baseDirectory: dirname(fileURLToPath(import.meta.url)),
743
+ recommendedConfig: js.configs.recommended,
744
+ });\n
745
+ `,
746
+ },
747
+ ]);
748
+ }
530
749
  /**
531
750
  * Generate node list representing the imports and the exports blocks
532
751
  * Optionally add flat compat initialization
533
752
  */
534
- function createNodeList(importsMap, exportElements) {
753
+ function createNodeList(importsMap, exportElements, format) {
535
754
  const importsList = [];
536
- // generateRequire(varName, imp, ts.factory);
537
755
  Array.from(importsMap.entries()).forEach(([imp, varName]) => {
538
- importsList.push(generateRequire(varName, imp));
756
+ if (format === 'mjs') {
757
+ importsList.push(generateESMImport(varName, imp));
758
+ }
759
+ else {
760
+ importsList.push(generateRequire(varName, imp));
761
+ }
539
762
  });
763
+ const exports = format === 'mjs'
764
+ ? generateESMExport(exportElements)
765
+ : generateCJSExport(exportElements);
540
766
  return ts.factory.createNodeArray([
541
767
  // add plugin imports
542
768
  ...importsList,
543
769
  ts.createSourceFile('', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.JS),
544
- // creates:
545
- // module.exports = [ ... ];
546
- 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))),
770
+ exports,
547
771
  ]);
548
772
  }
773
+ function generateESMExport(elements) {
774
+ // creates: export default = [...]
775
+ return ts.factory.createExportAssignment(undefined, false, ts.factory.createArrayLiteralExpression(elements, true));
776
+ }
777
+ function generateCJSExport(elements) {
778
+ // creates: module.exports = [...]
779
+ 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)));
780
+ }
549
781
  function generateSpreadElement(name) {
550
782
  return ts.factory.createSpreadElement(ts.factory.createIdentifier(name));
551
783
  }
@@ -563,12 +795,17 @@ function generatePluginExtendsElementWithCompatFixup(plugin) {
563
795
  function stringifyNodeList(nodes) {
564
796
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
565
797
  const resultFile = ts.createSourceFile('', '', ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
566
- return (printer
798
+ const result = printer
567
799
  .printList(ts.ListFormat.MultiLine, nodes, resultFile)
568
800
  // add new line before compat initialization
569
- .replace(/const compat = new FlatCompat/, '\nconst compat = new FlatCompat')
570
- // add new line before module.exports = ...
571
- .replace(/module\.exports/, '\nmodule.exports'));
801
+ .replace(/const compat = new FlatCompat/, '\nconst compat = new FlatCompat');
802
+ if (result.includes('export default')) {
803
+ return result // add new line before export default = ...
804
+ .replace(/export default/, '\nexport default');
805
+ }
806
+ else {
807
+ return result.replace(/module.exports/, '\nmodule.exports');
808
+ }
572
809
  }
573
810
  /**
574
811
  * generates AST require statement
@@ -578,6 +815,26 @@ function generateRequire(variableName, imp) {
578
815
  ts.factory.createVariableDeclaration(variableName, undefined, undefined, ts.factory.createCallExpression(ts.factory.createIdentifier('require'), undefined, [ts.factory.createStringLiteral(imp)])),
579
816
  ], ts.NodeFlags.Const));
580
817
  }
818
+ // Top level imports
819
+ function generateESMImport(variableName, imp) {
820
+ let importClause;
821
+ if (typeof variableName === 'string') {
822
+ // For single variable import e.g import foo from 'module';
823
+ importClause = ts.factory.createImportClause(false, ts.factory.createIdentifier(variableName), undefined);
824
+ }
825
+ else {
826
+ // For object binding pattern import e.g import { a, b, c } from 'module';
827
+ importClause = ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports(variableName.elements.map((element) => {
828
+ const propertyName = element.propertyName
829
+ ? ts.isIdentifier(element.propertyName)
830
+ ? element.propertyName
831
+ : ts.factory.createIdentifier(element.propertyName.getText())
832
+ : undefined;
833
+ return ts.factory.createImportSpecifier(false, propertyName, element.name);
834
+ })));
835
+ }
836
+ return ts.factory.createImportDeclaration(undefined, importClause, ts.factory.createStringLiteral(imp));
837
+ }
581
838
  /**
582
839
  * FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L222
583
840
  *
@@ -603,7 +860,7 @@ function overrideNeedsCompat(override) {
603
860
  * Generates an AST object or spread element representing a modern flat config entry,
604
861
  * based on a given legacy eslintrc JSON override object
605
862
  */
606
- function generateFlatOverride(_override) {
863
+ function generateFlatOverride(_override, format) {
607
864
  const override = mapFilePaths(_override);
608
865
  // We do not need the compat tooling for this override
609
866
  if (!overrideNeedsCompat(override)) {
@@ -659,12 +916,10 @@ function generateFlatOverride(_override) {
659
916
  return propertyAssignment;
660
917
  }
661
918
  else {
662
- // Change parser to require statement.
663
- return ts.factory.createPropertyAssignment('parser', ts.factory.createCallExpression(ts.factory.createIdentifier('require'), undefined, [
664
- ts.factory.createStringLiteral(override['languageOptions']?.['parserOptions']?.parser ??
665
- override['languageOptions']?.parser ??
666
- override.parser),
667
- ]));
919
+ // Change parser to import statement.
920
+ return format === 'mjs'
921
+ ? generateESMParserImport(override)
922
+ : generateCJSParserImport(override);
668
923
  }
669
924
  },
670
925
  });
@@ -719,6 +974,20 @@ function generateFlatOverride(_override) {
719
974
  ], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createParenthesizedExpression(ts.factory.createObjectLiteralExpression(objectLiteralElements, true))),
720
975
  ]));
721
976
  }
977
+ function generateESMParserImport(override) {
978
+ return ts.factory.createPropertyAssignment('parser', ts.factory.createAwaitExpression(ts.factory.createCallExpression(ts.factory.createIdentifier('import'), undefined, [
979
+ ts.factory.createStringLiteral(override['languageOptions']?.['parserOptions']?.parser ??
980
+ override['languageOptions']?.parser ??
981
+ override.parser),
982
+ ])));
983
+ }
984
+ function generateCJSParserImport(override) {
985
+ return ts.factory.createPropertyAssignment('parser', ts.factory.createCallExpression(ts.factory.createIdentifier('require'), undefined, [
986
+ ts.factory.createStringLiteral(override['languageOptions']?.['parserOptions']?.parser ??
987
+ override['languageOptions']?.parser ??
988
+ override.parser),
989
+ ]));
990
+ }
722
991
  function generateFlatPredefinedConfig(predefinedConfigName, moduleName = 'nx', spread = true) {
723
992
  const node = ts.factory.createElementAccessExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(moduleName), ts.factory.createIdentifier('configs')), ts.factory.createStringLiteral(predefinedConfigName));
724
993
  return spread ? ts.factory.createSpreadElement(node) : node;
@@ -230,7 +230,7 @@ function getRootForDirectory(directory, roots) {
230
230
  function getProjectUsingESLintConfig(configFilePath, projectRoot, eslintVersion, options, context) {
231
231
  const rootEslintConfig = [
232
232
  config_file_1.baseEsLintConfigFile,
233
- config_file_1.baseEsLintFlatConfigFile,
233
+ ...config_file_1.BASE_ESLINT_CONFIG_FILENAMES,
234
234
  ...config_file_1.ESLINT_CONFIG_FILENAMES,
235
235
  ].find((f) => (0, node_fs_1.existsSync)((0, posix_1.join)(context.workspaceRoot, f)));
236
236
  // Add a lint target for each child project without an eslint config, with the root level config as an input
@@ -1,8 +1,9 @@
1
1
  export declare const ESLINT_FLAT_CONFIG_FILENAMES: string[];
2
2
  export declare const ESLINT_OLD_CONFIG_FILENAMES: string[];
3
3
  export declare const ESLINT_CONFIG_FILENAMES: string[];
4
+ export declare const BASE_ESLINT_CONFIG_FILENAMES: string[];
4
5
  export declare const baseEsLintConfigFile = ".eslintrc.base.json";
5
- export declare const baseEsLintFlatConfigFile = "eslint.base.config.cjs";
6
+ export declare const baseEsLintFlatConfigFile = "eslint.base.config.mjs";
6
7
  export declare const legacyBaseEsLintFlatConfigFile = "eslint.base.config.js";
7
8
  export declare function isFlatConfig(configFilePath: string): boolean;
8
9
  export declare function findFlatConfigFile(directory: string, workspaceRoot: string): string | null;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.legacyBaseEsLintFlatConfigFile = exports.baseEsLintFlatConfigFile = exports.baseEsLintConfigFile = exports.ESLINT_CONFIG_FILENAMES = exports.ESLINT_OLD_CONFIG_FILENAMES = exports.ESLINT_FLAT_CONFIG_FILENAMES = void 0;
3
+ exports.legacyBaseEsLintFlatConfigFile = exports.baseEsLintFlatConfigFile = exports.baseEsLintConfigFile = exports.BASE_ESLINT_CONFIG_FILENAMES = exports.ESLINT_CONFIG_FILENAMES = exports.ESLINT_OLD_CONFIG_FILENAMES = exports.ESLINT_FLAT_CONFIG_FILENAMES = void 0;
4
4
  exports.isFlatConfig = isFlatConfig;
5
5
  exports.findFlatConfigFile = findFlatConfigFile;
6
6
  exports.findOldConfigFile = findOldConfigFile;
@@ -20,8 +20,9 @@ exports.ESLINT_CONFIG_FILENAMES = [
20
20
  ...exports.ESLINT_OLD_CONFIG_FILENAMES,
21
21
  ...exports.ESLINT_FLAT_CONFIG_FILENAMES,
22
22
  ];
23
+ exports.BASE_ESLINT_CONFIG_FILENAMES = flat_config_1.baseEslintConfigFilenames;
23
24
  exports.baseEsLintConfigFile = '.eslintrc.base.json';
24
- exports.baseEsLintFlatConfigFile = 'eslint.base.config.cjs';
25
+ exports.baseEsLintFlatConfigFile = 'eslint.base.config.mjs';
25
26
  // Make sure we can handle previous file extension as well for migrations or custom generators.
26
27
  exports.legacyBaseEsLintFlatConfigFile = 'eslint.base.config.js';
27
28
  function isFlatConfig(configFilePath) {
@@ -1,4 +1,5 @@
1
1
  import { Tree } from '@nx/devkit';
2
2
  export declare const eslintFlatConfigFilenames: string[];
3
+ export declare const baseEslintConfigFilenames: string[];
3
4
  export declare function getRootESLintFlatConfigFilename(tree: Tree): string;
4
5
  export declare function useFlatConfig(tree?: Tree): boolean;
@@ -1,13 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.eslintFlatConfigFilenames = void 0;
3
+ exports.baseEslintConfigFilenames = exports.eslintFlatConfigFilenames = void 0;
4
4
  exports.getRootESLintFlatConfigFilename = getRootESLintFlatConfigFilename;
5
5
  exports.useFlatConfig = useFlatConfig;
6
6
  const semver_1 = require("semver");
7
- // todo: add support for eslint.config.mjs,
8
7
  exports.eslintFlatConfigFilenames = [
9
8
  'eslint.config.cjs',
10
9
  'eslint.config.js',
10
+ 'eslint.config.mjs',
11
+ ];
12
+ exports.baseEslintConfigFilenames = [
13
+ 'eslint.base.js',
14
+ 'eslint.base.config.cjs',
15
+ 'eslint.base.config.js',
16
+ 'eslint.base.config.mjs',
11
17
  ];
12
18
  function getRootESLintFlatConfigFilename(tree) {
13
19
  for (const file of exports.eslintFlatConfigFilenames) {
@@ -3,6 +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.13.0";
6
+ export declare const eslint9__typescriptESLintVersion = "^8.19.0";
7
7
  export declare const eslint9__eslintVersion = "^9.8.0";
8
8
  export declare const eslintCompat = "^1.1.1";
@@ -7,6 +7,6 @@ exports.eslintrcVersion = '^2.1.1';
7
7
  exports.eslintConfigPrettierVersion = '^9.0.0';
8
8
  exports.typescriptESLintVersion = '^7.16.0';
9
9
  // Updated linting stack for ESLint v9, typescript-eslint v8
10
- exports.eslint9__typescriptESLintVersion = '^8.13.0';
10
+ exports.eslint9__typescriptESLintVersion = '^8.19.0';
11
11
  exports.eslint9__eslintVersion = '^9.8.0';
12
12
  exports.eslintCompat = '^1.1.1';