@elastic/eslint-plugin-eui 0.0.3 → 0.1.1

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
package/README.md CHANGED
@@ -4,8 +4,8 @@ This package contains an eslint plugin that enforces some default rules for usin
4
4
 
5
5
  ## Setup
6
6
 
7
- 1. install `@elastic/eslint-plugin-eui` as a dev dependency
8
- 2. extend `plugin:@elastic/eui/recommended` in your eslint config
7
+ 1. Install `@elastic/eslint-plugin-eui` as a dev dependency.
8
+ 2. Extend `plugin:@elastic/eui/recommended` in your ESLint config.
9
9
 
10
10
  ## Rules
11
11
 
@@ -13,13 +13,149 @@ This package contains an eslint plugin that enforces some default rules for usin
13
13
 
14
14
  `<EuiButton />` should either be a button or a link, for a11y purposes. When given an `href` the button behaves as a link, otherwise an `onClick` handler is expected and it will behave as a button.
15
15
 
16
- In some cases it makes sense to disable this rule locally, such as when <kbd>cmd</kbd>+click should open the link in a new tab, but a standard click should use the `history.pushState()` API to change the URL without triggering a full page load.
16
+ In some cases it makes sense to disable this rule locally, such as when <kbd>cmd</kbd> + click should open the link in a new tab, but a standard click should use the `history.pushState()` API to change the URL without triggering a full page load.
17
17
 
18
- ## Publishing
18
+ ### `@elastic/eui/no-restricted-eui-imports`
19
+
20
+ At times, we deprecate features that may need more highlighting and/or that are not possible to annotate with JSDoc `@deprecated`, e.g. JSON token imports: `@elastic/eui/dist/eui_theme_*.json` (for context: https://github.com/elastic/kibana/issues/199715#json-tokens).
21
+
22
+ We don't use `no-restricted-imports` because ESLint doesn't allow multiple error levels at once and it may conflict with the consumer's existing ESLint configuration for that rule. We need to assure that our rule will produce a warning (as a recommendation).
23
+
24
+ All deprecations still must follow our [deprecation process](../../wiki/eui-team-processes/deprecations.md).
25
+
26
+ ### `@elastic/eui/no-css-color`
27
+
28
+ This rule warns engineers to not use literal css color in the codebase, particularly for CSS properties that apply color to either the html element or text nodes, but rather urge users to defer to using the color tokens provided by EUI.
29
+
30
+ This rule kicks in on the following JSXAttributes; `style`, `className` and `css` and supports various approaches to providing styling declarations.
31
+
32
+ #### Example
33
+
34
+ The following code:
35
+
36
+ ```tsx
37
+ // Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx
38
+
39
+ import React from 'react';
40
+ import { EuiText } from '@elastic/eui';
41
+
42
+ function MyComponent() {
43
+ return (
44
+ <EuiText style={{ color: 'red' }}>You know, for search</EuiText>
45
+ )
46
+ }
47
+ ```
48
+
49
+ ```tsx
50
+ // Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx
51
+
52
+ import React from 'react';
53
+ import { EuiText } from '@elastic/eui';
54
+
55
+ function MyComponent() {
56
+
57
+ const style = {
58
+ color: 'red'
59
+ }
60
+
61
+ return (
62
+ <EuiText style={{ color: style.color }}>You know, for search</EuiText>
63
+ )
64
+ }
65
+ ```
66
+
67
+ ```tsx
68
+ // Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx
69
+
70
+ import React from 'react';
71
+ import { EuiText } from '@elastic/eui';
72
+
73
+ function MyComponent() {
74
+ const colorValue = '#dd4040';
75
+
76
+ return (
77
+ <EuiText style={{ color: colorValue }}>You know, for search</EuiText>
78
+ )
79
+ }
80
+ ```
81
+
82
+ will all raise an eslint report with an appropriate message of severity that matches the configuration of the rule, further more all the examples above
83
+ will also match for when the attribute in question is `css`. The `css` attribute will also raise a report the following cases below;
84
+
85
+ ```tsx
86
+ // Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx
19
87
 
20
- This package is published separately from the rest of EUI, as required by eslint. The code is not transpiled, so make sure to use `require()` statements rather than `import`, and once the code is updated run:
88
+ import React from 'react';
89
+ import { css } from '@emotion/css';
90
+ import { EuiText } from '@elastic/eui';
91
+
92
+ function MyComponent() {
93
+ return (
94
+ <EuiText css={css`color: '#dd4040' `}>You know, for search</EuiText>
95
+ )
96
+ }
97
+ ```
98
+
99
+ ```tsx
100
+ // Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx
101
+
102
+ import React from 'react';
103
+ import { EuiText } from '@elastic/eui';
104
+
105
+ function MyComponent() {
106
+ return (
107
+ <EuiText css={() => ({ color: '#dd4040' })}>You know, for search</EuiText>
108
+ )
109
+ }
110
+ ```
111
+
112
+ A special case is also covered for the `className` attribute, where the rule will also raise a report for the following case below;
113
+
114
+
115
+ ```tsx
116
+ // Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx
117
+
118
+ import React from 'react';
119
+ import { css } from '@emotion/css';
120
+ import { EuiText } from '@elastic/eui';
121
+
122
+ function MyComponent() {
123
+ return (
124
+ <EuiText className={css`color: '#dd4040'`}>You know, for search</EuiText>
125
+ )
126
+ }
127
+ ```
128
+
129
+ It's worth pointing out that although the examples provided are specific to EUI components, this rule applies to all JSX elements.
130
+
131
+ ### `@elastic/eui/prefer-css-attributes-for-eui-components`
132
+
133
+ This rule warns about the use of `style` attribute and encourages to replace it with `css` attribute. Using the `css` attribute ensures better integration with Emotion's styling capabilities.
134
+
135
+ #### Example
136
+
137
+ The following code:
138
+
139
+ ```jsx
140
+ <EuiCode style={{ color: '#dd4040' }}>This is a test</EuiCode>
141
+ ```
142
+
143
+ will raise an ESLint report and suggest replacing the `style` attribute with `css`:
144
+
145
+ ```jsx
146
+ <EuiCode css={{ color: '#dd4040' }}>This is a test</EuiCode>
147
+ ```
148
+
149
+ ## Testing
150
+
151
+ ### Against an existing package
152
+
153
+ To test the local changes to the plugin, you must:
154
+
155
+ 1. Run `yarn pack` in the directory.
156
+ 2. In your project's `package.json`, point `@elastic/eslint-plugin-eui` to `file:/path/to/package.tgz`.
157
+ 3. Install dependencies: `yarn kbn bootstrap --no-validate`.
158
+
159
+ ## Publishing
21
160
 
22
- 1. `npm version patch|minor|major`
23
- 2. commit version bump
24
- 3. `npm publish` in this directory
25
- 4. push the version bump upstream
161
+ Refer to the [wiki](../../wiki/eui-team-processes/releasing-versions.md) for instructions on how to release this package.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":""}
@@ -1,3 +1,9 @@
1
+ "use strict";
2
+
3
+ var _href_or_on_click = require("./rules/href_or_on_click");
4
+ var _no_restricted_eui_imports = require("./rules/no_restricted_eui_imports");
5
+ var _no_css_color = require("./rules/no_css_color");
6
+ var _prefer_css_attribute_for_eui_components = require("./rules/prefer_css_attribute_for_eui_components");
1
7
  /*
2
8
  * Licensed to Elasticsearch B.V. under one or more contributor
3
9
  * license agreements. See the NOTICE file distributed with
@@ -17,16 +23,23 @@
17
23
  * under the License.
18
24
  */
19
25
 
20
- module.exports = {
26
+ const config = {
21
27
  rules: {
22
- 'href-or-on-click': require('./rules/href_or_on_click'),
28
+ 'href-or-on-click': _href_or_on_click.HrefOnClick,
29
+ 'no-restricted-eui-imports': _no_restricted_eui_imports.NoRestrictedEuiImports,
30
+ 'no-css-color': _no_css_color.NoCssColor,
31
+ 'prefer-css-attributes-for-eui-components': _prefer_css_attribute_for_eui_components.PreferCSSAttributeForEuiComponents
23
32
  },
24
33
  configs: {
25
34
  recommended: {
26
35
  plugins: ['@elastic/eslint-plugin-eui'],
27
36
  rules: {
28
37
  '@elastic/eui/href-or-on-click': 'warn',
29
- },
30
- },
31
- },
38
+ '@elastic/eui/no-restricted-eui-imports': 'warn',
39
+ '@elastic/eui/no-css-color': 'warn',
40
+ '@elastic/eui/prefer-css-attributes-for-eui-components': 'warn'
41
+ }
42
+ }
43
+ }
32
44
  };
45
+ module.exports = config;
@@ -0,0 +1,3 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const HrefOnClick: ESLintUtils.RuleModule<"hrefOrOnClick", [], unknown, ESLintUtils.RuleListener>;
3
+ //# sourceMappingURL=href_or_on_click.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"href_or_on_click.d.ts","sourceRoot":"","sources":["../../../src/rules/href_or_on_click.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAY,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIjE,eAAO,MAAM,WAAW,gFA8CtB,CAAC"}
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.HrefOnClick = 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 componentNames = ['EuiButton', 'EuiButtonEmpty', 'EuiLink', 'EuiBadge'];
28
+ const HrefOnClick = exports.HrefOnClick = _utils.ESLintUtils.RuleCreator.withoutDocs({
29
+ create(context) {
30
+ return {
31
+ JSXOpeningElement(node) {
32
+ // Ensure node name is one of the valid component names
33
+ if (node.name.type !== 'JSXIdentifier' || !componentNames.includes(node.name.name)) {
34
+ return;
35
+ }
36
+
37
+ // Check if the node has both `href` and `onClick` attributes
38
+ const hasHref = node.attributes.some(attr => attr.type === 'JSXAttribute' && attr.name.name === 'href');
39
+ const hasOnClick = node.attributes.some(attr => attr.type === 'JSXAttribute' && attr.name.name === 'onClick');
40
+
41
+ // Report an issue if both attributes are present
42
+ if (hasHref && hasOnClick) {
43
+ context.report({
44
+ node,
45
+ messageId: 'hrefOrOnClick',
46
+ data: {
47
+ name: node.name.name
48
+ }
49
+ });
50
+ }
51
+ }
52
+ };
53
+ },
54
+ meta: {
55
+ type: 'problem',
56
+ docs: {
57
+ description: 'Discourage supplying both `href` and `onClick` to certain EUI components.'
58
+ },
59
+ schema: [],
60
+ messages: {
61
+ 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`.)'
62
+ }
63
+ },
64
+ defaultOptions: []
65
+ });
@@ -0,0 +1,3 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const NoCssColor: ESLintUtils.RuleModule<"noCssColor" | "noCssColorSpecific" | "noCSSColorSpecificDeclaredVariable", [], unknown, ESLintUtils.RuleListener>;
3
+ //# sourceMappingURL=no_css_color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no_css_color.d.ts","sourceRoot":"","sources":["../../../src/rules/no_css_color.ts"],"names":[],"mappings":"AAUA,OAAO,EAAY,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAyNjE,eAAO,MAAM,UAAU,2IAsRrB,CAAC"}
@@ -0,0 +1,329 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NoCssColor = void 0;
7
+ var _cssstyle = require("cssstyle");
8
+ var _utils = require("@typescript-eslint/utils");
9
+ var _resolve_member_expression_root = require("../utils/resolve_member_expression_root");
10
+ /*
11
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
12
+ * or more contributor license agreements. Licensed under the "Elastic License
13
+ * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
14
+ * Public License v 1"; you may not use this file except in compliance with, at
15
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
16
+ * License v3.0 only", or the "Server Side Public License, v 1".
17
+ */
18
+
19
+ /**
20
+ * @description List of superset css properties that can apply color to html box element elements and text nodes, leveraging the
21
+ * css style package allows us to directly singly check for these properties even if the actual declaration was written using the shorthand form
22
+ */
23
+ const propertiesSupportingCssColor = ['color', 'background', 'border'];
24
+
25
+ /**
26
+ * @description Builds off the existing color definition to match css declarations that can apply color to
27
+ * html elements and text nodes for string declarations
28
+ */
29
+ const htmlElementColorDeclarationRegex = RegExp(String.raw`(${propertiesSupportingCssColor.join('|')})`);
30
+ const checkPropertySpecifiesInvalidCSSColor = ([property, value]) => {
31
+ if (!property || !value) return false;
32
+ const style = new _cssstyle.CSSStyleDeclaration();
33
+
34
+ // @ts-expect-error the types for this packages specifies an index signature of number, alongside other valid CSS properties
35
+ style[property.trim()] = typeof value === 'string' ? value.trim() : value;
36
+ const anchor = propertiesSupportingCssColor.find(resolvedProperty => property.includes(resolvedProperty));
37
+ if (!anchor) return false;
38
+
39
+ // build the resolved color property to check if the value is a string after parsing the style declaration
40
+ const resolvedColorProperty = anchor === 'color' ? 'color' : anchor + 'Color';
41
+
42
+ // 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
43
+ // @ts-expect-error the types for this packages specifics an index signature of number, alongside other valid CSS properties
44
+ return Boolean(style[resolvedColorProperty]);
45
+ };
46
+
47
+ /**
48
+ * @description method to inspect values of interest found on an object
49
+ */
50
+ const raiseReportIfPropertyHasInvalidCssColor = (context, propertyNode, messageToReport) => {
51
+ let didReport = false;
52
+ if (propertyNode.key.type === 'Identifier' && !htmlElementColorDeclarationRegex.test(propertyNode.key.name)) {
53
+ return didReport;
54
+ }
55
+ if (propertyNode.value.type === 'Literal') {
56
+ if (didReport = checkPropertySpecifiesInvalidCSSColor([
57
+ // @ts-expect-error the key name is present in this scenario
58
+ propertyNode.key.name, propertyNode.value.value])) {
59
+ context.report(messageToReport);
60
+ }
61
+ } else if (propertyNode.value.type === 'Identifier') {
62
+ const identifierDeclaration = context.sourceCode.getScope(propertyNode).variables.find(variable => variable.name === propertyNode.value.name);
63
+ const identifierDeclarationInit = identifierDeclaration?.defs[0].node?.init;
64
+ if (identifierDeclarationInit?.type === 'Literal' && checkPropertySpecifiesInvalidCSSColor([
65
+ // @ts-expect-error the key name is present in this scenario
66
+ propertyNode.key.name, identifierDeclarationInit.value])) {
67
+ context.report({
68
+ loc: propertyNode.value.loc,
69
+ messageId: 'noCSSColorSpecificDeclaredVariable',
70
+ data: {
71
+ // @ts-expect-error the key name is always present else this code will not execute
72
+ property: String(propertyNode.key.name),
73
+ line: String(propertyNode.value.loc.start.line),
74
+ variableName: propertyNode.value.name
75
+ }
76
+ });
77
+ didReport = true;
78
+ }
79
+ } else if (propertyNode.value.type === 'MemberExpression') {
80
+ // @ts-expect-error we ignore the case where this node could be a private identifier
81
+ const MemberExpressionLeafName = propertyNode.value.property.name;
82
+ const memberExpressionRootName = (0, _resolve_member_expression_root.resolveMemberExpressionRoot)(propertyNode.value).name;
83
+ const expressionRootDeclaration = context.sourceCode.getScope(propertyNode).variables.find(variable => variable.name === memberExpressionRootName);
84
+ const expressionRootDeclarationInit = expressionRootDeclaration?.defs[0].node?.init;
85
+ if (expressionRootDeclarationInit?.type === 'ObjectExpression') {
86
+ expressionRootDeclarationInit.properties.forEach(property => {
87
+ // 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
88
+ if (property.type === 'Property' && property.key.type === 'Identifier' && property.key?.name === MemberExpressionLeafName) {
89
+ raiseReportIfPropertyHasInvalidCssColor(context, property, {
90
+ loc: propertyNode.value.loc,
91
+ messageId: 'noCSSColorSpecificDeclaredVariable',
92
+ data: {
93
+ // @ts-expect-error the key name is always present else this code will not execute
94
+ property: String(propertyNode.key.name),
95
+ line: String(propertyNode.value.loc.start.line),
96
+ variableName: memberExpressionRootName
97
+ }
98
+ });
99
+ }
100
+ });
101
+ } else if (expressionRootDeclarationInit?.type === 'CallExpression') {
102
+ // 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
103
+ }
104
+ }
105
+ return didReport;
106
+ };
107
+
108
+ /**
109
+ *
110
+ * @description style object declaration have a depth of 1, this function handles the properties of the object
111
+ */
112
+ const handleObjectProperties = (context, propertyParentNode, property, reportMessage) => {
113
+ if (property.type === 'Property') {
114
+ raiseReportIfPropertyHasInvalidCssColor(context, property, reportMessage);
115
+ } else if (property.type === 'SpreadElement') {
116
+ const spreadElementIdentifierName = property.argument.name;
117
+ const spreadElementDeclaration = context.sourceCode.getScope(propertyParentNode.value.expression).references.find(ref => ref.identifier.name === spreadElementIdentifierName)?.resolved;
118
+ if (!spreadElementDeclaration) {
119
+ return;
120
+ }
121
+ reportMessage = {
122
+ loc: propertyParentNode.loc,
123
+ messageId: 'noCSSColorSpecificDeclaredVariable',
124
+ data: {
125
+ // @ts-expect-error the key name is always present else this code will not execute
126
+ property: String(property.argument.name),
127
+ variableName: spreadElementIdentifierName,
128
+ line: String(property.loc.start.line)
129
+ }
130
+ };
131
+ const spreadElementDeclarationNode = 'init' in spreadElementDeclaration.defs[0].node ? spreadElementDeclaration.defs[0].node.init : undefined;
132
+
133
+ // evaluate only statically defined declarations, other possibilities like callExpressions in this context complicate things
134
+ if (spreadElementDeclarationNode?.type === 'ObjectExpression') {
135
+ spreadElementDeclarationNode.properties.forEach(spreadProperty => {
136
+ handleObjectProperties(context, propertyParentNode, spreadProperty, reportMessage);
137
+ });
138
+ }
139
+ }
140
+ };
141
+ const NoCssColor = exports.NoCssColor = _utils.ESLintUtils.RuleCreator.withoutDocs({
142
+ create(context) {
143
+ return {
144
+ // accounts for instances where declarations are created using the template tagged css function
145
+ TaggedTemplateExpression(node) {
146
+ if (node.tag.type !== 'Identifier' || node.tag.type === 'Identifier' && node.tag.name !== 'css') {
147
+ return;
148
+ }
149
+ for (let i = 0; i < node.quasi.quasis.length; i++) {
150
+ const declarationTemplateNode = node.quasi.quasis[i];
151
+ if (htmlElementColorDeclarationRegex.test(declarationTemplateNode.value.raw)) {
152
+ const cssText = declarationTemplateNode.value.raw.replace(/(\{|\}|\\n)/g, '').trim();
153
+ cssText.split(';').forEach(declaration => {
154
+ if (declaration.length > 0 && checkPropertySpecifiesInvalidCSSColor(declaration.split(':'))) {
155
+ context.report({
156
+ node: declarationTemplateNode,
157
+ messageId: 'noCssColor'
158
+ });
159
+ }
160
+ });
161
+ }
162
+ }
163
+ },
164
+ JSXAttribute(node) {
165
+ if (!(node.name.name === 'style' || node.name.name === 'css')) {
166
+ return;
167
+ }
168
+
169
+ /**
170
+ * @description Accounts for instances where a variable is used to define a style object
171
+ *
172
+ * @example
173
+ * const codeStyle = { color: '#dd4040' };
174
+ * <EuiCode style={codeStyle}>This is an example</EuiCode>
175
+ *
176
+ * @example
177
+ * const codeStyle = { color: '#dd4040' };
178
+ * <EuiCode css={codeStyle}>This is an example</EuiCode>
179
+ *
180
+ * @example
181
+ * const codeStyle = css({ color: '#dd4040' });
182
+ * <EuiCode css={codeStyle}>This is an example</EuiCode>
183
+ */
184
+ if (node.value?.type === 'JSXExpressionContainer' && node.value.expression.type === 'Identifier') {
185
+ const styleVariableName = node.value.expression.name;
186
+ const nodeScope = context.sourceCode.getScope(node.value.expression);
187
+ const variableDeclarationMatches = nodeScope.references.find(ref => ref.identifier.name === styleVariableName)?.resolved;
188
+ let variableInitializationNode;
189
+ if (variableInitializationNode = variableDeclarationMatches?.defs?.[0]?.node && 'init' in variableDeclarationMatches.defs[0].node && variableDeclarationMatches.defs[0].node.init) {
190
+ if (variableInitializationNode.type === 'ObjectExpression') {
191
+ variableInitializationNode.properties.forEach(property => {
192
+ handleObjectProperties(context, node, property, {
193
+ loc: property.loc,
194
+ messageId: 'noCSSColorSpecificDeclaredVariable',
195
+ data: {
196
+ property: property.type === 'SpreadElement' ? String(property.argument.name) : String(property.key.name),
197
+ variableName: styleVariableName,
198
+ line: String(property.loc.start.line)
199
+ }
200
+ });
201
+ });
202
+ } else if (variableInitializationNode.type === 'CallExpression' && variableInitializationNode.callee.name === 'css') {
203
+ const cssFunctionArgument = variableInitializationNode.arguments[0];
204
+ if (cssFunctionArgument.type === 'ObjectExpression') {
205
+ cssFunctionArgument.properties.forEach(property => {
206
+ handleObjectProperties(context, node, property, {
207
+ loc: node.loc,
208
+ messageId: 'noCSSColorSpecificDeclaredVariable',
209
+ data: {
210
+ property: property.type === 'SpreadElement' ? String(property.argument.name) : String(property.key.name),
211
+ variableName: styleVariableName,
212
+ line: String(property.loc.start.line)
213
+ }
214
+ });
215
+ });
216
+ }
217
+ }
218
+ }
219
+ return;
220
+ }
221
+
222
+ /**
223
+ *
224
+ * @description Accounts for instances where a style object is inlined in the JSX attribute
225
+ *
226
+ * @example
227
+ * <EuiCode style={{ color: '#dd4040' }}>This is an example</EuiCode>
228
+ *
229
+ * @example
230
+ * <EuiCode css={{ color: '#dd4040' }}>This is an example</EuiCode>
231
+ *
232
+ * @example
233
+ * const styleRules = { color: '#dd4040' };
234
+ * <EuiCode style={{ color: styleRules.color }}>This is an example</EuiCode>
235
+ *
236
+ * @example
237
+ * const styleRules = { color: '#dd4040' };
238
+ * <EuiCode css={{ color: styleRules.color }}>This is an example</EuiCode>
239
+ */
240
+ if (node.value?.type === 'JSXExpressionContainer' && node.value.expression.type === 'ObjectExpression') {
241
+ const declarationPropertiesNode = node.value.expression.properties;
242
+ declarationPropertiesNode?.forEach(property => {
243
+ handleObjectProperties(context, node, property, {
244
+ loc: property.loc,
245
+ messageId: 'noCssColorSpecific',
246
+ data: {
247
+ property: property.type === 'SpreadElement' ?
248
+ // @ts-expect-error the key name is always present else this code will not execute
249
+ String(property.argument.name) :
250
+ // @ts-expect-error the key name is always present else this code will not execute
251
+ String(property.key.name)
252
+ }
253
+ });
254
+ });
255
+ return;
256
+ }
257
+ if (node.name.name === 'css' && node.value?.type === 'JSXExpressionContainer') {
258
+ /**
259
+ * @example
260
+ * <EuiCode css={`{ color: '#dd4040' }`}>This is an example</EuiCode>
261
+ */
262
+ if (node.value.expression.type === 'TemplateLiteral') {
263
+ for (let i = 0; i < node.value.expression.quasis.length; i++) {
264
+ const declarationTemplateNode = node.value.expression.quasis[i];
265
+ if (htmlElementColorDeclarationRegex.test(declarationTemplateNode.value.raw)) {
266
+ const cssText = declarationTemplateNode.value.raw.replace(/(\{|\}|\\n)/g, '').trim();
267
+ cssText.split(';').forEach(declaration => {
268
+ if (declaration.length > 0 && checkPropertySpecifiesInvalidCSSColor(declaration.split(':'))) {
269
+ context.report({
270
+ node: declarationTemplateNode,
271
+ messageId: 'noCssColor'
272
+ });
273
+ }
274
+ });
275
+ }
276
+ }
277
+ }
278
+
279
+ /**
280
+ * @example
281
+ * <EuiCode css={() => ({ color: '#dd4040' })}>This is an example</EuiCode>
282
+ */
283
+ if (node.value.expression.type === 'FunctionExpression' || node.value.expression.type === 'ArrowFunctionExpression') {
284
+ let declarationPropertiesNode = [];
285
+ if (node.value.expression.body.type === 'ObjectExpression') {
286
+ declarationPropertiesNode = node.value.expression.body.properties;
287
+ }
288
+ if (node.value.expression.body.type === 'BlockStatement') {
289
+ const functionReturnStatementNode = node.value.expression.body.body?.find(_node => {
290
+ return _node.type === 'ReturnStatement';
291
+ });
292
+ if (!functionReturnStatementNode) {
293
+ return;
294
+ }
295
+ declarationPropertiesNode = functionReturnStatementNode.argument?.properties.filter(property => property.type === 'Property');
296
+ }
297
+ if (!declarationPropertiesNode.length) {
298
+ return;
299
+ }
300
+ declarationPropertiesNode.forEach(property => {
301
+ handleObjectProperties(context, node, property, {
302
+ loc: property.loc,
303
+ messageId: 'noCssColorSpecific',
304
+ data: {
305
+ // @ts-expect-error the key name is always present else this code will not execute
306
+ property: property.key.name
307
+ }
308
+ });
309
+ });
310
+ return;
311
+ }
312
+ }
313
+ }
314
+ };
315
+ },
316
+ meta: {
317
+ type: 'suggestion',
318
+ docs: {
319
+ description: 'Use color definitions from eui theme as opposed to CSS color values'
320
+ },
321
+ messages: {
322
+ noCSSColorSpecificDeclaredVariable: 'Avoid using a literal CSS color value for "{{property}}", use an EUI theme color instead in declared variable {{variableName}} on line {{line}}',
323
+ noCssColorSpecific: 'Avoid using a literal CSS color value for "{{property}}", use an EUI theme color instead',
324
+ noCssColor: 'Avoid using a literal CSS color value, use an EUI theme color instead'
325
+ },
326
+ schema: []
327
+ },
328
+ defaultOptions: []
329
+ });
@@ -0,0 +1,8 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type Option = {
3
+ patterns: string[];
4
+ message: string;
5
+ };
6
+ export declare const NoRestrictedEuiImports: TSESLint.RuleModule<never, Readonly<Option>[]>;
7
+ export {};
8
+ //# sourceMappingURL=no_restricted_eui_imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no_restricted_eui_imports.d.ts","sourceRoot":"","sources":["../../../src/rules/no_restricted_eui_imports.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAY,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAE9D,KAAK,MAAM,GAAG;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAUF,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CA4CjF,CAAC"}