@atlaskit/eslint-plugin-design-system 4.0.0 → 4.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 (65) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cjs/index.js +14 -3
  3. package/dist/cjs/rules/ensure-design-token-usage/index.js +3 -0
  4. package/dist/cjs/rules/no-deprecated-apis/index.js +91 -0
  5. package/dist/cjs/rules/no-deprecated-design-token-usage/index.js +76 -0
  6. package/dist/cjs/rules/no-deprecated-imports/index.js +45 -33
  7. package/dist/cjs/rules/no-deprecated-imports/paths.js +43 -15
  8. package/dist/cjs/rules/no-unsafe-design-token-usage/index.js +19 -5
  9. package/dist/cjs/rules/use-visually-hidden/constants.js +12 -0
  10. package/dist/cjs/rules/use-visually-hidden/fix-jsx.js +39 -0
  11. package/dist/cjs/rules/use-visually-hidden/fix-vanilla.js +35 -0
  12. package/dist/cjs/rules/use-visually-hidden/index.js +194 -0
  13. package/dist/cjs/rules/use-visually-hidden/utils.js +91 -0
  14. package/dist/cjs/rules/utils/get-import-node-by-source.js +23 -0
  15. package/dist/cjs/rules/utils/is-color.js +3 -2
  16. package/dist/cjs/rules/utils/is-node.js +33 -3
  17. package/dist/cjs/rules/utils/remove-named-import.js +29 -0
  18. package/dist/cjs/version.json +1 -1
  19. package/dist/es2019/index.js +10 -2
  20. package/dist/es2019/rules/ensure-design-token-usage/index.js +3 -0
  21. package/dist/es2019/rules/no-deprecated-apis/index.js +72 -0
  22. package/dist/es2019/rules/no-deprecated-design-token-usage/index.js +58 -0
  23. package/dist/es2019/rules/no-deprecated-imports/index.js +45 -32
  24. package/dist/es2019/rules/no-deprecated-imports/paths.js +41 -14
  25. package/dist/es2019/rules/no-unsafe-design-token-usage/index.js +12 -5
  26. package/dist/es2019/rules/use-visually-hidden/constants.js +3 -0
  27. package/dist/es2019/rules/use-visually-hidden/fix-jsx.js +24 -0
  28. package/dist/es2019/rules/use-visually-hidden/fix-vanilla.js +21 -0
  29. package/dist/es2019/rules/use-visually-hidden/index.js +169 -0
  30. package/dist/es2019/rules/use-visually-hidden/utils.js +62 -0
  31. package/dist/es2019/rules/utils/get-import-node-by-source.js +10 -0
  32. package/dist/es2019/rules/utils/is-color.js +2 -1
  33. package/dist/es2019/rules/utils/is-node.js +19 -2
  34. package/dist/es2019/rules/utils/remove-named-import.js +16 -0
  35. package/dist/es2019/version.json +1 -1
  36. package/dist/esm/index.js +10 -2
  37. package/dist/esm/rules/ensure-design-token-usage/index.js +3 -0
  38. package/dist/esm/rules/no-deprecated-apis/index.js +81 -0
  39. package/dist/esm/rules/no-deprecated-design-token-usage/index.js +66 -0
  40. package/dist/esm/rules/no-deprecated-imports/index.js +45 -32
  41. package/dist/esm/rules/no-deprecated-imports/paths.js +41 -14
  42. package/dist/esm/rules/no-unsafe-design-token-usage/index.js +20 -5
  43. package/dist/esm/rules/use-visually-hidden/constants.js +3 -0
  44. package/dist/esm/rules/use-visually-hidden/fix-jsx.js +26 -0
  45. package/dist/esm/rules/use-visually-hidden/fix-vanilla.js +23 -0
  46. package/dist/esm/rules/use-visually-hidden/index.js +180 -0
  47. package/dist/esm/rules/use-visually-hidden/utils.js +74 -0
  48. package/dist/esm/rules/utils/get-import-node-by-source.js +14 -0
  49. package/dist/esm/rules/utils/is-color.js +2 -1
  50. package/dist/esm/rules/utils/is-node.js +22 -1
  51. package/dist/esm/rules/utils/remove-named-import.js +20 -0
  52. package/dist/esm/version.json +1 -1
  53. package/dist/types/index.d.ts +5 -0
  54. package/dist/types/rules/no-deprecated-apis/index.d.ts +3 -0
  55. package/dist/types/rules/no-deprecated-design-token-usage/index.d.ts +3 -0
  56. package/dist/types/rules/no-deprecated-imports/paths.d.ts +33 -14
  57. package/dist/types/rules/use-visually-hidden/constants.d.ts +3 -0
  58. package/dist/types/rules/use-visually-hidden/fix-jsx.d.ts +3 -0
  59. package/dist/types/rules/use-visually-hidden/fix-vanilla.d.ts +3 -0
  60. package/dist/types/rules/use-visually-hidden/index.d.ts +3 -0
  61. package/dist/types/rules/use-visually-hidden/utils.d.ts +35 -0
  62. package/dist/types/rules/utils/get-import-node-by-source.d.ts +8 -0
  63. package/dist/types/rules/utils/is-node.d.ts +7 -0
  64. package/dist/types/rules/utils/remove-named-import.d.ts +8 -0
  65. package/package.json +6 -2
@@ -0,0 +1,81 @@
1
+ // eslint-disable-next-line import/no-unresolved
2
+ import { getImportedNodeBySource } from '../utils/get-import-node-by-source';
3
+ import { getClosestNodeOfType } from '../utils/is-node';
4
+ var unsafeOverridesConfig = {
5
+ cssFn: ['@atlaskit/menu', '@atlaskit/side-navigation'],
6
+ overrides: ['@atlaskit/drawer', '@atlaskit/menu', '@atlaskit/side-navigation']
7
+ };
8
+ var unsafeOverrides = ['cssFn', 'overrides'];
9
+ var rule = {
10
+ meta: {
11
+ type: 'suggestion',
12
+ docs: {
13
+ description: 'Disallow specified APIs that have been marked as deprecated and/or discouraged.',
14
+ recommended: true
15
+ },
16
+ messages: {
17
+ noDeprecatedApis: "The prop \"{{propName}}\" has been deprecated. It will be removed in the next major release."
18
+ }
19
+ },
20
+ create: function create(context) {
21
+ return {
22
+ // find JSX atribute - find name of attribute - get source and find relevant identifiers.
23
+ JSXAttribute: function (_JSXAttribute) {
24
+ function JSXAttribute(_x) {
25
+ return _JSXAttribute.apply(this, arguments);
26
+ }
27
+
28
+ JSXAttribute.toString = function () {
29
+ return _JSXAttribute.toString();
30
+ };
31
+
32
+ return JSXAttribute;
33
+ }(function (node) {
34
+ var _node$name, _closesetJSXElement$n;
35
+
36
+ if (!unsafeOverrides.includes(node === null || node === void 0 ? void 0 : (_node$name = node.name) === null || _node$name === void 0 ? void 0 : _node$name.name)) {
37
+ return;
38
+ }
39
+
40
+ var source = context.getSourceCode();
41
+ var bannedApi = node.name.name; // traverse the tree to the nearest JSX Element and get its name
42
+
43
+ var closesetJSXElement = getClosestNodeOfType(node, 'JSXOpeningElement'); // @ts-ignore
44
+
45
+ var jsxElementName = closesetJSXElement === null || closesetJSXElement === void 0 ? void 0 : (_closesetJSXElement$n = closesetJSXElement.name) === null || _closesetJSXElement$n === void 0 ? void 0 : _closesetJSXElement$n.name;
46
+
47
+ if (!jsxElementName) {
48
+ return;
49
+ } // find an import for the path of the banned api
50
+
51
+
52
+ unsafeOverridesConfig[bannedApi].forEach(function (path) {
53
+ var importNode = getImportedNodeBySource(source, path);
54
+
55
+ if (!importNode) {
56
+ return;
57
+ } // find an import that matches our JSX element
58
+
59
+
60
+ var hasTargetNode = importNode.specifiers.some(function (node) {
61
+ return node.local.name === jsxElementName;
62
+ });
63
+
64
+ if (!hasTargetNode) {
65
+ return;
66
+ } // if we're here the import exists and there is a valid lint error.
67
+
68
+
69
+ context.report({
70
+ node: node,
71
+ messageId: 'noDeprecatedApis',
72
+ data: {
73
+ propName: bannedApi
74
+ }
75
+ });
76
+ });
77
+ })
78
+ };
79
+ }
80
+ };
81
+ export default rule;
@@ -0,0 +1,66 @@
1
+ import renameMapping from '@atlaskit/tokens/rename-mapping';
2
+
3
+ var getCleanPathId = function getCleanPathId(path) {
4
+ return path.split('.').filter(function (el) {
5
+ return el !== '[default]';
6
+ }).join('.');
7
+ };
8
+
9
+ var rule = {
10
+ meta: {
11
+ docs: {
12
+ recommended: true
13
+ },
14
+ fixable: 'code',
15
+ type: 'problem',
16
+ messages: {
17
+ tokenRenamed: 'The token "{{name}}" is deprecated in favour of "{{replacement}}".'
18
+ }
19
+ },
20
+ create: function create(context) {
21
+ return {
22
+ 'CallExpression[callee.name="token"]': function CallExpressionCalleeNameToken(node) {
23
+ if (node.type !== 'CallExpression') {
24
+ return;
25
+ }
26
+
27
+ if (node.arguments[0].type !== 'Literal') {
28
+ return;
29
+ }
30
+
31
+ var tokenKey = node.arguments[0].value;
32
+
33
+ if (!tokenKey) {
34
+ return;
35
+ }
36
+
37
+ if (typeof tokenKey !== 'string') {
38
+ return;
39
+ }
40
+
41
+ var migrationMeta = renameMapping.filter(function (t) {
42
+ return t.state === 'deprecated';
43
+ }).find(function (t) {
44
+ return t.path === tokenKey;
45
+ });
46
+
47
+ if (migrationMeta) {
48
+ var cleanTokenKey = getCleanPathId(migrationMeta.replacement);
49
+ context.report({
50
+ messageId: 'tokenRenamed',
51
+ node: node,
52
+ data: {
53
+ name: tokenKey,
54
+ replacement: cleanTokenKey
55
+ },
56
+ fix: function fix(fixer) {
57
+ return fixer.replaceText(node.arguments[0], "'".concat(cleanTokenKey, "'"));
58
+ }
59
+ });
60
+ return;
61
+ }
62
+ }
63
+ };
64
+ }
65
+ };
66
+ export default rule;
@@ -49,17 +49,24 @@ var rule = {
49
49
  }
50
50
  },
51
51
  create: function create(context) {
52
- var sourceCode = context.getSourceCode();
53
52
  var restrictedPathMessages = restrictedPaths.reduce(function (memo, importSource) {
54
53
  if (typeof importSource === 'string') {
55
54
  memo[importSource] = {
56
55
  message: ''
57
56
  };
58
57
  } else {
59
- memo[importSource.name] = {
60
- message: importSource.message // importNames: importSource.importNames
58
+ if ('message' in importSource) {
59
+ memo[importSource.path] = {
60
+ message: importSource.message
61
+ };
62
+ }
61
63
 
62
- };
64
+ if ('imports' in importSource) {
65
+ memo[importSource.path] = {
66
+ // @ts-ignore
67
+ imports: importSource.imports
68
+ };
69
+ }
63
70
  }
64
71
 
65
72
  return memo;
@@ -67,26 +74,45 @@ var rule = {
67
74
  /**
68
75
  * Report a restricted path.
69
76
  * @param {string} importSource path of the import
70
- * @param {Map<string,Object[]>} importNames Map of import names that are being imported
71
77
  * @param {node} node representing the restricted path reference
78
+ * @param {Map<string,Rule.Node>} importNames Map of import names that are being imported
72
79
  * @returns {void}
73
80
  * @private
74
81
  */
75
82
 
76
- function checkRestrictedPathAndReport(importSource, importNames, node) {
83
+ function checkRestrictedPathAndReport(importSource, node, importNames) {
77
84
  if (!Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) {
78
85
  return;
79
86
  }
80
87
 
81
- var customMessage = restrictedPathMessages[importSource].message;
82
- context.report({
83
- node: node,
84
- messageId: customMessage ? 'pathWithCustomMessage' : 'path',
85
- data: {
86
- importSource: importSource,
87
- customMessage: customMessage
88
- }
89
- });
88
+ var config = restrictedPathMessages[importSource]; // The message will only exist if the import is completely banned,
89
+ // eg a deprecated package
90
+
91
+ if ('message' in config) {
92
+ context.report({
93
+ node: node,
94
+ messageId: config.message ? 'pathWithCustomMessage' : 'path',
95
+ data: {
96
+ importSource: importSource,
97
+ customMessage: config.message
98
+ }
99
+ });
100
+ } // if there are specific named exports that are banned,
101
+ // iterate through and check if they're being imported
102
+
103
+
104
+ if ('imports' in config) {
105
+ var _config$imports;
106
+
107
+ (_config$imports = config.imports) === null || _config$imports === void 0 ? void 0 : _config$imports.forEach(function (restrictedImport) {
108
+ if (importNames.has(restrictedImport.importName)) {
109
+ context.report({
110
+ node: importNames.get(restrictedImport.importName),
111
+ message: restrictedImport.message
112
+ });
113
+ }
114
+ });
115
+ }
90
116
  }
91
117
  /**
92
118
  * Checks a node to see if any problems should be reported.
@@ -100,12 +126,7 @@ var rule = {
100
126
  var importSource = node.source.value.trim();
101
127
  var importNames = new Map();
102
128
 
103
- if (node.type === 'ExportAllDeclaration') {
104
- var starToken = sourceCode.getFirstToken(node, 1);
105
- importNames.set('*', [{
106
- loc: starToken === null || starToken === void 0 ? void 0 : starToken.loc
107
- }]);
108
- } else if ('specifiers' in node) {
129
+ if ('specifiers' in node) {
109
130
  // @ts-ignore
110
131
  var _iterator = _createForOfIteratorHelper(node.specifiers),
111
132
  _step;
@@ -114,9 +135,6 @@ var rule = {
114
135
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
115
136
  var specifier = _step.value;
116
137
  var name = void 0;
117
- var specifierData = {
118
- loc: specifier.loc
119
- };
120
138
 
121
139
  if (specifier.type === 'ImportDefaultSpecifier') {
122
140
  name = 'default';
@@ -129,11 +147,7 @@ var rule = {
129
147
  }
130
148
 
131
149
  if (name) {
132
- if (importNames.has(name)) {
133
- importNames.get(name).push(specifierData);
134
- } else {
135
- importNames.set(name, [specifierData]);
136
- }
150
+ importNames.set(name, specifier);
137
151
  }
138
152
  }
139
153
  } catch (err) {
@@ -143,7 +157,7 @@ var rule = {
143
157
  }
144
158
  }
145
159
 
146
- checkRestrictedPathAndReport(importSource, importNames, node);
160
+ checkRestrictedPathAndReport(importSource, node, importNames);
147
161
  };
148
162
 
149
163
  return {
@@ -152,8 +166,7 @@ var rule = {
152
166
  if (node.source) {
153
167
  checkNode(node);
154
168
  }
155
- },
156
- ExportAllDeclaration: checkNode
169
+ }
157
170
  };
158
171
  }
159
172
  };
@@ -1,43 +1,70 @@
1
+ export var namedThemeExports = [{
2
+ importName: 'assistive',
3
+ message: 'The assistive mixin is deprecated. Please use `@atlaskit/visually-hidden` instead.'
4
+ }, {
5
+ importName: 'visuallyHidden',
6
+ message: 'The visuallyHidden mixin is deprecated. Please use `@atlaskit/visually-hidden` instead.'
7
+ }, {
8
+ importName: 'focusRing',
9
+ message: 'The focusRing mixin is deprecated. Please use `@atlaskit/visually-hidden` instead.'
10
+ }];
1
11
  export var restrictedPaths = [{
2
- name: '@atlaskit/navigation-next',
12
+ path: '@atlaskit/navigation-next',
3
13
  message: "navigation-next is deprecated. Please use '@atlaskit/atlassian-navigation' instead."
4
14
  }, {
5
- name: '@atlaskit/field-base',
15
+ path: '@atlaskit/field-base',
6
16
  message: "field-base is deprecated. Please use the '@atlaskit/form' package instead."
7
17
  }, {
8
- name: '@atlaskit/field-radio-group',
18
+ path: '@atlaskit/field-radio-group',
9
19
  message: "field-radio-group is deprecated. Please use '@atlaskit/radio' instead, and check the migration guide."
10
20
  }, {
11
- name: '@atlaskit/field-range',
21
+ path: '@atlaskit/field-range',
12
22
  message: "field-range is deprecated. Please use '@atlaskit/range' instead."
13
23
  }, {
14
- name: '@atlaskit/field-text',
24
+ path: '@atlaskit/field-text',
15
25
  message: "field-text is deprecated. Please use '@atlaskit/textfield' instead."
16
26
  }, {
17
- name: '@atlaskit/field-text-area',
27
+ path: '@atlaskit/field-text-area',
18
28
  message: "field-text-area is deprecated. Please use '@atlaskit/textarea' instead."
19
29
  }, {
20
- name: '@atlaskit/navigation',
30
+ path: '@atlaskit/navigation',
21
31
  message: "navigation is deprecated. Please use '@atlaskit/atlassian-navigation' instead."
22
32
  }, {
23
- name: '@atlaskit/global-navigation',
33
+ path: '@atlaskit/global-navigation',
24
34
  message: "global-navigation is deprecated. Please use '@atlaskit/atlassian-navigation' for the horizontal nav bar, '@atlaskit/side-navigation' for the side nav, and '@atlaskit/page-layout' to layout your application."
25
35
  }, {
26
- name: '@atlaskit/input',
36
+ path: '@atlaskit/input',
27
37
  message: 'input is deprecated. This was an internal component and should not be used directly.'
28
38
  }, {
29
- name: '@atlaskit/layer',
39
+ path: '@atlaskit/layer',
30
40
  message: 'layer is deprecated. This was an internal component and should not be used directly.'
31
41
  }, {
32
- name: '@atlaskit/single-select',
42
+ path: '@atlaskit/single-select',
33
43
  message: "single-select is deprecated. Please use '@atlaskit/select' instead."
34
44
  }, {
35
- name: '@atlaskit/multi-select',
45
+ path: '@atlaskit/multi-select',
36
46
  message: "multi-select is deprecated. Please use '@atlaskit/select' instead."
37
47
  }, {
38
- name: '@atlaskit/droplist',
48
+ path: '@atlaskit/droplist',
39
49
  message: "droplist is deprecated. For the pop-up behaviour please use '@atlaskit/popup' and for common menu components please use '@atlaskit/menu'."
40
50
  }, {
41
- name: '@atlaskit/item',
51
+ path: '@atlaskit/item',
42
52
  message: "item is deprecated. Please use '@atlaskit/menu' instead."
53
+ }, // TODO uncomment me when we formally deprecate typography
54
+ // {
55
+ // path: '@atlaskit/theme/typography',
56
+ // message: 'The typography mixins are deprecated. Please use `@atlaskit/heading` instead.',
57
+ // },
58
+ {
59
+ path: '@atlaskit/theme/constants',
60
+ imports: namedThemeExports
61
+ }, {
62
+ path: '@atlaskit/theme',
63
+ imports: namedThemeExports // .concat(
64
+ // { importName: 'typography', message: 'The typography mixins are deprecated. Please use `@atlaskit/heading` instead.',}
65
+ // )
66
+
67
+ }, {
68
+ path: '@atlaskit/icon-priority',
69
+ message: "icon-priority is deprecated due to limited usage in Cloud products. It will be deleted after 21 April 2022."
43
70
  }];
@@ -2,6 +2,13 @@ import renameMapping from '@atlaskit/tokens/rename-mapping';
2
2
  import tokens from '@atlaskit/tokens/token-names';
3
3
  import { isDecendantOfStyleBlock, isDecendantOfStyleJsxAttribute } from '../utils/is-node';
4
4
  import { isToken } from '../utils/is-token';
5
+
6
+ var getCleanPathId = function getCleanPathId(path) {
7
+ return path.split('.').filter(function (el) {
8
+ return el !== '[default]';
9
+ }).join('.');
10
+ };
11
+
5
12
  var defaultConfig = {
6
13
  shouldEnforceFallbacks: false
7
14
  };
@@ -16,7 +23,7 @@ var rule = {
16
23
  directTokenUsage: "Access the global theme using the token function.\n\n```\nimport { token } from '@atlaskit/tokens';\n\ntoken('{{tokenKey}}');\n```\n",
17
24
  staticToken: "Token string should be inlined directly into the function call.\n\n```\ntoken('color.background.blanket');\n```\n",
18
25
  invalidToken: 'The token "{{name}}" does not exist.',
19
- tokenRenamed: 'The token "{{name}}" has been renamed.',
26
+ tokenRemoved: 'The token "{{name}}" is removed in favour of "{{replacement}}".',
20
27
  tokenFallbackEnforced: "Token function requires a fallback, preferably something that best matches the light/default theme in case tokens aren't present.\n\n```\ntoken('color.background.blanket', N500A);\n```\n ",
21
28
  tokenFallbackRestricted: "Token function must not use a fallback.\n\n```\ntoken('color.background.blanket');\n```\n "
22
29
  }
@@ -117,15 +124,23 @@ var rule = {
117
124
  return;
118
125
  }
119
126
 
120
- if (typeof tokenKey === 'string' && tokenKey in renameMapping) {
127
+ var migrationMeta = renameMapping.filter(function (t) {
128
+ return t.state === 'deleted';
129
+ }).find(function (t) {
130
+ return t.path === tokenKey;
131
+ });
132
+
133
+ if (typeof tokenKey === 'string' && migrationMeta) {
134
+ var cleanTokenKey = getCleanPathId(migrationMeta.replacement);
121
135
  context.report({
122
- messageId: 'tokenRenamed',
136
+ messageId: 'tokenRemoved',
123
137
  node: node,
124
138
  data: {
125
- name: tokenKey
139
+ name: tokenKey,
140
+ replacement: cleanTokenKey
126
141
  },
127
142
  fix: function fix(fixer) {
128
- return fixer.replaceText(node.arguments[0], "'".concat(renameMapping[tokenKey], "'"));
143
+ return fixer.replaceText(node.arguments[0], "'".concat(cleanTokenKey, "'"));
129
144
  }
130
145
  });
131
146
  return;
@@ -0,0 +1,3 @@
1
+ export var IMPORT_NAME = 'AKVisuallyHidden';
2
+ export var VISUALLY_HIDDEN_SOURCE = '@atlaskit/visually-hidden';
3
+ export var VISUALLY_HIDDEN_IMPORT = "import ".concat(IMPORT_NAME, " from '").concat(VISUALLY_HIDDEN_SOURCE, "';\n");
@@ -0,0 +1,26 @@
1
+ import { getImportedNodeBySource } from '../utils/get-import-node-by-source';
2
+ import { getClosestNodeOfType } from '../utils/is-node';
3
+ import { IMPORT_NAME, VISUALLY_HIDDEN_IMPORT, VISUALLY_HIDDEN_SOURCE } from './constants';
4
+ import { getFirstImport } from './utils';
5
+ export default (function (source, node) {
6
+ return function (fixer) {
7
+ var fixes = [];
8
+ var importedNode = getFirstImport(source);
9
+ var visuallyHiddenNode = getImportedNodeBySource(source, VISUALLY_HIDDEN_SOURCE);
10
+
11
+ if (!importedNode) {
12
+ return [];
13
+ }
14
+
15
+ var jsxOpeningElement = getClosestNodeOfType(node, 'JSXOpeningElement');
16
+
17
+ if (visuallyHiddenNode) {
18
+ fixes.push(fixer.replaceText(jsxOpeningElement, visuallyHiddenNode.specifiers[0].local.name));
19
+ } else {
20
+ fixes.push(fixer.insertTextBefore(importedNode, VISUALLY_HIDDEN_IMPORT));
21
+ fixes.push(fixer.replaceText(jsxOpeningElement, "<".concat(IMPORT_NAME, " />")));
22
+ }
23
+
24
+ return fixes;
25
+ };
26
+ });
@@ -0,0 +1,23 @@
1
+ import { getImportedNodeBySource } from '../utils/get-import-node-by-source';
2
+ import { IMPORT_NAME, VISUALLY_HIDDEN_IMPORT, VISUALLY_HIDDEN_SOURCE } from './constants';
3
+ import { getFirstImport } from './utils';
4
+ export default (function (source, node) {
5
+ return function (fixer) {
6
+ var fixes = [];
7
+ var importedNode = getFirstImport(source);
8
+ var visuallyHiddenNode = getImportedNodeBySource(source, VISUALLY_HIDDEN_SOURCE);
9
+
10
+ if (!importedNode) {
11
+ return [];
12
+ }
13
+
14
+ if (visuallyHiddenNode) {
15
+ fixes.push(fixer.replaceText(node, visuallyHiddenNode.specifiers[0].local.name));
16
+ } else {
17
+ fixes.push(fixer.insertTextBefore(importedNode, VISUALLY_HIDDEN_IMPORT));
18
+ fixes.push(fixer.replaceText(node, IMPORT_NAME));
19
+ }
20
+
21
+ return fixes;
22
+ };
23
+ });
@@ -0,0 +1,180 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+
3
+ /* eslint-disable @atlaskit/design-system/use-visually-hidden */
4
+ import { getClosestNodeOfType, isStyledObjectNode, isStyledTemplateNode } from '../utils/is-node';
5
+ import fixJsx from './fix-jsx';
6
+ import fixVanilla from './fix-vanilla';
7
+ import { countMatchingKeyValues, getObjectLikeness, makeTemplateLiteralIntoEntries } from './utils';
8
+ var THEME_IMPORT_NAMES = ['visuallyHidden', 'assistive'];
9
+ var rule = {
10
+ meta: {
11
+ type: 'suggestion',
12
+ fixable: 'code',
13
+ docs: {
14
+ description: 'Suggest usage of the `@atlaskit/visually-hidden` component instead of either the `@atlaskit/theme` mixins or just something the user has rolled themselves.',
15
+ recommended: true
16
+ },
17
+ messages: {
18
+ noDeprecatedUsage: 'Using the export `{{local}}` from `{{import}}` as a mixin is discouraged. Please use `@atlaskit/visually-hidden` instead.',
19
+ noDeprecated: 'The export `{{local}}` from `{{import}}` is deprecated. Please use `@atlaskit/visually-hidden` instead.',
20
+ suggestion: 'This CSS closely matches the implementation of a visually hidden element. You should consider using the `@atlaskit/visually-hidden` component instead.'
21
+ }
22
+ },
23
+ create: function create(context) {
24
+ var source = context.getSourceCode();
25
+ return {
26
+ ImportDeclaration: function ImportDeclaration(node) {
27
+ var isThemeNode = node.source.value === '@atlaskit/theme' || node.source.value === '@atlaskit/theme/constants';
28
+
29
+ if (!isThemeNode) {
30
+ return;
31
+ }
32
+
33
+ var visuallyHiddenOrAssistive = node.specifiers.filter(function (specifier) {
34
+ return specifier.type === 'ImportSpecifier';
35
+ }).find(function (specifier) {
36
+ return THEME_IMPORT_NAMES.includes(specifier.imported.name);
37
+ });
38
+
39
+ if (!visuallyHiddenOrAssistive) {
40
+ return;
41
+ }
42
+
43
+ context.getDeclaredVariables(visuallyHiddenOrAssistive).forEach(function (someNode) {
44
+ someNode.references.map(function (innerNode) {
45
+ return innerNode.identifier;
46
+ }).forEach(function (idNode) {
47
+ // @ts-ignore JSX is not typed correctly in eslint
48
+ if ((idNode === null || idNode === void 0 ? void 0 : idNode.parent.type) === 'JSXExpressionContainer') {
49
+ context.report({
50
+ node: idNode.parent,
51
+ messageId: 'noDeprecatedUsage',
52
+ data: {
53
+ import: "".concat(node.source.value),
54
+ local: visuallyHiddenOrAssistive.local.name
55
+ },
56
+ fix: fixJsx(source, idNode)
57
+ }); // this is either a styled usage OR mixin usage in a styled usage
58
+ } else if (idNode.parent.type === 'CallExpression') {
59
+ if (isStyledObjectNode(idNode.parent) || isStyledTemplateNode(idNode.parent)) {
60
+ context.report({
61
+ node: idNode.parent,
62
+ messageId: 'noDeprecatedUsage',
63
+ data: {
64
+ import: "".concat(node.source.value),
65
+ local: visuallyHiddenOrAssistive.local.name
66
+ },
67
+ fix: fixVanilla(source, idNode.parent)
68
+ });
69
+ }
70
+
71
+ if (idNode.parent.callee === idNode) {
72
+ context.report({
73
+ node: idNode.parent,
74
+ messageId: 'noDeprecatedUsage',
75
+ data: {
76
+ import: "".concat(node.source.value),
77
+ local: visuallyHiddenOrAssistive.local.name
78
+ },
79
+ fix: fixVanilla(source, getClosestNodeOfType(idNode.parent, 'TaggedTemplateExpression'))
80
+ });
81
+ }
82
+ }
83
+ });
84
+ });
85
+ return context.report({
86
+ node: visuallyHiddenOrAssistive,
87
+ messageId: 'noDeprecated',
88
+ data: {
89
+ import: "".concat(node.source.value),
90
+ local: visuallyHiddenOrAssistive.local.name
91
+ }
92
+ });
93
+ },
94
+ CallExpression: function CallExpression(node) {
95
+ var _node$arguments$;
96
+
97
+ if (node.type !== 'CallExpression') {
98
+ return;
99
+ }
100
+
101
+ if (!(node.callee.type === 'MemberExpression' || node.callee.type === 'Identifier')) {
102
+ return;
103
+ }
104
+
105
+ var isStyled = isStyledObjectNode(node);
106
+
107
+ if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name !== 'styled') {
108
+ return;
109
+ }
110
+
111
+ if (node.callee.type === 'Identifier' && node.callee.name !== 'css') {
112
+ return;
113
+ } // This is an object style (probably)
114
+
115
+
116
+ if (node.arguments && ((_node$arguments$ = node.arguments[0]) === null || _node$arguments$ === void 0 ? void 0 : _node$arguments$.type) === 'ObjectExpression') {
117
+ var matchingScore = getObjectLikeness(node.arguments[0]);
118
+
119
+ if (matchingScore > 0.8) {
120
+ return context.report({
121
+ node: node.parent,
122
+ messageId: 'suggestion',
123
+ fix: isStyled ? fixVanilla(source, node) : undefined
124
+ });
125
+ }
126
+ }
127
+
128
+ return null;
129
+ },
130
+ ObjectExpression: function ObjectExpression(node) {
131
+ if (node.parent.type === 'CallExpression') {
132
+ return;
133
+ }
134
+
135
+ var matchingScore = getObjectLikeness(node);
136
+
137
+ if (matchingScore > 0.8) {
138
+ return context.report({
139
+ node: node,
140
+ messageId: 'suggestion'
141
+ });
142
+ }
143
+ },
144
+ 'TaggedTemplateExpression[tag.name="css"],TaggedTemplateExpression[tag.object.name="styled"]': function TaggedTemplateExpressionTagNameCssTaggedTemplateExpressionTagObjectNameStyled(node) {
145
+ if (node.type !== 'TaggedTemplateExpression') {
146
+ return;
147
+ }
148
+
149
+ var templateString = node.quasi.quasis.map(function (q) {
150
+ return q.value.raw;
151
+ }).join('');
152
+ var styleEntries = makeTemplateLiteralIntoEntries(templateString);
153
+
154
+ if (!styleEntries) {
155
+ return;
156
+ }
157
+
158
+ var count = countMatchingKeyValues(styleEntries.map(function (_ref) {
159
+ var _ref2 = _slicedToArray(_ref, 2),
160
+ key = _ref2[0],
161
+ value = _ref2[1];
162
+
163
+ return {
164
+ key: key,
165
+ value: value
166
+ };
167
+ }));
168
+
169
+ if (count > 0.8) {
170
+ return context.report({
171
+ node: node,
172
+ messageId: 'suggestion',
173
+ fix: node.tag.type !== 'Identifier' ? fixVanilla(source, node) : undefined
174
+ });
175
+ }
176
+ }
177
+ };
178
+ }
179
+ };
180
+ export default rule;