@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.
- package/CHANGELOG.md +25 -0
- package/constellation/index/usage.mdx +42 -8
- package/dist/cjs/rules/consistent-css-prop-usage/index.js +254 -32
- package/dist/cjs/rules/utils/create-no-exported-rule/is-styled-component.js +6 -10
- package/dist/cjs/rules/utils/get-first-supported-import.js +28 -0
- package/dist/cjs/rules/utils/is-supported-import.js +2 -1
- package/dist/es2019/rules/consistent-css-prop-usage/index.js +251 -33
- package/dist/es2019/rules/utils/create-no-exported-rule/is-styled-component.js +4 -8
- package/dist/es2019/rules/utils/get-first-supported-import.js +22 -0
- package/dist/es2019/rules/utils/is-supported-import.js +2 -1
- package/dist/esm/rules/consistent-css-prop-usage/index.js +255 -33
- package/dist/esm/rules/utils/create-no-exported-rule/is-styled-component.js +6 -10
- package/dist/esm/rules/utils/get-first-supported-import.js +22 -0
- package/dist/esm/rules/utils/is-supported-import.js +2 -1
- package/dist/types/rules/consistent-css-prop-usage/types.d.ts +7 -2
- package/dist/types/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +1 -1
- package/dist/types/rules/utils/get-first-supported-import.d.ts +17 -0
- package/dist/types/rules/utils/is-supported-import.d.ts +1 -0
- package/dist/types-ts4.5/rules/consistent-css-prop-usage/types.d.ts +7 -2
- package/dist/types-ts4.5/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +1 -1
- package/dist/types-ts4.5/rules/utils/get-first-supported-import.d.ts +17 -0
- package/dist/types-ts4.5/rules/utils/is-supported-import.d.ts +1 -0
- package/package.json +3 -1
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
2
|
|
|
3
|
-
import { getIdentifierInParentScope, isNodeOfType } from 'eslint-codemod-utils';
|
|
3
|
+
import { getIdentifierInParentScope, insertAtStartOfFile, insertImportDeclaration, isNodeOfType } from 'eslint-codemod-utils';
|
|
4
|
+
import estraverse from 'estraverse';
|
|
4
5
|
import assign from 'lodash/assign';
|
|
6
|
+
import { Import } from '../../ast-nodes';
|
|
5
7
|
import { createLintRule } from '../utils/create-rule';
|
|
6
|
-
|
|
8
|
+
import { getFirstSupportedImport } from '../utils/get-first-supported-import';
|
|
9
|
+
import { getModuleOfIdentifier } from '../utils/get-import-node-by-source';
|
|
10
|
+
import { CSS_IN_JS_IMPORTS } from '../utils/is-supported-import';
|
|
11
|
+
// File-level tracking of styles hoisted from the cssAtTopOfModule/cssAtBottomOfModule fixers
|
|
7
12
|
let hoistedCss = [];
|
|
13
|
+
const isDOMElementName = elementName => elementName.charAt(0) !== elementName.charAt(0).toUpperCase() && elementName.charAt(0) === elementName.charAt(0).toLowerCase();
|
|
8
14
|
function isCssCallExpression(node, cssFunctions) {
|
|
9
15
|
cssFunctions = [...cssFunctions, 'cssMap'];
|
|
10
16
|
return !!(isNodeOfType(node, 'CallExpression') && node.callee && node.callee.type === 'Identifier' && cssFunctions.includes(node.callee.name) && node.arguments.length && node.arguments[0].type === 'ObjectExpression');
|
|
11
17
|
}
|
|
12
18
|
function findSpreadProperties(node) {
|
|
13
|
-
// @ts-ignore
|
|
14
19
|
return node.properties.filter(property => property.type === 'SpreadElement' ||
|
|
15
|
-
// @ts-
|
|
20
|
+
// @ts-expect-error
|
|
16
21
|
property.type === 'ExperimentalSpreadProperty');
|
|
17
22
|
}
|
|
18
23
|
const getProgramNode = expression => {
|
|
@@ -22,9 +27,8 @@ const getProgramNode = expression => {
|
|
|
22
27
|
return expression.parent;
|
|
23
28
|
};
|
|
24
29
|
|
|
25
|
-
// TODO: This can be optimised by implementing a fixer at the very end (Program:exit) and handling all validations at once
|
|
26
30
|
/**
|
|
27
|
-
* Generates the declarator string when fixing the
|
|
31
|
+
* Generates the declarator string when fixing the cssAtTopOfModule/cssAtBottomOfModule cases.
|
|
28
32
|
* When `styles` already exists, `styles_1, styles_2, ..., styles_X` are incrementally created for each unhoisted style.
|
|
29
33
|
* The generated `styles` varibale declaration names must be manually modified to be more informative at the discretion of owning teams.
|
|
30
34
|
*/
|
|
@@ -54,7 +58,7 @@ const getDeclaratorString = context => {
|
|
|
54
58
|
hoistedCss = [...hoistedCss, `${declaratorName}${count}`];
|
|
55
59
|
return `${declaratorName}${count}`;
|
|
56
60
|
};
|
|
57
|
-
function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
61
|
+
function analyzeIdentifier(context, sourceIdentifier, configuration, cssAttributeName) {
|
|
58
62
|
var _getIdentifierInParen, _getIdentifierInParen2;
|
|
59
63
|
const scope = context.getScope();
|
|
60
64
|
const [identifier] = (_getIdentifierInParen = (_getIdentifierInParen2 = getIdentifierInParentScope(scope, sourceIdentifier.name)) === null || _getIdentifierInParen2 === void 0 ? void 0 : _getIdentifierInParen2.identifiers) !== null && _getIdentifierInParen !== void 0 ? _getIdentifierInParen : [];
|
|
@@ -74,8 +78,11 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
|
74
78
|
// When variable is declared inside the component
|
|
75
79
|
context.report({
|
|
76
80
|
node: sourceIdentifier,
|
|
77
|
-
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : '
|
|
81
|
+
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
|
|
78
82
|
fix: fixer => {
|
|
83
|
+
if (configuration.fixNamesOnly) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
79
86
|
return fixCssNotInModuleScope(fixer, context, configuration, identifier);
|
|
80
87
|
}
|
|
81
88
|
});
|
|
@@ -83,10 +90,30 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
|
83
90
|
}
|
|
84
91
|
if (identifier.parent && identifier.parent.init && !isCssCallExpression(identifier.parent.init, configuration.cssFunctions)) {
|
|
85
92
|
// When variable value is not of type css({})
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
93
|
+
const value = identifier.parent.init;
|
|
94
|
+
if (!value) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const valueExpression =
|
|
98
|
+
// @ts-expect-error remove once eslint types are switched to @typescript-eslint
|
|
99
|
+
value.type === 'TSAsExpression' ? value.expression : value;
|
|
100
|
+
if (['ObjectExpression', 'TemplateLiteral'].includes(valueExpression.type)) {
|
|
101
|
+
context.report({
|
|
102
|
+
node: identifier,
|
|
103
|
+
messageId: 'cssObjectTypeOnly',
|
|
104
|
+
fix: fixer => {
|
|
105
|
+
if (configuration.fixNamesOnly) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
return addCssFunctionCall(fixer, context, identifier.parent, configuration, cssAttributeName);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
context.report({
|
|
113
|
+
node: identifier,
|
|
114
|
+
messageId: 'cssObjectTypeOnly'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
90
117
|
return;
|
|
91
118
|
}
|
|
92
119
|
const spreadProperties = isNodeOfType(identifier.parent.init, 'CallExpression') && findSpreadProperties(identifier.parent.init.arguments[0]);
|
|
@@ -102,12 +129,131 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
|
102
129
|
}
|
|
103
130
|
|
|
104
131
|
/**
|
|
105
|
-
*
|
|
132
|
+
* Returns a fixer that adds `import { css } from 'import-source'` or
|
|
133
|
+
* `import { xcss } from 'import-source'` to the start of the file, depending
|
|
134
|
+
* on the value of cssAttributeName and importSource.
|
|
135
|
+
*/
|
|
136
|
+
const addImportSource = (context, fixer, configuration, cssAttributeName) => {
|
|
137
|
+
const importSource = cssAttributeName === 'xcss' ? configuration.xcssImportSource : configuration.cssImportSource;
|
|
138
|
+
|
|
139
|
+
// Add the `import { css } from 'my-css-in-js-library';` statement
|
|
140
|
+
const packageImport = getFirstSupportedImport(context, [importSource]);
|
|
141
|
+
if (packageImport) {
|
|
142
|
+
const addCssImport = Import.insertNamedSpecifiers(packageImport, [cssAttributeName], fixer);
|
|
143
|
+
if (addCssImport) {
|
|
144
|
+
return addCssImport;
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
return insertAtStartOfFile(fixer, `${insertImportDeclaration(importSource, [cssAttributeName])};\n`);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns a list of fixes that:
|
|
153
|
+
* - add the `css` or `xcss` function call around the current node.
|
|
154
|
+
* - add an import statement for the package from which `css` is imported
|
|
155
|
+
*/
|
|
156
|
+
const addCssFunctionCall = (fixer, context, node, configuration, cssAttributeName) => {
|
|
157
|
+
const fixes = [];
|
|
158
|
+
const sourceCode = context.getSourceCode();
|
|
159
|
+
if (node.type !== 'VariableDeclarator' || !node.init || !cssAttributeName) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
const compiledImportFix = addImportSource(context, fixer, configuration, cssAttributeName);
|
|
163
|
+
if (compiledImportFix) {
|
|
164
|
+
fixes.push(compiledImportFix);
|
|
165
|
+
}
|
|
166
|
+
const init = node.init;
|
|
167
|
+
const initString = sourceCode.getText(init);
|
|
168
|
+
if (node.init.type === 'TemplateLiteral') {
|
|
169
|
+
fixes.push(fixer.replaceText(init, `${cssAttributeName}${initString}`));
|
|
170
|
+
} else {
|
|
171
|
+
fixes.push(fixer.replaceText(init, `${cssAttributeName}(${initString})`));
|
|
172
|
+
}
|
|
173
|
+
return fixes;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if the expression has or potentially has a local variable
|
|
178
|
+
* (as opposed to an imported one), erring on the side ot "yes"
|
|
179
|
+
* when an expression is too complicated to analyse.
|
|
180
|
+
*
|
|
181
|
+
* This is useful because expressions containing local variables
|
|
182
|
+
* cannot be easily hoisted, whereas this is not a problem with imported
|
|
183
|
+
* variables.
|
|
184
|
+
*
|
|
185
|
+
* @param context Context of the rule.
|
|
186
|
+
* @param node Any node that is potentially hoistable.
|
|
187
|
+
* @returns Whether the node potentially has a local variable (and thus is not safe to hoist).
|
|
188
|
+
*/
|
|
189
|
+
const potentiallyHasLocalVariable = (context, node) => {
|
|
190
|
+
let hasPotentiallyLocalVariable = false;
|
|
191
|
+
const isImportedVariable = identifier => !!getModuleOfIdentifier(context.getSourceCode(), identifier);
|
|
192
|
+
estraverse.traverse(node, {
|
|
193
|
+
enter: function (node, _parent) {
|
|
194
|
+
if (isNodeOfType(node, 'SpreadElement') ||
|
|
195
|
+
// @ts-expect-error remove once we can be sure that no parser interprets
|
|
196
|
+
// the spread operator as ExperimentalSpreadProperty anymore
|
|
197
|
+
isNodeOfType(node, 'ExperimentalSpreadProperty')) {
|
|
198
|
+
// Spread elements could contain anything... so we don't bother.
|
|
199
|
+
//
|
|
200
|
+
// e.g. <div css={css({ ...(!height && { visibility: 'hidden' })} />
|
|
201
|
+
hasPotentiallyLocalVariable = true;
|
|
202
|
+
this.break();
|
|
203
|
+
}
|
|
204
|
+
if (!isNodeOfType(node, 'Property')) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
switch (node.value.type) {
|
|
208
|
+
case 'Literal':
|
|
209
|
+
break;
|
|
210
|
+
case 'Identifier':
|
|
211
|
+
// e.g. css({ margin: myVariable })
|
|
212
|
+
if (!isImportedVariable(node.value.name)) {
|
|
213
|
+
hasPotentiallyLocalVariable = true;
|
|
214
|
+
}
|
|
215
|
+
this.break();
|
|
216
|
+
break;
|
|
217
|
+
case 'MemberExpression':
|
|
218
|
+
// e.g. css({ margin: props.color })
|
|
219
|
+
// css({ margin: props.media.color })
|
|
220
|
+
if (node.value.object.type === 'Identifier' && isImportedVariable(node.value.object.name)) {
|
|
221
|
+
// We found an imported variable, don't do anything.
|
|
222
|
+
} else {
|
|
223
|
+
// e.g. css({ margin: [some complicated expression].media.color })
|
|
224
|
+
// This can potentially get too complex, so we assume there's a local
|
|
225
|
+
// variable in there somewhere.
|
|
226
|
+
hasPotentiallyLocalVariable = true;
|
|
227
|
+
}
|
|
228
|
+
this.break();
|
|
229
|
+
break;
|
|
230
|
+
case 'TemplateLiteral':
|
|
231
|
+
if (!!node.value.expressions.length) {
|
|
232
|
+
// Too many edge cases here, don't bother...
|
|
233
|
+
// e.g. css({ animation: `${expandStyles(right, rightExpanded, isExpanded)} 0.2s ease-in-out` });
|
|
234
|
+
hasPotentiallyLocalVariable = true;
|
|
235
|
+
this.break();
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
default:
|
|
239
|
+
// Catch-all for values such as "A && B", "A ? B : C"
|
|
240
|
+
hasPotentiallyLocalVariable = true;
|
|
241
|
+
this.break();
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
return hasPotentiallyLocalVariable;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Fixer for the cssAtTopOfModule/cssAtBottomOfModule violation cases.
|
|
106
251
|
* This deals with Identifiers and Expressions passed from the traverseExpressionWithConfig() function.
|
|
252
|
+
*
|
|
107
253
|
* @param fixer The ESLint RuleFixer object
|
|
108
|
-
* @param context The context of the
|
|
254
|
+
* @param context The context of the rule
|
|
109
255
|
* @param configuration The configuration of the rule, determining whether the fix is implmeneted at the top or bottom of the module
|
|
110
|
-
* @param node
|
|
256
|
+
* @param node Any potentially hoistable node, or an identifier.
|
|
111
257
|
* @param cssAttributeName An optional parameter only added when we fix an ObjectExpression
|
|
112
258
|
*/
|
|
113
259
|
const fixCssNotInModuleScope = (fixer, context, configuration, node, cssAttributeName) => {
|
|
@@ -124,35 +270,42 @@ const fixCssNotInModuleScope = (fixer, context, configuration, node, cssAttribut
|
|
|
124
270
|
fixerNodePlacement = programNode.body.length === 1 ? programNode.body[0] : programNode.body.find(node => node.type !== 'ImportDeclaration');
|
|
125
271
|
}
|
|
126
272
|
let moduleString;
|
|
127
|
-
let
|
|
273
|
+
let fixes = [];
|
|
128
274
|
if (node.type === 'Identifier') {
|
|
129
275
|
const identifier = node;
|
|
130
276
|
const declarator = identifier.parent.parent;
|
|
131
277
|
moduleString = sourceCode.getText(declarator);
|
|
132
|
-
|
|
278
|
+
fixes.push(fixer.remove(declarator));
|
|
133
279
|
} else {
|
|
280
|
+
if (potentiallyHasLocalVariable(context, node)) {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
134
283
|
const declarator = getDeclaratorString(context);
|
|
135
284
|
const text = sourceCode.getText(node);
|
|
136
285
|
|
|
137
286
|
// If this has been passed, then we know it's an ObjectExpression
|
|
138
287
|
if (cssAttributeName) {
|
|
139
288
|
moduleString = `const ${declarator} = ${cssAttributeName}(${text});`;
|
|
289
|
+
const compiledImportFix = addImportSource(context, fixer, configuration, cssAttributeName);
|
|
290
|
+
if (compiledImportFix) {
|
|
291
|
+
fixes.push(compiledImportFix);
|
|
292
|
+
}
|
|
140
293
|
} else {
|
|
141
|
-
moduleString =
|
|
294
|
+
moduleString = `const ${declarator} = ${text};`;
|
|
142
295
|
}
|
|
143
|
-
|
|
296
|
+
fixes.push(fixer.replaceText(node, declarator));
|
|
144
297
|
}
|
|
145
|
-
return [...
|
|
146
|
-
// Insert the node either before or after
|
|
298
|
+
return [...fixes,
|
|
299
|
+
// Insert the node either before or after, depending on the rule configuration
|
|
147
300
|
configuration.stylesPlacement === 'bottom' ? fixer.insertTextAfter(fixerNodePlacement, '\n' + moduleString) : fixer.insertTextBefore(fixerNodePlacement, moduleString + '\n')];
|
|
148
301
|
};
|
|
149
302
|
|
|
150
303
|
/**
|
|
151
304
|
* Handle different cases based on what's been passed in the css-related JSXAttribute
|
|
152
|
-
* @param context the context of the
|
|
305
|
+
* @param context the context of the rule
|
|
153
306
|
* @param expression the expression of the JSXAttribute value
|
|
154
307
|
* @param configuration what css-related functions to account for (eg. css, xcss, cssMap), and whether to detect bottom vs top expressions
|
|
155
|
-
* @param cssAttributeName used to encapsulate ObjectExpressions when
|
|
308
|
+
* @param cssAttributeName used to encapsulate ObjectExpressions when cssAtTopOfModule/cssAtBottomOfModule violations are triggered
|
|
156
309
|
*/
|
|
157
310
|
const traverseExpressionWithConfig = (context, expression, configuration, cssAttributeName) => {
|
|
158
311
|
function traverseExpression(expression) {
|
|
@@ -160,7 +313,7 @@ const traverseExpressionWithConfig = (context, expression, configuration, cssAtt
|
|
|
160
313
|
case 'Identifier':
|
|
161
314
|
// {styles}
|
|
162
315
|
// We've found an identifier - time to analyze it!
|
|
163
|
-
analyzeIdentifier(context, expression, configuration);
|
|
316
|
+
analyzeIdentifier(context, expression, configuration, cssAttributeName);
|
|
164
317
|
break;
|
|
165
318
|
case 'ArrayExpression':
|
|
166
319
|
// {[styles, moreStyles]}
|
|
@@ -187,8 +340,12 @@ const traverseExpressionWithConfig = (context, expression, configuration, cssAtt
|
|
|
187
340
|
// We've found elements that shouldn't be here! Report an error.
|
|
188
341
|
context.report({
|
|
189
342
|
node: expression,
|
|
190
|
-
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : '
|
|
343
|
+
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
|
|
191
344
|
fix: fixer => {
|
|
345
|
+
if (configuration.fixNamesOnly) {
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
|
|
192
349
|
// Don't fix CallExpressions unless they're from cssFunctions or cssMap
|
|
193
350
|
if (expression.type === 'CallExpression' && !isCssCallExpression(expression, configuration.cssFunctions)) {
|
|
194
351
|
return [];
|
|
@@ -200,6 +357,17 @@ const traverseExpressionWithConfig = (context, expression, configuration, cssAtt
|
|
|
200
357
|
}
|
|
201
358
|
});
|
|
202
359
|
break;
|
|
360
|
+
|
|
361
|
+
// @ts-expect-error - our ESLint-related types assume vanilla JS, when in fact
|
|
362
|
+
// it is running @typescript-eslint
|
|
363
|
+
//
|
|
364
|
+
// Switching to the more accurate @typescript-eslint types would break
|
|
365
|
+
// eslint-codemod-utils and all ESLint rules in packages/design-system,
|
|
366
|
+
// so we just leave this as-is.
|
|
367
|
+
case 'TSAsExpression':
|
|
368
|
+
// @ts-expect-error
|
|
369
|
+
traverseExpression(expression.expression);
|
|
370
|
+
break;
|
|
203
371
|
default:
|
|
204
372
|
// Do nothing!
|
|
205
373
|
break;
|
|
@@ -209,10 +377,15 @@ const traverseExpressionWithConfig = (context, expression, configuration, cssAtt
|
|
|
209
377
|
};
|
|
210
378
|
const defaultConfig = {
|
|
211
379
|
cssFunctions: ['css', 'xcss'],
|
|
212
|
-
stylesPlacement: 'top'
|
|
380
|
+
stylesPlacement: 'top',
|
|
381
|
+
cssImportSource: CSS_IN_JS_IMPORTS.compiled,
|
|
382
|
+
xcssImportSource: CSS_IN_JS_IMPORTS.atlaskitPrimitives,
|
|
383
|
+
excludeReactComponents: false,
|
|
384
|
+
fixNamesOnly: false
|
|
213
385
|
};
|
|
214
386
|
const rule = createLintRule({
|
|
215
387
|
meta: {
|
|
388
|
+
type: 'problem',
|
|
216
389
|
name: 'consistent-css-prop-usage',
|
|
217
390
|
docs: {
|
|
218
391
|
description: 'Ensures consistency with `css` and `xcss` prop usages',
|
|
@@ -222,13 +395,42 @@ const rule = createLintRule({
|
|
|
222
395
|
},
|
|
223
396
|
fixable: 'code',
|
|
224
397
|
messages: {
|
|
225
|
-
|
|
398
|
+
cssAtTopOfModule: `Create styles at the top of the module scope using the respective css function.`,
|
|
226
399
|
cssAtBottomOfModule: `Create styles at the bottom of the module scope using the respective css function.`,
|
|
227
|
-
cssObjectTypeOnly: `Create styles using objects passed to
|
|
228
|
-
cssInModule: `Imported styles should not be used
|
|
400
|
+
cssObjectTypeOnly: `Create styles using objects passed to a css function call, e.g. \`css({ textAlign: 'center'; })\`.`,
|
|
401
|
+
cssInModule: `Imported styles should not be used; instead define in the module, import a component, or use a design token.`,
|
|
229
402
|
cssArrayStylesOnly: `Compose styles with an array on the css prop instead of using object spread.`,
|
|
403
|
+
noMemberExpressions: `Styles should be a regular variable (e.g. 'buttonStyles'), not a member of an object (e.g. 'myObject.styles').`,
|
|
230
404
|
shouldEndInStyles: 'Declared styles should end in "styles".'
|
|
231
|
-
}
|
|
405
|
+
},
|
|
406
|
+
schema: [{
|
|
407
|
+
type: 'object',
|
|
408
|
+
properties: {
|
|
409
|
+
cssFunctions: {
|
|
410
|
+
type: 'array',
|
|
411
|
+
items: [{
|
|
412
|
+
type: 'string'
|
|
413
|
+
}]
|
|
414
|
+
},
|
|
415
|
+
stylesPlacement: {
|
|
416
|
+
type: 'string',
|
|
417
|
+
enum: ['top', 'bottom']
|
|
418
|
+
},
|
|
419
|
+
cssImportSource: {
|
|
420
|
+
type: 'string'
|
|
421
|
+
},
|
|
422
|
+
xcssImportSource: {
|
|
423
|
+
type: 'string'
|
|
424
|
+
},
|
|
425
|
+
excludeReactComponents: {
|
|
426
|
+
type: 'boolean'
|
|
427
|
+
},
|
|
428
|
+
fixNamesOnly: {
|
|
429
|
+
type: 'boolean'
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
additionalProperties: false
|
|
433
|
+
}]
|
|
232
434
|
},
|
|
233
435
|
create(context) {
|
|
234
436
|
const mergedConfig = assign({}, defaultConfig, context.options[0]);
|
|
@@ -265,11 +467,22 @@ const rule = createLintRule({
|
|
|
265
467
|
}
|
|
266
468
|
});
|
|
267
469
|
},
|
|
268
|
-
JSXAttribute(
|
|
470
|
+
JSXAttribute(nodeOriginal) {
|
|
471
|
+
const node = nodeOriginal;
|
|
269
472
|
const {
|
|
270
473
|
name,
|
|
271
474
|
value
|
|
272
475
|
} = node;
|
|
476
|
+
if (mergedConfig.excludeReactComponents && node.parent.type === 'JSXOpeningElement') {
|
|
477
|
+
// e.g. <item.before />
|
|
478
|
+
if (node.parent.name.type === 'JSXMemberExpression') {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
// e.g. <div />, <MenuItem />
|
|
482
|
+
if (node.parent.name.type === 'JSXIdentifier' && !isDOMElementName(node.parent.name.name)) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
273
486
|
|
|
274
487
|
// Always reset to empty array
|
|
275
488
|
hoistedCss = [];
|
|
@@ -277,11 +490,16 @@ const rule = createLintRule({
|
|
|
277
490
|
// When not a jsx expression. For eg. css=""
|
|
278
491
|
if ((value === null || value === void 0 ? void 0 : value.type) !== 'JSXExpressionContainer') {
|
|
279
492
|
context.report({
|
|
280
|
-
node
|
|
281
|
-
messageId: mergedConfig.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : '
|
|
493
|
+
node,
|
|
494
|
+
messageId: mergedConfig.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule'
|
|
282
495
|
});
|
|
283
496
|
return;
|
|
284
497
|
}
|
|
498
|
+
if (value.expression.type === 'JSXEmptyExpression') {
|
|
499
|
+
// e.g. the comment in
|
|
500
|
+
// <div css={/* Hello there */} />
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
285
503
|
traverseExpressionWithConfig(context, value.expression, mergedConfig, name.name);
|
|
286
504
|
}
|
|
287
505
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getFirstSupportedImport } from '../get-first-supported-import';
|
|
2
2
|
/**
|
|
3
3
|
* Given a list of node, find and return the callee of the first Compiled or styled-components `styled` function call found in the list.
|
|
4
4
|
*
|
|
@@ -40,13 +40,9 @@ const findNode = nodes => {
|
|
|
40
40
|
* @returns The local name used to import the `styled` API.
|
|
41
41
|
*/
|
|
42
42
|
const getStyledImportSpecifierName = (context, importSources) => {
|
|
43
|
-
var
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
};
|
|
47
|
-
const source = context.getSourceCode();
|
|
48
|
-
const supportedImports = source.ast.body.filter(isSupportedImport);
|
|
49
|
-
return (_supportedImports$0$s = supportedImports[0].specifiers.find(spec => spec.type === 'ImportSpecifier' && spec.imported.name === 'styled')) === null || _supportedImports$0$s === void 0 ? void 0 : _supportedImports$0$s.local.name;
|
|
43
|
+
var _supportedImport$spec;
|
|
44
|
+
const supportedImport = getFirstSupportedImport(context, importSources);
|
|
45
|
+
return supportedImport === null || supportedImport === void 0 ? void 0 : (_supportedImport$spec = supportedImport.specifiers.find(spec => spec.type === 'ImportSpecifier' && spec.imported.name === 'styled' || spec.type === 'ImportDefaultSpecifier' && spec.local.name === 'styled')) === null || _supportedImport$spec === void 0 ? void 0 : _supportedImport$spec.local.name;
|
|
50
46
|
};
|
|
51
47
|
|
|
52
48
|
/**
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
/**
|
|
3
|
+
* Get the first import declaration in the file that matches any of the packages
|
|
4
|
+
* in `importSources`.
|
|
5
|
+
*
|
|
6
|
+
* @param context Rule context.
|
|
7
|
+
* @param importSources The packages to check import statements for. If importSources
|
|
8
|
+
* contains more than one package, the first import statement
|
|
9
|
+
* detected in the file that matches any of the packages will be
|
|
10
|
+
* returned.
|
|
11
|
+
* @returns The first import declaration found in the file.
|
|
12
|
+
*/
|
|
13
|
+
export const getFirstSupportedImport = (context, importSources) => {
|
|
14
|
+
const isSupportedImport = node => {
|
|
15
|
+
return isNodeOfType(node, 'ImportDeclaration') && typeof node.source.value === 'string' && importSources.includes(node.source.value);
|
|
16
|
+
};
|
|
17
|
+
const source = context.getSourceCode();
|
|
18
|
+
const supportedImports = source.ast.body.filter(isSupportedImport);
|
|
19
|
+
if (supportedImports.length) {
|
|
20
|
+
return supportedImports[0];
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -5,7 +5,8 @@ export const CSS_IN_JS_IMPORTS = {
|
|
|
5
5
|
emotionReact: '@emotion/react',
|
|
6
6
|
emotionCore: '@emotion/core',
|
|
7
7
|
styledComponents: 'styled-components',
|
|
8
|
-
atlaskitCss: '@atlaskit/css'
|
|
8
|
+
atlaskitCss: '@atlaskit/css',
|
|
9
|
+
atlaskitPrimitives: '@atlaskit/primitives'
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
// A CSS-in-JS library an import of a valid css, cx, cssMap, etc.
|