@atlaskit/eslint-plugin-design-system 8.25.2 → 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 +42 -0
- package/README.md +4 -0
- package/constellation/index/usage.mdx +402 -6
- package/dist/cjs/presets/all.codegen.js +5 -1
- package/dist/cjs/presets/recommended.codegen.js +5 -1
- package/dist/cjs/rules/consistent-css-prop-usage/index.js +254 -32
- package/dist/cjs/rules/index.codegen.js +9 -1
- package/dist/cjs/rules/no-empty-styled-expression/index.js +75 -0
- package/dist/cjs/rules/no-exported-css/index.js +37 -0
- package/dist/cjs/rules/no-exported-keyframes/index.js +37 -0
- package/dist/cjs/rules/no-invalid-css-map/index.js +102 -0
- package/dist/cjs/rules/no-invalid-css-map/utils.js +193 -0
- package/dist/cjs/rules/utils/create-no-exported-rule/check-if-supported-export.js +158 -0
- package/dist/cjs/rules/utils/create-no-exported-rule/is-styled-component.js +80 -0
- package/dist/cjs/rules/utils/create-no-exported-rule/main.js +66 -0
- package/dist/cjs/rules/utils/get-first-supported-import.js +28 -0
- package/dist/cjs/rules/utils/is-supported-import.js +53 -16
- package/dist/es2019/presets/all.codegen.js +5 -1
- package/dist/es2019/presets/recommended.codegen.js +5 -1
- package/dist/es2019/rules/consistent-css-prop-usage/index.js +251 -33
- package/dist/es2019/rules/index.codegen.js +9 -1
- package/dist/es2019/rules/no-empty-styled-expression/index.js +65 -0
- package/dist/es2019/rules/no-exported-css/index.js +31 -0
- package/dist/es2019/rules/no-exported-keyframes/index.js +31 -0
- package/dist/es2019/rules/no-invalid-css-map/index.js +95 -0
- package/dist/es2019/rules/no-invalid-css-map/utils.js +134 -0
- package/dist/es2019/rules/utils/create-no-exported-rule/check-if-supported-export.js +142 -0
- package/dist/es2019/rules/utils/create-no-exported-rule/is-styled-component.js +70 -0
- package/dist/es2019/rules/utils/create-no-exported-rule/main.js +59 -0
- package/dist/es2019/rules/utils/get-first-supported-import.js +22 -0
- package/dist/es2019/rules/utils/is-supported-import.js +50 -15
- package/dist/esm/presets/all.codegen.js +5 -1
- package/dist/esm/presets/recommended.codegen.js +5 -1
- package/dist/esm/rules/consistent-css-prop-usage/index.js +255 -33
- package/dist/esm/rules/index.codegen.js +9 -1
- package/dist/esm/rules/no-empty-styled-expression/index.js +68 -0
- package/dist/esm/rules/no-exported-css/index.js +31 -0
- package/dist/esm/rules/no-exported-keyframes/index.js +31 -0
- package/dist/esm/rules/no-invalid-css-map/index.js +96 -0
- package/dist/esm/rules/no-invalid-css-map/utils.js +186 -0
- package/dist/esm/rules/utils/create-no-exported-rule/check-if-supported-export.js +151 -0
- package/dist/esm/rules/utils/create-no-exported-rule/is-styled-component.js +74 -0
- package/dist/esm/rules/utils/create-no-exported-rule/main.js +60 -0
- package/dist/esm/rules/utils/get-first-supported-import.js +22 -0
- package/dist/esm/rules/utils/is-supported-import.js +51 -15
- package/dist/types/index.codegen.d.ts +8 -0
- package/dist/types/presets/all.codegen.d.ts +5 -1
- package/dist/types/presets/recommended.codegen.d.ts +5 -1
- package/dist/types/rules/consistent-css-prop-usage/types.d.ts +7 -2
- package/dist/types/rules/index.codegen.d.ts +4 -0
- package/dist/types/rules/no-empty-styled-expression/index.d.ts +3 -0
- package/dist/types/rules/no-exported-css/index.d.ts +3 -0
- package/dist/types/rules/no-exported-keyframes/index.d.ts +3 -0
- package/dist/types/rules/no-invalid-css-map/index.d.ts +3 -0
- package/dist/types/rules/no-invalid-css-map/utils.d.ts +14 -0
- package/dist/types/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +1 -1
- package/dist/types/rules/utils/create-no-exported-rule/check-if-supported-export.d.ts +15 -0
- package/dist/types/rules/utils/create-no-exported-rule/is-styled-component.d.ts +14 -0
- package/dist/types/rules/utils/create-no-exported-rule/main.d.ts +19 -0
- package/dist/types/rules/utils/create-rule.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 +26 -8
- package/dist/types-ts4.5/index.codegen.d.ts +8 -0
- package/dist/types-ts4.5/presets/all.codegen.d.ts +5 -1
- package/dist/types-ts4.5/presets/recommended.codegen.d.ts +5 -1
- package/dist/types-ts4.5/rules/consistent-css-prop-usage/types.d.ts +7 -2
- package/dist/types-ts4.5/rules/index.codegen.d.ts +4 -0
- package/dist/types-ts4.5/rules/no-empty-styled-expression/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-exported-css/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-exported-keyframes/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-invalid-css-map/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-invalid-css-map/utils.d.ts +14 -0
- 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/create-no-exported-rule/check-if-supported-export.d.ts +15 -0
- package/dist/types-ts4.5/rules/utils/create-no-exported-rule/is-styled-component.d.ts +14 -0
- package/dist/types-ts4.5/rules/utils/create-no-exported-rule/main.d.ts +19 -0
- package/dist/types-ts4.5/rules/utils/create-rule.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 +26 -8
- package/package.json +3 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
3
4
|
Object.defineProperty(exports, "__esModule", {
|
|
4
5
|
value: true
|
|
5
6
|
});
|
|
6
|
-
exports.isCxFunction = exports.isCss = exports.CSS_IN_JS_IMPORTS = void 0;
|
|
7
|
+
exports.isStyled = exports.isKeyframes = exports.isCxFunction = exports.isCssMap = exports.isCss = exports.getImportSources = exports.DEFAULT_IMPORT_SOURCES = exports.CSS_IN_JS_IMPORTS = void 0;
|
|
8
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
7
9
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
8
10
|
|
|
9
11
|
var CSS_IN_JS_IMPORTS = exports.CSS_IN_JS_IMPORTS = {
|
|
@@ -11,35 +13,69 @@ var CSS_IN_JS_IMPORTS = exports.CSS_IN_JS_IMPORTS = {
|
|
|
11
13
|
emotionReact: '@emotion/react',
|
|
12
14
|
emotionCore: '@emotion/core',
|
|
13
15
|
styledComponents: 'styled-components',
|
|
14
|
-
atlaskitCss: '@atlaskit/css'
|
|
16
|
+
atlaskitCss: '@atlaskit/css',
|
|
17
|
+
atlaskitPrimitives: '@atlaskit/primitives'
|
|
15
18
|
};
|
|
16
19
|
|
|
17
|
-
//
|
|
20
|
+
// A CSS-in-JS library an import of a valid css, cx, cssMap, etc.
|
|
18
21
|
// function might originate from, e.g. @compiled/react, @emotion/core.
|
|
19
22
|
|
|
23
|
+
// All ESLint rules originating from `@compiled/eslint-plugin` should apply to these libraries.
|
|
24
|
+
var DEFAULT_IMPORT_SOURCES = exports.DEFAULT_IMPORT_SOURCES = [CSS_IN_JS_IMPORTS.compiled, CSS_IN_JS_IMPORTS.atlaskitCss];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Given the ESLint rule context, extract and parse the value of the importSources rule option.
|
|
28
|
+
* The importSources option is used to define additional libraries for which an ESLint rule
|
|
29
|
+
* should apply to.
|
|
30
|
+
*
|
|
31
|
+
* Note that `@compiled/react` and `@atlaskit/css` are always included in importSources, regardless
|
|
32
|
+
* of what importSources is configured to by the user.
|
|
33
|
+
*
|
|
34
|
+
* @param context The rule context.
|
|
35
|
+
* @returns An array of strings representing what CSS-in-JS packages that should be checked, based
|
|
36
|
+
* on the rule options configuration.
|
|
37
|
+
*/
|
|
38
|
+
var getImportSources = exports.getImportSources = function getImportSources(context) {
|
|
39
|
+
var options = context.options;
|
|
40
|
+
if (!options.length) {
|
|
41
|
+
return DEFAULT_IMPORT_SOURCES;
|
|
42
|
+
}
|
|
43
|
+
if (options[0].importSources && Array.isArray(options[0].importSources)) {
|
|
44
|
+
return [].concat(DEFAULT_IMPORT_SOURCES, (0, _toConsumableArray2.default)(options[0].importSources));
|
|
45
|
+
}
|
|
46
|
+
return DEFAULT_IMPORT_SOURCES;
|
|
47
|
+
};
|
|
20
48
|
var isSupportedImportWrapper = function isSupportedImportWrapper(functionName) {
|
|
21
|
-
// This will need to be extended to support default imports once we start
|
|
22
|
-
// checking cases like `import css from '@emotion/css'`
|
|
23
49
|
var checkDefinitionHasImport = function checkDefinitionHasImport(def, importSources) {
|
|
24
|
-
|
|
25
|
-
|
|
50
|
+
if (def.type !== 'ImportBinding') {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (!def.parent || !importSources.includes(def.parent.source.value)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return (
|
|
57
|
+
// import { functionName } from 'import-source';
|
|
58
|
+
def.node.type === 'ImportSpecifier' && def.node.imported.name === functionName ||
|
|
59
|
+
// import functionName from 'import-source';
|
|
60
|
+
def.node.type === 'ImportDefaultSpecifier' && def.node.local.name === functionName
|
|
61
|
+
);
|
|
26
62
|
};
|
|
27
63
|
|
|
28
64
|
/**
|
|
29
65
|
* Checks whether:
|
|
30
66
|
*
|
|
31
|
-
* 1.
|
|
67
|
+
* 1. A function name `nodeToCheck` matches the name of the function we
|
|
32
68
|
* want to check for (e.g. `cx`, `css`, `cssMap`, or `keyframes`), and
|
|
33
|
-
* 2.
|
|
69
|
+
* 2. Whether `nodeToCheck` originates from one of the libraries listed
|
|
34
70
|
* in `importSources`.
|
|
35
71
|
*
|
|
36
|
-
* @param nodeToCheck
|
|
37
|
-
* @param referencesInScope
|
|
72
|
+
* @param nodeToCheck The function callee we are checking (e.g. The `css` in `css()`).
|
|
73
|
+
* @param referencesInScope List of references that are in scope. We'll use this
|
|
38
74
|
* to check where the function callee is imported from.
|
|
39
|
-
* @param importSources
|
|
40
|
-
* comes from
|
|
75
|
+
* @param importSources List of libraries that we want to ensure `nodeToCheck`
|
|
76
|
+
* comes from.
|
|
41
77
|
*
|
|
42
|
-
* @returns
|
|
78
|
+
* @returns Whether the above conditions are true.
|
|
43
79
|
*/
|
|
44
80
|
var isSupportedImport = function isSupportedImport(nodeToCheck, referencesInScope, importSources) {
|
|
45
81
|
return nodeToCheck.type === 'Identifier' && referencesInScope.some(function (reference) {
|
|
@@ -57,5 +93,6 @@ var isSupportedImportWrapper = function isSupportedImportWrapper(functionName) {
|
|
|
57
93
|
//
|
|
58
94
|
var isCss = exports.isCss = isSupportedImportWrapper('css');
|
|
59
95
|
var isCxFunction = exports.isCxFunction = isSupportedImportWrapper('cx');
|
|
60
|
-
|
|
61
|
-
|
|
96
|
+
var isCssMap = exports.isCssMap = isSupportedImportWrapper('cssMap');
|
|
97
|
+
var isKeyframes = exports.isKeyframes = isSupportedImportWrapper('keyframes');
|
|
98
|
+
var isStyled = exports.isStyled = isSupportedImportWrapper('styled');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::914085544778f4543f43e3e30d0982e0>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
export default {
|
|
@@ -16,6 +16,10 @@ export default {
|
|
|
16
16
|
'@atlaskit/design-system/no-deprecated-apis': 'error',
|
|
17
17
|
'@atlaskit/design-system/no-deprecated-design-token-usage': 'warn',
|
|
18
18
|
'@atlaskit/design-system/no-deprecated-imports': 'error',
|
|
19
|
+
'@atlaskit/design-system/no-empty-styled-expression': 'warn',
|
|
20
|
+
'@atlaskit/design-system/no-exported-css': 'warn',
|
|
21
|
+
'@atlaskit/design-system/no-exported-keyframes': 'warn',
|
|
22
|
+
'@atlaskit/design-system/no-invalid-css-map': 'error',
|
|
19
23
|
'@atlaskit/design-system/no-margin': 'warn',
|
|
20
24
|
'@atlaskit/design-system/no-nested-styles': 'error',
|
|
21
25
|
'@atlaskit/design-system/no-physical-properties': 'error',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::577269c832952ce359cde6a50f26f4e0>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
export default {
|
|
@@ -14,6 +14,10 @@ export default {
|
|
|
14
14
|
'@atlaskit/design-system/no-deprecated-apis': 'error',
|
|
15
15
|
'@atlaskit/design-system/no-deprecated-design-token-usage': 'warn',
|
|
16
16
|
'@atlaskit/design-system/no-deprecated-imports': 'error',
|
|
17
|
+
'@atlaskit/design-system/no-empty-styled-expression': 'warn',
|
|
18
|
+
'@atlaskit/design-system/no-exported-css': 'warn',
|
|
19
|
+
'@atlaskit/design-system/no-exported-keyframes': 'warn',
|
|
20
|
+
'@atlaskit/design-system/no-invalid-css-map': 'error',
|
|
17
21
|
'@atlaskit/design-system/no-nested-styles': 'error',
|
|
18
22
|
'@atlaskit/design-system/no-unsafe-design-token-usage': 'error',
|
|
19
23
|
'@atlaskit/design-system/no-unsafe-style-overrides': 'warn',
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::0a2d88c9772eb438048415f13550f592>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
import consistentCssPropUsage from './consistent-css-prop-usage';
|
|
@@ -13,6 +13,10 @@ import noCssTaggedTemplateExpression from './no-css-tagged-template-expression';
|
|
|
13
13
|
import noDeprecatedApis from './no-deprecated-apis';
|
|
14
14
|
import noDeprecatedDesignTokenUsage from './no-deprecated-design-token-usage';
|
|
15
15
|
import noDeprecatedImports from './no-deprecated-imports';
|
|
16
|
+
import noEmptyStyledExpression from './no-empty-styled-expression';
|
|
17
|
+
import noExportedCss from './no-exported-css';
|
|
18
|
+
import noExportedKeyframes from './no-exported-keyframes';
|
|
19
|
+
import noInvalidCssMap from './no-invalid-css-map';
|
|
16
20
|
import noMargin from './no-margin';
|
|
17
21
|
import noNestedStyles from './no-nested-styles';
|
|
18
22
|
import noPhysicalProperties from './no-physical-properties';
|
|
@@ -37,6 +41,10 @@ export default {
|
|
|
37
41
|
'no-deprecated-apis': noDeprecatedApis,
|
|
38
42
|
'no-deprecated-design-token-usage': noDeprecatedDesignTokenUsage,
|
|
39
43
|
'no-deprecated-imports': noDeprecatedImports,
|
|
44
|
+
'no-empty-styled-expression': noEmptyStyledExpression,
|
|
45
|
+
'no-exported-css': noExportedCss,
|
|
46
|
+
'no-exported-keyframes': noExportedKeyframes,
|
|
47
|
+
'no-invalid-css-map': noInvalidCssMap,
|
|
40
48
|
'no-margin': noMargin,
|
|
41
49
|
'no-nested-styles': noNestedStyles,
|
|
42
50
|
'no-physical-properties': noPhysicalProperties,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createLintRule } from '../utils/create-rule';
|
|
2
|
+
import { getImportSources, isStyled } from '../utils/is-supported-import';
|
|
3
|
+
const isEmptyStyledExpression = node => {
|
|
4
|
+
const [firstArg] = node.arguments;
|
|
5
|
+
if (node.arguments.length === 0) {
|
|
6
|
+
return true;
|
|
7
|
+
} else if (node.arguments.length === 1 && (firstArg === null || firstArg === void 0 ? void 0 : firstArg.type) === 'ArrayExpression') {
|
|
8
|
+
return firstArg.elements.length === 0;
|
|
9
|
+
} else if (node.arguments.length === 1 && (firstArg === null || firstArg === void 0 ? void 0 : firstArg.type) === 'ObjectExpression') {
|
|
10
|
+
return firstArg.properties.length === 0;
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
};
|
|
14
|
+
const createNoEmptyStyledExpressionRule = (isEmptyStyledExpression, messageId) => context => {
|
|
15
|
+
const importSources = getImportSources(context);
|
|
16
|
+
return {
|
|
17
|
+
'CallExpression[callee.type="MemberExpression"]': node => {
|
|
18
|
+
const {
|
|
19
|
+
references
|
|
20
|
+
} = context.getScope();
|
|
21
|
+
|
|
22
|
+
// If we have styled.div(...), make sure `callee` only refers to the
|
|
23
|
+
// `styled` part instead of the whole `styled.div` expression.
|
|
24
|
+
const callee = node.callee.type === 'MemberExpression' ? node.callee.object : node.callee;
|
|
25
|
+
if (!isStyled(callee, references, importSources)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!isEmptyStyledExpression(node)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
context.report({
|
|
32
|
+
messageId,
|
|
33
|
+
node
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const noEmptyStyledExpressionRule = createLintRule({
|
|
39
|
+
meta: {
|
|
40
|
+
name: 'no-empty-styled-expression',
|
|
41
|
+
docs: {
|
|
42
|
+
description: 'Forbids any styled expression to be used when passing empty arguments to styled.div() (or other JSX elements).',
|
|
43
|
+
recommended: true,
|
|
44
|
+
severity: 'warn'
|
|
45
|
+
},
|
|
46
|
+
messages: {
|
|
47
|
+
unexpected: 'Found an empty expression, or empty object argument passed to `styled` function call. This unnecessarily causes a major performance penalty - please use a plain JSX element or a React fragment instead (e.g. `<div>Hello</div>` or `<>Hello</>`).'
|
|
48
|
+
},
|
|
49
|
+
type: 'problem',
|
|
50
|
+
schema: [{
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
importSources: {
|
|
54
|
+
type: 'array',
|
|
55
|
+
items: [{
|
|
56
|
+
type: 'string'
|
|
57
|
+
}]
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
additionalProperties: false
|
|
61
|
+
}]
|
|
62
|
+
},
|
|
63
|
+
create: createNoEmptyStyledExpressionRule(isEmptyStyledExpression, 'unexpected')
|
|
64
|
+
});
|
|
65
|
+
export default noEmptyStyledExpressionRule;
|