@atlaskit/eslint-plugin-design-system 11.0.2 → 11.2.0

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 (38) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +49 -49
  3. package/dist/cjs/rules/no-deprecated-apis/index.js +14 -27
  4. package/dist/cjs/rules/no-legacy-icons/checks.js +14 -3
  5. package/dist/cjs/rules/no-legacy-icons/helpers.js +3 -14
  6. package/dist/cjs/rules/no-legacy-icons/index.js +3 -0
  7. package/dist/cjs/rules/use-tokens-typography/index.js +1 -1
  8. package/dist/cjs/rules/use-tokens-typography/transformers/style-object.js +37 -16
  9. package/dist/cjs/rules/use-tokens-typography/utils.js +10 -0
  10. package/dist/es2019/rules/no-deprecated-apis/index.js +15 -24
  11. package/dist/es2019/rules/no-legacy-icons/checks.js +14 -3
  12. package/dist/es2019/rules/no-legacy-icons/helpers.js +3 -14
  13. package/dist/es2019/rules/no-legacy-icons/index.js +3 -0
  14. package/dist/es2019/rules/use-tokens-typography/index.js +1 -1
  15. package/dist/es2019/rules/use-tokens-typography/transformers/style-object.js +35 -14
  16. package/dist/es2019/rules/use-tokens-typography/utils.js +3 -0
  17. package/dist/esm/rules/no-deprecated-apis/index.js +15 -27
  18. package/dist/esm/rules/no-legacy-icons/checks.js +14 -3
  19. package/dist/esm/rules/no-legacy-icons/helpers.js +3 -14
  20. package/dist/esm/rules/no-legacy-icons/index.js +3 -0
  21. package/dist/esm/rules/use-tokens-typography/index.js +1 -1
  22. package/dist/esm/rules/use-tokens-typography/transformers/style-object.js +38 -17
  23. package/dist/esm/rules/use-tokens-typography/utils.js +9 -0
  24. package/dist/types/index.codegen.d.ts +3 -9
  25. package/dist/types/rules/index.codegen.d.ts +1 -3
  26. package/dist/types/rules/no-deprecated-apis/index.d.ts +2 -5
  27. package/dist/types/rules/no-legacy-icons/helpers.d.ts +1 -0
  28. package/dist/types/rules/use-tokens-typography/utils.d.ts +80 -0
  29. package/dist/types/rules/utils/create-no-tagged-template-expression-rule/index.d.ts +1 -1
  30. package/dist/types/rules/utils/create-rule.d.ts +1 -1
  31. package/dist/types-ts4.5/index.codegen.d.ts +3 -15
  32. package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -5
  33. package/dist/types-ts4.5/rules/no-deprecated-apis/index.d.ts +2 -7
  34. package/dist/types-ts4.5/rules/no-legacy-icons/helpers.d.ts +1 -0
  35. package/dist/types-ts4.5/rules/use-tokens-typography/utils.d.ts +80 -0
  36. package/dist/types-ts4.5/rules/utils/create-no-tagged-template-expression-rule/index.d.ts +1 -1
  37. package/dist/types-ts4.5/rules/utils/create-rule.d.ts +1 -1
  38. package/package.json +4 -3
@@ -1,33 +1,30 @@
1
- import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';
2
- import { createRule } from '../utils/create-rule';
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import { createLintRule } from '../utils/create-rule';
3
3
  import { getConfig } from '../utils/get-deprecated-config';
4
4
  import { isDeprecatedJSXAttributeConfig } from '../utils/types';
5
5
  export const noDeprecatedJSXAttributeMessageId = 'noDeprecatedJSXAttributes';
6
- const isNodeOfType = (node, nodeType) => ASTUtils.isNodeOfType(nodeType)(node);
7
6
  const isImportDeclaration = programStatement => {
8
7
  return (programStatement === null || programStatement === void 0 ? void 0 : programStatement.type) === 'ImportDeclaration';
9
8
  };
10
9
  const findJSXElementName = jsxAttributeNode => {
11
- if (!jsxAttributeNode.parent || !isNodeOfType(jsxAttributeNode.parent, AST_NODE_TYPES.JSXOpeningElement)) {
10
+ if (!jsxAttributeNode.parent || !isNodeOfType(jsxAttributeNode === null || jsxAttributeNode === void 0 ? void 0 : jsxAttributeNode.parent, 'JSXOpeningElement')) {
12
11
  return;
13
12
  }
14
13
  const openingElement = jsxAttributeNode.parent;
15
- if (!isNodeOfType(openingElement.name, AST_NODE_TYPES.JSXIdentifier)) {
14
+ if (!isNodeOfType(openingElement.name, 'JSXIdentifier')) {
16
15
  return;
17
16
  }
18
17
  return openingElement.name.name;
19
18
  };
20
19
  export const name = 'no-deprecated-apis';
21
- const rule = createRule({
22
- name,
23
- defaultOptions: [{
24
- deprecatedConfig: getConfig('jsxAttributes')
25
- }],
20
+ const rule = createLintRule({
26
21
  meta: {
22
+ name,
27
23
  type: 'suggestion',
28
24
  docs: {
29
25
  description: 'Disallow using deprecated APIs.',
30
- recommended: 'error'
26
+ recommended: true,
27
+ severity: 'error'
31
28
  },
32
29
  messages: {
33
30
  noDeprecatedJSXAttributes: 'The JSX attribute {{propName}} has been deprecated.'
@@ -57,7 +54,7 @@ const rule = createRule({
57
54
  }
58
55
  },
59
56
  required: ['moduleSpecifier'],
60
- additionalProperites: false
57
+ additionalProperties: false
61
58
  }
62
59
  }
63
60
  }
@@ -65,24 +62,18 @@ const rule = createRule({
65
62
  }
66
63
  }]
67
64
  },
68
- create(context, [options]) {
65
+ create(context) {
69
66
  var _context$options$;
70
- // Get rule configuration
71
- const {
72
- deprecatedConfig: defaultDeprecatedConfig
73
- } = options;
74
-
75
67
  // Get the rule configuration specified otherwise use default config.
76
68
  // A bit confusing as it seems that the default options have precedence over the user specified options.
77
- const deprecatedConfig = ((_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$.deprecatedConfig) || defaultDeprecatedConfig;
69
+ const deprecatedConfig = ((_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$.deprecatedConfig) || getConfig('jsxAttributes');
78
70
  return {
79
71
  // find JSX atribute - find name of attribute - get source and find relevant identifiers.
80
72
  JSXAttribute(node) {
81
- const jsxAttributeIdentifier = node.name;
82
- if (!isNodeOfType(jsxAttributeIdentifier, AST_NODE_TYPES.JSXIdentifier)) {
73
+ if (!isNodeOfType(node, 'JSXAttribute') || !isNodeOfType(node.name, 'JSXIdentifier')) {
83
74
  return;
84
75
  }
85
- const jsxAttributeName = jsxAttributeIdentifier.name;
76
+ const jsxAttributeName = node.name.name;
86
77
  if (!isDeprecatedJSXAttributeConfig(deprecatedConfig) || !deprecatedConfig[jsxAttributeName]) {
87
78
  return;
88
79
  }
@@ -90,12 +81,12 @@ const rule = createRule({
90
81
  if (!jsxElementName) {
91
82
  return;
92
83
  }
93
- const source = context.getSourceCode();
84
+ const source = context.sourceCode;
94
85
 
95
86
  // find an import for the path of the banned api
96
87
  deprecatedConfig[jsxAttributeName].forEach(importItem => {
97
88
  var _importItem$namedSpec;
98
- const importNode = source.ast.body.filter(isImportDeclaration).find(node => node.source.value.includes(importItem.moduleSpecifier));
89
+ const importNode = source.ast.body.filter(isImportDeclaration).find(node => node && node.source.value && typeof node.source.value === 'string' && node.source.value.includes(importItem.moduleSpecifier));
99
90
  if (!importNode) {
100
91
  return;
101
92
  }
@@ -1,7 +1,7 @@
1
1
  import { isNodeOfType } from 'eslint-codemod-utils';
2
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
- //create global variables to be shared by the checks
4
+ // Create global variables to be shared by the checks
5
5
  const {
6
6
  getPrimaryColor,
7
7
  getConfigFlag
@@ -19,6 +19,7 @@ export const createChecks = context => {
19
19
  const shouldErrorForAutoMigration = getConfigFlag('shouldErrorForAutoMigration', true);
20
20
  const isQuietMode = getConfigFlag('quiet', false);
21
21
  const shouldUseMigrationPath = getConfigFlag('shouldUseMigrationPath', true);
22
+ const shouldUseSafeMigrationMode = getConfigFlag('shouldUseSafeMigrationMode', false);
22
23
 
23
24
  // Sorted list of ranges
24
25
  let errorRanges = [];
@@ -334,10 +335,11 @@ export const createChecks = context => {
334
335
  // Find size prop on node
335
336
  let size = 'medium';
336
337
  let primaryColor = null;
338
+ let hasPrimaryColorProp = false;
339
+ let hasSecondaryColorProp = false;
337
340
  let afterSpreadSet = new Set();
338
341
  let requiredAttributesAfterSpread = new Set(['size', 'primaryColor', 'secondaryColor']);
339
342
  let hasSpread = false;
340
- let hasPrimaryColorProp = false;
341
343
  for (const attr of node.openingElement.attributes) {
342
344
  // Detect spread props
343
345
  if (isNodeOfType(attr, 'JSXSpreadAttribute')) {
@@ -368,9 +370,14 @@ export const createChecks = context => {
368
370
  primaryColor = getPrimaryColor(attr);
369
371
  hasPrimaryColorProp = true;
370
372
  break;
373
+ case 'secondaryColor':
374
+ hasSecondaryColorProp = true;
375
+ break;
371
376
  }
372
377
  }
373
378
  let hasManualMigration = false;
379
+
380
+ // Flag manual migration if primary color cannot be migrated
374
381
  if (primaryColor && !canMigrateColor(primaryColor) || hasPrimaryColorProp && !primaryColor) {
375
382
  createCantMigrateColorError(node, primaryColor ? `the value of '${primaryColor}'` : 'a statically unknown value', errorsManual, legacyIconImports[name].packageName, name);
376
383
  hasManualMigration = true;
@@ -382,12 +389,14 @@ export const createChecks = context => {
382
389
  createCantMigrateSizeUnknown(node, errorsManual, legacyIconImports[name].packageName, name);
383
390
  hasManualMigration = true;
384
391
  }
392
+
385
393
  // Do a set comparison - is requiredAttributesAfterSpread a subset of afterSpreadSet?
386
394
  if (hasSpread === true && !Array.from(requiredAttributesAfterSpread).every(val => afterSpreadSet.has(val)) && !insideNewButton) {
387
395
  const missingProps = Array.from(requiredAttributesAfterSpread).filter(val => !afterSpreadSet.has(val));
388
396
  createCantMigrateSpreadPropsError(node, missingProps, errorsManual, legacyIconImports[name].packageName, name);
389
397
  hasManualMigration = true;
390
398
  }
399
+
391
400
  // Check if it is an exported component?
392
401
  if (legacyIconImports[name].exported) {
393
402
  createCantMigrateReExportError(node, legacyIconImports[name].packageName, name, errorsManual);
@@ -415,7 +424,9 @@ export const createChecks = context => {
415
424
  spacing = 'spacious';
416
425
  }
417
426
  }
418
- if (!hasManualMigration && (newIcon || upcomingIcon) && isNewIconMigratable) {
427
+ if (shouldUseSafeMigrationMode && !hasManualMigration && (newIcon !== null && newIcon !== void 0 && newIcon.isMigrationUnsafe || size !== 'medium' || hasSecondaryColorProp)) {
428
+ createCantFindSuitableReplacementError(node, legacyIconImports[name].packageName, name, errorsManual, upcomingIcon ? true : migrationMapObject ? true : false);
429
+ } else if (!hasManualMigration && (newIcon || upcomingIcon) && isNewIconMigratable) {
419
430
  createAutoMigrationError({
420
431
  node,
421
432
  importSource: legacyIconImports[name].packageName,
@@ -462,8 +462,7 @@ const createPropFixes = ({
462
462
  }) => {
463
463
  const fixes = [];
464
464
  const {
465
- spacing,
466
- insideNewButton
465
+ spacing
467
466
  } = metadata;
468
467
  if (shouldUseMigrationPath && !legacyImportNode) {
469
468
  return fixes;
@@ -485,16 +484,6 @@ const createPropFixes = ({
485
484
  fixes.push(fixer.replaceText(primaryColor.name, 'color'));
486
485
  }
487
486
 
488
- // add color="currentColor" if
489
- // 1. primaryColor prop is not set
490
- // 2. icon is not imported from migration entrypoint
491
- // 3. icon element is not inside a new button
492
- if (legacyImportNode && !primaryColor && !migrationImportNode &&
493
- // value type need to be a string in Rule.ReportDescriptor
494
- insideNewButton !== 'true') {
495
- fixes.push(fixer.insertTextAfter(openingElement.name, ` color="currentColor"`));
496
- }
497
-
498
487
  // rename or remove size prop based on shouldUseMigrationPath,
499
488
  // add spacing="spacious" if
500
489
  // 1. it's in error metadata, which means size is medium
@@ -611,10 +600,10 @@ export const throwAutoErrors = ({
611
600
  }
612
601
  return result;
613
602
  }, new Set());
614
- //group errors by import source and remove any unwanted errors
603
+ // Group errors by import source and remove any unwanted errors
615
604
  const groupedErrorList = Object.entries(errorsAuto).reduce((result, option) => {
616
605
  const [key, error] = option;
617
- //return early if no data
606
+ // Return early if no data
618
607
  if (!error.data) {
619
608
  return result;
620
609
  }
@@ -22,6 +22,9 @@ const rule = createLintRule({
22
22
  shouldErrorForAutoMigration: {
23
23
  type: 'boolean'
24
24
  },
25
+ shouldUseSafeMigrationMode: {
26
+ type: 'boolean'
27
+ },
25
28
  quiet: {
26
29
  type: 'boolean'
27
30
  },
@@ -24,7 +24,7 @@ const rule = createLintRule({
24
24
  severity: 'warn'
25
25
  },
26
26
  messages: {
27
- noRawTypographyValues: 'Typography primitives or tokens should be used instead of hard-coded values.\n\n@meta <<{{payload}}>>.'
27
+ noRawTypographyValues: 'Typography primitives or tokens should be used instead of hard-coded values.\n\n@meta <<{{payload}}>>.\n\nNOTE: Using tokens with the `fontSize` property is invalid. Any `font.heading` or `font.body` tokens must use the CSS `font` property.'
28
28
  },
29
29
  schema: ruleSchema
30
30
  },
@@ -4,7 +4,7 @@ import { isNodeOfType } from 'eslint-codemod-utils';
4
4
  import { Object as ASTObject, ObjectEntry, Root } from '../../../ast-nodes';
5
5
  import { getValueForPropertyNode, normaliseValue } from '../../ensure-design-token-usage/utils';
6
6
  import { isDecendantOfGlobalToken, isDecendantOfStyleBlock, isDecendantOfType } from '../../utils/is-node';
7
- import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, insertFallbackImportFull, insertFallbackImportSpecifier, insertTokensImport, isValidPropertyNode, notUndefined } from '../utils';
7
+ import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, insertFallbackImportFull, insertFallbackImportSpecifier, insertTokensImport, isValidPropertyNode, isValidTypographyToken, notUndefined } from '../utils';
8
8
  export const StyleObject = {
9
9
  lint(node, {
10
10
  context,
@@ -67,18 +67,39 @@ export const StyleObject = {
67
67
  }
68
68
 
69
69
  // -- Match tokens --
70
- let matchingTokens = findTypographyTokenForValues(fontSizeValue, lineHeightValue);
71
- if (matchingTokens.length) {
72
- // If we have multiple matching tokens, try matching fontWeight
73
- let matchingTokensWithWeight = matchingTokens.filter(token => fontWeightValue ? token.values.fontWeight === fontWeightValue : token);
74
- if (matchingTokensWithWeight.length) {
75
- // Possibly narrowed down tokens
76
- matchingTokens = matchingTokensWithWeight;
77
- } else {
78
- // Ended up with 0 matches by matching fontWeight
79
- // return body token and add fontWeight manually
80
- matchingTokens = matchingTokens.filter(token => token.tokenName.includes('.body'));
81
- shouldAddFontWeight = true;
70
+ // Check if fontSize is a token (this is invalid syntax but unfortunately a common occurence)
71
+ // We may as well auto-fix `fontSize` to `font` and keep the token.
72
+ // Other tokens like `fontSize: token('space.100')` will not autofix, but still report
73
+ let matchingTokens = [];
74
+ const isFontSizeAToken = isDecendantOfGlobalToken(fontSizeNode.value);
75
+ if (isFontSizeAToken) {
76
+ // Specifically match for valid, non-deprecated font.heading|body|code tokens
77
+ const match = fontSizeValue.match(/font.(body|heading|code)[^']*/);
78
+ if (match) {
79
+ const matchedTokenName = match[0];
80
+ // This is really just a double check to be 100% certain the token exists
81
+ // and that we're not trying to apply a deprecated fontSize token to the font property
82
+ if (isValidTypographyToken(matchedTokenName)) {
83
+ matchingTokens = [{
84
+ tokenName: matchedTokenName
85
+ }];
86
+ }
87
+ }
88
+ } else {
89
+ // Standard matching against fontSize/lineHeight values
90
+ matchingTokens = findTypographyTokenForValues(fontSizeValue, lineHeightValue);
91
+ if (matchingTokens.length) {
92
+ // If we have multiple matching tokens, try matching fontWeight
93
+ let matchingTokensWithWeight = matchingTokens.filter(token => fontWeightValue ? token.values.fontWeight === fontWeightValue : token);
94
+ if (matchingTokensWithWeight.length) {
95
+ // Possibly narrowed down tokens
96
+ matchingTokens = matchingTokensWithWeight;
97
+ } else {
98
+ // Ended up with 0 matches by matching fontWeight
99
+ // return body token and add fontWeight manually
100
+ matchingTokens = matchingTokens.filter(token => token.tokenName.includes('.body'));
101
+ shouldAddFontWeight = true;
102
+ }
82
103
  }
83
104
  }
84
105
 
@@ -185,7 +206,7 @@ export const StyleObject = {
185
206
 
186
207
  // -- Font size --
187
208
  const fontSizeNode = ASTObject.getEntryByPropertyName(node, 'fontSize');
188
- if (!fontSizeNode || !isValidPropertyNode(fontSizeNode) || isDecendantOfGlobalToken(fontSizeNode.value)) {
209
+ if (!fontSizeNode || !isValidPropertyNode(fontSizeNode)) {
189
210
  return {
190
211
  success: false
191
212
  };
@@ -31,6 +31,9 @@ export const typographyValueToToken = typographyTokens
31
31
  values: individualValues
32
32
  };
33
33
  });
34
+ export function isValidTypographyToken(tokenName) {
35
+ return typographyTokens.filter(t => t.attributes.group === 'typography').filter(t => t.cleanName.includes('font.heading') || t.cleanName.includes('font.body') || t.cleanName.includes('font.code')).find(t => t.cleanName === tokenName);
36
+ }
34
37
  export function findTypographyTokenForValues(fontSize, lineHeight) {
35
38
  let matchingTokens = typographyValueToToken.filter(token => token.values.fontSize === fontSize)
36
39
  // If lineHeight == 1, we don't match to a token
@@ -1,36 +1,30 @@
1
- import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
- import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';
3
- import { createRule } from '../utils/create-rule';
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import { createLintRule } from '../utils/create-rule';
4
3
  import { getConfig } from '../utils/get-deprecated-config';
5
4
  import { isDeprecatedJSXAttributeConfig } from '../utils/types';
6
5
  export var noDeprecatedJSXAttributeMessageId = 'noDeprecatedJSXAttributes';
7
- var isNodeOfType = function isNodeOfType(node, nodeType) {
8
- return ASTUtils.isNodeOfType(nodeType)(node);
9
- };
10
6
  var isImportDeclaration = function isImportDeclaration(programStatement) {
11
7
  return (programStatement === null || programStatement === void 0 ? void 0 : programStatement.type) === 'ImportDeclaration';
12
8
  };
13
9
  var findJSXElementName = function findJSXElementName(jsxAttributeNode) {
14
- if (!jsxAttributeNode.parent || !isNodeOfType(jsxAttributeNode.parent, AST_NODE_TYPES.JSXOpeningElement)) {
10
+ if (!jsxAttributeNode.parent || !isNodeOfType(jsxAttributeNode === null || jsxAttributeNode === void 0 ? void 0 : jsxAttributeNode.parent, 'JSXOpeningElement')) {
15
11
  return;
16
12
  }
17
13
  var openingElement = jsxAttributeNode.parent;
18
- if (!isNodeOfType(openingElement.name, AST_NODE_TYPES.JSXIdentifier)) {
14
+ if (!isNodeOfType(openingElement.name, 'JSXIdentifier')) {
19
15
  return;
20
16
  }
21
17
  return openingElement.name.name;
22
18
  };
23
19
  export var name = 'no-deprecated-apis';
24
- var rule = createRule({
25
- name: name,
26
- defaultOptions: [{
27
- deprecatedConfig: getConfig('jsxAttributes')
28
- }],
20
+ var rule = createLintRule({
29
21
  meta: {
22
+ name: name,
30
23
  type: 'suggestion',
31
24
  docs: {
32
25
  description: 'Disallow using deprecated APIs.',
33
- recommended: 'error'
26
+ recommended: true,
27
+ severity: 'error'
34
28
  },
35
29
  messages: {
36
30
  noDeprecatedJSXAttributes: 'The JSX attribute {{propName}} has been deprecated.'
@@ -60,7 +54,7 @@ var rule = createRule({
60
54
  }
61
55
  },
62
56
  required: ['moduleSpecifier'],
63
- additionalProperites: false
57
+ additionalProperties: false
64
58
  }
65
59
  }
66
60
  }
@@ -68,24 +62,18 @@ var rule = createRule({
68
62
  }
69
63
  }]
70
64
  },
71
- create: function create(context, _ref) {
65
+ create: function create(context) {
72
66
  var _context$options$;
73
- var _ref2 = _slicedToArray(_ref, 1),
74
- options = _ref2[0];
75
- // Get rule configuration
76
- var defaultDeprecatedConfig = options.deprecatedConfig;
77
-
78
67
  // Get the rule configuration specified otherwise use default config.
79
68
  // A bit confusing as it seems that the default options have precedence over the user specified options.
80
- var deprecatedConfig = ((_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$.deprecatedConfig) || defaultDeprecatedConfig;
69
+ var deprecatedConfig = ((_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$.deprecatedConfig) || getConfig('jsxAttributes');
81
70
  return {
82
71
  // find JSX atribute - find name of attribute - get source and find relevant identifiers.
83
72
  JSXAttribute: function JSXAttribute(node) {
84
- var jsxAttributeIdentifier = node.name;
85
- if (!isNodeOfType(jsxAttributeIdentifier, AST_NODE_TYPES.JSXIdentifier)) {
73
+ if (!isNodeOfType(node, 'JSXAttribute') || !isNodeOfType(node.name, 'JSXIdentifier')) {
86
74
  return;
87
75
  }
88
- var jsxAttributeName = jsxAttributeIdentifier.name;
76
+ var jsxAttributeName = node.name.name;
89
77
  if (!isDeprecatedJSXAttributeConfig(deprecatedConfig) || !deprecatedConfig[jsxAttributeName]) {
90
78
  return;
91
79
  }
@@ -93,13 +81,13 @@ var rule = createRule({
93
81
  if (!jsxElementName) {
94
82
  return;
95
83
  }
96
- var source = context.getSourceCode();
84
+ var source = context.sourceCode;
97
85
 
98
86
  // find an import for the path of the banned api
99
87
  deprecatedConfig[jsxAttributeName].forEach(function (importItem) {
100
88
  var _importItem$namedSpec;
101
89
  var importNode = source.ast.body.filter(isImportDeclaration).find(function (node) {
102
- return node.source.value.includes(importItem.moduleSpecifier);
90
+ return node && node.source.value && typeof node.source.value === 'string' && node.source.value.includes(importItem.moduleSpecifier);
103
91
  });
104
92
  if (!importNode) {
105
93
  return;
@@ -4,7 +4,7 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
4
4
  import { isNodeOfType } from 'eslint-codemod-utils';
5
5
  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';
6
6
  export var createChecks = function createChecks(context) {
7
- //create global variables to be shared by the checks
7
+ // Create global variables to be shared by the checks
8
8
  var _createHelpers = createHelpers(context),
9
9
  getPrimaryColor = _createHelpers.getPrimaryColor,
10
10
  getConfigFlag = _createHelpers.getConfigFlag;
@@ -21,6 +21,7 @@ export var createChecks = function createChecks(context) {
21
21
  var shouldErrorForAutoMigration = getConfigFlag('shouldErrorForAutoMigration', true);
22
22
  var isQuietMode = getConfigFlag('quiet', false);
23
23
  var shouldUseMigrationPath = getConfigFlag('shouldUseMigrationPath', true);
24
+ var shouldUseSafeMigrationMode = getConfigFlag('shouldUseSafeMigrationMode', false);
24
25
 
25
26
  // Sorted list of ranges
26
27
  var errorRanges = [];
@@ -405,10 +406,11 @@ export var createChecks = function createChecks(context) {
405
406
  // Find size prop on node
406
407
  var size = 'medium';
407
408
  var primaryColor = null;
409
+ var hasPrimaryColorProp = false;
410
+ var hasSecondaryColorProp = false;
408
411
  var afterSpreadSet = new Set();
409
412
  var requiredAttributesAfterSpread = new Set(['size', 'primaryColor', 'secondaryColor']);
410
413
  var hasSpread = false;
411
- var hasPrimaryColorProp = false;
412
414
  var _iterator8 = _createForOfIteratorHelper(node.openingElement.attributes),
413
415
  _step8;
414
416
  try {
@@ -443,6 +445,9 @@ export var createChecks = function createChecks(context) {
443
445
  primaryColor = getPrimaryColor(attr);
444
446
  hasPrimaryColorProp = true;
445
447
  break;
448
+ case 'secondaryColor':
449
+ hasSecondaryColorProp = true;
450
+ break;
446
451
  }
447
452
  }
448
453
  } catch (err) {
@@ -451,6 +456,8 @@ export var createChecks = function createChecks(context) {
451
456
  _iterator8.f();
452
457
  }
453
458
  var hasManualMigration = false;
459
+
460
+ // Flag manual migration if primary color cannot be migrated
454
461
  if (primaryColor && !canMigrateColor(primaryColor) || hasPrimaryColorProp && !primaryColor) {
455
462
  createCantMigrateColorError(node, primaryColor ? "the value of '".concat(primaryColor, "'") : 'a statically unknown value', errorsManual, legacyIconImports[name].packageName, name);
456
463
  hasManualMigration = true;
@@ -462,6 +469,7 @@ export var createChecks = function createChecks(context) {
462
469
  createCantMigrateSizeUnknown(node, errorsManual, legacyIconImports[name].packageName, name);
463
470
  hasManualMigration = true;
464
471
  }
472
+
465
473
  // Do a set comparison - is requiredAttributesAfterSpread a subset of afterSpreadSet?
466
474
  if (hasSpread === true && !Array.from(requiredAttributesAfterSpread).every(function (val) {
467
475
  return afterSpreadSet.has(val);
@@ -472,6 +480,7 @@ export var createChecks = function createChecks(context) {
472
480
  createCantMigrateSpreadPropsError(node, missingProps, errorsManual, legacyIconImports[name].packageName, name);
473
481
  hasManualMigration = true;
474
482
  }
483
+
475
484
  // Check if it is an exported component?
476
485
  if (legacyIconImports[name].exported) {
477
486
  createCantMigrateReExportError(node, legacyIconImports[name].packageName, name, errorsManual);
@@ -501,7 +510,9 @@ export var createChecks = function createChecks(context) {
501
510
  spacing = 'spacious';
502
511
  }
503
512
  }
504
- if (!hasManualMigration && (newIcon || upcomingIcon) && isNewIconMigratable) {
513
+ if (shouldUseSafeMigrationMode && !hasManualMigration && (newIcon !== null && newIcon !== void 0 && newIcon.isMigrationUnsafe || size !== 'medium' || hasSecondaryColorProp)) {
514
+ createCantFindSuitableReplacementError(node, legacyIconImports[name].packageName, name, errorsManual, upcomingIcon ? true : migrationMapObject ? true : false);
515
+ } else if (!hasManualMigration && (newIcon || upcomingIcon) && isNewIconMigratable) {
505
516
  createAutoMigrationError({
506
517
  node: node,
507
518
  importSource: legacyIconImports[name].packageName,
@@ -470,8 +470,7 @@ var createPropFixes = function createPropFixes(_ref7) {
470
470
  migrationImportNode = _ref7.migrationImportNode,
471
471
  newIconName = _ref7.newIconName;
472
472
  var fixes = [];
473
- var spacing = metadata.spacing,
474
- insideNewButton = metadata.insideNewButton;
473
+ var spacing = metadata.spacing;
475
474
  if (shouldUseMigrationPath && !legacyImportNode) {
476
475
  return fixes;
477
476
  }
@@ -488,16 +487,6 @@ var createPropFixes = function createPropFixes(_ref7) {
488
487
  fixes.push(fixer.replaceText(primaryColor.name, 'color'));
489
488
  }
490
489
 
491
- // add color="currentColor" if
492
- // 1. primaryColor prop is not set
493
- // 2. icon is not imported from migration entrypoint
494
- // 3. icon element is not inside a new button
495
- if (legacyImportNode && !primaryColor && !migrationImportNode &&
496
- // value type need to be a string in Rule.ReportDescriptor
497
- insideNewButton !== 'true') {
498
- fixes.push(fixer.insertTextAfter(openingElement.name, " color=\"currentColor\""));
499
- }
500
-
501
490
  // rename or remove size prop based on shouldUseMigrationPath,
502
491
  // add spacing="spacious" if
503
492
  // 1. it's in error metadata, which means size is medium
@@ -627,12 +616,12 @@ export var throwAutoErrors = function throwAutoErrors(_ref10) {
627
616
  }
628
617
  return result;
629
618
  }, new Set());
630
- //group errors by import source and remove any unwanted errors
619
+ // Group errors by import source and remove any unwanted errors
631
620
  var groupedErrorList = Object.entries(errorsAuto).reduce(function (result, option) {
632
621
  var _option2 = _slicedToArray(option, 2),
633
622
  key = _option2[0],
634
623
  error = _option2[1];
635
- //return early if no data
624
+ // Return early if no data
636
625
  if (!error.data) {
637
626
  return result;
638
627
  }
@@ -22,6 +22,9 @@ var rule = createLintRule({
22
22
  shouldErrorForAutoMigration: {
23
23
  type: 'boolean'
24
24
  },
25
+ shouldUseSafeMigrationMode: {
26
+ type: 'boolean'
27
+ },
25
28
  quiet: {
26
29
  type: 'boolean'
27
30
  },
@@ -26,7 +26,7 @@ var rule = createLintRule({
26
26
  severity: 'warn'
27
27
  },
28
28
  messages: {
29
- noRawTypographyValues: 'Typography primitives or tokens should be used instead of hard-coded values.\n\n@meta <<{{payload}}>>.'
29
+ noRawTypographyValues: 'Typography primitives or tokens should be used instead of hard-coded values.\n\n@meta <<{{payload}}>>.\n\nNOTE: Using tokens with the `fontSize` property is invalid. Any `font.heading` or `font.body` tokens must use the CSS `font` property.'
30
30
  },
31
31
  schema: ruleSchema
32
32
  },
@@ -7,7 +7,7 @@ import { isNodeOfType } from 'eslint-codemod-utils';
7
7
  import { Object as ASTObject, ObjectEntry, Root } from '../../../ast-nodes';
8
8
  import { getValueForPropertyNode, normaliseValue } from '../../ensure-design-token-usage/utils';
9
9
  import { isDecendantOfGlobalToken, isDecendantOfStyleBlock, isDecendantOfType } from '../../utils/is-node';
10
- import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, insertFallbackImportFull, insertFallbackImportSpecifier, insertTokensImport, isValidPropertyNode, notUndefined } from '../utils';
10
+ import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, insertFallbackImportFull, insertFallbackImportSpecifier, insertTokensImport, isValidPropertyNode, isValidTypographyToken, notUndefined } from '../utils';
11
11
  export var StyleObject = {
12
12
  lint: function lint(node, _ref) {
13
13
  var context = _ref.context,
@@ -66,22 +66,43 @@ export var StyleObject = {
66
66
  }
67
67
 
68
68
  // -- Match tokens --
69
- var matchingTokens = findTypographyTokenForValues(fontSizeValue, lineHeightValue);
70
- if (matchingTokens.length) {
71
- // If we have multiple matching tokens, try matching fontWeight
72
- var matchingTokensWithWeight = matchingTokens.filter(function (token) {
73
- return fontWeightValue ? token.values.fontWeight === fontWeightValue : token;
74
- });
75
- if (matchingTokensWithWeight.length) {
76
- // Possibly narrowed down tokens
77
- matchingTokens = matchingTokensWithWeight;
78
- } else {
79
- // Ended up with 0 matches by matching fontWeight
80
- // return body token and add fontWeight manually
81
- matchingTokens = matchingTokens.filter(function (token) {
82
- return token.tokenName.includes('.body');
69
+ // Check if fontSize is a token (this is invalid syntax but unfortunately a common occurence)
70
+ // We may as well auto-fix `fontSize` to `font` and keep the token.
71
+ // Other tokens like `fontSize: token('space.100')` will not autofix, but still report
72
+ var matchingTokens = [];
73
+ var isFontSizeAToken = isDecendantOfGlobalToken(fontSizeNode.value);
74
+ if (isFontSizeAToken) {
75
+ // Specifically match for valid, non-deprecated font.heading|body|code tokens
76
+ var match = fontSizeValue.match(/font.(body|heading|code)[^']*/);
77
+ if (match) {
78
+ var matchedTokenName = match[0];
79
+ // This is really just a double check to be 100% certain the token exists
80
+ // and that we're not trying to apply a deprecated fontSize token to the font property
81
+ if (isValidTypographyToken(matchedTokenName)) {
82
+ matchingTokens = [{
83
+ tokenName: matchedTokenName
84
+ }];
85
+ }
86
+ }
87
+ } else {
88
+ // Standard matching against fontSize/lineHeight values
89
+ matchingTokens = findTypographyTokenForValues(fontSizeValue, lineHeightValue);
90
+ if (matchingTokens.length) {
91
+ // If we have multiple matching tokens, try matching fontWeight
92
+ var matchingTokensWithWeight = matchingTokens.filter(function (token) {
93
+ return fontWeightValue ? token.values.fontWeight === fontWeightValue : token;
83
94
  });
84
- shouldAddFontWeight = true;
95
+ if (matchingTokensWithWeight.length) {
96
+ // Possibly narrowed down tokens
97
+ matchingTokens = matchingTokensWithWeight;
98
+ } else {
99
+ // Ended up with 0 matches by matching fontWeight
100
+ // return body token and add fontWeight manually
101
+ matchingTokens = matchingTokens.filter(function (token) {
102
+ return token.tokenName.includes('.body');
103
+ });
104
+ shouldAddFontWeight = true;
105
+ }
85
106
  }
86
107
  }
87
108
 
@@ -186,7 +207,7 @@ export var StyleObject = {
186
207
 
187
208
  // -- Font size --
188
209
  var fontSizeNode = ASTObject.getEntryByPropertyName(node, 'fontSize');
189
- if (!fontSizeNode || !isValidPropertyNode(fontSizeNode) || isDecendantOfGlobalToken(fontSizeNode.value)) {
210
+ if (!fontSizeNode || !isValidPropertyNode(fontSizeNode)) {
190
211
  return {
191
212
  success: false
192
213
  };
@@ -49,6 +49,15 @@ export var typographyValueToToken = typographyTokens
49
49
  values: individualValues
50
50
  };
51
51
  });
52
+ export function isValidTypographyToken(tokenName) {
53
+ return typographyTokens.filter(function (t) {
54
+ return t.attributes.group === 'typography';
55
+ }).filter(function (t) {
56
+ return t.cleanName.includes('font.heading') || t.cleanName.includes('font.body') || t.cleanName.includes('font.code');
57
+ }).find(function (t) {
58
+ return t.cleanName === tokenName;
59
+ });
60
+ }
52
61
  export function findTypographyTokenForValues(fontSize, lineHeight) {
53
62
  var matchingTokens = typographyValueToToken.filter(function (token) {
54
63
  return token.values.fontSize === fontSize;