@atlaskit/eslint-plugin-design-system 10.18.0 → 10.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { isNodeOfType } from 'eslint-codemod-utils';
2
- import { addToListOfRanges, canAutoMigrateNewIconBasedOnSize, canMigrateColor, checkIfNewIconExist, createAutoMigrationError, createCantFindSuitableReplacementError, createCantMigrateColorError, createCantMigrateFunctionUnknownError, createCantMigrateIdentifierError, createCantMigrateIdentifierMapOrArrayError, createCantMigrateReExportError, createCantMigrateSizeUnknown, createCantMigrateSpreadPropsError, createGuidance, createHelpers, createImportFix, createPropFixes, getMigrationMapObject, getUpcomingIcons, isInRangeList, isInsideLegacyButton, isInsideNewButton, isSize, locToString } from './helpers';
2
+ import { addToListOfRanges, canAutoMigrateNewIconBasedOnSize, canMigrateColor, createAutoMigrationError, createCantFindSuitableReplacementError, createCantMigrateColorError, createCantMigrateFunctionUnknownError, createCantMigrateIdentifierError, createCantMigrateIdentifierMapOrArrayError, createCantMigrateReExportError, createCantMigrateSizeUnknown, createCantMigrateSpreadPropsError, createGuidance, createHelpers, getMigrationMapObject, getUpcomingIcons, isInsideLegacyButton, isInsideNewButton, isSize, locToString, throwAutoErrors, throwManualErrors } from './helpers';
3
3
  export const createChecks = context => {
4
4
  //create global variables to be shared by the checks
5
5
  const {
@@ -13,7 +13,6 @@ export const createChecks = context => {
13
13
  const errorsManual = {};
14
14
  const errorsAuto = {};
15
15
  let guidance = {};
16
- let autoIconJSXElementOccurrenceCount = 0;
17
16
 
18
17
  // Extract parameters
19
18
  const shouldErrorForManualMigration = getConfigFlag('shouldErrorForManualMigration', true);
@@ -38,7 +37,8 @@ export const createChecks = context => {
38
37
  legacyIconImports[spec.local.name] = {
39
38
  packageName: moduleSource,
40
39
  exported: false,
41
- importNode: node
40
+ importNode: node,
41
+ importSpecifier: spec.local.name
42
42
  };
43
43
  }
44
44
  }
@@ -51,7 +51,8 @@ export const createChecks = context => {
51
51
  migrationIconImports[spec.local.name] = {
52
52
  packageName: moduleSource,
53
53
  exported: false,
54
- importNode: node
54
+ importNode: node,
55
+ importSpecifier: spec.local.name
55
56
  };
56
57
  }
57
58
  });
@@ -90,7 +91,9 @@ export const createChecks = context => {
90
91
  if (Object.keys(legacyIconImports).includes(decl.init.name)) {
91
92
  legacyIconImports[decl.id.name] = {
92
93
  packageName: legacyIconImports[decl.init.name].packageName,
93
- exported: legacyIconImports[decl.init.name].exported || isExported
94
+ exported: legacyIconImports[decl.init.name].exported || isExported,
95
+ importNode: legacyIconImports[decl.init.name].importNode,
96
+ importSpecifier: legacyIconImports[decl.init.name].importSpecifier
94
97
  };
95
98
  } else if (newButtonImports.has(decl.init.name)) {
96
99
  newButtonImports.add(decl.id.name);
@@ -129,7 +132,7 @@ export const createChecks = context => {
129
132
  * @param node The named export node found by ESLint
130
133
  */
131
134
  const checkExportNamedVariables = node => {
132
- // export {default as AddIcon} from '@atlaskit/icon/glyph/add';
135
+ // Case: export {default as AddIcon} from '@atlaskit/icon/glyph/add';
133
136
  if (node.source && isNodeOfType(node.source, 'Literal') && Object.keys(node.source).includes('value')) {
134
137
  const moduleSource = node.source.value;
135
138
  if (typeof moduleSource === 'string' && ['@atlaskit/icon/glyph/', '@atlaskit/icon-object/glyph/'].find(val => moduleSource.startsWith(val)) && node.specifiers.length) {
@@ -143,7 +146,7 @@ export const createChecks = context => {
143
146
  }
144
147
  }
145
148
  } else if (node.declaration && isNodeOfType(node.declaration, 'VariableDeclaration')) {
146
- // export const Icon = AddIcon;
149
+ // Case: export const Icon = AddIcon;
147
150
  for (const decl of node.declaration.declarations) {
148
151
  if (isNodeOfType(decl, 'VariableDeclarator') && Object.keys(decl).includes('init') && decl.init && isNodeOfType(decl.init, 'Identifier') && Object.keys(legacyIconImports).includes(decl.init.name)) {
149
152
  createCantMigrateReExportError(node, legacyIconImports[decl.init.name].packageName, decl.init.name, errorsManual);
@@ -166,7 +169,9 @@ export const createChecks = context => {
166
169
  //update legacy imports to be exported
167
170
  legacyIconImports[spec.local.name] = {
168
171
  packageName: legacyIconImports[spec.local.name].packageName,
169
- exported: true
172
+ exported: true,
173
+ importNode: legacyIconImports[spec.local.name].importNode,
174
+ importSpecifier: legacyIconImports[spec.local.name].importSpecifier
170
175
  };
171
176
  createCantMigrateReExportError(spec, legacyIconImports[spec.local.name].packageName, spec.exported.name, errorsManual);
172
177
  addToListOfRanges(spec, errorRanges);
@@ -399,7 +404,6 @@ export const createChecks = context => {
399
404
  const sizeProp = node.openingElement.attributes.find(attribute => attribute.type === 'JSXAttribute' && (attribute.name.name === 'size' || attribute.name.name === 'LEGACY_size'));
400
405
  const shouldAddSpaciousSpacing = (sizeProp && sizeProp.type === 'JSXAttribute' && ((_sizeProp$value2 = sizeProp.value) === null || _sizeProp$value2 === void 0 ? void 0 : _sizeProp$value2.type) === 'Literal' && sizeProp.value.value === 'medium' || !sizeProp) && !insideNewButton && !insideLegacyButton;
401
406
  if (!hasManualMigration && (newIcon || upcomingIcon) && isNewIconMigratable) {
402
- autoIconJSXElementOccurrenceCount++;
403
407
  createAutoMigrationError({
404
408
  node,
405
409
  importSource: legacyIconImports[name].packageName,
@@ -444,108 +448,26 @@ export const createChecks = context => {
444
448
  * Throws the relevant errors in the correct order based on configs.
445
449
  */
446
450
  const throwErrors = () => {
451
+ // Throw manual errors
447
452
  if (shouldErrorForManualMigration) {
448
- for (const [key, errorList] of Object.entries(errorsManual)) {
449
- const node = 'node' in errorList.errors[0] ? errorList.errors[0].node : null;
450
- const cantMigrateIdentifierError = errorList.errors.find(x => 'messageId' in x && x.messageId === 'cantMigrateIdentifier');
451
- if (node) {
452
- let isInRange = false;
453
- if (cantMigrateIdentifierError && isInRangeList(node, errorRanges)) {
454
- isInRange = true;
455
- }
456
- if (isInRange && errorList.errors.length - 1 > 0 || !isInRange && errorList.errors.length > 0) {
457
- const guidanceMessage = Object.keys(guidance).includes(key) ? guidance[key] : '';
458
- context.report({
459
- node,
460
- messageId: 'noLegacyIconsManualMigration',
461
- data: {
462
- iconName: errorList.iconName,
463
- importSource: errorList.importSource,
464
- guidance: isQuietMode ? guidanceMessage : `${guidanceMessage}For more information see the below errors.\n`
465
- }
466
- });
467
- if (!isQuietMode) {
468
- for (const error of errorList.errors) {
469
- if ('messageId' in error && (error.messageId !== 'cantMigrateIdentifier' || error.messageId === 'cantMigrateIdentifier' && !isInRange)) {
470
- context.report(error);
471
- }
472
- }
473
- }
474
- }
475
- }
476
- }
453
+ throwManualErrors({
454
+ errorsManual,
455
+ errorRanges,
456
+ guidance,
457
+ context,
458
+ isQuietMode
459
+ });
477
460
  }
478
461
  if (shouldErrorForAutoMigration) {
479
- for (const [key, error] of Object.entries(errorsAuto)) {
480
- // If there's a manual error that exists for this same component,
481
- // don't throw the auto error
482
- if (Object.keys(errorsManual).includes(key)) {
483
- const cantMigrateIdentifierError = errorsManual[key].errors.find(x => 'messageId' in x && x.messageId === 'cantMigrateIdentifier');
484
- if (!cantMigrateIdentifierError || cantMigrateIdentifierError && errorsManual[key].errors.length > 1) {
485
- delete errorsAuto[key];
486
- continue;
487
- }
488
- }
489
- const node = 'node' in error ? error.node : null;
490
- if (node) {
491
- const guidanceMessage = Object.keys(guidance).includes(key) ? guidance[key] : '';
492
- if (Object.keys(error).includes('data') && error.data) {
493
- error.data.guidance = guidanceMessage;
494
- }
495
- context.report({
496
- ...error,
497
- fix: fixer => {
498
- var _legacyIconImports$er, _migrationIconImports;
499
- // don't migration if the new icon is not available
500
- if (!error.data || shouldUseMigrationPath && !checkIfNewIconExist(error)) {
501
- return [];
502
- }
503
- const fixArguments = {
504
- metadata: error.data,
505
- legacyImportNode: (_legacyIconImports$er = legacyIconImports[error.data.iconName]) === null || _legacyIconImports$er === void 0 ? void 0 : _legacyIconImports$er.importNode,
506
- migrationImportNode: (_migrationIconImports = migrationIconImports[error.data.iconName]) === null || _migrationIconImports === void 0 ? void 0 : _migrationIconImports.importNode,
507
- shouldUseMigrationPath
508
- };
509
- const propsFixes = createPropFixes({
510
- ...fixArguments,
511
- node,
512
- fixer
513
- });
514
- let importFixes = [];
515
- // Otherwise if there are multiple occurrences of the icon, import path will be handled after the prop fix
516
- if (autoIconJSXElementOccurrenceCount <= 1) {
517
- importFixes = createImportFix({
518
- ...fixArguments,
519
- fixer
520
- });
521
- }
522
- return [...propsFixes, ...importFixes];
523
- }
524
- });
525
- }
526
- }
527
-
528
- // Update import path at the end if there are multiple occurrences of the icon
529
- if (autoIconJSXElementOccurrenceCount > 1) {
530
- for (const [_, error] of Object.entries(errorsAuto)) {
531
- context.report({
532
- ...error,
533
- fix: fixer => {
534
- var _legacyIconImports$er2, _migrationIconImports2;
535
- if (!error.data || shouldUseMigrationPath && !checkIfNewIconExist(error)) {
536
- return [];
537
- }
538
- return createImportFix({
539
- metadata: error.data,
540
- fixer,
541
- legacyImportNode: (_legacyIconImports$er2 = legacyIconImports[error.data.iconName]) === null || _legacyIconImports$er2 === void 0 ? void 0 : _legacyIconImports$er2.importNode,
542
- migrationImportNode: (_migrationIconImports2 = migrationIconImports[error.data.iconName]) === null || _migrationIconImports2 === void 0 ? void 0 : _migrationIconImports2.importNode,
543
- shouldUseMigrationPath
544
- });
545
- }
546
- });
547
- }
548
- }
462
+ throwAutoErrors({
463
+ errorsManual,
464
+ errorsAuto,
465
+ legacyIconImports,
466
+ guidance,
467
+ migrationIconImports,
468
+ shouldUseMigrationPath,
469
+ context
470
+ });
549
471
  }
550
472
  };
551
473
  return {
@@ -54,7 +54,7 @@ export const canAutoMigrateNewIconBasedOnSize = guidance => {
54
54
  * @param iconPackage string
55
55
  * @returns object of new icon name and import path
56
56
  */
57
- export const getNewIconNameAndImportPath = (iconPackage, shouldUseMigrationPath) => {
57
+ const getNewIconNameAndImportPath = (iconPackage, shouldUseMigrationPath) => {
58
58
  const legacyIconName = getIconKey(iconPackage);
59
59
  const migrationMapObject = getMigrationMapObject(iconPackage);
60
60
  if (!migrationMapObject || !migrationMapObject.newIcon) {
@@ -284,7 +284,7 @@ const pushManualError = (key, errors, myError, importSource, iconName) => {
284
284
  };
285
285
  }
286
286
  };
287
- export const getLiteralStringValue = value => {
287
+ const getLiteralStringValue = value => {
288
288
  if (!value) {
289
289
  return;
290
290
  }
@@ -354,7 +354,7 @@ export const addToListOfRanges = (node, sortedListOfRangesForErrors) => {
354
354
  });
355
355
  }
356
356
  };
357
- export const isInRangeList = (node, sortedListOfRangesForErrors) => {
357
+ const isInRangeList = (node, sortedListOfRangesForErrors) => {
358
358
  const {
359
359
  range
360
360
  } = node;
@@ -395,6 +395,18 @@ export const isInsideLegacyButton = (node, legacyButtonImports) => {
395
395
  return insideLegacyButton;
396
396
  };
397
397
  const findProp = (attributes, propName) => attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.name === propName);
398
+ const getNewIconNameForRenaming = (isInManualArray, importSource, importSpecifier) => {
399
+ let newIconName;
400
+ if (isInManualArray) {
401
+ newIconName = getNewIconNameAndImportPath(importSource).iconName;
402
+ const keyToName = newIconName ? newIconName[0].toUpperCase() + newIconName.slice(1) + 'Icon' : undefined;
403
+ newIconName = keyToName;
404
+ if (newIconName === undefined || importSpecifier === keyToName) {
405
+ newIconName = `${keyToName}New`;
406
+ }
407
+ }
408
+ return newIconName;
409
+ };
398
410
 
399
411
  /**
400
412
  *
@@ -406,25 +418,25 @@ const findProp = (attributes, propName) => attributes.find(attr => attr.type ===
406
418
  * @param migrationImportNode The migration import declaration node to replace, only present if shouldUseMigrationPath is false
407
419
  * @returns A list of fixers to migrate the icon
408
420
  */
409
- export const createImportFix = ({
421
+ const createImportFix = ({
410
422
  fixer,
411
423
  legacyImportNode,
412
424
  metadata,
413
425
  shouldUseMigrationPath,
414
- migrationImportNode
426
+ migrationImportNode,
427
+ newIconName
415
428
  }) => {
416
429
  const fixes = [];
417
430
  const {
418
431
  importSource
419
432
  } = metadata;
420
433
  const importPath = migrationImportNode ? importSource.replace('/migration', '').split('--')[0] : getNewIconNameAndImportPath(importSource, shouldUseMigrationPath).importPath;
421
-
422
- // replace old icon import with icon import
423
- if (legacyImportNode && importPath) {
424
- fixes.push(fixer.replaceText(legacyImportNode.source, `'${literal(importPath)}'`));
425
- }
426
- if (migrationImportNode && !shouldUseMigrationPath && importPath) {
427
- fixes.push(fixer.replaceText(migrationImportNode.source, `'${literal(importPath)}'`));
434
+ const useMigrationPath = legacyImportNode && importPath;
435
+ const useFinalPath = migrationImportNode && !shouldUseMigrationPath && importPath;
436
+ if (useMigrationPath) {
437
+ fixes.push(newIconName ? fixer.insertTextBefore(legacyImportNode, `import ${newIconName} from '${importPath}';\n`) : fixer.replaceText(legacyImportNode.source, `'${literal(importPath)}'`));
438
+ } else if (useFinalPath) {
439
+ fixes.push(newIconName ? fixer.insertTextBefore(migrationImportNode, `import ${newIconName} from '${importPath}';\n`) : fixer.replaceText(migrationImportNode.source, `'${literal(importPath)}'`));
428
440
  }
429
441
  return fixes;
430
442
  };
@@ -439,13 +451,14 @@ export const createImportFix = ({
439
451
  * @param migrationImportNode The migration import declaration node to replace, only present if shouldUseMigrationPath is false
440
452
  * @returns A list of fixers to migrate the icon
441
453
  */
442
- export const createPropFixes = ({
454
+ const createPropFixes = ({
443
455
  node,
444
456
  fixer,
445
457
  legacyImportNode,
446
458
  metadata,
447
459
  shouldUseMigrationPath,
448
- migrationImportNode
460
+ migrationImportNode,
461
+ newIconName
449
462
  }) => {
450
463
  const fixes = [];
451
464
  const {
@@ -462,6 +475,9 @@ export const createPropFixes = ({
462
475
  const {
463
476
  openingElement
464
477
  } = node;
478
+ if (newIconName) {
479
+ fixes.push(fixer.replaceText(openingElement.name, newIconName));
480
+ }
465
481
  const {
466
482
  attributes
467
483
  } = openingElement;
@@ -520,6 +536,8 @@ export const createPropFixes = ({
520
536
  }
521
537
  });
522
538
  }
539
+ } else if (node.type === 'Identifier' && newIconName) {
540
+ fixes.push(fixer.replaceText(node, newIconName));
523
541
  }
524
542
  return fixes;
525
543
  };
@@ -527,7 +545,7 @@ export const createPropFixes = ({
527
545
  /**
528
546
  * Check if the new icon exists in the migration map
529
547
  */
530
- export const checkIfNewIconExist = error => {
548
+ const checkIfNewIconExist = error => {
531
549
  var _error$data;
532
550
  if (!((_error$data = error.data) !== null && _error$data !== void 0 && _error$data.importSource)) {
533
551
  return false;
@@ -537,4 +555,171 @@ export const checkIfNewIconExist = error => {
537
555
  newIcon
538
556
  } = baseMigrationMap[iconKey] || {};
539
557
  return Boolean(newIcon);
558
+ };
559
+ export const throwManualErrors = ({
560
+ errorsManual,
561
+ errorRanges,
562
+ guidance,
563
+ context,
564
+ isQuietMode
565
+ }) => {
566
+ for (const [key, errorList] of Object.entries(errorsManual)) {
567
+ const node = 'node' in errorList.errors[0] ? errorList.errors[0].node : null;
568
+ if (!node) {
569
+ return;
570
+ }
571
+ const cantMigrateIdentifierError = errorList.errors.find(x => 'messageId' in x && x.messageId === 'cantMigrateIdentifier');
572
+ let isInRange = false;
573
+ if (cantMigrateIdentifierError && isInRangeList(node, errorRanges)) {
574
+ isInRange = true;
575
+ }
576
+ if (isInRange && errorList.errors.length - 1 > 0 || !isInRange && errorList.errors.length > 0) {
577
+ const guidanceMessage = Object.keys(guidance).includes(key) ? guidance[key] : '';
578
+ context.report({
579
+ node,
580
+ messageId: 'noLegacyIconsManualMigration',
581
+ data: {
582
+ iconName: errorList.iconName,
583
+ importSource: errorList.importSource,
584
+ guidance: isQuietMode ? guidanceMessage : `${guidanceMessage}For more information see the below errors.\n`
585
+ }
586
+ });
587
+ if (!isQuietMode) {
588
+ for (const error of errorList.errors) {
589
+ if ('messageId' in error && (error.messageId !== 'cantMigrateIdentifier' || error.messageId === 'cantMigrateIdentifier' && !isInRange)) {
590
+ context.report(error);
591
+ }
592
+ }
593
+ }
594
+ }
595
+ }
596
+ };
597
+
598
+ // Loops through automatic errors and them after adding the required suggestion/fix
599
+ export const throwAutoErrors = ({
600
+ errorsManual,
601
+ errorsAuto,
602
+ legacyIconImports,
603
+ guidance,
604
+ migrationIconImports,
605
+ shouldUseMigrationPath,
606
+ context
607
+ }) => {
608
+ // Set of all the import sources that have manual errors (required later to check if a source has both manual and auto
609
+ // errors in one file making it impossible to just remove the legacy import)
610
+ const allManualErrorSources = Object.entries(errorsManual).reduce((result, option) => {
611
+ const [key, errorInfo] = option;
612
+ if (!errorsAuto.hasOwnProperty(key)) {
613
+ result.add(errorInfo.importSource);
614
+ }
615
+ return result;
616
+ }, new Set());
617
+ //group errors by import source and remove any unwanted errors
618
+ const groupedErrorList = Object.entries(errorsAuto).reduce((result, option) => {
619
+ const [key, error] = option;
620
+ //return early if no data
621
+ if (!error.data) {
622
+ return result;
623
+ }
624
+ if (Object.keys(errorsManual).includes(key)) {
625
+ const cantMigrateIdentifierError = errorsManual[key].errors.find(x => 'messageId' in x && x.messageId === 'cantMigrateIdentifier');
626
+ // If cantMigrateIdentifier is the only manual error found we still want to throw the auto error
627
+ if (!(cantMigrateIdentifierError && errorsManual[key].errors.length === 1)) {
628
+ return result;
629
+ }
630
+ }
631
+ const importSource = error.data.importSource;
632
+ if (!result.hasOwnProperty(importSource)) {
633
+ result[importSource] = [];
634
+ }
635
+ result[importSource].push({
636
+ key,
637
+ ...error
638
+ });
639
+ return result;
640
+ }, {});
641
+ for (const [importSource, errorList] of Object.entries(groupedErrorList)) {
642
+ const autoFixers = [];
643
+ // appliedErrorsForImport will contain all the errors FOR A SINGLE IMPORT and will be merged into errorListForReport
644
+ const appliedErrorsForImport = [];
645
+ // Loop over auto errors for a single import source
646
+ for (const [_, error] of errorList.entries()) {
647
+ var _legacyIconImports$er, _legacyIconImports$er2, _migrationIconImports;
648
+ const {
649
+ key
650
+ } = error;
651
+ const node = 'node' in error ? error.node : null;
652
+ // Check if there is a manual error for the same import source somewhere else in the same file
653
+ // If that is the case we'll need to provide a suggestion instead of auto-fixing as the suggestion will
654
+ // add another import without removing the old import and this needs to be validated
655
+ const isInManualArray = allManualErrorSources.has(importSource);
656
+ // New icon name for renaming if the icon is in the manual array
657
+ const newIconName = getNewIconNameForRenaming(isInManualArray, importSource, errorList[0].data ? (_legacyIconImports$er = legacyIconImports[errorList[0].data.iconName]) === null || _legacyIconImports$er === void 0 ? void 0 : _legacyIconImports$er.importSpecifier : undefined);
658
+ if (!node) {
659
+ continue;
660
+ }
661
+ const guidanceMessage = guidance.hasOwnProperty(key) ? guidance[key] : '';
662
+ if (Object.keys(error).includes('data') && error.data) {
663
+ error.data.guidance = guidanceMessage;
664
+ }
665
+ const fixArguments = error.data ? {
666
+ metadata: error.data,
667
+ legacyImportNode: (_legacyIconImports$er2 = legacyIconImports[error.data.iconName]) === null || _legacyIconImports$er2 === void 0 ? void 0 : _legacyIconImports$er2.importNode,
668
+ migrationImportNode: (_migrationIconImports = migrationIconImports[error.data.iconName]) === null || _migrationIconImports === void 0 ? void 0 : _migrationIconImports.importNode,
669
+ shouldUseMigrationPath,
670
+ newIconName: isInManualArray ? newIconName : undefined
671
+ } : null;
672
+ if (!error.data || shouldUseMigrationPath && !checkIfNewIconExist(error) || !fixArguments) {
673
+ continue;
674
+ }
675
+ if (isInManualArray) {
676
+ // provide suggestion if there is a manual error for the same import source and thus the legacy import can't be removed
677
+ error.suggest = [{
678
+ desc: 'Rename icon import, import from the new package, and update props.',
679
+ fix: fixer => {
680
+ return [...createPropFixes({
681
+ ...fixArguments,
682
+ node,
683
+ fixer
684
+ }), ...createImportFix({
685
+ ...fixArguments,
686
+ fixer
687
+ })];
688
+ }
689
+ }];
690
+ } else {
691
+ // Update Guidance message for auto-fixing
692
+ if (error.data) {
693
+ error.data.guidance = error.data.guidance + `\nTo automatically fix this icon, run the auto-fixer attached to the first use of ${importSource} in this file - either manually, or by saving this file.`;
694
+ }
695
+ // There should only be 1 import fix for each import source and thus only add this at the start of the list
696
+ if (autoFixers.length === 0) {
697
+ autoFixers.push(fixer => createImportFix({
698
+ ...fixArguments,
699
+ fixer
700
+ }));
701
+ }
702
+ // Push the prop fix regardless
703
+ autoFixers.push(fixer => createPropFixes({
704
+ ...fixArguments,
705
+ node,
706
+ fixer
707
+ }));
708
+ }
709
+ // Add the error to the appliedErrorsForImport, ready to be thrown later
710
+ appliedErrorsForImport.push(error);
711
+ }
712
+ // We want to have only 1 fix for each import source that is not in the manual array
713
+ // NOTE: If in the manual array, suggestions have been applied above and autoFixers.length will be 0 which will mean no fix is added
714
+ if (autoFixers.length > 0) {
715
+ // Add the fix to only one of the errors in the list of errors from the current import source
716
+ appliedErrorsForImport[0].fix = fixer => {
717
+ return autoFixers.flatMap(autoFixer => autoFixer(fixer));
718
+ };
719
+ }
720
+ // throw errors
721
+ appliedErrorsForImport.forEach(error => {
722
+ context.report(error);
723
+ });
724
+ }
540
725
  };
@@ -6,6 +6,7 @@ const rule = createLintRule({
6
6
  meta: {
7
7
  name: 'no-legacy-icons',
8
8
  fixable: 'code',
9
+ hasSuggestions: true,
9
10
  type: 'problem',
10
11
  docs: {
11
12
  description: 'Enforces no legacy icons are used.',