@atlaskit/eslint-plugin-design-system 8.21.0 → 8.23.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
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @atlaskit/eslint-plugin-design-system
|
|
2
2
|
|
|
3
|
+
## 8.23.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#68090](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/68090) [`251d7c1fca48`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/251d7c1fca48) - Modified fix naming convention for consistent-css-prop-usage rule to align with camelCase convention
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
|
|
13
|
+
## 8.22.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- [#63589](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/63589) [`f59d997d1913`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/f59d997d1913) - Implemented new fixers for cssOnTopOfModule and cssAtBottomOfModule violation cases
|
|
18
|
+
|
|
3
19
|
## 8.21.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
@@ -66,6 +66,8 @@ This rule has options - see below.
|
|
|
66
66
|
|
|
67
67
|
👎 Example of **incorrect** code for this rule:
|
|
68
68
|
|
|
69
|
+
**Calling a css/xcss function or direct objects inside the JSX attribute.**
|
|
70
|
+
|
|
69
71
|
```js
|
|
70
72
|
function Button({ children }) {
|
|
71
73
|
return <div css={css({...})}>{children}</div>;
|
|
@@ -73,6 +75,8 @@ function Button({ children }) {
|
|
|
73
75
|
}
|
|
74
76
|
```
|
|
75
77
|
|
|
78
|
+
**Inserting a non css-function based object identifier into a css JSX attribute.**
|
|
79
|
+
|
|
76
80
|
```js
|
|
77
81
|
const container = {
|
|
78
82
|
^^^^^^^^^ should be a css function call
|
|
@@ -84,6 +88,8 @@ function Button({ children }) {
|
|
|
84
88
|
}
|
|
85
89
|
```
|
|
86
90
|
|
|
91
|
+
**Importing styles from another file.**
|
|
92
|
+
|
|
87
93
|
```js
|
|
88
94
|
import { container } from './styles';
|
|
89
95
|
^^^^^^^^^ styles should be local, not shared
|
|
@@ -93,6 +99,8 @@ function Button({ children }) {
|
|
|
93
99
|
}
|
|
94
100
|
```
|
|
95
101
|
|
|
102
|
+
**Nesting styles with objects instead of arrays.**
|
|
103
|
+
|
|
96
104
|
```js
|
|
97
105
|
const baseContainerStyles = css({
|
|
98
106
|
zIndex: 5,
|
|
@@ -111,6 +119,13 @@ function Button({ children }) {
|
|
|
111
119
|
|
|
112
120
|
👍 Example of **correct** code for this rule:
|
|
113
121
|
|
|
122
|
+
**Using the css() function to create a style object that follows the naming convention (ends in Styles) and passing it as a variable into the css={...} JSX attribute.**
|
|
123
|
+
|
|
124
|
+
With the following options turned on:
|
|
125
|
+
|
|
126
|
+
- cssFunctions = ['css']
|
|
127
|
+
- stylesPlacement = 'top'
|
|
128
|
+
|
|
114
129
|
```js
|
|
115
130
|
const containerStyles = css({
|
|
116
131
|
zIndex: 1,
|
|
@@ -121,7 +136,38 @@ function Button({ children }) {
|
|
|
121
136
|
}
|
|
122
137
|
```
|
|
123
138
|
|
|
139
|
+
**Technically correct usage of the cssMap function.**
|
|
140
|
+
|
|
141
|
+
With the following options turned on:
|
|
142
|
+
|
|
143
|
+
- cssFunctions = ['css']
|
|
144
|
+
- stylesPlacement = 'top'
|
|
145
|
+
|
|
124
146
|
```js
|
|
147
|
+
const borderStyles = cssMap({
|
|
148
|
+
'solid': '1px solid';
|
|
149
|
+
'none': '0px';
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
function Button({ children }) {
|
|
153
|
+
return <button css={borderStyles[solid]}>{children}</button>;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Create composite styles with arrays, not objects.**
|
|
158
|
+
|
|
159
|
+
With the following options turned on:
|
|
160
|
+
|
|
161
|
+
- cssFunctions = ['css']
|
|
162
|
+
- stylesPlacement = 'bottom'
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
function Button({ children }) {
|
|
166
|
+
return (
|
|
167
|
+
<button css={[baseContainerStyles, containerStyles]}>{children}</button>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
125
171
|
const baseContainerStyles = css({
|
|
126
172
|
zIndex: 5,
|
|
127
173
|
});
|
|
@@ -129,12 +175,15 @@ const baseContainerStyles = css({
|
|
|
129
175
|
const containerStyles = css({
|
|
130
176
|
zIndex: 7,
|
|
131
177
|
});
|
|
178
|
+
```
|
|
132
179
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
180
|
+
**Ternaries can be used inline**
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
const baseStyles = css({ color: token('color.text.primary') });
|
|
184
|
+
const disabledStyles = css({ color: token('color.text.disabled') });
|
|
185
|
+
|
|
186
|
+
<div css={props.disabled ? disabledStyles : baseStyles}></div>;
|
|
138
187
|
```
|
|
139
188
|
|
|
140
189
|
<h3>Options</h3>
|
|
@@ -6,15 +6,15 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
});
|
|
7
7
|
exports.default = void 0;
|
|
8
8
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
|
-
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
10
9
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
10
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
11
11
|
var _eslintCodemodUtils = require("eslint-codemod-utils");
|
|
12
12
|
var _assign = _interopRequireDefault(require("lodash/assign"));
|
|
13
13
|
var _createRule = require("../utils/create-rule");
|
|
14
14
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
15
15
|
|
|
16
|
-
var declarationSuffix = 'Styles';
|
|
17
16
|
function isCssCallExpression(node, cssFunctions) {
|
|
17
|
+
cssFunctions = [].concat((0, _toConsumableArray2.default)(cssFunctions), ['cssMap']);
|
|
18
18
|
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');
|
|
19
19
|
}
|
|
20
20
|
function findSpreadProperties(node) {
|
|
@@ -25,6 +25,44 @@ function findSpreadProperties(node) {
|
|
|
25
25
|
property.type === 'ExperimentalSpreadProperty';
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
+
var getTopLevelNode = function getTopLevelNode(expression) {
|
|
29
|
+
while (expression.parent.type !== 'Program') {
|
|
30
|
+
expression = expression.parent;
|
|
31
|
+
}
|
|
32
|
+
return expression;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// TODO: This can be optimised by implementing a fixer at the very end (Program:exit) and handling all validations at once
|
|
36
|
+
/**
|
|
37
|
+
* Generates the declarator string when fixing the cssOnTopOfModule/cssAtBottomOfModule cases.
|
|
38
|
+
* When `styles` already exists, `styles_1, styles_2, ..., styles_X` are incrementally created for each unhoisted style.
|
|
39
|
+
* The generated `styles` varibale declaration names must be manually modified to be more informative at the discretion of owning teams.
|
|
40
|
+
*/
|
|
41
|
+
var getDeclaratorString = function getDeclaratorString(context) {
|
|
42
|
+
var scope = context.getScope();
|
|
43
|
+
|
|
44
|
+
// Get to ModuleScope
|
|
45
|
+
while (scope && scope.upper && scope.upper.type !== 'global') {
|
|
46
|
+
var _scope;
|
|
47
|
+
scope = (_scope = scope) === null || _scope === void 0 ? void 0 : _scope.upper;
|
|
48
|
+
}
|
|
49
|
+
var variables = scope.variables.map(function (variable) {
|
|
50
|
+
return variable.name;
|
|
51
|
+
});
|
|
52
|
+
var count = 2;
|
|
53
|
+
var declaratorName = 'styles';
|
|
54
|
+
|
|
55
|
+
// Base case
|
|
56
|
+
if (!variables.includes(declaratorName)) {
|
|
57
|
+
return declaratorName;
|
|
58
|
+
} else {
|
|
59
|
+
// If styles already exists, increment the number
|
|
60
|
+
while (variables.includes("".concat(declaratorName).concat(count))) {
|
|
61
|
+
count++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return "".concat(declaratorName).concat(count);
|
|
65
|
+
};
|
|
28
66
|
function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
29
67
|
var _getIdentifierInParen, _getIdentifierInParen2;
|
|
30
68
|
var scope = context.getScope();
|
|
@@ -47,11 +85,14 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
|
47
85
|
// When variable is declared inside the component
|
|
48
86
|
context.report({
|
|
49
87
|
node: sourceIdentifier,
|
|
50
|
-
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule'
|
|
88
|
+
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule',
|
|
89
|
+
fix: function fix(fixer) {
|
|
90
|
+
return fixCssNotInModuleScope(fixer, context, configuration, identifier);
|
|
91
|
+
}
|
|
51
92
|
});
|
|
52
93
|
return;
|
|
53
94
|
}
|
|
54
|
-
if (identifier.parent && identifier.parent.init && !isCssCallExpression(identifier.parent.init,
|
|
95
|
+
if (identifier.parent && identifier.parent.init && !isCssCallExpression(identifier.parent.init, configuration.cssFunctions)) {
|
|
55
96
|
// When variable value is not of type css({})
|
|
56
97
|
context.report({
|
|
57
98
|
node: identifier,
|
|
@@ -71,13 +112,50 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
|
71
112
|
}
|
|
72
113
|
}
|
|
73
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Fixer for the cssOnTopOfModule/cssAtBottomOfModule violation cases.
|
|
117
|
+
* This deals with Identifiers and Expressions passed from the traverseExpressionWithConfig() function.
|
|
118
|
+
* @param fixer The ESLint RuleFixer object
|
|
119
|
+
* @param context The context of the node
|
|
120
|
+
* @param configuration The configuration of the rule, determining whether the fix is implmeneted at the top or bottom of the module
|
|
121
|
+
* @param node Either an IdentifierWithParent node. Expression, or SpreadElement that we handle
|
|
122
|
+
* @param cssAttributeName An optional parameter only added when we fix an ObjectExpression
|
|
123
|
+
*/
|
|
124
|
+
var fixCssNotInModuleScope = function fixCssNotInModuleScope(fixer, context, configuration, node, cssAttributeName) {
|
|
125
|
+
var sourceCode = context.getSourceCode();
|
|
126
|
+
var topLevelNode = getTopLevelNode(node);
|
|
127
|
+
var moduleString;
|
|
128
|
+
var implementFixer = [];
|
|
129
|
+
if (node.type === 'Identifier') {
|
|
130
|
+
var identifier = node;
|
|
131
|
+
var declarator = identifier.parent.parent;
|
|
132
|
+
moduleString = sourceCode.getText(declarator);
|
|
133
|
+
implementFixer.push(fixer.remove(declarator));
|
|
134
|
+
} else {
|
|
135
|
+
var _declarator = getDeclaratorString(context);
|
|
136
|
+
var text = sourceCode.getText(node);
|
|
137
|
+
|
|
138
|
+
// If this has been passed, then we know it's an ObjectExpression
|
|
139
|
+
if (cssAttributeName) {
|
|
140
|
+
moduleString = "const ".concat(_declarator, " = ").concat(cssAttributeName, "(").concat(text, ");");
|
|
141
|
+
} else {
|
|
142
|
+
moduleString = moduleString = "const ".concat(_declarator, " = ").concat(text, ";");
|
|
143
|
+
}
|
|
144
|
+
implementFixer.push(fixer.replaceText(node, _declarator));
|
|
145
|
+
}
|
|
146
|
+
return [].concat(implementFixer, [
|
|
147
|
+
// Insert the node either before or after
|
|
148
|
+
configuration.stylesPlacement === 'bottom' ? fixer.insertTextAfter(topLevelNode, '\n' + moduleString) : fixer.insertTextBefore(topLevelNode, moduleString + '\n')]);
|
|
149
|
+
};
|
|
150
|
+
|
|
74
151
|
/**
|
|
75
152
|
* Handle different cases based on what's been passed in the css-related JSXAttribute
|
|
76
153
|
* @param context the context of the node
|
|
77
154
|
* @param expression the expression of the JSXAttribute value
|
|
78
155
|
* @param configuration what css-related functions to account for (eg. css, xcss, cssMap), and whether to detect bottom vs top expressions
|
|
156
|
+
* @param cssAttributeName used to encapsulate ObjectExpressions when cssOnTopOfModule/cssAtBottomOfModule violations are triggered
|
|
79
157
|
*/
|
|
80
|
-
var traverseExpressionWithConfig = function traverseExpressionWithConfig(context, expression, configuration) {
|
|
158
|
+
var traverseExpressionWithConfig = function traverseExpressionWithConfig(context, expression, configuration, cssAttributeName) {
|
|
81
159
|
function traverseExpression(expression) {
|
|
82
160
|
switch (expression.type) {
|
|
83
161
|
case 'Identifier':
|
|
@@ -105,14 +183,24 @@ var traverseExpressionWithConfig = function traverseExpressionWithConfig(context
|
|
|
105
183
|
traverseExpression(expression.consequent);
|
|
106
184
|
traverseExpression(expression.alternate);
|
|
107
185
|
break;
|
|
108
|
-
case 'CallExpression':
|
|
109
186
|
case 'ObjectExpression':
|
|
187
|
+
case 'CallExpression':
|
|
110
188
|
case 'TaggedTemplateExpression':
|
|
111
189
|
case 'TemplateLiteral':
|
|
112
190
|
// We've found elements that shouldn't be here! Report an error.
|
|
113
191
|
context.report({
|
|
114
192
|
node: expression,
|
|
115
|
-
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule'
|
|
193
|
+
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule',
|
|
194
|
+
fix: function fix(fixer) {
|
|
195
|
+
// Don't fix CallExpressions unless they're from cssFunctions or cssMap
|
|
196
|
+
if (expression.type === 'CallExpression' && !isCssCallExpression(expression, configuration.cssFunctions)) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
if (expression.type === 'ObjectExpression') {
|
|
200
|
+
return fixCssNotInModuleScope(fixer, context, configuration, expression, cssAttributeName);
|
|
201
|
+
}
|
|
202
|
+
return fixCssNotInModuleScope(fixer, context, configuration, expression);
|
|
203
|
+
}
|
|
116
204
|
});
|
|
117
205
|
break;
|
|
118
206
|
default:
|
|
@@ -148,6 +236,7 @@ var rule = (0, _createRule.createLintRule)({
|
|
|
148
236
|
create: function create(context) {
|
|
149
237
|
var _ref3;
|
|
150
238
|
var mergedConfig = (0, _assign.default)({}, defaultConfig, context.options[0]);
|
|
239
|
+
var declarationSuffix = 'Styles';
|
|
151
240
|
var callSelectorFunctions = [].concat((0, _toConsumableArray2.default)(mergedConfig.cssFunctions), ['cssMap']);
|
|
152
241
|
var callSelector = callSelectorFunctions.map(function (fn) {
|
|
153
242
|
return "CallExpression[callee.name=".concat(fn, "]");
|
|
@@ -158,7 +247,7 @@ var rule = (0, _createRule.createLintRule)({
|
|
|
158
247
|
return;
|
|
159
248
|
}
|
|
160
249
|
var identifier = node.parent.id;
|
|
161
|
-
if (identifier.type === 'Identifier' && identifier.name.endsWith(declarationSuffix)) {
|
|
250
|
+
if (identifier.type === 'Identifier' && (identifier.name.endsWith(declarationSuffix) || identifier.name.startsWith(declarationSuffix.toLowerCase()) || identifier.name === declarationSuffix.toLowerCase())) {
|
|
162
251
|
// Already prefixed! Nothing to do.
|
|
163
252
|
return;
|
|
164
253
|
}
|
|
@@ -196,7 +285,7 @@ var rule = (0, _createRule.createLintRule)({
|
|
|
196
285
|
});
|
|
197
286
|
return;
|
|
198
287
|
}
|
|
199
|
-
traverseExpressionWithConfig(context, value.expression, mergedConfig);
|
|
288
|
+
traverseExpressionWithConfig(context, value.expression, mergedConfig, name.name);
|
|
200
289
|
}
|
|
201
290
|
}), _ref3;
|
|
202
291
|
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import { getIdentifierInParentScope, isNodeOfType } from 'eslint-codemod-utils';
|
|
4
4
|
import assign from 'lodash/assign';
|
|
5
5
|
import { createLintRule } from '../utils/create-rule';
|
|
6
|
-
const declarationSuffix = 'Styles';
|
|
7
6
|
function isCssCallExpression(node, cssFunctions) {
|
|
7
|
+
cssFunctions = [...cssFunctions, 'cssMap'];
|
|
8
8
|
return !!(isNodeOfType(node, 'CallExpression') && node.callee && node.callee.type === 'Identifier' && cssFunctions.includes(node.callee.name) && node.arguments.length && node.arguments[0].type === 'ObjectExpression');
|
|
9
9
|
}
|
|
10
10
|
function findSpreadProperties(node) {
|
|
@@ -13,6 +13,42 @@ function findSpreadProperties(node) {
|
|
|
13
13
|
// @ts-ignore
|
|
14
14
|
property.type === 'ExperimentalSpreadProperty');
|
|
15
15
|
}
|
|
16
|
+
const getTopLevelNode = expression => {
|
|
17
|
+
while (expression.parent.type !== 'Program') {
|
|
18
|
+
expression = expression.parent;
|
|
19
|
+
}
|
|
20
|
+
return expression;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// TODO: This can be optimised by implementing a fixer at the very end (Program:exit) and handling all validations at once
|
|
24
|
+
/**
|
|
25
|
+
* Generates the declarator string when fixing the cssOnTopOfModule/cssAtBottomOfModule cases.
|
|
26
|
+
* When `styles` already exists, `styles_1, styles_2, ..., styles_X` are incrementally created for each unhoisted style.
|
|
27
|
+
* The generated `styles` varibale declaration names must be manually modified to be more informative at the discretion of owning teams.
|
|
28
|
+
*/
|
|
29
|
+
const getDeclaratorString = context => {
|
|
30
|
+
let scope = context.getScope();
|
|
31
|
+
|
|
32
|
+
// Get to ModuleScope
|
|
33
|
+
while (scope && scope.upper && scope.upper.type !== 'global') {
|
|
34
|
+
var _scope;
|
|
35
|
+
scope = (_scope = scope) === null || _scope === void 0 ? void 0 : _scope.upper;
|
|
36
|
+
}
|
|
37
|
+
const variables = scope.variables.map(variable => variable.name);
|
|
38
|
+
let count = 2;
|
|
39
|
+
let declaratorName = 'styles';
|
|
40
|
+
|
|
41
|
+
// Base case
|
|
42
|
+
if (!variables.includes(declaratorName)) {
|
|
43
|
+
return declaratorName;
|
|
44
|
+
} else {
|
|
45
|
+
// If styles already exists, increment the number
|
|
46
|
+
while (variables.includes(`${declaratorName}${count}`)) {
|
|
47
|
+
count++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return `${declaratorName}${count}`;
|
|
51
|
+
};
|
|
16
52
|
function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
17
53
|
var _getIdentifierInParen, _getIdentifierInParen2;
|
|
18
54
|
const scope = context.getScope();
|
|
@@ -33,11 +69,14 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
|
33
69
|
// When variable is declared inside the component
|
|
34
70
|
context.report({
|
|
35
71
|
node: sourceIdentifier,
|
|
36
|
-
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule'
|
|
72
|
+
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule',
|
|
73
|
+
fix: fixer => {
|
|
74
|
+
return fixCssNotInModuleScope(fixer, context, configuration, identifier);
|
|
75
|
+
}
|
|
37
76
|
});
|
|
38
77
|
return;
|
|
39
78
|
}
|
|
40
|
-
if (identifier.parent && identifier.parent.init && !isCssCallExpression(identifier.parent.init,
|
|
79
|
+
if (identifier.parent && identifier.parent.init && !isCssCallExpression(identifier.parent.init, configuration.cssFunctions)) {
|
|
41
80
|
// When variable value is not of type css({})
|
|
42
81
|
context.report({
|
|
43
82
|
node: identifier,
|
|
@@ -57,13 +96,50 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
|
57
96
|
}
|
|
58
97
|
}
|
|
59
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Fixer for the cssOnTopOfModule/cssAtBottomOfModule violation cases.
|
|
101
|
+
* This deals with Identifiers and Expressions passed from the traverseExpressionWithConfig() function.
|
|
102
|
+
* @param fixer The ESLint RuleFixer object
|
|
103
|
+
* @param context The context of the node
|
|
104
|
+
* @param configuration The configuration of the rule, determining whether the fix is implmeneted at the top or bottom of the module
|
|
105
|
+
* @param node Either an IdentifierWithParent node. Expression, or SpreadElement that we handle
|
|
106
|
+
* @param cssAttributeName An optional parameter only added when we fix an ObjectExpression
|
|
107
|
+
*/
|
|
108
|
+
const fixCssNotInModuleScope = (fixer, context, configuration, node, cssAttributeName) => {
|
|
109
|
+
const sourceCode = context.getSourceCode();
|
|
110
|
+
const topLevelNode = getTopLevelNode(node);
|
|
111
|
+
let moduleString;
|
|
112
|
+
let implementFixer = [];
|
|
113
|
+
if (node.type === 'Identifier') {
|
|
114
|
+
const identifier = node;
|
|
115
|
+
const declarator = identifier.parent.parent;
|
|
116
|
+
moduleString = sourceCode.getText(declarator);
|
|
117
|
+
implementFixer.push(fixer.remove(declarator));
|
|
118
|
+
} else {
|
|
119
|
+
const declarator = getDeclaratorString(context);
|
|
120
|
+
const text = sourceCode.getText(node);
|
|
121
|
+
|
|
122
|
+
// If this has been passed, then we know it's an ObjectExpression
|
|
123
|
+
if (cssAttributeName) {
|
|
124
|
+
moduleString = `const ${declarator} = ${cssAttributeName}(${text});`;
|
|
125
|
+
} else {
|
|
126
|
+
moduleString = moduleString = `const ${declarator} = ${text};`;
|
|
127
|
+
}
|
|
128
|
+
implementFixer.push(fixer.replaceText(node, declarator));
|
|
129
|
+
}
|
|
130
|
+
return [...implementFixer,
|
|
131
|
+
// Insert the node either before or after
|
|
132
|
+
configuration.stylesPlacement === 'bottom' ? fixer.insertTextAfter(topLevelNode, '\n' + moduleString) : fixer.insertTextBefore(topLevelNode, moduleString + '\n')];
|
|
133
|
+
};
|
|
134
|
+
|
|
60
135
|
/**
|
|
61
136
|
* Handle different cases based on what's been passed in the css-related JSXAttribute
|
|
62
137
|
* @param context the context of the node
|
|
63
138
|
* @param expression the expression of the JSXAttribute value
|
|
64
139
|
* @param configuration what css-related functions to account for (eg. css, xcss, cssMap), and whether to detect bottom vs top expressions
|
|
140
|
+
* @param cssAttributeName used to encapsulate ObjectExpressions when cssOnTopOfModule/cssAtBottomOfModule violations are triggered
|
|
65
141
|
*/
|
|
66
|
-
const traverseExpressionWithConfig = (context, expression, configuration) => {
|
|
142
|
+
const traverseExpressionWithConfig = (context, expression, configuration, cssAttributeName) => {
|
|
67
143
|
function traverseExpression(expression) {
|
|
68
144
|
switch (expression.type) {
|
|
69
145
|
case 'Identifier':
|
|
@@ -89,14 +165,24 @@ const traverseExpressionWithConfig = (context, expression, configuration) => {
|
|
|
89
165
|
traverseExpression(expression.consequent);
|
|
90
166
|
traverseExpression(expression.alternate);
|
|
91
167
|
break;
|
|
92
|
-
case 'CallExpression':
|
|
93
168
|
case 'ObjectExpression':
|
|
169
|
+
case 'CallExpression':
|
|
94
170
|
case 'TaggedTemplateExpression':
|
|
95
171
|
case 'TemplateLiteral':
|
|
96
172
|
// We've found elements that shouldn't be here! Report an error.
|
|
97
173
|
context.report({
|
|
98
174
|
node: expression,
|
|
99
|
-
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule'
|
|
175
|
+
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule',
|
|
176
|
+
fix: fixer => {
|
|
177
|
+
// Don't fix CallExpressions unless they're from cssFunctions or cssMap
|
|
178
|
+
if (expression.type === 'CallExpression' && !isCssCallExpression(expression, configuration.cssFunctions)) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
if (expression.type === 'ObjectExpression') {
|
|
182
|
+
return fixCssNotInModuleScope(fixer, context, configuration, expression, cssAttributeName);
|
|
183
|
+
}
|
|
184
|
+
return fixCssNotInModuleScope(fixer, context, configuration, expression);
|
|
185
|
+
}
|
|
100
186
|
});
|
|
101
187
|
break;
|
|
102
188
|
default:
|
|
@@ -131,6 +217,7 @@ const rule = createLintRule({
|
|
|
131
217
|
},
|
|
132
218
|
create(context) {
|
|
133
219
|
const mergedConfig = assign({}, defaultConfig, context.options[0]);
|
|
220
|
+
const declarationSuffix = 'Styles';
|
|
134
221
|
const callSelectorFunctions = [...mergedConfig.cssFunctions, 'cssMap'];
|
|
135
222
|
const callSelector = callSelectorFunctions.map(fn => `CallExpression[callee.name=${fn}]`).join(',');
|
|
136
223
|
return {
|
|
@@ -140,7 +227,7 @@ const rule = createLintRule({
|
|
|
140
227
|
return;
|
|
141
228
|
}
|
|
142
229
|
const identifier = node.parent.id;
|
|
143
|
-
if (identifier.type === 'Identifier' && identifier.name.endsWith(declarationSuffix)) {
|
|
230
|
+
if (identifier.type === 'Identifier' && (identifier.name.endsWith(declarationSuffix) || identifier.name.startsWith(declarationSuffix.toLowerCase()) || identifier.name === declarationSuffix.toLowerCase())) {
|
|
144
231
|
// Already prefixed! Nothing to do.
|
|
145
232
|
return;
|
|
146
233
|
}
|
|
@@ -177,7 +264,7 @@ const rule = createLintRule({
|
|
|
177
264
|
});
|
|
178
265
|
return;
|
|
179
266
|
}
|
|
180
|
-
traverseExpressionWithConfig(context, value.expression, mergedConfig);
|
|
267
|
+
traverseExpressionWithConfig(context, value.expression, mergedConfig, name.name);
|
|
181
268
|
}
|
|
182
269
|
}
|
|
183
270
|
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
-
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
3
2
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
3
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
4
4
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
5
5
|
|
|
6
6
|
import { getIdentifierInParentScope, isNodeOfType } from 'eslint-codemod-utils';
|
|
7
7
|
import assign from 'lodash/assign';
|
|
8
8
|
import { createLintRule } from '../utils/create-rule';
|
|
9
|
-
var declarationSuffix = 'Styles';
|
|
10
9
|
function isCssCallExpression(node, cssFunctions) {
|
|
10
|
+
cssFunctions = [].concat(_toConsumableArray(cssFunctions), ['cssMap']);
|
|
11
11
|
return !!(isNodeOfType(node, 'CallExpression') && node.callee && node.callee.type === 'Identifier' && cssFunctions.includes(node.callee.name) && node.arguments.length && node.arguments[0].type === 'ObjectExpression');
|
|
12
12
|
}
|
|
13
13
|
function findSpreadProperties(node) {
|
|
@@ -18,6 +18,44 @@ function findSpreadProperties(node) {
|
|
|
18
18
|
property.type === 'ExperimentalSpreadProperty';
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
+
var getTopLevelNode = function getTopLevelNode(expression) {
|
|
22
|
+
while (expression.parent.type !== 'Program') {
|
|
23
|
+
expression = expression.parent;
|
|
24
|
+
}
|
|
25
|
+
return expression;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// TODO: This can be optimised by implementing a fixer at the very end (Program:exit) and handling all validations at once
|
|
29
|
+
/**
|
|
30
|
+
* Generates the declarator string when fixing the cssOnTopOfModule/cssAtBottomOfModule cases.
|
|
31
|
+
* When `styles` already exists, `styles_1, styles_2, ..., styles_X` are incrementally created for each unhoisted style.
|
|
32
|
+
* The generated `styles` varibale declaration names must be manually modified to be more informative at the discretion of owning teams.
|
|
33
|
+
*/
|
|
34
|
+
var getDeclaratorString = function getDeclaratorString(context) {
|
|
35
|
+
var scope = context.getScope();
|
|
36
|
+
|
|
37
|
+
// Get to ModuleScope
|
|
38
|
+
while (scope && scope.upper && scope.upper.type !== 'global') {
|
|
39
|
+
var _scope;
|
|
40
|
+
scope = (_scope = scope) === null || _scope === void 0 ? void 0 : _scope.upper;
|
|
41
|
+
}
|
|
42
|
+
var variables = scope.variables.map(function (variable) {
|
|
43
|
+
return variable.name;
|
|
44
|
+
});
|
|
45
|
+
var count = 2;
|
|
46
|
+
var declaratorName = 'styles';
|
|
47
|
+
|
|
48
|
+
// Base case
|
|
49
|
+
if (!variables.includes(declaratorName)) {
|
|
50
|
+
return declaratorName;
|
|
51
|
+
} else {
|
|
52
|
+
// If styles already exists, increment the number
|
|
53
|
+
while (variables.includes("".concat(declaratorName).concat(count))) {
|
|
54
|
+
count++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return "".concat(declaratorName).concat(count);
|
|
58
|
+
};
|
|
21
59
|
function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
22
60
|
var _getIdentifierInParen, _getIdentifierInParen2;
|
|
23
61
|
var scope = context.getScope();
|
|
@@ -40,11 +78,14 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
|
40
78
|
// When variable is declared inside the component
|
|
41
79
|
context.report({
|
|
42
80
|
node: sourceIdentifier,
|
|
43
|
-
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule'
|
|
81
|
+
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule',
|
|
82
|
+
fix: function fix(fixer) {
|
|
83
|
+
return fixCssNotInModuleScope(fixer, context, configuration, identifier);
|
|
84
|
+
}
|
|
44
85
|
});
|
|
45
86
|
return;
|
|
46
87
|
}
|
|
47
|
-
if (identifier.parent && identifier.parent.init && !isCssCallExpression(identifier.parent.init,
|
|
88
|
+
if (identifier.parent && identifier.parent.init && !isCssCallExpression(identifier.parent.init, configuration.cssFunctions)) {
|
|
48
89
|
// When variable value is not of type css({})
|
|
49
90
|
context.report({
|
|
50
91
|
node: identifier,
|
|
@@ -64,13 +105,50 @@ function analyzeIdentifier(context, sourceIdentifier, configuration) {
|
|
|
64
105
|
}
|
|
65
106
|
}
|
|
66
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Fixer for the cssOnTopOfModule/cssAtBottomOfModule violation cases.
|
|
110
|
+
* This deals with Identifiers and Expressions passed from the traverseExpressionWithConfig() function.
|
|
111
|
+
* @param fixer The ESLint RuleFixer object
|
|
112
|
+
* @param context The context of the node
|
|
113
|
+
* @param configuration The configuration of the rule, determining whether the fix is implmeneted at the top or bottom of the module
|
|
114
|
+
* @param node Either an IdentifierWithParent node. Expression, or SpreadElement that we handle
|
|
115
|
+
* @param cssAttributeName An optional parameter only added when we fix an ObjectExpression
|
|
116
|
+
*/
|
|
117
|
+
var fixCssNotInModuleScope = function fixCssNotInModuleScope(fixer, context, configuration, node, cssAttributeName) {
|
|
118
|
+
var sourceCode = context.getSourceCode();
|
|
119
|
+
var topLevelNode = getTopLevelNode(node);
|
|
120
|
+
var moduleString;
|
|
121
|
+
var implementFixer = [];
|
|
122
|
+
if (node.type === 'Identifier') {
|
|
123
|
+
var identifier = node;
|
|
124
|
+
var declarator = identifier.parent.parent;
|
|
125
|
+
moduleString = sourceCode.getText(declarator);
|
|
126
|
+
implementFixer.push(fixer.remove(declarator));
|
|
127
|
+
} else {
|
|
128
|
+
var _declarator = getDeclaratorString(context);
|
|
129
|
+
var text = sourceCode.getText(node);
|
|
130
|
+
|
|
131
|
+
// If this has been passed, then we know it's an ObjectExpression
|
|
132
|
+
if (cssAttributeName) {
|
|
133
|
+
moduleString = "const ".concat(_declarator, " = ").concat(cssAttributeName, "(").concat(text, ");");
|
|
134
|
+
} else {
|
|
135
|
+
moduleString = moduleString = "const ".concat(_declarator, " = ").concat(text, ";");
|
|
136
|
+
}
|
|
137
|
+
implementFixer.push(fixer.replaceText(node, _declarator));
|
|
138
|
+
}
|
|
139
|
+
return [].concat(implementFixer, [
|
|
140
|
+
// Insert the node either before or after
|
|
141
|
+
configuration.stylesPlacement === 'bottom' ? fixer.insertTextAfter(topLevelNode, '\n' + moduleString) : fixer.insertTextBefore(topLevelNode, moduleString + '\n')]);
|
|
142
|
+
};
|
|
143
|
+
|
|
67
144
|
/**
|
|
68
145
|
* Handle different cases based on what's been passed in the css-related JSXAttribute
|
|
69
146
|
* @param context the context of the node
|
|
70
147
|
* @param expression the expression of the JSXAttribute value
|
|
71
148
|
* @param configuration what css-related functions to account for (eg. css, xcss, cssMap), and whether to detect bottom vs top expressions
|
|
149
|
+
* @param cssAttributeName used to encapsulate ObjectExpressions when cssOnTopOfModule/cssAtBottomOfModule violations are triggered
|
|
72
150
|
*/
|
|
73
|
-
var traverseExpressionWithConfig = function traverseExpressionWithConfig(context, expression, configuration) {
|
|
151
|
+
var traverseExpressionWithConfig = function traverseExpressionWithConfig(context, expression, configuration, cssAttributeName) {
|
|
74
152
|
function traverseExpression(expression) {
|
|
75
153
|
switch (expression.type) {
|
|
76
154
|
case 'Identifier':
|
|
@@ -98,14 +176,24 @@ var traverseExpressionWithConfig = function traverseExpressionWithConfig(context
|
|
|
98
176
|
traverseExpression(expression.consequent);
|
|
99
177
|
traverseExpression(expression.alternate);
|
|
100
178
|
break;
|
|
101
|
-
case 'CallExpression':
|
|
102
179
|
case 'ObjectExpression':
|
|
180
|
+
case 'CallExpression':
|
|
103
181
|
case 'TaggedTemplateExpression':
|
|
104
182
|
case 'TemplateLiteral':
|
|
105
183
|
// We've found elements that shouldn't be here! Report an error.
|
|
106
184
|
context.report({
|
|
107
185
|
node: expression,
|
|
108
|
-
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule'
|
|
186
|
+
messageId: configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssOnTopOfModule',
|
|
187
|
+
fix: function fix(fixer) {
|
|
188
|
+
// Don't fix CallExpressions unless they're from cssFunctions or cssMap
|
|
189
|
+
if (expression.type === 'CallExpression' && !isCssCallExpression(expression, configuration.cssFunctions)) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
if (expression.type === 'ObjectExpression') {
|
|
193
|
+
return fixCssNotInModuleScope(fixer, context, configuration, expression, cssAttributeName);
|
|
194
|
+
}
|
|
195
|
+
return fixCssNotInModuleScope(fixer, context, configuration, expression);
|
|
196
|
+
}
|
|
109
197
|
});
|
|
110
198
|
break;
|
|
111
199
|
default:
|
|
@@ -141,6 +229,7 @@ var rule = createLintRule({
|
|
|
141
229
|
create: function create(context) {
|
|
142
230
|
var _ref3;
|
|
143
231
|
var mergedConfig = assign({}, defaultConfig, context.options[0]);
|
|
232
|
+
var declarationSuffix = 'Styles';
|
|
144
233
|
var callSelectorFunctions = [].concat(_toConsumableArray(mergedConfig.cssFunctions), ['cssMap']);
|
|
145
234
|
var callSelector = callSelectorFunctions.map(function (fn) {
|
|
146
235
|
return "CallExpression[callee.name=".concat(fn, "]");
|
|
@@ -151,7 +240,7 @@ var rule = createLintRule({
|
|
|
151
240
|
return;
|
|
152
241
|
}
|
|
153
242
|
var identifier = node.parent.id;
|
|
154
|
-
if (identifier.type === 'Identifier' && identifier.name.endsWith(declarationSuffix)) {
|
|
243
|
+
if (identifier.type === 'Identifier' && (identifier.name.endsWith(declarationSuffix) || identifier.name.startsWith(declarationSuffix.toLowerCase()) || identifier.name === declarationSuffix.toLowerCase())) {
|
|
155
244
|
// Already prefixed! Nothing to do.
|
|
156
245
|
return;
|
|
157
246
|
}
|
|
@@ -189,7 +278,7 @@ var rule = createLintRule({
|
|
|
189
278
|
});
|
|
190
279
|
return;
|
|
191
280
|
}
|
|
192
|
-
traverseExpressionWithConfig(context, value.expression, mergedConfig);
|
|
281
|
+
traverseExpressionWithConfig(context, value.expression, mergedConfig, name.name);
|
|
193
282
|
}
|
|
194
283
|
}), _ref3;
|
|
195
284
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/eslint-plugin-design-system",
|
|
3
3
|
"description": "The essential plugin for use with the Atlassian Design System.",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.23.0",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"@types/eslint": "^8.4.5",
|
|
56
56
|
"eslint": "^7.7.0",
|
|
57
57
|
"jscodeshift": "^0.13.0",
|
|
58
|
+
"outdent": "^0.5.0",
|
|
58
59
|
"prettier": "^2.8.0",
|
|
59
60
|
"react": "^16.8.0",
|
|
60
61
|
"ts-jest": "26.5.6",
|