@elastic/eslint-plugin-eui 0.0.3 → 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.
Files changed (39) hide show
  1. package/README.md +145 -9
  2. package/lib/cjs/index.d.ts +2 -0
  3. package/lib/cjs/index.d.ts.map +1 -0
  4. package/{index.js → lib/cjs/index.js} +18 -5
  5. package/lib/cjs/rules/href_or_on_click.d.ts +3 -0
  6. package/lib/cjs/rules/href_or_on_click.d.ts.map +1 -0
  7. package/lib/cjs/rules/href_or_on_click.js +65 -0
  8. package/lib/cjs/rules/no_css_color.d.ts +3 -0
  9. package/lib/cjs/rules/no_css_color.d.ts.map +1 -0
  10. package/lib/cjs/rules/no_css_color.js +329 -0
  11. package/lib/cjs/rules/no_restricted_eui_imports.d.ts +8 -0
  12. package/lib/cjs/rules/no_restricted_eui_imports.d.ts.map +1 -0
  13. package/lib/cjs/rules/no_restricted_eui_imports.js +76 -0
  14. package/lib/cjs/rules/prefer_css_attribute_for_eui_components.d.ts +3 -0
  15. package/lib/cjs/rules/prefer_css_attribute_for_eui_components.d.ts.map +1 -0
  16. package/lib/cjs/rules/prefer_css_attribute_for_eui_components.js +63 -0
  17. package/lib/cjs/utils/resolve_member_expression_root.d.ts +3 -0
  18. package/lib/cjs/utils/resolve_member_expression_root.d.ts.map +1 -0
  19. package/lib/cjs/utils/resolve_member_expression_root.js +13 -0
  20. package/lib/esm/index.d.ts +1 -0
  21. package/lib/esm/index.js +45 -0
  22. package/lib/esm/index.js.map +1 -0
  23. package/lib/esm/rules/href_or_on_click.d.ts +2 -0
  24. package/lib/esm/rules/href_or_on_click.js +61 -0
  25. package/lib/esm/rules/href_or_on_click.js.map +1 -0
  26. package/lib/esm/rules/no_css_color.d.ts +2 -0
  27. package/lib/esm/rules/no_css_color.js +360 -0
  28. package/lib/esm/rules/no_css_color.js.map +1 -0
  29. package/lib/esm/rules/no_restricted_eui_imports.d.ts +7 -0
  30. package/lib/esm/rules/no_restricted_eui_imports.js +76 -0
  31. package/lib/esm/rules/no_restricted_eui_imports.js.map +1 -0
  32. package/lib/esm/rules/prefer_css_attribute_for_eui_components.d.ts +2 -0
  33. package/lib/esm/rules/prefer_css_attribute_for_eui_components.js +63 -0
  34. package/lib/esm/rules/prefer_css_attribute_for_eui_components.js.map +1 -0
  35. package/lib/esm/utils/resolve_member_expression_root.d.ts +2 -0
  36. package/lib/esm/utils/resolve_member_expression_root.js +11 -0
  37. package/lib/esm/utils/resolve_member_expression_root.js.map +1 -0
  38. package/package.json +51 -8
  39. package/rules/href_or_on_click.js +0 -33
@@ -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,3 @@
1
+ import { TSESTree } from '@typescript-eslint/typescript-estree';
2
+ export declare const resolveMemberExpressionRoot: (node: TSESTree.MemberExpression) => TSESTree.Identifier;
3
+ //# sourceMappingURL=resolve_member_expression_root.d.ts.map
@@ -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 {};
@@ -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,2 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const HrefOnClick: ESLintUtils.RuleModule<"hrefOrOnClick", [], unknown, ESLintUtils.RuleListener>;
@@ -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,2 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const NoCssColor: ESLintUtils.RuleModule<"noCssColor" | "noCssColorSpecific" | "noCSSColorSpecificDeclaredVariable", [], unknown, ESLintUtils.RuleListener>;
@@ -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