@atlaskit/eslint-plugin-design-system 13.17.2 → 13.18.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 +18 -0
- package/README.md +2 -0
- package/dist/cjs/presets/all-flat.codegen.js +3 -1
- package/dist/cjs/presets/all.codegen.js +3 -1
- package/dist/cjs/presets/recommended-flat.codegen.js +2 -1
- package/dist/cjs/presets/recommended.codegen.js +2 -1
- package/dist/cjs/rules/enforce-inline-styles-in-select/index.js +76 -0
- package/dist/cjs/rules/enforce-inline-styles-in-select/utils.js +231 -0
- package/dist/cjs/rules/index.codegen.js +5 -1
- package/dist/cjs/rules/no-nested-styles/index.js +27 -20
- package/dist/cjs/rules/no-utility-icons/checks.js +0 -1
- package/dist/cjs/rules/use-correct-field/index.js +167 -0
- package/dist/es2019/presets/all-flat.codegen.js +3 -1
- package/dist/es2019/presets/all.codegen.js +3 -1
- package/dist/es2019/presets/recommended-flat.codegen.js +2 -1
- package/dist/es2019/presets/recommended.codegen.js +2 -1
- package/dist/es2019/rules/enforce-inline-styles-in-select/index.js +68 -0
- package/dist/es2019/rules/enforce-inline-styles-in-select/utils.js +217 -0
- package/dist/es2019/rules/index.codegen.js +5 -1
- package/dist/es2019/rules/no-nested-styles/index.js +27 -18
- package/dist/es2019/rules/no-utility-icons/checks.js +0 -1
- package/dist/es2019/rules/use-correct-field/index.js +153 -0
- package/dist/esm/presets/all-flat.codegen.js +3 -1
- package/dist/esm/presets/all.codegen.js +3 -1
- package/dist/esm/presets/recommended-flat.codegen.js +2 -1
- package/dist/esm/presets/recommended.codegen.js +2 -1
- package/dist/esm/rules/enforce-inline-styles-in-select/index.js +70 -0
- package/dist/esm/rules/enforce-inline-styles-in-select/utils.js +225 -0
- package/dist/esm/rules/index.codegen.js +5 -1
- package/dist/esm/rules/no-nested-styles/index.js +27 -20
- package/dist/esm/rules/no-utility-icons/checks.js +0 -1
- package/dist/esm/rules/use-correct-field/index.js +161 -0
- package/dist/types/index.codegen.d.ts +14 -0
- package/dist/types/presets/all-flat.codegen.d.ts +2 -0
- package/dist/types/presets/all.codegen.d.ts +2 -0
- package/dist/types/presets/recommended-flat.codegen.d.ts +1 -0
- package/dist/types/presets/recommended.codegen.d.ts +1 -0
- package/dist/types/rules/enforce-inline-styles-in-select/index.d.ts +3 -0
- package/dist/types/rules/enforce-inline-styles-in-select/utils.d.ts +2 -0
- package/dist/types/rules/index.codegen.d.ts +2 -0
- package/dist/types/rules/use-correct-field/index.d.ts +5 -0
- package/dist/types-ts4.5/index.codegen.d.ts +14 -0
- package/dist/types-ts4.5/presets/all-flat.codegen.d.ts +2 -0
- package/dist/types-ts4.5/presets/all.codegen.d.ts +2 -0
- package/dist/types-ts4.5/presets/recommended-flat.codegen.d.ts +1 -0
- package/dist/types-ts4.5/presets/recommended.codegen.d.ts +1 -0
- package/dist/types-ts4.5/rules/enforce-inline-styles-in-select/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/enforce-inline-styles-in-select/utils.d.ts +2 -0
- package/dist/types-ts4.5/rules/index.codegen.d.ts +2 -0
- package/dist/types-ts4.5/rules/use-correct-field/index.d.ts +5 -0
- package/package.json +6 -7
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useRangeFieldMessage = exports.useCheckboxFieldMessage = exports.default = void 0;
|
|
7
|
+
var _eslintCodemodUtils = require("eslint-codemod-utils");
|
|
8
|
+
var _createRule = require("../utils/create-rule");
|
|
9
|
+
var specialFieldsByImport = {
|
|
10
|
+
'@atlaskit/checkbox': {
|
|
11
|
+
component: 'Checkbox',
|
|
12
|
+
field: 'CheckboxField',
|
|
13
|
+
local: undefined
|
|
14
|
+
},
|
|
15
|
+
'@atlaskit/range': {
|
|
16
|
+
component: 'Range',
|
|
17
|
+
field: 'RangeField',
|
|
18
|
+
local: undefined
|
|
19
|
+
},
|
|
20
|
+
'@atlaskit/toggle': {
|
|
21
|
+
component: 'Toggle',
|
|
22
|
+
field: 'CheckboxField',
|
|
23
|
+
local: undefined
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var useCheckboxFieldMessage = exports.useCheckboxFieldMessage = 'Convert Field to CheckboxField';
|
|
27
|
+
var useRangeFieldMessage = exports.useRangeFieldMessage = 'Convert Field to RangeField';
|
|
28
|
+
var rule = (0, _createRule.createLintRule)({
|
|
29
|
+
meta: {
|
|
30
|
+
name: 'use-correct-field',
|
|
31
|
+
type: 'suggestion',
|
|
32
|
+
fixable: 'code',
|
|
33
|
+
hasSuggestions: true,
|
|
34
|
+
docs: {
|
|
35
|
+
description: 'Ensure makers use appropriate field component for their respective form elements.',
|
|
36
|
+
recommended: true,
|
|
37
|
+
severity: 'warn'
|
|
38
|
+
},
|
|
39
|
+
messages: {
|
|
40
|
+
useCheckboxField: 'Checkbox components should use the `CheckboxField` component',
|
|
41
|
+
useRangeField: 'Range components should use the `RangeField` component',
|
|
42
|
+
useCheckboxFieldForToggle: 'Toggle components should use the `CheckboxField` component'
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
create: function create(context) {
|
|
46
|
+
var fieldImport;
|
|
47
|
+
var allPackages = [];
|
|
48
|
+
return {
|
|
49
|
+
ImportDeclaration: function ImportDeclaration(node) {
|
|
50
|
+
var source = node.source.value;
|
|
51
|
+
if (typeof source !== 'string') {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!node.specifiers.length) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
var defaultImport = node.specifiers.filter(function (spec) {
|
|
58
|
+
return (0, _eslintCodemodUtils.isNodeOfType)(spec, 'ImportDefaultSpecifier');
|
|
59
|
+
});
|
|
60
|
+
if (source in specialFieldsByImport) {
|
|
61
|
+
allPackages.push(node);
|
|
62
|
+
// set local to local value
|
|
63
|
+
if (defaultImport.length && (0, _eslintCodemodUtils.isNodeOfType)(defaultImport[0], 'ImportDefaultSpecifier') && (0, _eslintCodemodUtils.isNodeOfType)(defaultImport[0].local, 'Identifier')) {
|
|
64
|
+
specialFieldsByImport[source].local = defaultImport[0].local.name;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if ('@atlaskit/form' !== source) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
var namedImport = node.specifiers.filter(function (spec) {
|
|
71
|
+
return (0, _eslintCodemodUtils.isNodeOfType)(spec, 'ImportSpecifier');
|
|
72
|
+
});
|
|
73
|
+
if (namedImport.length && namedImport[0].type === 'ImportSpecifier' && namedImport[0].imported.name === 'Field') {
|
|
74
|
+
fieldImport = namedImport[0].local;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
JSXElement: function JSXElement(node) {
|
|
78
|
+
if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXElement')) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!(0, _eslintCodemodUtils.isNodeOfType)(node.openingElement.name, 'JSXIdentifier')) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
var name = node.openingElement.name.name;
|
|
85
|
+
|
|
86
|
+
// if it's not a field import, skip
|
|
87
|
+
if (!fieldImport || name !== fieldImport.name) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// If no imports are for the inputs that have special fields, exit early
|
|
92
|
+
if (allPackages.every(function (n) {
|
|
93
|
+
return typeof n.source.value !== 'string' || !Object.keys(specialFieldsByImport).includes(n.source.value);
|
|
94
|
+
})) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
var fieldRenderProp = node.children.find(function (c) {
|
|
98
|
+
return (0, _eslintCodemodUtils.isNodeOfType)(c, 'JSXExpressionContainer');
|
|
99
|
+
});
|
|
100
|
+
if (!fieldRenderProp) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// I'm not early exiting because it doesn't work with ts for some reason
|
|
104
|
+
if ((0, _eslintCodemodUtils.isNodeOfType)(fieldRenderProp, 'JSXExpressionContainer')) {
|
|
105
|
+
if (!(0, _eslintCodemodUtils.isNodeOfType)(fieldRenderProp.expression, 'ArrowFunctionExpression')) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
var q = [fieldRenderProp.expression.body];
|
|
109
|
+
var found;
|
|
110
|
+
while (q.length > 0 && !found) {
|
|
111
|
+
var child = q.pop();
|
|
112
|
+
if (!(0, _eslintCodemodUtils.isNodeOfType)(child, 'JSXElement') || !(0, _eslintCodemodUtils.isNodeOfType)(child.openingElement.name, 'JSXIdentifier')) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
var elementName = child.openingElement.name.name;
|
|
116
|
+
for (var importName in specialFieldsByImport) {
|
|
117
|
+
// if this child is one of the found component names
|
|
118
|
+
// then break out of the while loop and use the found object
|
|
119
|
+
var localName = specialFieldsByImport[importName].local;
|
|
120
|
+
if (localName === elementName) {
|
|
121
|
+
found = specialFieldsByImport[importName].component;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!found) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// if checkbox is inside of the field's render prop
|
|
131
|
+
if (found === 'Checkbox' || found === 'Toggle') {
|
|
132
|
+
context.report({
|
|
133
|
+
node: node,
|
|
134
|
+
messageId: found === 'Checkbox' ? 'useCheckboxField' : 'useCheckboxFieldForToggle',
|
|
135
|
+
suggest: [{
|
|
136
|
+
desc: useCheckboxFieldMessage,
|
|
137
|
+
fix: function fix(fixer) {
|
|
138
|
+
var fixes = [];
|
|
139
|
+
fixes.push(fixer.insertTextBefore(fieldImport, 'CheckboxField, '));
|
|
140
|
+
fixes.push(fixer.replaceText(node.openingElement.name, 'CheckboxField'));
|
|
141
|
+
node.closingElement && fixes.push(fixer.replaceText(node.closingElement.name, 'CheckboxField'));
|
|
142
|
+
return fixes;
|
|
143
|
+
}
|
|
144
|
+
}]
|
|
145
|
+
});
|
|
146
|
+
} else if (found === 'Range') {
|
|
147
|
+
context.report({
|
|
148
|
+
node: node,
|
|
149
|
+
messageId: 'useRangeField',
|
|
150
|
+
suggest: [{
|
|
151
|
+
desc: useRangeFieldMessage,
|
|
152
|
+
fix: function fix(fixer) {
|
|
153
|
+
var fixes = [];
|
|
154
|
+
fixes.push(fixer.insertTextBefore(fieldImport, 'RangeField, '));
|
|
155
|
+
fixes.push(fixer.replaceText(node.openingElement.name, 'RangeField'));
|
|
156
|
+
node.closingElement && fixes.push(fixer.replaceText(node.closingElement.name, 'RangeField'));
|
|
157
|
+
return fixes;
|
|
158
|
+
}
|
|
159
|
+
}]
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
var _default = exports.default = rule;
|
|
@@ -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::8fb6e53ac7d9be7b9252dd64ec022d26>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -9,6 +9,7 @@ export default {
|
|
|
9
9
|
plugins: {},
|
|
10
10
|
rules: {
|
|
11
11
|
'@atlaskit/design-system/consistent-css-prop-usage': 'error',
|
|
12
|
+
'@atlaskit/design-system/enforce-inline-styles-in-select': 'error',
|
|
12
13
|
'@atlaskit/design-system/ensure-design-token-usage': 'error',
|
|
13
14
|
'@atlaskit/design-system/ensure-design-token-usage/preview': 'warn',
|
|
14
15
|
'@atlaskit/design-system/ensure-icon-color': 'error',
|
|
@@ -51,6 +52,7 @@ export default {
|
|
|
51
52
|
'@atlaskit/design-system/no-utility-icons': 'warn',
|
|
52
53
|
'@atlaskit/design-system/prefer-primitives': 'warn',
|
|
53
54
|
'@atlaskit/design-system/use-button-group-label': 'warn',
|
|
55
|
+
'@atlaskit/design-system/use-correct-field': 'warn',
|
|
54
56
|
'@atlaskit/design-system/use-cx-function-in-xcss': 'error',
|
|
55
57
|
'@atlaskit/design-system/use-datetime-picker-calendar-button': 'warn',
|
|
56
58
|
'@atlaskit/design-system/use-drawer-label': 'warn',
|
|
@@ -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::b727e6e2b1813b88da0df6ff6083be3c>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -8,6 +8,7 @@ export default {
|
|
|
8
8
|
plugins: ['@atlaskit/design-system'],
|
|
9
9
|
rules: {
|
|
10
10
|
'@atlaskit/design-system/consistent-css-prop-usage': 'error',
|
|
11
|
+
'@atlaskit/design-system/enforce-inline-styles-in-select': 'error',
|
|
11
12
|
'@atlaskit/design-system/ensure-design-token-usage': 'error',
|
|
12
13
|
'@atlaskit/design-system/ensure-design-token-usage/preview': 'warn',
|
|
13
14
|
'@atlaskit/design-system/ensure-icon-color': 'error',
|
|
@@ -50,6 +51,7 @@ export default {
|
|
|
50
51
|
'@atlaskit/design-system/no-utility-icons': 'warn',
|
|
51
52
|
'@atlaskit/design-system/prefer-primitives': 'warn',
|
|
52
53
|
'@atlaskit/design-system/use-button-group-label': 'warn',
|
|
54
|
+
'@atlaskit/design-system/use-correct-field': 'warn',
|
|
53
55
|
'@atlaskit/design-system/use-cx-function-in-xcss': 'error',
|
|
54
56
|
'@atlaskit/design-system/use-datetime-picker-calendar-button': 'warn',
|
|
55
57
|
'@atlaskit/design-system/use-drawer-label': 'warn',
|
|
@@ -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::af48ffd75d906d008c5701554187b606>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -38,6 +38,7 @@ export default {
|
|
|
38
38
|
'@atlaskit/design-system/no-unsupported-drag-and-drop-libraries': 'error',
|
|
39
39
|
'@atlaskit/design-system/no-utility-icons': 'warn',
|
|
40
40
|
'@atlaskit/design-system/use-button-group-label': 'warn',
|
|
41
|
+
'@atlaskit/design-system/use-correct-field': 'warn',
|
|
41
42
|
'@atlaskit/design-system/use-cx-function-in-xcss': 'error',
|
|
42
43
|
'@atlaskit/design-system/use-datetime-picker-calendar-button': 'warn',
|
|
43
44
|
'@atlaskit/design-system/use-drawer-label': 'warn',
|
|
@@ -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::96e803ae8ae873736bab690b2af62bed>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -37,6 +37,7 @@ export default {
|
|
|
37
37
|
'@atlaskit/design-system/no-unsupported-drag-and-drop-libraries': 'error',
|
|
38
38
|
'@atlaskit/design-system/no-utility-icons': 'warn',
|
|
39
39
|
'@atlaskit/design-system/use-button-group-label': 'warn',
|
|
40
|
+
'@atlaskit/design-system/use-correct-field': 'warn',
|
|
40
41
|
'@atlaskit/design-system/use-cx-function-in-xcss': 'error',
|
|
41
42
|
'@atlaskit/design-system/use-datetime-picker-calendar-button': 'warn',
|
|
42
43
|
'@atlaskit/design-system/use-drawer-label': 'warn',
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import { createLintRule } from '../utils/create-rule';
|
|
3
|
+
import { checkStylesObject } from './utils';
|
|
4
|
+
const rule = createLintRule({
|
|
5
|
+
meta: {
|
|
6
|
+
name: 'enforce-inline-styles-in-select',
|
|
7
|
+
docs: {
|
|
8
|
+
description: 'Disallow unsupported CSS selectors in styles prop for @atlaskit/select and require inline styles only',
|
|
9
|
+
recommended: false,
|
|
10
|
+
severity: 'error'
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
noPseudoClass: "This selector '{{pseudo}}' is not allowed in styles for @atlaskit/select. Please use the `components` API in select with `xcss` props.",
|
|
14
|
+
noVariableStyles: 'Variable-defined styles are not allowed for @atlaskit/select. Please use inline styles object or the `components` API with `xcss` props.'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
// Track imports of @atlaskit/select
|
|
19
|
+
const atlaskitSelectImports = new Set();
|
|
20
|
+
return {
|
|
21
|
+
ImportDeclaration(node) {
|
|
22
|
+
if (node.source.value !== '@atlaskit/select') {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
node.specifiers.forEach(spec => {
|
|
26
|
+
if (isNodeOfType(spec, 'ImportDefaultSpecifier')) {
|
|
27
|
+
atlaskitSelectImports.add(spec.local.name);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
JSXElement(node) {
|
|
32
|
+
if (!isNodeOfType(node, 'JSXElement')) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if this is a Select component from @atlaskit/select
|
|
37
|
+
if (isNodeOfType(node.openingElement.name, 'JSXIdentifier') && atlaskitSelectImports.has(node.openingElement.name.name)) {
|
|
38
|
+
// Look for styles prop
|
|
39
|
+
const stylesAttr = node.openingElement.attributes.find(attr => isNodeOfType(attr, 'JSXAttribute') && isNodeOfType(attr.name, 'JSXIdentifier') && attr.name.name === 'styles');
|
|
40
|
+
if (stylesAttr && isNodeOfType(stylesAttr, 'JSXAttribute') && stylesAttr.value) {
|
|
41
|
+
if (isNodeOfType(stylesAttr.value, 'JSXExpressionContainer')) {
|
|
42
|
+
const expression = stylesAttr.value.expression;
|
|
43
|
+
|
|
44
|
+
// Check if it's an inline object expression
|
|
45
|
+
if (isNodeOfType(expression, 'ObjectExpression')) {
|
|
46
|
+
// This is an inline styles object - check for unsupported selectors
|
|
47
|
+
checkStylesObject(node, expression, context);
|
|
48
|
+
} else if (isNodeOfType(expression, 'Identifier')) {
|
|
49
|
+
// This is a variable reference - not allowed
|
|
50
|
+
context.report({
|
|
51
|
+
node: expression,
|
|
52
|
+
messageId: 'noVariableStyles'
|
|
53
|
+
});
|
|
54
|
+
} else {
|
|
55
|
+
// Any other expression type (function calls, member expressions, etc.) - not allowed
|
|
56
|
+
context.report({
|
|
57
|
+
node: expression,
|
|
58
|
+
messageId: 'noVariableStyles'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
export default rule;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
const unsupportedSelectors = [':',
|
|
3
|
+
// pseudo-classes/elements
|
|
4
|
+
'[',
|
|
5
|
+
// attribute selectors
|
|
6
|
+
'>',
|
|
7
|
+
// child combinator
|
|
8
|
+
'+',
|
|
9
|
+
// adjacent sibling combinator
|
|
10
|
+
'~',
|
|
11
|
+
// general sibling combinator
|
|
12
|
+
' ',
|
|
13
|
+
// descendant combinator
|
|
14
|
+
'*',
|
|
15
|
+
// universal selector
|
|
16
|
+
'#',
|
|
17
|
+
// ID selector
|
|
18
|
+
'.',
|
|
19
|
+
// class selector
|
|
20
|
+
'@',
|
|
21
|
+
// at-rules
|
|
22
|
+
'&',
|
|
23
|
+
// parent selector
|
|
24
|
+
'|',
|
|
25
|
+
// namespace separator
|
|
26
|
+
'^',
|
|
27
|
+
// starts with
|
|
28
|
+
'$',
|
|
29
|
+
// ends with
|
|
30
|
+
'=' // equals
|
|
31
|
+
];
|
|
32
|
+
function checkForPseudoClasses(node, objectExpression, context) {
|
|
33
|
+
if (!isNodeOfType(objectExpression, 'ObjectExpression')) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
objectExpression.properties.forEach(prop => {
|
|
37
|
+
if (isNodeOfType(prop, 'Property')) {
|
|
38
|
+
// Check if property key is a pseudo-class
|
|
39
|
+
let keyValue = null;
|
|
40
|
+
if (isNodeOfType(prop.key, 'Literal')) {
|
|
41
|
+
keyValue = prop.key.value;
|
|
42
|
+
} else if (isNodeOfType(prop.key, 'Identifier')) {
|
|
43
|
+
keyValue = prop.key.name;
|
|
44
|
+
}
|
|
45
|
+
if (keyValue && typeof keyValue === 'string' && unsupportedSelectors.some(selector => keyValue.includes(selector))) {
|
|
46
|
+
context.report({
|
|
47
|
+
node: prop.key,
|
|
48
|
+
messageId: 'noPseudoClass',
|
|
49
|
+
data: {
|
|
50
|
+
pseudo: keyValue
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Recursively check nested objects (for function return values)
|
|
56
|
+
if (isNodeOfType(prop.value, 'ArrowFunctionExpression') || isNodeOfType(prop.value, 'FunctionExpression')) {
|
|
57
|
+
// Check the function body for returned object expressions
|
|
58
|
+
const body = prop.value.body;
|
|
59
|
+
if (isNodeOfType(body, 'ObjectExpression')) {
|
|
60
|
+
checkForPseudoClasses(node, body, context);
|
|
61
|
+
} else if (isNodeOfType(body, 'BlockStatement')) {
|
|
62
|
+
// Look for return statements
|
|
63
|
+
body.body.forEach(stmt => {
|
|
64
|
+
if (isNodeOfType(stmt, 'ReturnStatement') && stmt.argument && isNodeOfType(stmt.argument, 'ObjectExpression')) {
|
|
65
|
+
checkForPseudoClasses(node, stmt.argument, context);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
} else if (isNodeOfType(prop.value, 'ObjectExpression')) {
|
|
70
|
+
checkForPseudoClasses(node, prop.value, context);
|
|
71
|
+
}
|
|
72
|
+
} else if (isNodeOfType(prop, 'SpreadElement')) {
|
|
73
|
+
// Handle spread elements like ...styles or ...conditionalStyles
|
|
74
|
+
checkSpreadElement(node, prop, context);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function checkSpreadElement(node, spreadElement, context) {
|
|
79
|
+
if (!isNodeOfType(spreadElement, 'SpreadElement')) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const argument = spreadElement.argument;
|
|
83
|
+
|
|
84
|
+
// Handle direct identifier (e.g., ...styles)
|
|
85
|
+
if (isNodeOfType(argument, 'Identifier')) {
|
|
86
|
+
const scope = context.getScope();
|
|
87
|
+
let variable = null;
|
|
88
|
+
let currentScope = scope;
|
|
89
|
+
|
|
90
|
+
// Search through scope chain
|
|
91
|
+
while (currentScope && !variable) {
|
|
92
|
+
variable = currentScope.variables.find(v => v.name === argument.name);
|
|
93
|
+
currentScope = currentScope.upper;
|
|
94
|
+
}
|
|
95
|
+
if (variable && variable.defs.length > 0) {
|
|
96
|
+
const def = variable.defs[0];
|
|
97
|
+
if (isNodeOfType(def.node, 'VariableDeclarator') && def.node.init) {
|
|
98
|
+
if (isNodeOfType(def.node.init, 'ObjectExpression')) {
|
|
99
|
+
checkForPseudoClasses(node, def.node.init, context);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Handle conditional expressions (e.g., ...(condition ? { ':hover': ... } : undefined))
|
|
105
|
+
else if (isNodeOfType(argument, 'ConditionalExpression')) {
|
|
106
|
+
// Check both consequent and alternate
|
|
107
|
+
if (isNodeOfType(argument.consequent, 'ObjectExpression')) {
|
|
108
|
+
checkForPseudoClasses(node, argument.consequent, context);
|
|
109
|
+
}
|
|
110
|
+
if (argument.alternate && isNodeOfType(argument.alternate, 'ObjectExpression')) {
|
|
111
|
+
checkForPseudoClasses(node, argument.alternate, context);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Handle logical expressions (e.g., ...condition && { ':hover': ... })
|
|
115
|
+
else if (isNodeOfType(argument, 'LogicalExpression')) {
|
|
116
|
+
if (isNodeOfType(argument.right, 'ObjectExpression')) {
|
|
117
|
+
checkForPseudoClasses(node, argument.right, context);
|
|
118
|
+
}
|
|
119
|
+
if (isNodeOfType(argument.left, 'ObjectExpression')) {
|
|
120
|
+
checkForPseudoClasses(node, argument.left, context);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Handle direct object expressions (e.g., ...{ ':hover': ... })
|
|
124
|
+
else if (isNodeOfType(argument, 'ObjectExpression')) {
|
|
125
|
+
checkForPseudoClasses(node, argument, context);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export function checkStylesObject(node, stylesValue, context) {
|
|
129
|
+
if (!stylesValue) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (isNodeOfType(stylesValue, 'ObjectExpression')) {
|
|
133
|
+
stylesValue.properties.forEach(prop => {
|
|
134
|
+
if (!isNodeOfType(prop, 'Property') || !prop.value || !isNodeOfType(prop.value, 'ArrowFunctionExpression') && !isNodeOfType(prop.value, 'FunctionExpression')) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const body = prop.value.body;
|
|
138
|
+
if (isNodeOfType(body, 'ObjectExpression')) {
|
|
139
|
+
checkForPseudoClasses(node, body, context);
|
|
140
|
+
} else if (isNodeOfType(body, 'BlockStatement')) {
|
|
141
|
+
const visitor = {
|
|
142
|
+
ReturnStatement(returnStmt) {
|
|
143
|
+
if (returnStmt.argument && isNodeOfType(returnStmt.argument, 'ObjectExpression')) {
|
|
144
|
+
checkForPseudoClasses(node, returnStmt.argument, context);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
AssignmentExpression(assignExpr) {
|
|
148
|
+
// Handle cases like styles[':hover'] = { ... }
|
|
149
|
+
const left = assignExpr.left;
|
|
150
|
+
if (!isNodeOfType(left, 'MemberExpression')) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const property = left.property;
|
|
154
|
+
if (!isNodeOfType(property, 'Literal')) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const value = property.value;
|
|
158
|
+
if (typeof value !== 'string') {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (unsupportedSelectors.some(selector => value.includes(selector))) {
|
|
162
|
+
context.report({
|
|
163
|
+
node: property,
|
|
164
|
+
messageId: 'noPseudoClass',
|
|
165
|
+
data: {
|
|
166
|
+
pseudo: value
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
body.body.forEach(stmt => {
|
|
173
|
+
if (isNodeOfType(stmt, 'ReturnStatement')) {
|
|
174
|
+
visitor.ReturnStatement(stmt);
|
|
175
|
+
} else if (isNodeOfType(stmt, 'ExpressionStatement') && isNodeOfType(stmt.expression, 'AssignmentExpression')) {
|
|
176
|
+
visitor.AssignmentExpression(stmt.expression);
|
|
177
|
+
} else if (isNodeOfType(stmt, 'IfStatement')) {
|
|
178
|
+
const checkBlock = block => {
|
|
179
|
+
if (isNodeOfType(block, 'BlockStatement')) {
|
|
180
|
+
block.body.forEach(innerStmt => {
|
|
181
|
+
if (isNodeOfType(innerStmt, 'ExpressionStatement') && isNodeOfType(innerStmt.expression, 'AssignmentExpression')) {
|
|
182
|
+
visitor.AssignmentExpression(innerStmt.expression);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
} else if (isNodeOfType(block, 'ExpressionStatement') && isNodeOfType(block.expression, 'AssignmentExpression')) {
|
|
186
|
+
visitor.AssignmentExpression(block.expression);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
if (stmt.consequent) {
|
|
190
|
+
checkBlock(stmt.consequent);
|
|
191
|
+
}
|
|
192
|
+
if (stmt.alternate) {
|
|
193
|
+
checkBlock(stmt.alternate);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
} else if (isNodeOfType(stylesValue, 'Identifier')) {
|
|
200
|
+
// track the variable
|
|
201
|
+
const scope = context.getScope();
|
|
202
|
+
let variable = null;
|
|
203
|
+
let currentScope = scope;
|
|
204
|
+
|
|
205
|
+
// Search through scope chain
|
|
206
|
+
while (currentScope && !variable) {
|
|
207
|
+
variable = currentScope.variables.find(v => v.name === stylesValue.name);
|
|
208
|
+
currentScope = currentScope.upper;
|
|
209
|
+
}
|
|
210
|
+
if (variable && variable.defs.length > 0) {
|
|
211
|
+
const def = variable.defs[0];
|
|
212
|
+
if (isNodeOfType(def.node, 'VariableDeclarator') && def.node.init) {
|
|
213
|
+
checkStylesObject(node, def.node.init, context);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::3c4282c17de41bbfb755836f00326036>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
import consistentCssPropUsage from './consistent-css-prop-usage';
|
|
7
|
+
import enforceInlineStylesInSelect from './enforce-inline-styles-in-select';
|
|
7
8
|
import ensureDesignTokenUsage from './ensure-design-token-usage';
|
|
8
9
|
import ensureDesignTokenUsagePreview from './ensure-design-token-usage-preview';
|
|
9
10
|
import ensureIconColor from './ensure-icon-color';
|
|
@@ -47,6 +48,7 @@ import noUnsupportedDragAndDropLibraries from './no-unsupported-drag-and-drop-li
|
|
|
47
48
|
import noUtilityIcons from './no-utility-icons';
|
|
48
49
|
import preferPrimitives from './prefer-primitives';
|
|
49
50
|
import useButtonGroupLabel from './use-button-group-label';
|
|
51
|
+
import useCorrectField from './use-correct-field';
|
|
50
52
|
import useCxFunctionInXcss from './use-cx-function-in-xcss';
|
|
51
53
|
import useDatetimePickerCalendarButton from './use-datetime-picker-calendar-button';
|
|
52
54
|
import useDrawerLabel from './use-drawer-label';
|
|
@@ -68,6 +70,7 @@ import useTokensTypography from './use-tokens-typography';
|
|
|
68
70
|
import useVisuallyHidden from './use-visually-hidden';
|
|
69
71
|
export const rules = {
|
|
70
72
|
'consistent-css-prop-usage': consistentCssPropUsage,
|
|
73
|
+
'enforce-inline-styles-in-select': enforceInlineStylesInSelect,
|
|
71
74
|
'ensure-design-token-usage': ensureDesignTokenUsage,
|
|
72
75
|
'ensure-design-token-usage/preview': ensureDesignTokenUsagePreview,
|
|
73
76
|
'ensure-icon-color': ensureIconColor,
|
|
@@ -111,6 +114,7 @@ export const rules = {
|
|
|
111
114
|
'no-utility-icons': noUtilityIcons,
|
|
112
115
|
'prefer-primitives': preferPrimitives,
|
|
113
116
|
'use-button-group-label': useButtonGroupLabel,
|
|
117
|
+
'use-correct-field': useCorrectField,
|
|
114
118
|
'use-cx-function-in-xcss': useCxFunctionInXcss,
|
|
115
119
|
'use-datetime-picker-calendar-button': useDatetimePickerCalendarButton,
|
|
116
120
|
'use-drawer-label': useDrawerLabel,
|
|
@@ -50,16 +50,21 @@ const getKeyValue = (node, context) => {
|
|
|
50
50
|
}
|
|
51
51
|
return '';
|
|
52
52
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
53
|
+
|
|
54
|
+
// const isWidthMediaQuery = (rawSelector: string): boolean => {
|
|
55
|
+
// const selectors = parseSelector(rawSelector);
|
|
56
|
+
|
|
57
|
+
// if (selectors[0].startsWith('@')) {
|
|
58
|
+
// // If the selector includes a min-width/max-width query, return false - the primitives media object should be used instead:
|
|
59
|
+
// // https://staging.atlassian.design/components/primitives/responsive/breakpoints/examples
|
|
60
|
+
// // Otherwise return true, non-width queries are acceptable
|
|
61
|
+
// return selectors.some(
|
|
62
|
+
// (selector) => selector.includes('min-width') || selector.includes('max-width'),
|
|
63
|
+
// );
|
|
64
|
+
// }
|
|
65
|
+
// return false;
|
|
66
|
+
// };
|
|
67
|
+
|
|
63
68
|
const isAllowedNestedSelector = rawSelector => {
|
|
64
69
|
if (rawSelector.trim() === '&') {
|
|
65
70
|
// This can be written without the nest.
|
|
@@ -87,7 +92,8 @@ const rule = createLintRule({
|
|
|
87
92
|
severity: 'error'
|
|
88
93
|
},
|
|
89
94
|
messages: {
|
|
90
|
-
noWidthQueries:
|
|
95
|
+
// noWidthQueries:
|
|
96
|
+
// 'Media queries that target min-width or max-width are not allowed. Use the media object provided by the Atlassian Design System instead. https://staging.atlassian.design/components/primitives/responsive/breakpoints/examples',
|
|
91
97
|
noNestedStyles: 'Nested styles are not allowed as they can change unexpectedly when child markup changes and result in duplicates when extracting to CSS.',
|
|
92
98
|
noDirectNestedStyles: `Styles applied with data attributes are not allowed, split them into discrete CSS declarations and apply them conditionally with JavaScript.
|
|
93
99
|
|
|
@@ -112,13 +118,16 @@ const disabledStyles = css({ opacity: 0.5 });
|
|
|
112
118
|
});
|
|
113
119
|
return;
|
|
114
120
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
|
|
122
|
+
// if (isWidthMediaQuery(getKeyValue(node.key as Rule.Node, context))) {
|
|
123
|
+
// context.report({
|
|
124
|
+
// node,
|
|
125
|
+
// messageId: 'noWidthQueries',
|
|
126
|
+
// });
|
|
127
|
+
|
|
128
|
+
// return;
|
|
129
|
+
// }
|
|
130
|
+
|
|
122
131
|
if (!isAllowedNestedSelector(getKeyValue(node.key, context))) {
|
|
123
132
|
context.report({
|
|
124
133
|
node,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
2
|
const specialCases = {
|
|
3
|
-
'@atlaskit/icon/utility/cross': '@atlaskit/icon/core/close',
|
|
4
3
|
'@atlaskit/icon/utility/migration/cross--editor-close': '@atlaskit/icon/core/migration/close--editor-close'
|
|
5
4
|
};
|
|
6
5
|
const iconPropsinNewButton = ['icon', 'iconBefore', 'iconAfter'];
|