@atlaskit/eslint-plugin-design-system 8.26.0 → 8.27.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 (23) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/constellation/index/usage.mdx +42 -8
  3. package/dist/cjs/rules/consistent-css-prop-usage/index.js +254 -32
  4. package/dist/cjs/rules/utils/create-no-exported-rule/is-styled-component.js +6 -10
  5. package/dist/cjs/rules/utils/get-first-supported-import.js +28 -0
  6. package/dist/cjs/rules/utils/is-supported-import.js +2 -1
  7. package/dist/es2019/rules/consistent-css-prop-usage/index.js +251 -33
  8. package/dist/es2019/rules/utils/create-no-exported-rule/is-styled-component.js +4 -8
  9. package/dist/es2019/rules/utils/get-first-supported-import.js +22 -0
  10. package/dist/es2019/rules/utils/is-supported-import.js +2 -1
  11. package/dist/esm/rules/consistent-css-prop-usage/index.js +255 -33
  12. package/dist/esm/rules/utils/create-no-exported-rule/is-styled-component.js +6 -10
  13. package/dist/esm/rules/utils/get-first-supported-import.js +22 -0
  14. package/dist/esm/rules/utils/is-supported-import.js +2 -1
  15. package/dist/types/rules/consistent-css-prop-usage/types.d.ts +7 -2
  16. package/dist/types/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +1 -1
  17. package/dist/types/rules/utils/get-first-supported-import.d.ts +17 -0
  18. package/dist/types/rules/utils/is-supported-import.d.ts +1 -0
  19. package/dist/types-ts4.5/rules/consistent-css-prop-usage/types.d.ts +7 -2
  20. package/dist/types-ts4.5/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +1 -1
  21. package/dist/types-ts4.5/rules/utils/get-first-supported-import.d.ts +17 -0
  22. package/dist/types-ts4.5/rules/utils/is-supported-import.d.ts +1 -0
  23. package/package.json +3 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @atlaskit/eslint-plugin-design-system
2
2
 
3
+ ## 8.27.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#72966](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/72966) [`ec187f466e23`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/ec187f466e23) - Update `consistent-css-prop-usage` to incorporate some updates previously made to the `@compiled/eslint-plugin` equivalent.
8
+
9
+ 1. Add autofixer to add the `css` function for the following scenario:
10
+
11
+ ```
12
+ const styles = { ... };
13
+ <div css={styles} />
14
+ ```
15
+
16
+ Note that this autofixer will not run if local variables are used inside the style object (e.g. `{ height: makeTaller ? '5px' : '2px' }`), or if there are spread elements, template literals, and other tricky-to-parse code. These continue to require fixing manually.
17
+
18
+ (This rule would previously only autofix if the file was originally `<div css={{ ... }} />`)
19
+
20
+ 2. Add `import { css } from '@compiled/react'` (or `xcss`) automatically when fixing. The package from which to import the `css` function can be specified through the `importSource` option.
21
+
22
+ 3. Add `excludeReactComponents` to exclude linting React components (i.e. components that start with uppercase). Sometimes it may not be desirable to have this rule apply to React components (e.g. `@atlaskit/button`), which could either use the Emotion or Compiled APIs when they expose a `css` prop. Passing a function from the wrong library can result in the styling erroneously not being applied.
23
+
24
+ 4. Treat `{ ... } as const` statements the same way as `{ ... }` objects.
25
+
26
+ 5. Add `fixNamesOnly` to disable all autofixers _except_ the autofixer that adds `styles` to the end of existing style variables. For example, in `<div css={buttonComponent} />; const buttonComponent = css({ ... })`, `buttonComponent` will continue to be renamed to `buttonComponentStyles`. Autofixers that will be _disabled_ include hoisting the styles to the top-most scope, and adding the `css` function call around style objects.
27
+
3
28
  ## 8.26.0
4
29
 
5
30
  ### Minor Changes
@@ -58,14 +58,19 @@ Every product should be defining styles in the same way, using the same tools, e
58
58
 
59
59
  This rule checks for the following cases:
60
60
 
61
- - When styles are defined inline.
62
- - When styles are not using `css` object api.
63
- - When styles are coming from outside of the module i.e. using imports.
64
- - When styles are spread inside another styles and not using array composition.
61
+ - Styles should not be defined inline; it should instead be in a standalone variable.
62
+ - The exception for this is style composition (e.g. `<div css={[baseStyles, moreStyles]} />`), which is a way to combine styles from two variables.
63
+ - Styles must be wrapped in a `css` function call.
64
+ - Styles must be defined in the same file as their usage, and not be imported.
65
+ - Styles should not contain spread operators (e.g. `css({ ...spreadStyles })`).
66
+ - Styles must all be defined at the top of the file, or at the bottom of the file.
67
+ - Styles must be in a variable whose name ends in `styles` (or `Styles`).
68
+
69
+ This rule also has an autofixer that enforces and fixes the code (where practical) to meet the above requirements.
65
70
 
66
71
  All the above can also work for custom `css` functions, such as `xcss` (https://atlassian.design/components/primitives/xcss/).
67
72
 
68
- This rule has options - see below.
73
+ This rule has several options - see below.
69
74
 
70
75
  <h3>Examples</h3>
71
76
 
@@ -197,13 +202,44 @@ This rule comes with options to support different repository configurations.
197
202
 
198
203
  #### cssFunctions
199
204
 
200
- An array of function names the linting rule should target. Defaults to `['css', 'xcss']`. Functionality of cssMap will be linted regardless of the configuration of `cssFunctions` as it can be used with either attribute.
205
+ An array of function names the linting rule should target. Defaults to `['css', 'xcss']`. The functionality of `cssMap` will be linted regardless of the configuration of `cssFunctions`, as it can be used with either attribute.
201
206
 
202
207
  #### stylesPlacement
203
208
 
209
+ Either `top` or `bottom`.
210
+
204
211
  The rule prevents inline styles from being created. This option defines what the error message should say: "(...) styles at the top (...)" or "(...) styles at the bottom (...)".
205
212
  Defaults to `top`.
206
213
 
214
+ #### cssImportSource
215
+
216
+ When auto-fixing the contents of the `css` attribute, this rule will wrap CSS styles in a `css(...)` function call or `` css\`...\` `` template expression, and it will add an import declaration for the `css` function. `cssImportSource` is a string that determines what package `css` should be imported from.
217
+
218
+ This is `@compiled/react` by default.
219
+
220
+ #### xcssImportSource
221
+
222
+ When auto-fixing the contents of the `xcss` attribute, this rule will wrap XCSS styles in a `xcss(...)` function call, and it will add an import declaration for the `xcss` function. `cssImportSource` is a string that determines what package `xcss` should be imported from.
223
+
224
+ This is `@atlaskit/primitives` by default.
225
+
226
+ #### excludeReactComponents
227
+
228
+ Whether to exclude `css` attributes of React components from being affected by this ESLint rule. We assume that an element is a React component if its name starts with a capital letter, e.g. `<Button />`.
229
+
230
+ This is `false` by default.
231
+
232
+ #### fixNamesOnly
233
+
234
+ When enabled, this rule will only add `styles` at the end of existing style variables. All other autofixers will be disabled. For example:
235
+
236
+ ```tsx
237
+ // vvvvv will be renamed to `myCssStyles`
238
+ const myCss = { color: 'blue' };
239
+ ```
240
+
241
+ This is `false` by default.
242
+
207
243
  ## ensure-design-token-usage
208
244
 
209
245
  Using design tokens results in a harmonious experience for end users whilst providing theming and consistency.
@@ -708,8 +744,6 @@ In Compiled, exporting keyframes declarations may result in unexpected errors wh
708
744
 
709
745
  <h3>Examples</h3>
710
746
 
711
- <!-- To fill out -- tell us when this rule will mark violations. -->
712
-
713
747
  #### Incorrect
714
748
 
715
749
  ```tsx
@@ -9,21 +9,28 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
9
9
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10
10
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
11
11
  var _eslintCodemodUtils = require("eslint-codemod-utils");
12
+ var _estraverse = _interopRequireDefault(require("estraverse"));
12
13
  var _assign = _interopRequireDefault(require("lodash/assign"));
14
+ var _astNodes = require("../../ast-nodes");
13
15
  var _createRule = require("../utils/create-rule");
16
+ var _getFirstSupportedImport = require("../utils/get-first-supported-import");
17
+ var _getImportNodeBySource = require("../utils/get-import-node-by-source");
18
+ var _isSupportedImport = require("../utils/is-supported-import");
14
19
  // eslint-disable-next-line import/no-extraneous-dependencies
15
20
 
16
- // File-level tracking of styles hoisted from the cssOnTopOfModule/cssAtBottomOfModule fixers
21
+ // File-level tracking of styles hoisted from the cssAtTopOfModule/cssAtBottomOfModule fixers
17
22
  var hoistedCss = [];
23
+ var isDOMElementName = function isDOMElementName(elementName) {
24
+ return elementName.charAt(0) !== elementName.charAt(0).toUpperCase() && elementName.charAt(0) === elementName.charAt(0).toLowerCase();
25
+ };
18
26
  function isCssCallExpression(node, cssFunctions) {
19
27
  cssFunctions = [].concat((0, _toConsumableArray2.default)(cssFunctions), ['cssMap']);
20
28
  return !!((0, _eslintCodemodUtils.isNodeOfType)(node, 'CallExpression') && node.callee && node.callee.type === 'Identifier' && cssFunctions.includes(node.callee.name) && node.arguments.length && node.arguments[0].type === 'ObjectExpression');
21
29
  }
22
30
  function findSpreadProperties(node) {
23
- // @ts-ignore
24
31
  return node.properties.filter(function (property) {
25
32
  return property.type === 'SpreadElement' ||
26
- // @ts-ignore
33
+ // @ts-expect-error
27
34
  property.type === 'ExperimentalSpreadProperty';
28
35
  });
29
36
  }
@@ -34,9 +41,8 @@ var getProgramNode = function getProgramNode(expression) {
34
41
  return expression.parent;
35
42
  };
36
43
 
37
- // TODO: This can be optimised by implementing a fixer at the very end (Program:exit) and handling all validations at once
38
44
  /**
39
- * Generates the declarator string when fixing the cssOnTopOfModule/cssAtBottomOfModule cases.
45
+ * Generates the declarator string when fixing the cssAtTopOfModule/cssAtBottomOfModule cases.
40
46
  * When `styles` already exists, `styles_1, styles_2, ..., styles_X` are incrementally created for each unhoisted style.
41
47
  * The generated `styles` varibale declaration names must be manually modified to be more informative at the discretion of owning teams.
42
48
  */
@@ -68,7 +74,7 @@ var getDeclaratorString = function getDeclaratorString(context) {
68
74
  hoistedCss = [].concat((0, _toConsumableArray2.default)(hoistedCss), ["".concat(declaratorName).concat(count)]);
69
75
  return "".concat(declaratorName).concat(count);
70
76
  };
71
- function analyzeIdentifier(context, sourceIdentifier, configuration) {
77
+ function analyzeIdentifier(context, sourceIdentifier, configuration, cssAttributeName) {
72
78
  var _getIdentifierInParen, _getIdentifierInParen2;
73
79
  var scope = context.getScope();
74
80
  var _ref = (_getIdentifierInParen = (_getIdentifierInParen2 = (0, _eslintCodemodUtils.getIdentifierInParentScope)(scope, sourceIdentifier.name)) === null || _getIdentifierInParen2 === void 0 ? void 0 : _getIdentifierInParen2.identifiers) !== null && _getIdentifierInParen !== void 0 ? _getIdentifierInParen : [],
@@ -90,8 +96,11 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
90
96
  // When variable is declared inside the component
91
97
  context.report({
92
98
  node: sourceIdentifier,
93
- messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule',
99
+ messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
94
100
  fix: function fix(fixer) {
101
+ if (configuration.fixNamesOnly) {
102
+ return [];
103
+ }
95
104
  return fixCssNotInModuleScope(fixer, context, configuration, identifier);
96
105
  }
97
106
  });
@@ -99,10 +108,30 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
99
108
  }
100
109
  if (identifier.parent && identifier.parent.init && !isCssCallExpression(identifier.parent.init, configuration.cssFunctions)) {
101
110
  // When variable value is not of type css({})
102
- context.report({
103
- node: identifier,
104
- messageId: 'cssObjectTypeOnly'
105
- });
111
+ var value = identifier.parent.init;
112
+ if (!value) {
113
+ return;
114
+ }
115
+ var valueExpression =
116
+ // @ts-expect-error remove once eslint types are switched to @typescript-eslint
117
+ value.type === 'TSAsExpression' ? value.expression : value;
118
+ if (['ObjectExpression', 'TemplateLiteral'].includes(valueExpression.type)) {
119
+ context.report({
120
+ node: identifier,
121
+ messageId: 'cssObjectTypeOnly',
122
+ fix: function fix(fixer) {
123
+ if (configuration.fixNamesOnly) {
124
+ return [];
125
+ }
126
+ return addCssFunctionCall(fixer, context, identifier.parent, configuration, cssAttributeName);
127
+ }
128
+ });
129
+ } else {
130
+ context.report({
131
+ node: identifier,
132
+ messageId: 'cssObjectTypeOnly'
133
+ });
134
+ }
106
135
  return;
107
136
  }
108
137
  var spreadProperties = (0, _eslintCodemodUtils.isNodeOfType)(identifier.parent.init, 'CallExpression') && findSpreadProperties(identifier.parent.init.arguments[0]);
@@ -118,12 +147,133 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
118
147
  }
119
148
 
120
149
  /**
121
- * Fixer for the cssOnTopOfModule/cssAtBottomOfModule violation cases.
150
+ * Returns a fixer that adds `import { css } from 'import-source'` or
151
+ * `import { xcss } from 'import-source'` to the start of the file, depending
152
+ * on the value of cssAttributeName and importSource.
153
+ */
154
+ var addImportSource = function addImportSource(context, fixer, configuration, cssAttributeName) {
155
+ var importSource = cssAttributeName === 'xcss' ? configuration.xcssImportSource : configuration.cssImportSource;
156
+
157
+ // Add the `import { css } from 'my-css-in-js-library';` statement
158
+ var packageImport = (0, _getFirstSupportedImport.getFirstSupportedImport)(context, [importSource]);
159
+ if (packageImport) {
160
+ var addCssImport = _astNodes.Import.insertNamedSpecifiers(packageImport, [cssAttributeName], fixer);
161
+ if (addCssImport) {
162
+ return addCssImport;
163
+ }
164
+ } else {
165
+ return (0, _eslintCodemodUtils.insertAtStartOfFile)(fixer, "".concat((0, _eslintCodemodUtils.insertImportDeclaration)(importSource, [cssAttributeName]), ";\n"));
166
+ }
167
+ };
168
+
169
+ /**
170
+ * Returns a list of fixes that:
171
+ * - add the `css` or `xcss` function call around the current node.
172
+ * - add an import statement for the package from which `css` is imported
173
+ */
174
+ var addCssFunctionCall = function addCssFunctionCall(fixer, context, node, configuration, cssAttributeName) {
175
+ var fixes = [];
176
+ var sourceCode = context.getSourceCode();
177
+ if (node.type !== 'VariableDeclarator' || !node.init || !cssAttributeName) {
178
+ return [];
179
+ }
180
+ var compiledImportFix = addImportSource(context, fixer, configuration, cssAttributeName);
181
+ if (compiledImportFix) {
182
+ fixes.push(compiledImportFix);
183
+ }
184
+ var init = node.init;
185
+ var initString = sourceCode.getText(init);
186
+ if (node.init.type === 'TemplateLiteral') {
187
+ fixes.push(fixer.replaceText(init, "".concat(cssAttributeName).concat(initString)));
188
+ } else {
189
+ fixes.push(fixer.replaceText(init, "".concat(cssAttributeName, "(").concat(initString, ")")));
190
+ }
191
+ return fixes;
192
+ };
193
+
194
+ /**
195
+ * Check if the expression has or potentially has a local variable
196
+ * (as opposed to an imported one), erring on the side ot "yes"
197
+ * when an expression is too complicated to analyse.
198
+ *
199
+ * This is useful because expressions containing local variables
200
+ * cannot be easily hoisted, whereas this is not a problem with imported
201
+ * variables.
202
+ *
203
+ * @param context Context of the rule.
204
+ * @param node Any node that is potentially hoistable.
205
+ * @returns Whether the node potentially has a local variable (and thus is not safe to hoist).
206
+ */
207
+ var potentiallyHasLocalVariable = function potentiallyHasLocalVariable(context, node) {
208
+ var hasPotentiallyLocalVariable = false;
209
+ var isImportedVariable = function isImportedVariable(identifier) {
210
+ return !!(0, _getImportNodeBySource.getModuleOfIdentifier)(context.getSourceCode(), identifier);
211
+ };
212
+ _estraverse.default.traverse(node, {
213
+ enter: function enter(node, _parent) {
214
+ if ((0, _eslintCodemodUtils.isNodeOfType)(node, 'SpreadElement') ||
215
+ // @ts-expect-error remove once we can be sure that no parser interprets
216
+ // the spread operator as ExperimentalSpreadProperty anymore
217
+ (0, _eslintCodemodUtils.isNodeOfType)(node, 'ExperimentalSpreadProperty')) {
218
+ // Spread elements could contain anything... so we don't bother.
219
+ //
220
+ // e.g. <div css={css({ ...(!height && { visibility: 'hidden' })} />
221
+ hasPotentiallyLocalVariable = true;
222
+ this.break();
223
+ }
224
+ if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'Property')) {
225
+ return;
226
+ }
227
+ switch (node.value.type) {
228
+ case 'Literal':
229
+ break;
230
+ case 'Identifier':
231
+ // e.g. css({ margin: myVariable })
232
+ if (!isImportedVariable(node.value.name)) {
233
+ hasPotentiallyLocalVariable = true;
234
+ }
235
+ this.break();
236
+ break;
237
+ case 'MemberExpression':
238
+ // e.g. css({ margin: props.color })
239
+ // css({ margin: props.media.color })
240
+ if (node.value.object.type === 'Identifier' && isImportedVariable(node.value.object.name)) {
241
+ // We found an imported variable, don't do anything.
242
+ } else {
243
+ // e.g. css({ margin: [some complicated expression].media.color })
244
+ // This can potentially get too complex, so we assume there's a local
245
+ // variable in there somewhere.
246
+ hasPotentiallyLocalVariable = true;
247
+ }
248
+ this.break();
249
+ break;
250
+ case 'TemplateLiteral':
251
+ if (!!node.value.expressions.length) {
252
+ // Too many edge cases here, don't bother...
253
+ // e.g. css({ animation: `${expandStyles(right, rightExpanded, isExpanded)} 0.2s ease-in-out` });
254
+ hasPotentiallyLocalVariable = true;
255
+ this.break();
256
+ }
257
+ break;
258
+ default:
259
+ // Catch-all for values such as "A && B", "A ? B : C"
260
+ hasPotentiallyLocalVariable = true;
261
+ this.break();
262
+ break;
263
+ }
264
+ }
265
+ });
266
+ return hasPotentiallyLocalVariable;
267
+ };
268
+
269
+ /**
270
+ * Fixer for the cssAtTopOfModule/cssAtBottomOfModule violation cases.
122
271
  * This deals with Identifiers and Expressions passed from the traverseExpressionWithConfig() function.
272
+ *
123
273
  * @param fixer The ESLint RuleFixer object
124
- * @param context The context of the node
274
+ * @param context The context of the rule
125
275
  * @param configuration The configuration of the rule, determining whether the fix is implmeneted at the top or bottom of the module
126
- * @param node Either an IdentifierWithParent node. Expression, or SpreadElement that we handle
276
+ * @param node Any potentially hoistable node, or an identifier.
127
277
  * @param cssAttributeName An optional parameter only added when we fix an ObjectExpression
128
278
  */
129
279
  var fixCssNotInModuleScope = function fixCssNotInModuleScope(fixer, context, configuration, node, cssAttributeName) {
@@ -142,35 +292,42 @@ var fixCssNotInModuleScope = function fixCssNotInModuleScope(fixer, context, con
142
292
  });
143
293
  }
144
294
  var moduleString;
145
- var implementFixer = [];
295
+ var fixes = [];
146
296
  if (node.type === 'Identifier') {
147
297
  var identifier = node;
148
298
  var declarator = identifier.parent.parent;
149
299
  moduleString = sourceCode.getText(declarator);
150
- implementFixer.push(fixer.remove(declarator));
300
+ fixes.push(fixer.remove(declarator));
151
301
  } else {
302
+ if (potentiallyHasLocalVariable(context, node)) {
303
+ return [];
304
+ }
152
305
  var _declarator = getDeclaratorString(context);
153
306
  var text = sourceCode.getText(node);
154
307
 
155
308
  // If this has been passed, then we know it's an ObjectExpression
156
309
  if (cssAttributeName) {
157
310
  moduleString = "const ".concat(_declarator, " = ").concat(cssAttributeName, "(").concat(text, ");");
311
+ var compiledImportFix = addImportSource(context, fixer, configuration, cssAttributeName);
312
+ if (compiledImportFix) {
313
+ fixes.push(compiledImportFix);
314
+ }
158
315
  } else {
159
- moduleString = moduleString = "const ".concat(_declarator, " = ").concat(text, ";");
316
+ moduleString = "const ".concat(_declarator, " = ").concat(text, ";");
160
317
  }
161
- implementFixer.push(fixer.replaceText(node, _declarator));
318
+ fixes.push(fixer.replaceText(node, _declarator));
162
319
  }
163
- return [].concat(implementFixer, [
164
- // Insert the node either before or after
320
+ return [].concat(fixes, [
321
+ // Insert the node either before or after, depending on the rule configuration
165
322
  configuration.stylesPlacement === 'bottom' ? fixer.insertTextAfter(fixerNodePlacement, '\n' + moduleString) : fixer.insertTextBefore(fixerNodePlacement, moduleString + '\n')]);
166
323
  };
167
324
 
168
325
  /**
169
326
  * Handle different cases based on what's been passed in the css-related JSXAttribute
170
- * @param context the context of the node
327
+ * @param context the context of the rule
171
328
  * @param expression the expression of the JSXAttribute value
172
329
  * @param configuration what css-related functions to account for (eg. css, xcss, cssMap), and whether to detect bottom vs top expressions
173
- * @param cssAttributeName used to encapsulate ObjectExpressions when cssOnTopOfModule/cssAtBottomOfModule violations are triggered
330
+ * @param cssAttributeName used to encapsulate ObjectExpressions when cssAtTopOfModule/cssAtBottomOfModule violations are triggered
174
331
  */
175
332
  var traverseExpressionWithConfig = function traverseExpressionWithConfig(context, expression, configuration, cssAttributeName) {
176
333
  function traverseExpression(expression) {
@@ -178,7 +335,7 @@ var traverseExpressionWithConfig = function traverseExpressionWithConfig(context
178
335
  case 'Identifier':
179
336
  // {styles}
180
337
  // We've found an identifier - time to analyze it!
181
- analyzeIdentifier(context, expression, configuration);
338
+ analyzeIdentifier(context, expression, configuration, cssAttributeName);
182
339
  break;
183
340
  case 'ArrayExpression':
184
341
  // {[styles, moreStyles]}
@@ -207,8 +364,12 @@ var traverseExpressionWithConfig = function traverseExpressionWithConfig(context
207
364
  // We've found elements that shouldn't be here! Report an error.
208
365
  context.report({
209
366
  node: expression,
210
- messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule',
367
+ messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
211
368
  fix: function fix(fixer) {
369
+ if (configuration.fixNamesOnly) {
370
+ return [];
371
+ }
372
+
212
373
  // Don't fix CallExpressions unless they're from cssFunctions or cssMap
213
374
  if (expression.type === 'CallExpression' && !isCssCallExpression(expression, configuration.cssFunctions)) {
214
375
  return [];
@@ -220,6 +381,17 @@ var traverseExpressionWithConfig = function traverseExpressionWithConfig(context
220
381
  }
221
382
  });
222
383
  break;
384
+
385
+ // @ts-expect-error - our ESLint-related types assume vanilla JS, when in fact
386
+ // it is running @typescript-eslint
387
+ //
388
+ // Switching to the more accurate @typescript-eslint types would break
389
+ // eslint-codemod-utils and all ESLint rules in packages/design-system,
390
+ // so we just leave this as-is.
391
+ case 'TSAsExpression':
392
+ // @ts-expect-error
393
+ traverseExpression(expression.expression);
394
+ break;
223
395
  default:
224
396
  // Do nothing!
225
397
  break;
@@ -229,10 +401,15 @@ var traverseExpressionWithConfig = function traverseExpressionWithConfig(context
229
401
  };
230
402
  var defaultConfig = {
231
403
  cssFunctions: ['css', 'xcss'],
232
- stylesPlacement: 'top'
404
+ stylesPlacement: 'top',
405
+ cssImportSource: _isSupportedImport.CSS_IN_JS_IMPORTS.compiled,
406
+ xcssImportSource: _isSupportedImport.CSS_IN_JS_IMPORTS.atlaskitPrimitives,
407
+ excludeReactComponents: false,
408
+ fixNamesOnly: false
233
409
  };
234
410
  var rule = (0, _createRule.createLintRule)({
235
411
  meta: {
412
+ type: 'problem',
236
413
  name: 'consistent-css-prop-usage',
237
414
  docs: {
238
415
  description: 'Ensures consistency with `css` and `xcss` prop usages',
@@ -242,13 +419,42 @@ var rule = (0, _createRule.createLintRule)({
242
419
  },
243
420
  fixable: 'code',
244
421
  messages: {
245
- cssOnTopOfModule: "Create styles at the top of the module scope using the respective css function.",
422
+ cssAtTopOfModule: "Create styles at the top of the module scope using the respective css function.",
246
423
  cssAtBottomOfModule: "Create styles at the bottom of the module scope using the respective css function.",
247
- cssObjectTypeOnly: "Create styles using objects passed to the css function.",
248
- cssInModule: "Imported styles should not be used, instead define in the module, import a component, or use a design token.",
424
+ cssObjectTypeOnly: "Create styles using objects passed to a css function call, e.g. `css({ textAlign: 'center'; })`.",
425
+ cssInModule: "Imported styles should not be used; instead define in the module, import a component, or use a design token.",
249
426
  cssArrayStylesOnly: "Compose styles with an array on the css prop instead of using object spread.",
427
+ noMemberExpressions: "Styles should be a regular variable (e.g. 'buttonStyles'), not a member of an object (e.g. 'myObject.styles').",
250
428
  shouldEndInStyles: 'Declared styles should end in "styles".'
251
- }
429
+ },
430
+ schema: [{
431
+ type: 'object',
432
+ properties: {
433
+ cssFunctions: {
434
+ type: 'array',
435
+ items: [{
436
+ type: 'string'
437
+ }]
438
+ },
439
+ stylesPlacement: {
440
+ type: 'string',
441
+ enum: ['top', 'bottom']
442
+ },
443
+ cssImportSource: {
444
+ type: 'string'
445
+ },
446
+ xcssImportSource: {
447
+ type: 'string'
448
+ },
449
+ excludeReactComponents: {
450
+ type: 'boolean'
451
+ },
452
+ fixNamesOnly: {
453
+ type: 'boolean'
454
+ }
455
+ },
456
+ additionalProperties: false
457
+ }]
252
458
  },
253
459
  create: function create(context) {
254
460
  var _ref3;
@@ -290,9 +496,20 @@ var rule = (0, _createRule.createLintRule)({
290
496
  });
291
497
  }
292
498
  });
293
- }), (0, _defineProperty2.default)(_ref3, "JSXAttribute", function JSXAttribute(node) {
499
+ }), (0, _defineProperty2.default)(_ref3, "JSXAttribute", function JSXAttribute(nodeOriginal) {
500
+ var node = nodeOriginal;
294
501
  var name = node.name,
295
502
  value = node.value;
503
+ if (mergedConfig.excludeReactComponents && node.parent.type === 'JSXOpeningElement') {
504
+ // e.g. <item.before />
505
+ if (node.parent.name.type === 'JSXMemberExpression') {
506
+ return;
507
+ }
508
+ // e.g. <div />, <MenuItem />
509
+ if (node.parent.name.type === 'JSXIdentifier' && !isDOMElementName(node.parent.name.name)) {
510
+ return;
511
+ }
512
+ }
296
513
 
297
514
  // Always reset to empty array
298
515
  hoistedCss = [];
@@ -300,11 +517,16 @@ var rule = (0, _createRule.createLintRule)({
300
517
  // When not a jsx expression. For eg. css=""
301
518
  if ((value === null || value === void 0 ? void 0 : value.type) !== 'JSXExpressionContainer') {
302
519
  context.report({
303
- node: value,
304
- messageId: mergedConfig.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule'
520
+ node: node,
521
+ messageId: mergedConfig.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule'
305
522
  });
306
523
  return;
307
524
  }
525
+ if (value.expression.type === 'JSXEmptyExpression') {
526
+ // e.g. the comment in
527
+ // <div css={/* Hello there */} />
528
+ return;
529
+ }
308
530
  traverseExpressionWithConfig(context, value.expression, mergedConfig, name.name);
309
531
  }
310
532
  }), _ref3;
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.isStyledComponent = void 0;
7
- var _eslintCodemodUtils = require("eslint-codemod-utils");
7
+ var _getFirstSupportedImport = require("../get-first-supported-import");
8
8
  /**
9
9
  * Given a list of node, find and return the callee of the first Compiled or styled-components `styled` function call found in the list.
10
10
  *
@@ -48,15 +48,11 @@ var findNode = function findNode(nodes) {
48
48
  * @returns The local name used to import the `styled` API.
49
49
  */
50
50
  var getStyledImportSpecifierName = function getStyledImportSpecifierName(context, importSources) {
51
- var _supportedImports$0$s;
52
- var isSupportedImport = function isSupportedImport(node) {
53
- return (0, _eslintCodemodUtils.isNodeOfType)(node, 'ImportDeclaration') && typeof node.source.value === 'string' && importSources.includes(node.source.value);
54
- };
55
- var source = context.getSourceCode();
56
- var supportedImports = source.ast.body.filter(isSupportedImport);
57
- return (_supportedImports$0$s = supportedImports[0].specifiers.find(function (spec) {
58
- return spec.type === 'ImportSpecifier' && spec.imported.name === 'styled';
59
- })) === null || _supportedImports$0$s === void 0 ? void 0 : _supportedImports$0$s.local.name;
51
+ var _supportedImport$spec;
52
+ var supportedImport = (0, _getFirstSupportedImport.getFirstSupportedImport)(context, importSources);
53
+ return supportedImport === null || supportedImport === void 0 || (_supportedImport$spec = supportedImport.specifiers.find(function (spec) {
54
+ return spec.type === 'ImportSpecifier' && spec.imported.name === 'styled' || spec.type === 'ImportDefaultSpecifier' && spec.local.name === 'styled';
55
+ })) === null || _supportedImport$spec === void 0 ? void 0 : _supportedImport$spec.local.name;
60
56
  };
61
57
 
62
58
  /**
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getFirstSupportedImport = void 0;
7
+ var _eslintCodemodUtils = require("eslint-codemod-utils");
8
+ /**
9
+ * Get the first import declaration in the file that matches any of the packages
10
+ * in `importSources`.
11
+ *
12
+ * @param context Rule context.
13
+ * @param importSources The packages to check import statements for. If importSources
14
+ * contains more than one package, the first import statement
15
+ * detected in the file that matches any of the packages will be
16
+ * returned.
17
+ * @returns The first import declaration found in the file.
18
+ */
19
+ var getFirstSupportedImport = exports.getFirstSupportedImport = function getFirstSupportedImport(context, importSources) {
20
+ var isSupportedImport = function isSupportedImport(node) {
21
+ return (0, _eslintCodemodUtils.isNodeOfType)(node, 'ImportDeclaration') && typeof node.source.value === 'string' && importSources.includes(node.source.value);
22
+ };
23
+ var source = context.getSourceCode();
24
+ var supportedImports = source.ast.body.filter(isSupportedImport);
25
+ if (supportedImports.length) {
26
+ return supportedImports[0];
27
+ }
28
+ };
@@ -13,7 +13,8 @@ var CSS_IN_JS_IMPORTS = exports.CSS_IN_JS_IMPORTS = {
13
13
  emotionReact: '@emotion/react',
14
14
  emotionCore: '@emotion/core',
15
15
  styledComponents: 'styled-components',
16
- atlaskitCss: '@atlaskit/css'
16
+ atlaskitCss: '@atlaskit/css',
17
+ atlaskitPrimitives: '@atlaskit/primitives'
17
18
  };
18
19
 
19
20
  // A CSS-in-JS library an import of a valid css, cx, cssMap, etc.