@atlaskit/eslint-plugin-design-system 10.16.0 → 10.17.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.
- package/CHANGELOG.md +19 -0
- package/README.md +1 -1
- package/dist/cjs/rules/no-legacy-icons/checks.js +272 -84
- package/dist/cjs/rules/no-legacy-icons/helpers.js +214 -14
- package/dist/cjs/rules/no-legacy-icons/index.js +4 -0
- package/dist/cjs/rules/no-legacy-icons/upcoming-icons.js +1 -1
- package/dist/es2019/rules/no-legacy-icons/checks.js +190 -24
- package/dist/es2019/rules/no-legacy-icons/helpers.js +220 -6
- package/dist/es2019/rules/no-legacy-icons/index.js +4 -0
- package/dist/es2019/rules/no-legacy-icons/upcoming-icons.js +1 -1
- package/dist/esm/rules/no-legacy-icons/checks.js +273 -85
- package/dist/esm/rules/no-legacy-icons/helpers.js +214 -14
- package/dist/esm/rules/no-legacy-icons/index.js +4 -0
- package/dist/esm/rules/no-legacy-icons/upcoming-icons.js +1 -1
- package/dist/types/rules/no-legacy-icons/helpers.d.ts +88 -3
- package/dist/types-ts4.5/rules/no-legacy-icons/helpers.d.ts +88 -3
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
1
|
+
import { isNodeOfType, literal } from 'eslint-codemod-utils';
|
|
2
2
|
import baseMigrationMap, { migrationOutcomeDescriptionMap } from '@atlaskit/icon/UNSAFE_migration-map';
|
|
3
3
|
import { getImportName } from '../utils/get-import-name';
|
|
4
4
|
import { upcomingIcons } from './upcoming-icons';
|
|
@@ -50,10 +50,36 @@ export const canAutoMigrateNewIconBasedOnSize = guidance => {
|
|
|
50
50
|
return ['swap', 'swap-slight-visual-change', 'swap-visual-change', 'swap-size-shift-utility'].includes(guidance || '');
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
*
|
|
55
|
+
* @param iconPackage string
|
|
56
|
+
* @returns object of new icon name and import path
|
|
57
|
+
*/
|
|
58
|
+
export const getNewIconNameAndImportPath = (iconPackage, shouldUseMigrationPath) => {
|
|
59
|
+
const legacyIconName = getIconKey(iconPackage);
|
|
60
|
+
const migrationMapObject = getMigrationMapObject(iconPackage);
|
|
61
|
+
if (!migrationMapObject || !migrationMapObject.newIcon) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
const {
|
|
65
|
+
newIcon
|
|
66
|
+
} = migrationMapObject;
|
|
67
|
+
const migrationPath = newIcon.name === legacyIconName ? `${newIcon.package}/${newIcon.type}/migration/${newIcon.name}` : `${newIcon.package}/${newIcon.type}/migration/${newIcon.name}--${legacyIconName.replaceAll('/', '-')}`;
|
|
68
|
+
return {
|
|
69
|
+
iconName: newIcon.name,
|
|
70
|
+
importPath: shouldUseMigrationPath ? migrationPath : `${newIcon.package}/${newIcon.type}/${newIcon.name}`
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
53
74
|
/**
|
|
54
75
|
* Creates the written guidance for migrating a legacy icon to a new icon
|
|
55
76
|
*/
|
|
56
|
-
export const createGuidance = (
|
|
77
|
+
export const createGuidance = ({
|
|
78
|
+
iconPackage,
|
|
79
|
+
insideNewButton,
|
|
80
|
+
size,
|
|
81
|
+
shouldUseMigrationPath
|
|
82
|
+
}) => {
|
|
57
83
|
const migrationMapObject = getMigrationMapObject(iconPackage);
|
|
58
84
|
const upcomingIcon = getUpcomingIcons(iconPackage);
|
|
59
85
|
if (upcomingIcon) {
|
|
@@ -82,17 +108,21 @@ export const createGuidance = (iconPackage, insideNewButton = false, size) => {
|
|
|
82
108
|
if (!newIcon) {
|
|
83
109
|
return 'No equivalent icon in new set. An option is to contribute a custom icon into icon-labs package instead.\n';
|
|
84
110
|
}
|
|
111
|
+
const {
|
|
112
|
+
iconName,
|
|
113
|
+
importPath
|
|
114
|
+
} = getNewIconNameAndImportPath(iconPackage, shouldUseMigrationPath);
|
|
85
115
|
const buttonGuidanceStr = "Please set 'spacing' property of the new icon to 'none', to ensure appropriate spacing inside `@atlaskit/button`.\n";
|
|
86
116
|
let guidance = '';
|
|
87
117
|
if (size) {
|
|
88
118
|
if (migrationMapObject.sizeGuidance[size] && canAutoMigrateNewIconBasedOnSize(migrationMapObject.sizeGuidance[size])) {
|
|
89
|
-
guidance += `Fix: Use ${
|
|
119
|
+
guidance += `Fix: Use ${iconName} from ${importPath} instead.`;
|
|
90
120
|
} else {
|
|
91
121
|
guidance += `No equivalent icon for this size, ${size}, in new set.`;
|
|
92
122
|
}
|
|
93
123
|
guidance += `${Object.keys(migrationOutcomeDescriptionMap).includes(migrationMapObject.sizeGuidance[size]) ? ` Please: ${migrationOutcomeDescriptionMap[migrationMapObject.sizeGuidance[size]]}` : ' No migration size advice given.'}\n`;
|
|
94
124
|
} else {
|
|
95
|
-
guidance = `Use ${
|
|
125
|
+
guidance = `Use ${iconName} from ${importPath} instead.\nMigration suggestions, depending on the legacy icon size:\n`;
|
|
96
126
|
Object.entries(migrationMapObject.sizeGuidance).forEach(([size, value]) => {
|
|
97
127
|
guidance += `\t- ${size}: `;
|
|
98
128
|
if (!Object.keys(migrationOutcomeDescriptionMap).includes(value)) {
|
|
@@ -223,13 +253,23 @@ export const createCantMigrateSizeUnknown = (node, errors, importSource, iconNam
|
|
|
223
253
|
};
|
|
224
254
|
pushManualError(locToString(node), errors, myError, importSource, iconName);
|
|
225
255
|
};
|
|
226
|
-
export const createAutoMigrationError = (
|
|
256
|
+
export const createAutoMigrationError = ({
|
|
257
|
+
node,
|
|
258
|
+
importSource,
|
|
259
|
+
iconName,
|
|
260
|
+
errors,
|
|
261
|
+
shouldAddSpaciousSpacing,
|
|
262
|
+
insideNewButton
|
|
263
|
+
}) => {
|
|
227
264
|
const myError = {
|
|
228
265
|
node,
|
|
229
266
|
messageId: 'noLegacyIconsAutoMigration',
|
|
230
267
|
data: {
|
|
231
268
|
importSource,
|
|
232
|
-
iconName
|
|
269
|
+
iconName,
|
|
270
|
+
spacing: shouldAddSpaciousSpacing ? 'spacious' : '',
|
|
271
|
+
// value type need to be a string in Rule.ReportDescriptor
|
|
272
|
+
insideNewButton: String(insideNewButton)
|
|
233
273
|
}
|
|
234
274
|
};
|
|
235
275
|
errors[locToString(node)] = myError;
|
|
@@ -320,4 +360,178 @@ export const isInRangeList = (node, sortedListOfRangesForErrors) => {
|
|
|
320
360
|
}
|
|
321
361
|
const found = sortedListOfRangesForErrors.find(currRange => range[0] >= currRange.start && range[1] <= currRange.end);
|
|
322
362
|
return !!found;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
*
|
|
367
|
+
* @param node Icon JSXelement
|
|
368
|
+
* @param newButtonImports list of new button import specifiers
|
|
369
|
+
* @returns if Icon is inside a new button
|
|
370
|
+
*/
|
|
371
|
+
export const isInsideNewButton = (node, newButtonImports) => {
|
|
372
|
+
var _node$parent, _node$parent$parent, _node$parent2, _node$parent2$parent, _node$parent2$parent$;
|
|
373
|
+
let insideNewButton = false;
|
|
374
|
+
if (node.parent && isNodeOfType(node.parent, 'ArrowFunctionExpression') && (_node$parent = node.parent) !== null && _node$parent !== void 0 && (_node$parent$parent = _node$parent.parent) !== null && _node$parent$parent !== void 0 && _node$parent$parent.parent && isNodeOfType(node.parent.parent.parent, 'JSXAttribute') && isNodeOfType(node.parent.parent.parent.name, 'JSXIdentifier') && (_node$parent2 = node.parent) !== null && _node$parent2 !== void 0 && (_node$parent2$parent = _node$parent2.parent) !== null && _node$parent2$parent !== void 0 && (_node$parent2$parent$ = _node$parent2$parent.parent) !== null && _node$parent2$parent$ !== void 0 && _node$parent2$parent$.parent && isNodeOfType(node.parent.parent.parent.parent, 'JSXOpeningElement') && isNodeOfType(node.parent.parent.parent.parent.name, 'JSXIdentifier') && newButtonImports.has(node.parent.parent.parent.parent.name.name)) {
|
|
375
|
+
insideNewButton = true;
|
|
376
|
+
}
|
|
377
|
+
return insideNewButton;
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
*
|
|
382
|
+
* @param node Icon JSXelement
|
|
383
|
+
* @param newButtonImports list of legacy button import specifiers
|
|
384
|
+
* @returns if Icon is inside a legacy button
|
|
385
|
+
*/
|
|
386
|
+
export const isInsideLegacyButton = (node, legacyButtonImports) => {
|
|
387
|
+
var _node$parent3, _node$parent4, _node$parent4$parent, _node$parent5, _node$parent5$parent, _node$parent6, _node$parent6$parent;
|
|
388
|
+
let insideLegacyButton = false;
|
|
389
|
+
if (node.parent && isNodeOfType(node.parent, 'JSXExpressionContainer') && (_node$parent3 = node.parent) !== null && _node$parent3 !== void 0 && _node$parent3.parent && isNodeOfType(node.parent.parent, 'JSXAttribute') && (node.parent.parent.name.name === 'iconBefore' || node.parent.parent.name.name === 'iconAfter') && isNodeOfType((_node$parent4 = node.parent) === null || _node$parent4 === void 0 ? void 0 : (_node$parent4$parent = _node$parent4.parent) === null || _node$parent4$parent === void 0 ? void 0 : _node$parent4$parent.parent, 'JSXOpeningElement') && isNodeOfType((_node$parent5 = node.parent) === null || _node$parent5 === void 0 ? void 0 : (_node$parent5$parent = _node$parent5.parent) === null || _node$parent5$parent === void 0 ? void 0 : _node$parent5$parent.parent.name, 'JSXIdentifier') && legacyButtonImports.has((_node$parent6 = node.parent) === null || _node$parent6 === void 0 ? void 0 : (_node$parent6$parent = _node$parent6.parent) === null || _node$parent6$parent === void 0 ? void 0 : _node$parent6$parent.parent.name.name)) {
|
|
390
|
+
insideLegacyButton = true;
|
|
391
|
+
}
|
|
392
|
+
return insideLegacyButton;
|
|
393
|
+
};
|
|
394
|
+
const findProp = (attributes, propName) => attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.name === propName);
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
*
|
|
398
|
+
* Creates a list of fixers to update the icon import path
|
|
399
|
+
* @param metadata Metadata including the import source and spacing
|
|
400
|
+
* @param fixer The original fix function
|
|
401
|
+
* @param legacyImportNode The import declaration node to replace
|
|
402
|
+
* @param shouldUseMigrationPath The eslint rule config, whether to use migration entrypoint or not
|
|
403
|
+
* @param migrationImportNode The migration import declaration node to replace, only present if shouldUseMigrationPath is false
|
|
404
|
+
* @returns A list of fixers to migrate the icon
|
|
405
|
+
*/
|
|
406
|
+
export const createImportFix = ({
|
|
407
|
+
fixer,
|
|
408
|
+
legacyImportNode,
|
|
409
|
+
metadata,
|
|
410
|
+
shouldUseMigrationPath,
|
|
411
|
+
migrationImportNode
|
|
412
|
+
}) => {
|
|
413
|
+
const fixes = [];
|
|
414
|
+
const {
|
|
415
|
+
importSource
|
|
416
|
+
} = metadata;
|
|
417
|
+
const importPath = migrationImportNode ? importSource.replace('/migration', '').split('--')[0] : getNewIconNameAndImportPath(importSource, shouldUseMigrationPath).importPath;
|
|
418
|
+
|
|
419
|
+
// replace old icon import with icon import
|
|
420
|
+
if (legacyImportNode && importPath) {
|
|
421
|
+
fixes.push(fixer.replaceText(legacyImportNode.source, `'${literal(importPath)}'`));
|
|
422
|
+
}
|
|
423
|
+
if (migrationImportNode && !shouldUseMigrationPath && importPath) {
|
|
424
|
+
fixes.push(fixer.replaceText(migrationImportNode.source, `'${literal(importPath)}'`));
|
|
425
|
+
}
|
|
426
|
+
return fixes;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Creates a list of fixers to update the icon props
|
|
431
|
+
* @param node The Icon element to migrate
|
|
432
|
+
* @param metadata Metadata including the import source and spacing
|
|
433
|
+
* @param fixer The original fix function
|
|
434
|
+
* @param legacyImportNode The import declaration node to replace
|
|
435
|
+
* @param shouldUseMigrationPath The eslint rule config, whether to use migration entrypoint or not
|
|
436
|
+
* @param migrationImportNode The migration import declaration node to replace, only present if shouldUseMigrationPath is false
|
|
437
|
+
* @returns A list of fixers to migrate the icon
|
|
438
|
+
*/
|
|
439
|
+
export const createPropFixes = ({
|
|
440
|
+
node,
|
|
441
|
+
fixer,
|
|
442
|
+
legacyImportNode,
|
|
443
|
+
metadata,
|
|
444
|
+
shouldUseMigrationPath,
|
|
445
|
+
migrationImportNode
|
|
446
|
+
}) => {
|
|
447
|
+
const fixes = [];
|
|
448
|
+
const {
|
|
449
|
+
importSource,
|
|
450
|
+
spacing,
|
|
451
|
+
insideNewButton
|
|
452
|
+
} = metadata;
|
|
453
|
+
if (shouldUseMigrationPath && !legacyImportNode) {
|
|
454
|
+
return fixes;
|
|
455
|
+
}
|
|
456
|
+
const importPath = migrationImportNode ? importSource.replace('/migration', '').split('--')[0] : getNewIconNameAndImportPath(importSource, shouldUseMigrationPath).importPath;
|
|
457
|
+
const iconType = importPath !== null && importPath !== void 0 && importPath.startsWith('@atlaskit/icon/core') ? 'core' : 'utility';
|
|
458
|
+
if (node.type === 'JSXElement') {
|
|
459
|
+
const {
|
|
460
|
+
openingElement
|
|
461
|
+
} = node;
|
|
462
|
+
const {
|
|
463
|
+
attributes
|
|
464
|
+
} = openingElement;
|
|
465
|
+
|
|
466
|
+
// replace primaryColor prop with color
|
|
467
|
+
const primaryColor = findProp(attributes, 'primaryColor');
|
|
468
|
+
if (primaryColor && primaryColor.type === 'JSXAttribute') {
|
|
469
|
+
fixes.push(fixer.replaceText(primaryColor.name, 'color'));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// add color="currentColor" if
|
|
473
|
+
// 1. primaryColor prop is not set
|
|
474
|
+
// 2. icon is not imported from migration entrypoint
|
|
475
|
+
// 3. icon element is not inside a new button
|
|
476
|
+
if (legacyImportNode && !primaryColor && !migrationImportNode &&
|
|
477
|
+
// value type need to be a string in Rule.ReportDescriptor
|
|
478
|
+
insideNewButton !== 'true') {
|
|
479
|
+
fixes.push(fixer.insertTextAfter(openingElement.name, ` color="currentColor"`));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// rename or remove size prop based on shouldUseMigrationPath,
|
|
483
|
+
// add spacing="spacious" if
|
|
484
|
+
// 1. it's in error metadata, which means size is medium
|
|
485
|
+
// 2. no existing spacing prop
|
|
486
|
+
// 3. iconType is "core"
|
|
487
|
+
// 4. icon is not imported from migration entrypoint
|
|
488
|
+
const sizeProp = findProp(attributes, 'size');
|
|
489
|
+
const spacingProp = findProp(attributes, 'spacing');
|
|
490
|
+
if (spacing && !spacingProp && iconType === 'core' && !migrationImportNode) {
|
|
491
|
+
fixes.push(fixer.insertTextAfter(sizeProp || openingElement.name, ` spacing="${spacing}"`));
|
|
492
|
+
}
|
|
493
|
+
if (sizeProp && sizeProp.type === 'JSXAttribute') {
|
|
494
|
+
fixes.push(shouldUseMigrationPath ?
|
|
495
|
+
// replace size prop with LEGACY_size,
|
|
496
|
+
fixer.replaceText(sizeProp.name, 'LEGACY_size') :
|
|
497
|
+
// remove size prop if shouldUseMigrationPath is false
|
|
498
|
+
fixer.remove(sizeProp));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// rename or remove secondaryColor prop based on shouldUseMigrationPath
|
|
502
|
+
const secondaryColorProp = findProp(attributes, 'secondaryColor');
|
|
503
|
+
if (secondaryColorProp && secondaryColorProp.type === 'JSXAttribute') {
|
|
504
|
+
fixes.push(shouldUseMigrationPath ?
|
|
505
|
+
// replace secondaryColor prop with LEGACY_secondaryColor
|
|
506
|
+
fixer.replaceText(secondaryColorProp.name, 'LEGACY_secondaryColor') :
|
|
507
|
+
// remove secondaryColor prop if shouldUseMigrationPath is false
|
|
508
|
+
fixer.remove(secondaryColorProp));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// remove LEGACY props
|
|
512
|
+
if (!shouldUseMigrationPath) {
|
|
513
|
+
['LEGACY_size', 'LEGACY_margin', 'LEGACY_fallbackIcon', 'LEGACY_secondaryColor'].forEach(propName => {
|
|
514
|
+
const legacyProp = findProp(attributes, propName);
|
|
515
|
+
if (legacyProp && legacyProp.type === 'JSXAttribute') {
|
|
516
|
+
fixes.push(fixer.remove(legacyProp));
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return fixes;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Check if the new icon exists in the migration map
|
|
526
|
+
*/
|
|
527
|
+
export const checkIfNewIconExist = error => {
|
|
528
|
+
var _error$data;
|
|
529
|
+
if (!((_error$data = error.data) !== null && _error$data !== void 0 && _error$data.importSource)) {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
const iconKey = getIconKey(error.data.importSource);
|
|
533
|
+
const {
|
|
534
|
+
newIcon
|
|
535
|
+
} = baseMigrationMap[iconKey] || {};
|
|
536
|
+
return Boolean(newIcon);
|
|
323
537
|
};
|
|
@@ -5,6 +5,7 @@ import { createHelpers } from './helpers';
|
|
|
5
5
|
const rule = createLintRule({
|
|
6
6
|
meta: {
|
|
7
7
|
name: 'no-legacy-icons',
|
|
8
|
+
fixable: 'code',
|
|
8
9
|
type: 'problem',
|
|
9
10
|
docs: {
|
|
10
11
|
description: 'Enforces no legacy icons are used.',
|
|
@@ -22,6 +23,9 @@ const rule = createLintRule({
|
|
|
22
23
|
},
|
|
23
24
|
quiet: {
|
|
24
25
|
type: 'boolean'
|
|
26
|
+
},
|
|
27
|
+
shouldUseMigrationPath: {
|
|
28
|
+
type: 'boolean'
|
|
25
29
|
}
|
|
26
30
|
},
|
|
27
31
|
additionalProperties: false
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const upcomingIcons = [
|
|
1
|
+
export const upcomingIcons = [];
|