@elastic/eslint-plugin-eui 0.0.2 → 0.1.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/README.md +145 -9
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.d.ts.map +1 -0
- package/{index.js → lib/cjs/index.js} +19 -6
- package/lib/cjs/rules/href_or_on_click.d.ts +3 -0
- package/lib/cjs/rules/href_or_on_click.d.ts.map +1 -0
- package/lib/cjs/rules/href_or_on_click.js +65 -0
- package/lib/cjs/rules/no_css_color.d.ts +3 -0
- package/lib/cjs/rules/no_css_color.d.ts.map +1 -0
- package/lib/cjs/rules/no_css_color.js +329 -0
- package/lib/cjs/rules/no_restricted_eui_imports.d.ts +8 -0
- package/lib/cjs/rules/no_restricted_eui_imports.d.ts.map +1 -0
- package/lib/cjs/rules/no_restricted_eui_imports.js +76 -0
- package/lib/cjs/rules/prefer_css_attribute_for_eui_components.d.ts +3 -0
- package/lib/cjs/rules/prefer_css_attribute_for_eui_components.d.ts.map +1 -0
- package/lib/cjs/rules/prefer_css_attribute_for_eui_components.js +63 -0
- package/lib/cjs/utils/resolve_member_expression_root.d.ts +3 -0
- package/lib/cjs/utils/resolve_member_expression_root.d.ts.map +1 -0
- package/lib/cjs/utils/resolve_member_expression_root.js +13 -0
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.js +45 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/rules/href_or_on_click.d.ts +2 -0
- package/lib/esm/rules/href_or_on_click.js +61 -0
- package/lib/esm/rules/href_or_on_click.js.map +1 -0
- package/lib/esm/rules/no_css_color.d.ts +2 -0
- package/lib/esm/rules/no_css_color.js +360 -0
- package/lib/esm/rules/no_css_color.js.map +1 -0
- package/lib/esm/rules/no_restricted_eui_imports.d.ts +7 -0
- package/lib/esm/rules/no_restricted_eui_imports.js +76 -0
- package/lib/esm/rules/no_restricted_eui_imports.js.map +1 -0
- package/lib/esm/rules/prefer_css_attribute_for_eui_components.d.ts +2 -0
- package/lib/esm/rules/prefer_css_attribute_for_eui_components.js +63 -0
- package/lib/esm/rules/prefer_css_attribute_for_eui_components.js.map +1 -0
- package/lib/esm/utils/resolve_member_expression_root.d.ts +2 -0
- package/lib/esm/utils/resolve_member_expression_root.js +11 -0
- package/lib/esm/utils/resolve_member_expression_root.js.map +1 -0
- package/package.json +53 -10
- package/rules/href_or_on_click.js +0 -35
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.NoRestrictedEuiImports = void 0;
|
|
7
|
+
var _micromatch = _interopRequireDefault(require("micromatch"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
/*
|
|
10
|
+
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
11
|
+
* license agreements. See the NOTICE file distributed with
|
|
12
|
+
* this work for additional information regarding copyright
|
|
13
|
+
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
14
|
+
* the Apache License, Version 2.0 (the "License"); you may
|
|
15
|
+
* not use this file except in compliance with the License.
|
|
16
|
+
* You may obtain a copy of the License at
|
|
17
|
+
*
|
|
18
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
19
|
+
*
|
|
20
|
+
* Unless required by applicable law or agreed to in writing,
|
|
21
|
+
* software distributed under the License is distributed on an
|
|
22
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
23
|
+
* KIND, either express or implied. See the License for the
|
|
24
|
+
* specific language governing permissions and limitations
|
|
25
|
+
* under the License.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const DEFAULT_RESTRICTED_IMPORT_OPTIONS = [{
|
|
29
|
+
patterns: ['@elastic/eui/dist/eui_theme_*\\.json'],
|
|
30
|
+
message: 'For client-side, please use `useEuiTheme` instead. Direct JSON token imports will be removed as per the EUI Deprecation schedule: https://github.com/elastic/eui/issues/1469.'
|
|
31
|
+
}];
|
|
32
|
+
const NoRestrictedEuiImports = exports.NoRestrictedEuiImports = {
|
|
33
|
+
create(context) {
|
|
34
|
+
const userOptions = context.options || [];
|
|
35
|
+
const options = [...DEFAULT_RESTRICTED_IMPORT_OPTIONS, ...userOptions];
|
|
36
|
+
return {
|
|
37
|
+
ImportDeclaration(node) {
|
|
38
|
+
options.forEach(({
|
|
39
|
+
patterns,
|
|
40
|
+
message
|
|
41
|
+
}) => {
|
|
42
|
+
if (_micromatch.default.isMatch(node.source.value, patterns)) {
|
|
43
|
+
context.report({
|
|
44
|
+
loc: node.source.loc,
|
|
45
|
+
// @ts-expect-error @typescript-eslint types expect `messageId` here but `message` is also allowed in eslint API
|
|
46
|
+
message
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
meta: {
|
|
54
|
+
type: 'problem',
|
|
55
|
+
docs: {
|
|
56
|
+
description: 'Discourage the use of deprecated EUI imports.'
|
|
57
|
+
},
|
|
58
|
+
schema: [{
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
patterns: {
|
|
62
|
+
type: 'array',
|
|
63
|
+
items: {
|
|
64
|
+
type: 'string'
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
message: {
|
|
68
|
+
type: 'string'
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
additionalProperties: false
|
|
72
|
+
}],
|
|
73
|
+
messages: {}
|
|
74
|
+
},
|
|
75
|
+
defaultOptions: []
|
|
76
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const PreferCSSAttributeForEuiComponents: ESLintUtils.RuleModule<"preferCSSAttributeForEuiComponents", [], unknown, ESLintUtils.RuleListener>;
|
|
3
|
+
//# sourceMappingURL=prefer_css_attribute_for_eui_components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer_css_attribute_for_eui_components.d.ts","sourceRoot":"","sources":["../../../src/rules/prefer_css_attribute_for_eui_components.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAY,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEjE,eAAO,MAAM,kCAAkC,qGA0D3C,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.PreferCSSAttributeForEuiComponents = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
/*
|
|
9
|
+
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
10
|
+
* license agreements. See the NOTICE file distributed with
|
|
11
|
+
* this work for additional information regarding copyright
|
|
12
|
+
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
13
|
+
* the Apache License, Version 2.0 (the "License"); you may
|
|
14
|
+
* not use this file except in compliance with the License.
|
|
15
|
+
* You may obtain a copy of the License at
|
|
16
|
+
*
|
|
17
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
18
|
+
*
|
|
19
|
+
* Unless required by applicable law or agreed to in writing,
|
|
20
|
+
* software distributed under the License is distributed on an
|
|
21
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
22
|
+
* KIND, either express or implied. See the License for the
|
|
23
|
+
* specific language governing permissions and limitations
|
|
24
|
+
* under the License.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const PreferCSSAttributeForEuiComponents = exports.PreferCSSAttributeForEuiComponents = _utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
28
|
+
create(context) {
|
|
29
|
+
const isNamedEuiComponentRegex = /^Eui[A-Z]*/;
|
|
30
|
+
return {
|
|
31
|
+
JSXOpeningElement(node) {
|
|
32
|
+
if (node.name.type === 'JSXIdentifier' && isNamedEuiComponentRegex.test(node.name.name)) {
|
|
33
|
+
let styleAttrNode;
|
|
34
|
+
if (styleAttrNode = node.attributes.filter(attr => attr.type === 'JSXAttribute').find(attr => attr.name.name === 'style')) {
|
|
35
|
+
context.report({
|
|
36
|
+
node: styleAttrNode?.parent,
|
|
37
|
+
messageId: 'preferCSSAttributeForEuiComponents',
|
|
38
|
+
fix(fixer) {
|
|
39
|
+
const cssAttr = node.attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.name === 'css');
|
|
40
|
+
if (cssAttr) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return fixer.replaceTextRange(styleAttrNode?.name?.range, 'css');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
meta: {
|
|
52
|
+
type: 'suggestion',
|
|
53
|
+
docs: {
|
|
54
|
+
description: 'Prefer the JSX css attribute for EUI components'
|
|
55
|
+
},
|
|
56
|
+
messages: {
|
|
57
|
+
preferCSSAttributeForEuiComponents: 'Prefer the css attribute for EUI components'
|
|
58
|
+
},
|
|
59
|
+
fixable: 'code',
|
|
60
|
+
schema: []
|
|
61
|
+
},
|
|
62
|
+
defaultOptions: []
|
|
63
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve_member_expression_root.d.ts","sourceRoot":"","sources":["../../../src/utils/resolve_member_expression_root.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sCAAsC,CAAC;AAEhE,eAAO,MAAM,2BAA2B,SAChC,QAAQ,CAAC,gBAAgB,KAC9B,QAAQ,CAAC,UAMX,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.resolveMemberExpressionRoot = void 0;
|
|
7
|
+
const resolveMemberExpressionRoot = node => {
|
|
8
|
+
if (node.object.type === 'MemberExpression') {
|
|
9
|
+
return resolveMemberExpressionRoot(node.object);
|
|
10
|
+
}
|
|
11
|
+
return node.object;
|
|
12
|
+
};
|
|
13
|
+
exports.resolveMemberExpressionRoot = resolveMemberExpressionRoot;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
4
|
+
* license agreements. See the NOTICE file distributed with
|
|
5
|
+
* this work for additional information regarding copyright
|
|
6
|
+
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
7
|
+
* the Apache License, Version 2.0 (the "License"); you may
|
|
8
|
+
* not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing,
|
|
14
|
+
* software distributed under the License is distributed on an
|
|
15
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
* KIND, either express or implied. See the License for the
|
|
17
|
+
* specific language governing permissions and limitations
|
|
18
|
+
* under the License.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
const href_or_on_click_1 = require("./rules/href_or_on_click");
|
|
22
|
+
const no_restricted_eui_imports_1 = require("./rules/no_restricted_eui_imports");
|
|
23
|
+
const no_css_color_1 = require("./rules/no_css_color");
|
|
24
|
+
const prefer_css_attribute_for_eui_components_1 = require("./rules/prefer_css_attribute_for_eui_components");
|
|
25
|
+
const config = {
|
|
26
|
+
rules: {
|
|
27
|
+
'href-or-on-click': href_or_on_click_1.HrefOnClick,
|
|
28
|
+
'no-restricted-eui-imports': no_restricted_eui_imports_1.NoRestrictedEuiImports,
|
|
29
|
+
'no-css-color': no_css_color_1.NoCssColor,
|
|
30
|
+
'prefer-css-attributes-for-eui-components': prefer_css_attribute_for_eui_components_1.PreferCSSAttributeForEuiComponents,
|
|
31
|
+
},
|
|
32
|
+
configs: {
|
|
33
|
+
recommended: {
|
|
34
|
+
plugins: ['@elastic/eslint-plugin-eui'],
|
|
35
|
+
rules: {
|
|
36
|
+
'@elastic/eui/href-or-on-click': 'warn',
|
|
37
|
+
'@elastic/eui/no-restricted-eui-imports': 'warn',
|
|
38
|
+
'@elastic/eui/no-css-color': 'warn',
|
|
39
|
+
'@elastic/eui/prefer-css-attributes-for-eui-components': 'warn',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
module.exports = config;
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;AAEH,+DAAuD;AACvD,iFAA2E;AAC3E,uDAAkD;AAClD,6GAAqG;AAErG,MAAM,MAAM,GAAG;IACb,KAAK,EAAE;QACL,kBAAkB,EAAE,8BAAW;QAC/B,2BAA2B,EAAE,kDAAsB;QACnD,cAAc,EAAE,yBAAU;QAC1B,0CAA0C,EACxC,4EAAkC;KACrC;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,4BAA4B,CAAC;YACvC,KAAK,EAAE;gBACL,+BAA+B,EAAE,MAAM;gBACvC,wCAAwC,EAAE,MAAM;gBAChD,2BAA2B,EAAE,MAAM;gBACnC,uDAAuD,EAAE,MAAM;aAChE;SACF;KACF;CACF,CAAC;AAEF,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
4
|
+
* license agreements. See the NOTICE file distributed with
|
|
5
|
+
* this work for additional information regarding copyright
|
|
6
|
+
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
7
|
+
* the Apache License, Version 2.0 (the "License"); you may
|
|
8
|
+
* not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing,
|
|
14
|
+
* software distributed under the License is distributed on an
|
|
15
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
* KIND, either express or implied. See the License for the
|
|
17
|
+
* specific language governing permissions and limitations
|
|
18
|
+
* under the License.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.HrefOnClick = void 0;
|
|
22
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
23
|
+
const componentNames = ['EuiButton', 'EuiButtonEmpty', 'EuiLink', 'EuiBadge'];
|
|
24
|
+
exports.HrefOnClick = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
JSXOpeningElement(node) {
|
|
28
|
+
// Ensure node name is one of the valid component names
|
|
29
|
+
if (node.name.type !== 'JSXIdentifier' ||
|
|
30
|
+
!componentNames.includes(node.name.name)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Check if the node has both `href` and `onClick` attributes
|
|
34
|
+
const hasHref = node.attributes.some((attr) => attr.type === 'JSXAttribute' && attr.name.name === 'href');
|
|
35
|
+
const hasOnClick = node.attributes.some((attr) => attr.type === 'JSXAttribute' && attr.name.name === 'onClick');
|
|
36
|
+
// Report an issue if both attributes are present
|
|
37
|
+
if (hasHref && hasOnClick) {
|
|
38
|
+
context.report({
|
|
39
|
+
node,
|
|
40
|
+
messageId: 'hrefOrOnClick',
|
|
41
|
+
data: {
|
|
42
|
+
name: node.name.name,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
meta: {
|
|
50
|
+
type: 'problem',
|
|
51
|
+
docs: {
|
|
52
|
+
description: 'Discourage supplying both `href` and `onClick` to certain EUI components.',
|
|
53
|
+
},
|
|
54
|
+
schema: [],
|
|
55
|
+
messages: {
|
|
56
|
+
hrefOrOnClick: '<{{name}}> supplied with both `href` and `onClick`; is this intentional? (Valid use cases include programmatic navigation via `onClick` while preserving "Open in new tab" style functionality via `href`.)',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
defaultOptions: [],
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=href_or_on_click.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"href_or_on_click.js","sourceRoot":"","sources":["../../../src/rules/href_or_on_click.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAEH,oDAAiE;AAEjE,MAAM,cAAc,GAAG,CAAC,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAEjE,QAAA,WAAW,GAAG,mBAAW,CAAC,WAAW,CAAC,WAAW,CAAC;IAC7D,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,iBAAiB,CAAC,IAAgC;gBAChD,uDAAuD;gBACvD,IACE,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe;oBAClC,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EACxC,CAAC;oBACD,OAAO;gBACT,CAAC;gBAED,6DAA6D;gBAC7D,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAClC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,cAAc,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CACpE,CAAC;gBACF,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CACrC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,cAAc,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CACvE,CAAC;gBAEF,iDAAiD;gBACjD,IAAI,OAAO,IAAI,UAAU,EAAE,CAAC;oBAC1B,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,SAAS,EAAE,eAAe;wBAC1B,IAAI,EAAE;4BACJ,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI;yBACrB;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EACT,2EAA2E;SAC9E;QACD,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR,aAAa,EACX,6MAA6M;SAChN;KACF;IACD,cAAc,EAAE,EAAE;CACnB,CAAC,CAAC"}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
4
|
+
* or more contributor license agreements. Licensed under the "Elastic License
|
|
5
|
+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
|
6
|
+
* Public License v 1"; you may not use this file except in compliance with, at
|
|
7
|
+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
|
8
|
+
* License v3.0 only", or the "Server Side Public License, v 1".
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.NoCssColor = void 0;
|
|
12
|
+
const cssstyle_1 = require("cssstyle");
|
|
13
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
14
|
+
const resolve_member_expression_root_1 = require("../utils/resolve_member_expression_root");
|
|
15
|
+
/**
|
|
16
|
+
* @description List of superset css properties that can apply color to html box element elements and text nodes, leveraging the
|
|
17
|
+
* css style package allows us to directly singly check for these properties even if the actual declaration was written using the shorthand form
|
|
18
|
+
*/
|
|
19
|
+
const propertiesSupportingCssColor = ['color', 'background', 'border'];
|
|
20
|
+
/**
|
|
21
|
+
* @description Builds off the existing color definition to match css declarations that can apply color to
|
|
22
|
+
* html elements and text nodes for string declarations
|
|
23
|
+
*/
|
|
24
|
+
const htmlElementColorDeclarationRegex = RegExp(String.raw `(${propertiesSupportingCssColor.join('|')})`);
|
|
25
|
+
const checkPropertySpecifiesInvalidCSSColor = ([property, value]) => {
|
|
26
|
+
if (!property || !value)
|
|
27
|
+
return false;
|
|
28
|
+
const style = new cssstyle_1.CSSStyleDeclaration();
|
|
29
|
+
// @ts-expect-error the types for this packages specifies an index signature of number, alongside other valid CSS properties
|
|
30
|
+
style[property.trim()] = typeof value === 'string' ? value.trim() : value;
|
|
31
|
+
const anchor = propertiesSupportingCssColor.find((resolvedProperty) => property.includes(resolvedProperty));
|
|
32
|
+
if (!anchor)
|
|
33
|
+
return false;
|
|
34
|
+
// build the resolved color property to check if the value is a string after parsing the style declaration
|
|
35
|
+
const resolvedColorProperty = anchor === 'color' ? 'color' : anchor + 'Color';
|
|
36
|
+
// in trying to keep this rule simple, it's enough if we get a value back, because if it was an identifier we would have been able to set a value within this invocation
|
|
37
|
+
// @ts-expect-error the types for this packages specifics an index signature of number, alongside other valid CSS properties
|
|
38
|
+
return Boolean(style[resolvedColorProperty]);
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* @description method to inspect values of interest found on an object
|
|
42
|
+
*/
|
|
43
|
+
const raiseReportIfPropertyHasInvalidCssColor = (context, propertyNode, messageToReport) => {
|
|
44
|
+
let didReport = false;
|
|
45
|
+
if (propertyNode.key.type === 'Identifier' &&
|
|
46
|
+
!htmlElementColorDeclarationRegex.test(propertyNode.key.name)) {
|
|
47
|
+
return didReport;
|
|
48
|
+
}
|
|
49
|
+
if (propertyNode.value.type === 'Literal') {
|
|
50
|
+
if ((didReport = checkPropertySpecifiesInvalidCSSColor([
|
|
51
|
+
// @ts-expect-error the key name is present in this scenario
|
|
52
|
+
propertyNode.key.name,
|
|
53
|
+
propertyNode.value.value,
|
|
54
|
+
]))) {
|
|
55
|
+
context.report(messageToReport);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (propertyNode.value.type === 'Identifier') {
|
|
59
|
+
const identifierDeclaration = context.sourceCode
|
|
60
|
+
.getScope(propertyNode)
|
|
61
|
+
.variables.find((variable) => variable.name === propertyNode.value.name);
|
|
62
|
+
const identifierDeclarationInit = (identifierDeclaration?.defs[0].node).init;
|
|
63
|
+
if (identifierDeclarationInit?.type === 'Literal' &&
|
|
64
|
+
checkPropertySpecifiesInvalidCSSColor([
|
|
65
|
+
// @ts-expect-error the key name is present in this scenario
|
|
66
|
+
propertyNode.key.name,
|
|
67
|
+
identifierDeclarationInit.value,
|
|
68
|
+
])) {
|
|
69
|
+
context.report({
|
|
70
|
+
loc: propertyNode.value.loc,
|
|
71
|
+
messageId: 'noCSSColorSpecificDeclaredVariable',
|
|
72
|
+
data: {
|
|
73
|
+
// @ts-expect-error the key name is always present else this code will not execute
|
|
74
|
+
property: String(propertyNode.key.name),
|
|
75
|
+
line: String(propertyNode.value.loc.start.line),
|
|
76
|
+
variableName: propertyNode.value.name,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
didReport = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (propertyNode.value.type === 'MemberExpression') {
|
|
83
|
+
// @ts-expect-error we ignore the case where this node could be a private identifier
|
|
84
|
+
const MemberExpressionLeafName = propertyNode.value.property.name;
|
|
85
|
+
const memberExpressionRootName = (0, resolve_member_expression_root_1.resolveMemberExpressionRoot)(propertyNode.value).name;
|
|
86
|
+
const expressionRootDeclaration = context.sourceCode
|
|
87
|
+
.getScope(propertyNode)
|
|
88
|
+
.variables.find((variable) => variable.name === memberExpressionRootName);
|
|
89
|
+
const expressionRootDeclarationInit = (expressionRootDeclaration?.defs[0].node).init;
|
|
90
|
+
if (expressionRootDeclarationInit?.type === 'ObjectExpression') {
|
|
91
|
+
expressionRootDeclarationInit.properties.forEach((property) => {
|
|
92
|
+
// This is a naive approach expecting the value to be at depth 1, we should actually be traversing the object to the same depth as the expression
|
|
93
|
+
if (property.type === 'Property' &&
|
|
94
|
+
property.key.type === 'Identifier' &&
|
|
95
|
+
property.key?.name === MemberExpressionLeafName) {
|
|
96
|
+
raiseReportIfPropertyHasInvalidCssColor(context, property, {
|
|
97
|
+
loc: propertyNode.value.loc,
|
|
98
|
+
messageId: 'noCSSColorSpecificDeclaredVariable',
|
|
99
|
+
data: {
|
|
100
|
+
// @ts-expect-error the key name is always present else this code will not execute
|
|
101
|
+
property: String(propertyNode.key.name),
|
|
102
|
+
line: String(propertyNode.value.loc.start.line),
|
|
103
|
+
variableName: memberExpressionRootName,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else if (expressionRootDeclarationInit?.type === 'CallExpression') {
|
|
110
|
+
// TODO: if this object was returned from invoking a function the best we can do is probably validate that the method invoked is one that returns an euitheme object
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return didReport;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
*
|
|
117
|
+
* @description style object declaration have a depth of 1, this function handles the properties of the object
|
|
118
|
+
*/
|
|
119
|
+
const handleObjectProperties = (context, propertyParentNode, property, reportMessage) => {
|
|
120
|
+
if (property.type === 'Property') {
|
|
121
|
+
raiseReportIfPropertyHasInvalidCssColor(context, property, reportMessage);
|
|
122
|
+
}
|
|
123
|
+
else if (property.type === 'SpreadElement') {
|
|
124
|
+
const spreadElementIdentifierName = property.argument.name;
|
|
125
|
+
const spreadElementDeclaration = context.sourceCode
|
|
126
|
+
.getScope(propertyParentNode.value.expression)
|
|
127
|
+
.references.find((ref) => ref.identifier.name === spreadElementIdentifierName)?.resolved;
|
|
128
|
+
if (!spreadElementDeclaration) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
reportMessage = {
|
|
132
|
+
loc: propertyParentNode.loc,
|
|
133
|
+
messageId: 'noCSSColorSpecificDeclaredVariable',
|
|
134
|
+
data: {
|
|
135
|
+
// @ts-expect-error the key name is always present else this code will not execute
|
|
136
|
+
property: String(property.argument.name),
|
|
137
|
+
variableName: spreadElementIdentifierName,
|
|
138
|
+
line: String(property.loc.start.line),
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
const spreadElementDeclarationNode = 'init' in spreadElementDeclaration.defs[0].node
|
|
142
|
+
? spreadElementDeclaration.defs[0].node.init
|
|
143
|
+
: undefined;
|
|
144
|
+
// evaluate only statically defined declarations, other possibilities like callExpressions in this context complicate things
|
|
145
|
+
if (spreadElementDeclarationNode?.type === 'ObjectExpression') {
|
|
146
|
+
spreadElementDeclarationNode.properties.forEach((spreadProperty) => {
|
|
147
|
+
handleObjectProperties(context, propertyParentNode, spreadProperty, reportMessage);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
exports.NoCssColor = utils_1.ESLintUtils.RuleCreator.withoutDocs({
|
|
153
|
+
create(context) {
|
|
154
|
+
return {
|
|
155
|
+
// accounts for instances where declarations are created using the template tagged css function
|
|
156
|
+
TaggedTemplateExpression(node) {
|
|
157
|
+
if (node.tag.type !== 'Identifier' ||
|
|
158
|
+
(node.tag.type === 'Identifier' && node.tag.name !== 'css')) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
for (let i = 0; i < node.quasi.quasis.length; i++) {
|
|
162
|
+
const declarationTemplateNode = node.quasi.quasis[i];
|
|
163
|
+
if (htmlElementColorDeclarationRegex.test(declarationTemplateNode.value.raw)) {
|
|
164
|
+
const cssText = declarationTemplateNode.value.raw
|
|
165
|
+
.replace(/(\{|\}|\\n)/g, '')
|
|
166
|
+
.trim();
|
|
167
|
+
cssText.split(';').forEach((declaration) => {
|
|
168
|
+
if (declaration.length > 0 &&
|
|
169
|
+
checkPropertySpecifiesInvalidCSSColor(declaration.split(':'))) {
|
|
170
|
+
context.report({
|
|
171
|
+
node: declarationTemplateNode,
|
|
172
|
+
messageId: 'noCssColor',
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
JSXAttribute(node) {
|
|
180
|
+
if (!(node.name.name === 'style' || node.name.name === 'css')) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* @description Accounts for instances where a variable is used to define a style object
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* const codeStyle = { color: '#dd4040' };
|
|
188
|
+
* <EuiCode style={codeStyle}>This is an example</EuiCode>
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* const codeStyle = { color: '#dd4040' };
|
|
192
|
+
* <EuiCode css={codeStyle}>This is an example</EuiCode>
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* const codeStyle = css({ color: '#dd4040' });
|
|
196
|
+
* <EuiCode css={codeStyle}>This is an example</EuiCode>
|
|
197
|
+
*/
|
|
198
|
+
if (node.value?.type === 'JSXExpressionContainer' &&
|
|
199
|
+
node.value.expression.type === 'Identifier') {
|
|
200
|
+
const styleVariableName = node.value.expression.name;
|
|
201
|
+
const nodeScope = context.sourceCode.getScope(node.value.expression);
|
|
202
|
+
const variableDeclarationMatches = nodeScope.references.find((ref) => ref.identifier.name === styleVariableName)?.resolved;
|
|
203
|
+
let variableInitializationNode;
|
|
204
|
+
if ((variableInitializationNode =
|
|
205
|
+
variableDeclarationMatches?.defs?.[0]?.node &&
|
|
206
|
+
'init' in variableDeclarationMatches.defs[0].node &&
|
|
207
|
+
variableDeclarationMatches.defs[0].node.init)) {
|
|
208
|
+
if (variableInitializationNode.type === 'ObjectExpression') {
|
|
209
|
+
variableInitializationNode.properties.forEach((property) => {
|
|
210
|
+
handleObjectProperties(context, node, property, {
|
|
211
|
+
loc: property.loc,
|
|
212
|
+
messageId: 'noCSSColorSpecificDeclaredVariable',
|
|
213
|
+
data: {
|
|
214
|
+
property: property.type === 'SpreadElement'
|
|
215
|
+
? String(property.argument.name)
|
|
216
|
+
: String(property.key.name),
|
|
217
|
+
variableName: styleVariableName,
|
|
218
|
+
line: String(property.loc.start.line),
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
else if (variableInitializationNode.type === 'CallExpression' &&
|
|
224
|
+
variableInitializationNode.callee
|
|
225
|
+
.name === 'css') {
|
|
226
|
+
const cssFunctionArgument = variableInitializationNode.arguments[0];
|
|
227
|
+
if (cssFunctionArgument.type === 'ObjectExpression') {
|
|
228
|
+
cssFunctionArgument.properties.forEach((property) => {
|
|
229
|
+
handleObjectProperties(context, node, property, {
|
|
230
|
+
loc: node.loc,
|
|
231
|
+
messageId: 'noCSSColorSpecificDeclaredVariable',
|
|
232
|
+
data: {
|
|
233
|
+
property: property.type === 'SpreadElement'
|
|
234
|
+
? String(property.argument.name)
|
|
235
|
+
: String(property.key.name),
|
|
236
|
+
variableName: styleVariableName,
|
|
237
|
+
line: String(property.loc.start.line),
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
*
|
|
248
|
+
* @description Accounts for instances where a style object is inlined in the JSX attribute
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* <EuiCode style={{ color: '#dd4040' }}>This is an example</EuiCode>
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* <EuiCode css={{ color: '#dd4040' }}>This is an example</EuiCode>
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* const styleRules = { color: '#dd4040' };
|
|
258
|
+
* <EuiCode style={{ color: styleRules.color }}>This is an example</EuiCode>
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* const styleRules = { color: '#dd4040' };
|
|
262
|
+
* <EuiCode css={{ color: styleRules.color }}>This is an example</EuiCode>
|
|
263
|
+
*/
|
|
264
|
+
if (node.value?.type === 'JSXExpressionContainer' &&
|
|
265
|
+
node.value.expression.type === 'ObjectExpression') {
|
|
266
|
+
const declarationPropertiesNode = node.value.expression.properties;
|
|
267
|
+
declarationPropertiesNode?.forEach((property) => {
|
|
268
|
+
handleObjectProperties(context, node, property, {
|
|
269
|
+
loc: property.loc,
|
|
270
|
+
messageId: 'noCssColorSpecific',
|
|
271
|
+
data: {
|
|
272
|
+
property: property.type === 'SpreadElement'
|
|
273
|
+
? // @ts-expect-error the key name is always present else this code will not execute
|
|
274
|
+
String(property.argument.name)
|
|
275
|
+
: // @ts-expect-error the key name is always present else this code will not execute
|
|
276
|
+
String(property.key.name),
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (node.name.name === 'css' &&
|
|
283
|
+
node.value?.type === 'JSXExpressionContainer') {
|
|
284
|
+
/**
|
|
285
|
+
* @example
|
|
286
|
+
* <EuiCode css={`{ color: '#dd4040' }`}>This is an example</EuiCode>
|
|
287
|
+
*/
|
|
288
|
+
if (node.value.expression.type === 'TemplateLiteral') {
|
|
289
|
+
for (let i = 0; i < node.value.expression.quasis.length; i++) {
|
|
290
|
+
const declarationTemplateNode = node.value.expression.quasis[i];
|
|
291
|
+
if (htmlElementColorDeclarationRegex.test(declarationTemplateNode.value.raw)) {
|
|
292
|
+
const cssText = declarationTemplateNode.value.raw
|
|
293
|
+
.replace(/(\{|\}|\\n)/g, '')
|
|
294
|
+
.trim();
|
|
295
|
+
cssText.split(';').forEach((declaration) => {
|
|
296
|
+
if (declaration.length > 0 &&
|
|
297
|
+
checkPropertySpecifiesInvalidCSSColor(declaration.split(':'))) {
|
|
298
|
+
context.report({
|
|
299
|
+
node: declarationTemplateNode,
|
|
300
|
+
messageId: 'noCssColor',
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* @example
|
|
309
|
+
* <EuiCode css={() => ({ color: '#dd4040' })}>This is an example</EuiCode>
|
|
310
|
+
*/
|
|
311
|
+
if (node.value.expression.type === 'FunctionExpression' ||
|
|
312
|
+
node.value.expression.type === 'ArrowFunctionExpression') {
|
|
313
|
+
let declarationPropertiesNode = [];
|
|
314
|
+
if (node.value.expression.body.type === 'ObjectExpression') {
|
|
315
|
+
declarationPropertiesNode = node.value.expression.body.properties;
|
|
316
|
+
}
|
|
317
|
+
if (node.value.expression.body.type === 'BlockStatement') {
|
|
318
|
+
const functionReturnStatementNode = node.value.expression.body.body?.find((_node) => {
|
|
319
|
+
return _node.type === 'ReturnStatement';
|
|
320
|
+
});
|
|
321
|
+
if (!functionReturnStatementNode) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
declarationPropertiesNode = functionReturnStatementNode
|
|
325
|
+
.argument?.properties.filter((property) => property.type === 'Property');
|
|
326
|
+
}
|
|
327
|
+
if (!declarationPropertiesNode.length) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
declarationPropertiesNode.forEach((property) => {
|
|
331
|
+
handleObjectProperties(context, node, property, {
|
|
332
|
+
loc: property.loc,
|
|
333
|
+
messageId: 'noCssColorSpecific',
|
|
334
|
+
data: {
|
|
335
|
+
// @ts-expect-error the key name is always present else this code will not execute
|
|
336
|
+
property: property.key.name,
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
},
|
|
346
|
+
meta: {
|
|
347
|
+
type: 'suggestion',
|
|
348
|
+
docs: {
|
|
349
|
+
description: 'Use color definitions from eui theme as opposed to CSS color values',
|
|
350
|
+
},
|
|
351
|
+
messages: {
|
|
352
|
+
noCSSColorSpecificDeclaredVariable: 'Avoid using a literal CSS color value for "{{property}}", use an EUI theme color instead in declared variable {{variableName}} on line {{line}}',
|
|
353
|
+
noCssColorSpecific: 'Avoid using a literal CSS color value for "{{property}}", use an EUI theme color instead',
|
|
354
|
+
noCssColor: 'Avoid using a literal CSS color value, use an EUI theme color instead',
|
|
355
|
+
},
|
|
356
|
+
schema: [],
|
|
357
|
+
},
|
|
358
|
+
defaultOptions: [],
|
|
359
|
+
});
|
|
360
|
+
//# sourceMappingURL=no_css_color.js.map
|