From 7e46d2e756965c254a5f4d821d23ab44494ace97 Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak <32842468+weronikaolejniczak@users.noreply.github.com> Date: Wed, 2 Apr 2025 14:06:17 +0200 Subject: [PATCH] chore: update eslint-plugin-eui to 0.1.1 (#210082) ## Summary Bring in the changes from https://github.com/elastic/eui/pull/8304, specifically ESLint rules: - `no-restricted-eui-imports` - `no-css-color` (migrated from `@kbn/eslint-plugin-css`) - `prefer-css-attribute-for-eui-components` (migrated from `@kbn/eslint-plugin-css`) Relates to https://github.com/elastic/eui/issues/8201, https://github.com/elastic/eui-private/issues/275 ## QA ### Instructions 1. Checkout this branch: `gh pr checkout 210082`. 2. Reinstall dependencies: `yarn kbn bootstrap`. 3. See output of ESLint. There should be no errors. 4. Test below cases. ### Test cases #### `no-restricted-eui-imports` Example files: - JSON imports: `src/platform/packages/shared/kbn-ui-theme/src/theme.ts` - `@kbn/ui-theme`: `src/platform/plugins/private/vis_types/vega/public/data_model/utils.ts` #### `no-css-color` Example file: `src/platform/plugins/shared/kibana_react/public/page_template/no_data_page/no_data_card/elastic_agent_card.tsx:50` ![Screenshot 2025-02-26 at 15 01 53](https://github.com/user-attachments/assets/ec6f49bd-5832-4d1c-9cfd-74c40ad5498e) #### `prefer-css-attribute-for-eui-components` Example file: `x-pack/examples/alerting_example/public/alert_types/always_firing.tsx:166` --- .eslintrc.js | 2 +- .github/CODEOWNERS | 1 - package.json | 3 +- packages/kbn-eslint-config/.eslintrc.js | 33 +- packages/kbn-eslint-plugin-css/README.mdx | 129 ----- packages/kbn-eslint-plugin-css/index.ts | 20 - packages/kbn-eslint-plugin-css/jest.config.js | 14 - packages/kbn-eslint-plugin-css/kibana.jsonc | 6 - packages/kbn-eslint-plugin-css/package.json | 6 - .../src/rules/no_css_color.test.ts | 266 ---------- .../src/rules/no_css_color.ts | 453 ------------------ ...r_css_attribute_for_eui_components.test.ts | 85 ---- ...prefer_css_attribute_for_eui_components.ts | 62 --- packages/kbn-eslint-plugin-css/tsconfig.json | 11 - .../packages/shared/kbn-ui-theme/src/theme.ts | 4 - tsconfig.base.json | 2 - .../attack/attack_chain/tactic/index.tsx | 4 +- yarn.lock | 12 +- 18 files changed, 28 insertions(+), 1085 deletions(-) delete mode 100644 packages/kbn-eslint-plugin-css/README.mdx delete mode 100644 packages/kbn-eslint-plugin-css/index.ts delete mode 100644 packages/kbn-eslint-plugin-css/jest.config.js delete mode 100644 packages/kbn-eslint-plugin-css/kibana.jsonc delete mode 100644 packages/kbn-eslint-plugin-css/package.json delete mode 100644 packages/kbn-eslint-plugin-css/src/rules/no_css_color.test.ts delete mode 100644 packages/kbn-eslint-plugin-css/src/rules/no_css_color.ts delete mode 100644 packages/kbn-eslint-plugin-css/src/rules/prefer_css_attribute_for_eui_components.test.ts delete mode 100644 packages/kbn-eslint-plugin-css/src/rules/prefer_css_attribute_for_eui_components.ts delete mode 100644 packages/kbn-eslint-plugin-css/tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js index c70ec5d274a6..a9ec2e846c45 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -280,7 +280,7 @@ const RESTRICTED_IMPORTS = [ module.exports = { root: true, - extends: ['plugin:@elastic/eui/recommended', '@kbn/eslint-config'], + extends: ['@kbn/eslint-config'], overrides: [ /** diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3a90aecf7ac3..2e34b95a7a7f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -55,7 +55,6 @@ packages/kbn-dependency-ownership @elastic/kibana-security packages/kbn-dependency-usage @elastic/kibana-security packages/kbn-docs-utils @elastic/kibana-operations packages/kbn-eslint-config @elastic/kibana-operations -packages/kbn-eslint-plugin-css @elastic/appex-sharedux packages/kbn-eslint-plugin-disable @elastic/kibana-operations packages/kbn-eslint-plugin-eslint @elastic/kibana-operations packages/kbn-eslint-plugin-eui-a11y @elastic/obs-ux-infra_services-team diff --git a/package.json b/package.json index 9ae7d0f327e2..dc97960cfb90 100644 --- a/package.json +++ b/package.json @@ -1372,7 +1372,7 @@ "@cypress/debugging-proxy": "2.0.1", "@cypress/grep": "^4.0.1", "@cypress/webpack-preprocessor": "^6.0.2", - "@elastic/eslint-plugin-eui": "0.0.2", + "@elastic/eslint-plugin-eui": "0.1.1", "@elastic/makelogs": "^6.1.1", "@elastic/synthetics": "^1.18.0", "@emotion/babel-preset-css-prop": "^11.11.0", @@ -1475,7 +1475,6 @@ "@kbn/es": "link:src/platform/packages/shared/kbn-es", "@kbn/es-archiver": "link:src/platform/packages/shared/kbn-es-archiver", "@kbn/eslint-config": "link:packages/kbn-eslint-config", - "@kbn/eslint-plugin-css": "link:packages/kbn-eslint-plugin-css", "@kbn/eslint-plugin-disable": "link:packages/kbn-eslint-plugin-disable", "@kbn/eslint-plugin-eslint": "link:packages/kbn-eslint-plugin-eslint", "@kbn/eslint-plugin-eui-a11y": "link:packages/kbn-eslint-plugin-eui-a11y", diff --git a/packages/kbn-eslint-config/.eslintrc.js b/packages/kbn-eslint-config/.eslintrc.js index f929bdb8a613..2d215f794abf 100644 --- a/packages/kbn-eslint-config/.eslintrc.js +++ b/packages/kbn-eslint-config/.eslintrc.js @@ -20,7 +20,13 @@ const { USES_STYLED_COMPONENTS } = require('@kbn/babel-preset/styled_components_files'); module.exports = { - extends: ['./javascript.js', './typescript.js', './jest.js', './react.js'], + extends: [ + './javascript.js', + './typescript.js', + './jest.js', + './react.js', + 'plugin:@elastic/eui/recommended', + ], plugins: [ '@kbn/eslint-plugin-disable', @@ -29,7 +35,7 @@ module.exports = { '@kbn/eslint-plugin-telemetry', '@kbn/eslint-plugin-i18n', '@kbn/eslint-plugin-eui-a11y', - '@kbn/eslint-plugin-css', + '@elastic/eui', 'eslint-plugin-depend', 'prettier', ], @@ -129,16 +135,6 @@ module.exports = { exclude: USES_STYLED_COMPONENTS, disallowedMessage: `Prefer using @emotion/react instead. To use styled-components, ensure you plugin is enabled in packages/kbn-babel-preset/styled_components_files.js.`, }, - ...[ - '@elastic/eui/dist/eui_theme_amsterdam_light.json', - '@elastic/eui/dist/eui_theme_amsterdam_dark.json', - '@elastic/eui/dist/eui_theme_borealis_light.json', - '@elastic/eui/dist/eui_theme_borealis_dark.json', - ].map((from) => ({ - from, - to: false, - disallowedMessage: `Use "@kbn/ui-theme" to access theme vars.`, - })), { from: '@kbn/test/jest', to: '@kbn/test-jest-helpers', @@ -335,9 +331,20 @@ module.exports = { '@kbn/imports/no_boundary_crossing': 'error', '@kbn/imports/no_group_crossing_manifests': 'error', '@kbn/imports/no_group_crossing_imports': 'error', - '@kbn/css/no_css_color': 'warn', 'no-new-func': 'error', 'no-implied-eval': 'error', 'no-prototype-builtins': 'error', + + /** + * EUI Team rules + */ + + '@elastic/eui/no-restricted-eui-imports': [ + 'warn', + { + patterns: ['@kbn/ui-theme'], + message: 'For client-side, please use `useEuiTheme` instead.', + }, + ], }, }; diff --git a/packages/kbn-eslint-plugin-css/README.mdx b/packages/kbn-eslint-plugin-css/README.mdx deleted file mode 100644 index 1e121657bc57..000000000000 --- a/packages/kbn-eslint-plugin-css/README.mdx +++ /dev/null @@ -1,129 +0,0 @@ ---- -id: kibSharedUXEslintPluginCSS -slug: /kibana-dev-docs/shared-ux/packages/kbn-eslint-plugin-css -title: '@kbn/eslint-plugin-design-tokens' -description: Custom ESLint rules to guardrails for using eui in the Kibana repository -date: 2024-11-19 -tags: ['kibana', 'dev', 'contributor', 'shared_ux', 'eslint', 'eui'] ---- - -# Summary - -`@kbn/eslint-plugin-css` is an ESLint plugin providing custom ESLint rules to help setup guardrails for using eui in the Kibana repo especially around styling. - -The aim of this package is to help engineers to modify EUI components in a much complaint way. - -If a rule does not behave as you expect or you have an idea of how these rules can be improved, please reach out to the Shared UX team. - -# Rules - -## `@kbn/css/no_css_color` - -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. - -This rule kicks in on the following JSXAttributes; `style`, `className` and `css` and supports various approaches to providing styling declarations. - -### Example - -The following code: - -``` -// Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx - -import React from 'react'; -import { EuiText } from '@elastic/eui'; - -function MyComponent() { - return ( - You know, for search - ) -} -``` - -``` -// Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx - -import React from 'react'; -import { EuiText } from '@elastic/eui'; - -function MyComponent() { - - const style = { - color: 'red' - } - - return ( - You know, for search - ) -} -``` - -``` -// Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx - -import React from 'react'; -import { EuiText } from '@elastic/eui'; - -function MyComponent() { - const colorValue = '#dd4040'; - - return ( - You know, for search - ) -} -``` - -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 -will also match for when the attribute in question is `css`. The `css` attribute will also raise a report the following cases below; - -``` -// Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx - -import React from 'react'; -import { css } from '@emotion/css'; -import { EuiText } from '@elastic/eui'; - -function MyComponent() { - return ( - You know, for search - ) -} -``` - -``` -// Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx - -import React from 'react'; -import { EuiText } from '@elastic/eui'; - -function MyComponent() { - return ( - ({ color: '#dd4040' })}>You know, for search - ) -} -``` - -A special case is also covered for the `className` attribute, where the rule will also raise a report for the following case below; - - -``` -// Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx - -import React from 'react'; -import { css } from '@emotion/css'; -import { EuiText } from '@elastic/eui'; - -function MyComponent() { - return ( - You know, for search - ) -} -``` - -it's worth pointing out that although the examples provided are specific to EUI components, this rule applies to all JSX elements. - -## `@kbn/css/prefer_css_attributes_for_eui_components` - -This rule warns engineers to use the `css` attribute for EUI components instead of the `style` attribute. - diff --git a/packages/kbn-eslint-plugin-css/index.ts b/packages/kbn-eslint-plugin-css/index.ts deleted file mode 100644 index 9ea4bcc67619..000000000000 --- a/packages/kbn-eslint-plugin-css/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { NoCssColor } from './src/rules/no_css_color'; -import { PreferCSSAttributeForEuiComponents } from './src/rules/prefer_css_attribute_for_eui_components'; - -/** - * Custom ESLint rules, included as `'@kbn/eslint-plugin-design-tokens'` in the kibana eslint config - * @internal - */ -export const rules = { - no_css_color: NoCssColor, - prefer_css_attributes_for_eui_components: PreferCSSAttributeForEuiComponents, -}; diff --git a/packages/kbn-eslint-plugin-css/jest.config.js b/packages/kbn-eslint-plugin-css/jest.config.js deleted file mode 100644 index c8ae20237eae..000000000000 --- a/packages/kbn-eslint-plugin-css/jest.config.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../..', - roots: ['/packages/kbn-eslint-plugin-css'], -}; diff --git a/packages/kbn-eslint-plugin-css/kibana.jsonc b/packages/kbn-eslint-plugin-css/kibana.jsonc deleted file mode 100644 index 3ee8bff8736f..000000000000 --- a/packages/kbn-eslint-plugin-css/kibana.jsonc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/eslint-plugin-css", - "devOnly": true, - "owner": "@elastic/appex-sharedux" -} diff --git a/packages/kbn-eslint-plugin-css/package.json b/packages/kbn-eslint-plugin-css/package.json deleted file mode 100644 index c811f06f27cb..000000000000 --- a/packages/kbn-eslint-plugin-css/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@kbn/eslint-plugin-css", - "version": "1.0.0", - "private": true, - "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0" -} diff --git a/packages/kbn-eslint-plugin-css/src/rules/no_css_color.test.ts b/packages/kbn-eslint-plugin-css/src/rules/no_css_color.test.ts deleted file mode 100644 index d5323e7423ce..000000000000 --- a/packages/kbn-eslint-plugin-css/src/rules/no_css_color.test.ts +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { RuleTester } from 'eslint'; -import { NoCssColor } from './no_css_color'; - -const tsTester = [ - '@typescript-eslint/parser', - new RuleTester({ - parser: require.resolve('@typescript-eslint/parser'), - parserOptions: { - sourceType: 'module', - ecmaVersion: 2018, - ecmaFeatures: { - jsx: true, - }, - }, - }), -] as const; - -const babelTester = [ - '@babel/eslint-parser', - new RuleTester({ - parser: require.resolve('@babel/eslint-parser'), - parserOptions: { - sourceType: 'module', - ecmaVersion: 2018, - requireConfigFile: false, - babelOptions: { - presets: ['@kbn/babel-preset/node_preset'], - }, - }, - }), -] as const; - -const invalid: RuleTester.InvalidTestCase[] = [ - { - name: 'Raises an error when a CSS color is used in a JSX style attribute', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - - function TestComponent() { - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCssColorSpecific' }], - }, - { - name: 'Raises an error when a CSS color references a string variable that is passed to style prop of a JSX element', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - - function TestComponent() { - const codeColor = '#dd4040'; - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCSSColorSpecificDeclaredVariable' }], - }, - { - name: 'Raises an error when a CSS color is used in an object variable that is passed to style prop of a JSX element', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - - function TestComponent() { - const codeStyle = { color: '#dd4040' }; - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCSSColorSpecificDeclaredVariable' }], - }, - { - name: 'Raises an error when an object property that is a literal CSS color is used for the background property in a JSX style attribute', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - - function TestComponent() { - const baseStyle = { background: 'rgb(255, 255, 255)' }; - - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCSSColorSpecificDeclaredVariable' }], - }, - { - name: 'Raises an error when a CSS color is used in a variable that is spread into another variable that is passed to style prop of a JSX element', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - - function TestComponent() { - const baseStyle = { background: 'rgb(255, 255, 255)' }; - const codeStyle = { margin: '5px', ...baseStyle }; - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCSSColorSpecificDeclaredVariable' }], - }, - { - name: 'Raises an error when a CSS color is used for the background property in a JSX style attribute', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - - function TestComponent() { - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCssColorSpecific' }], - }, - { - name: 'Raises an error when a CSS color for the color property is used in a JSX css attribute for EuiComponents', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - - function TestComponent() { - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCssColorSpecific' }], - }, - { - name: 'Raises an error when a CSS color for the color property is used in with the tagged template css function', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import { css } from '@emotion/css'; - - const codeColor = css\` color: #dd4040; \`; - `, - errors: [{ messageId: 'noCssColor' }], - }, - { - name: 'Raises an error when a CSS color for the color property is used in a JSX css attribute for EuiComponents with the css template function', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - import { css } from '@emotion/css'; - - function TestComponent() { - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCssColor' }], - }, - { - name: 'Raises an error when a CSS color for the color property is used in a JSX className attribute for EuiComponents with the css template function defined outside the scope of the component', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - import { css } from '@emotion/css'; - - const codeCss = css({ - color: '#dd4040', - }) - - function TestComponent() { - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCSSColorSpecificDeclaredVariable' }], - }, - { - name: 'Raises an error when a CSS color for the color property is used in a JSX className attribute for EuiComponents with the css template function defined outside the scope of the component', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - import { css } from '@emotion/css'; - - const codeCss = css\` color: #dd4040; \` - - function TestComponent() { - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCssColor' }], - }, - { - name: 'Raises an error when a CSS color for the color property is used in a JSX css attribute for EuiComponents with an arrow function', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - - function TestComponent() { - return ( - ({ color: '#dd4040' })}>This is a test - ) - }`, - errors: [{ messageId: 'noCssColorSpecific' }], - }, - { - name: 'Raises an error when a CSS color for the color property is used in a JSX css attribute for EuiComponents with a regular function', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - - function TestComponent() { - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCssColorSpecific' }], - }, - { - name: 'Raises an error when a CSS color for the color property is used in a JSX className attribute for EuiComponents with the css template function', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - import { css } from '@emotion/css'; - - function TestComponent() { - return ( - This is a test - ) - }`, - errors: [{ messageId: 'noCssColor' }], - }, -]; - -const valid: RuleTester.ValidTestCase[] = [ - { - name: 'Does not raise an error when a CSS color is not used in a JSX css prop attribute', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - import { EuiCode } from '@elastic/eui'; - import { css } from '@emotion/react'; - function TestComponent() { - return ( - This is a test - ) - }`, - }, -]; - -for (const [name, tester] of [tsTester, babelTester]) { - describe(name, () => { - tester.run('@kbn/no_css_color', NoCssColor, { - valid, - invalid, - }); - }); -} diff --git a/packages/kbn-eslint-plugin-css/src/rules/no_css_color.ts b/packages/kbn-eslint-plugin-css/src/rules/no_css_color.ts deleted file mode 100644 index fb73fe53fda0..000000000000 --- a/packages/kbn-eslint-plugin-css/src/rules/no_css_color.ts +++ /dev/null @@ -1,453 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { Rule } from 'eslint'; -import { CSSStyleDeclaration } from 'cssstyle'; -import type { TSESTree } from '@typescript-eslint/typescript-estree'; - -/** - * @description List of superset css properties that can apply color to html box element elements and text nodes, leveraging the - * css style package allows us to directly singly check for these properties even if the actual declaration was written using the shorthand form - */ -const propertiesSupportingCssColor = ['color', 'background', 'border']; - -/** - * @description Builds off the existing color definition to match css declarations that can apply color to - * html elements and text nodes for string declarations - */ -const htmlElementColorDeclarationRegex = RegExp( - String.raw`(${propertiesSupportingCssColor.join('|')})` -); - -const checkPropertySpecifiesInvalidCSSColor = ([property, value]: string[]) => { - if (!property || !value) return false; - - const style = new CSSStyleDeclaration(); - - // @ts-ignore the types for this packages specifies an index signature of number, alongside other valid CSS properties - style[property.trim()] = typeof value === 'string' ? value.trim() : value; - - const anchor = propertiesSupportingCssColor.find((resolvedProperty) => - property.includes(resolvedProperty) - ); - - if (!anchor) return false; - - // build the resolved color property to check if the value is a string after parsing the style declaration - const resolvedColorProperty = anchor === 'color' ? 'color' : anchor + 'Color'; - - // 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 - // @ts-ignore the types for this packages specifics an index signature of number, alongside other valid CSS properties - return Boolean(style[resolvedColorProperty]); -}; - -const resolveMemberExpressionRoot = (node: TSESTree.MemberExpression): TSESTree.Identifier => { - if (node.object.type === 'MemberExpression') { - return resolveMemberExpressionRoot(node.object); - } - - return node.object as TSESTree.Identifier; -}; - -/** - * @description method to inspect values of interest found on an object - */ -const raiseReportIfPropertyHasInvalidCssColor = ( - context: Rule.RuleContext, - propertyNode: TSESTree.Property, - messageToReport: Rule.ReportDescriptor -) => { - let didReport = false; - - if ( - propertyNode.key.type === 'Identifier' && - !htmlElementColorDeclarationRegex.test(propertyNode.key.name) - ) { - return didReport; - } - - if (propertyNode.value.type === 'Literal') { - if ( - (didReport = checkPropertySpecifiesInvalidCSSColor([ - // @ts-expect-error the key name is present in this scenario - propertyNode.key.name, - propertyNode.value.value, - ])) - ) { - context.report(messageToReport); - } - } else if (propertyNode.value.type === 'Identifier') { - const identifierDeclaration = context.sourceCode - // @ts-expect-error - .getScope(propertyNode) - .variables.find( - (variable) => variable.name === (propertyNode.value as TSESTree.Identifier).name! - ); - - if ( - identifierDeclaration?.defs[0].node.init?.type === 'Literal' && - checkPropertySpecifiesInvalidCSSColor([ - // @ts-expect-error the key name is present in this scenario - propertyNode.key.name, - (identifierDeclaration.defs[0].node.init as TSESTree.Literal).value as string, - ]) - ) { - context.report({ - loc: propertyNode.value.loc, - messageId: 'noCSSColorSpecificDeclaredVariable', - data: { - // @ts-expect-error the key name is always present else this code will not execute - property: String(propertyNode.key.name), - line: String(propertyNode.value.loc.start.line), - variableName: propertyNode.value.name, - }, - }); - - didReport = true; - } - } else if (propertyNode.value.type === 'MemberExpression') { - // @ts-expect-error we ignore the case where this node could be a private identifier - const MemberExpressionLeafName = propertyNode.value.property.name; - const memberExpressionRootName = resolveMemberExpressionRoot(propertyNode.value).name; - - const expressionRootDeclaration = context.sourceCode - // @ts-expect-error - .getScope(propertyNode) - .variables.find((variable) => variable.name === memberExpressionRootName); - - const expressionRootDeclarationInit = expressionRootDeclaration?.defs[0].node.init; - - if (expressionRootDeclarationInit?.type === 'ObjectExpression') { - (expressionRootDeclarationInit as TSESTree.ObjectExpression).properties.forEach( - (property) => { - // 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 - if ( - property.type === 'Property' && - property.key.type === 'Identifier' && - property.key?.name === MemberExpressionLeafName - ) { - raiseReportIfPropertyHasInvalidCssColor(context, property, { - loc: propertyNode.value.loc, - messageId: 'noCSSColorSpecificDeclaredVariable', - data: { - // @ts-expect-error the key name is always present else this code will not execute - property: String(propertyNode.key.name), - line: String(propertyNode.value.loc.start.line), - variableName: memberExpressionRootName, - }, - }); - } - } - ); - } else if (expressionRootDeclarationInit?.type === 'CallExpression') { - // 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 - } - } - - return didReport; -}; - -/** - * - * @description style object declaration have a depth of 1, this function handles the properties of the object - */ -const handleObjectProperties = ( - context: Rule.RuleContext, - propertyParentNode: TSESTree.JSXAttribute, - property: TSESTree.ObjectLiteralElement, - reportMessage: Rule.ReportDescriptor -) => { - if (property.type === 'Property') { - raiseReportIfPropertyHasInvalidCssColor(context, property, reportMessage); - } else if (property.type === 'SpreadElement') { - const spreadElementIdentifierName = (property.argument as TSESTree.Identifier).name; - - const spreadElementDeclaration = context.sourceCode - // @ts-expect-error - .getScope(propertyParentNode!.value.expression!) - .references.find((ref) => ref.identifier.name === spreadElementIdentifierName)?.resolved; - - if (!spreadElementDeclaration) { - return; - } - - reportMessage = { - loc: propertyParentNode.loc, - messageId: 'noCSSColorSpecificDeclaredVariable', - data: { - // @ts-expect-error the key name is always present else this code will not execute - property: String(property.argument.name), - variableName: spreadElementIdentifierName, - line: String(property.loc.start.line), - }, - }; - - const spreadElementDeclarationNode = spreadElementDeclaration.defs[0].node.init; - - // evaluate only statically defined declarations, other possibilities like callExpressions in this context complicate things - if (spreadElementDeclarationNode?.type === 'ObjectExpression') { - (spreadElementDeclarationNode as TSESTree.ObjectExpression).properties.forEach( - (spreadProperty) => { - handleObjectProperties(context, propertyParentNode, spreadProperty, reportMessage); - } - ); - } - } -}; - -export const NoCssColor: Rule.RuleModule = { - meta: { - type: 'suggestion', - docs: { - description: 'Use color definitions from eui theme as opposed to CSS color values', - category: 'Best Practices', - recommended: true, - url: 'https://eui.elastic.co/#/theming/colors/values', - }, - messages: { - noCSSColorSpecificDeclaredVariable: - 'Avoid using a literal CSS color value for "{{property}}", use an EUI theme color instead in declared variable {{variableName}} on line {{line}}', - noCssColorSpecific: - 'Avoid using a literal CSS color value for "{{property}}", use an EUI theme color instead', - noCssColor: 'Avoid using a literal CSS color value, use an EUI theme color instead', - }, - schema: [], - }, - create(context) { - return { - // accounts for instances where declarations are created using the template tagged css function - TaggedTemplateExpression(node) { - if ( - node.tag.type !== 'Identifier' || - (node.tag.type === 'Identifier' && node.tag.name !== 'css') - ) { - return; - } - - for (let i = 0; i < node.quasi.quasis.length; i++) { - const declarationTemplateNode = node.quasi.quasis[i]; - - if (htmlElementColorDeclarationRegex.test(declarationTemplateNode.value.raw)) { - const cssText = declarationTemplateNode.value.raw.replace(/(\{|\}|\\n)/g, '').trim(); - - cssText.split(';').forEach((declaration) => { - if ( - declaration.length > 0 && - checkPropertySpecifiesInvalidCSSColor(declaration.split(':')) - ) { - context.report({ - node: declarationTemplateNode, - messageId: 'noCssColor', - }); - } - }); - } - } - }, - JSXAttribute(node: TSESTree.JSXAttribute) { - if (!(node.name.name === 'style' || node.name.name === 'css')) { - return; - } - - /** - * @description Accounts for instances where a variable is used to define a style object - * - * @example - * const codeStyle = { color: '#dd4040' }; - * This is an example - * - * @example - * const codeStyle = { color: '#dd4040' }; - * This is an example - * - * @example - * const codeStyle = css({ color: '#dd4040' }); - * This is an example - */ - if ( - node.value?.type === 'JSXExpressionContainer' && - node.value.expression.type === 'Identifier' - ) { - const styleVariableName = node.value.expression.name; - - const nodeScope = context.sourceCode.getScope(node.value.expression); - - const variableDeclarationMatches = nodeScope.references.find( - (ref) => ref.identifier.name === styleVariableName - )?.resolved; - - let variableInitializationNode; - - if ((variableInitializationNode = variableDeclarationMatches?.defs?.[0]?.node?.init)) { - if (variableInitializationNode.type === 'ObjectExpression') { - // @ts-ignore - variableInitializationNode.properties.forEach((property) => { - handleObjectProperties(context, node, property, { - loc: property.loc, - messageId: 'noCSSColorSpecificDeclaredVariable', - data: { - property: - property.type === 'SpreadElement' - ? String(property.argument.name) - : String(property.key.name), - variableName: styleVariableName, - line: String(property.loc.start.line), - }, - }); - }); - } else if ( - variableInitializationNode.type === 'CallExpression' && - variableInitializationNode.callee.name === 'css' - ) { - const cssFunctionArgument = variableInitializationNode.arguments[0]; - - if (cssFunctionArgument.type === 'ObjectExpression') { - // @ts-ignore - cssFunctionArgument.properties.forEach((property) => { - handleObjectProperties(context, node, property, { - loc: node.loc, - messageId: 'noCSSColorSpecificDeclaredVariable', - data: { - property: - property.type === 'SpreadElement' - ? String(property.argument.name) - : String(property.key.name), - variableName: styleVariableName, - line: String(property.loc.start.line), - }, - }); - }); - } - } - } - - return; - } - - /** - * - * @description Accounts for instances where a style object is inlined in the JSX attribute - * - * @example - * This is an example - * - * @example - * This is an example - * - * @example - * const styleRules = { color: '#dd4040' }; - * This is an example - * - * @example - * const styleRules = { color: '#dd4040' }; - * This is an example - */ - if ( - node.value?.type === 'JSXExpressionContainer' && - node.value.expression.type === 'ObjectExpression' - ) { - const declarationPropertiesNode = node.value.expression.properties; - - declarationPropertiesNode?.forEach((property) => { - handleObjectProperties(context, node, property, { - loc: property.loc, - messageId: 'noCssColorSpecific', - data: { - property: - property.type === 'SpreadElement' - ? // @ts-expect-error the key name is always present else this code will not execute - String(property.argument.name) - : // @ts-expect-error the key name is always present else this code will not execute - String(property.key.name), - }, - }); - }); - - return; - } - - if (node.name.name === 'css' && node.value?.type === 'JSXExpressionContainer') { - /** - * @example - * This is an example - */ - if (node.value.expression.type === 'TemplateLiteral') { - for (let i = 0; i < node.value.expression.quasis.length; i++) { - const declarationTemplateNode = node.value.expression.quasis[i]; - - if (htmlElementColorDeclarationRegex.test(declarationTemplateNode.value.raw)) { - const cssText = declarationTemplateNode.value.raw - .replace(/(\{|\}|\\n)/g, '') - .trim(); - - cssText.split(';').forEach((declaration) => { - if ( - declaration.length > 0 && - checkPropertySpecifiesInvalidCSSColor(declaration.split(':')) - ) { - context.report({ - node: declarationTemplateNode, - messageId: 'noCssColor', - }); - } - }); - } - } - } - - /** - * @example - * ({ color: '#dd4040' })}>This is an example - */ - if ( - node.value.expression.type === 'FunctionExpression' || - node.value.expression.type === 'ArrowFunctionExpression' - ) { - let declarationPropertiesNode: TSESTree.Property[] = []; - - if (node.value.expression.body.type === 'ObjectExpression') { - // @ts-expect-error - declarationPropertiesNode = node.value.expression.body.properties; - } - - if (node.value.expression.body.type === 'BlockStatement') { - const functionReturnStatementNode = node.value.expression.body.body?.find((_node) => { - return _node.type === 'ReturnStatement'; - }); - - if (!functionReturnStatementNode) { - return; - } - - declarationPropertiesNode = // @ts-expect-error - (functionReturnStatementNode as TSESTree.ReturnStatement).argument?.properties; - } - - if (!declarationPropertiesNode.length) { - return; - } - - declarationPropertiesNode.forEach((property) => { - handleObjectProperties(context, node, property, { - loc: property.loc, - messageId: 'noCssColorSpecific', - data: { - // @ts-expect-error the key name is always present else this code will not execute - property: property.key.name, - }, - }); - }); - - return; - } - } - }, - }; - }, -}; diff --git a/packages/kbn-eslint-plugin-css/src/rules/prefer_css_attribute_for_eui_components.test.ts b/packages/kbn-eslint-plugin-css/src/rules/prefer_css_attribute_for_eui_components.test.ts deleted file mode 100644 index f1534ec5c861..000000000000 --- a/packages/kbn-eslint-plugin-css/src/rules/prefer_css_attribute_for_eui_components.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { RuleTester } from 'eslint'; -import { PreferCSSAttributeForEuiComponents } from './prefer_css_attribute_for_eui_components'; - -const tsTester = [ - '@typescript-eslint/parser', - new RuleTester({ - parser: require.resolve('@typescript-eslint/parser'), - parserOptions: { - sourceType: 'module', - ecmaVersion: 2018, - ecmaFeatures: { - jsx: true, - }, - }, - }), -] as const; - -const babelTester = [ - '@babel/eslint-parser', - new RuleTester({ - parser: require.resolve('@babel/eslint-parser'), - parserOptions: { - sourceType: 'module', - ecmaVersion: 2018, - requireConfigFile: false, - babelOptions: { - presets: ['@kbn/babel-preset/node_preset'], - }, - }, - }), -] as const; - -const invalid: RuleTester.InvalidTestCase[] = [ - { - name: 'Prefer the JSX css attribute for EUI components', - filename: '/x-pack/plugins/observability_solution/observability/public/test_component.tsx', - code: ` - import React from 'react'; - - function TestComponent() { - return ( - This is a test - ) - }`, - errors: [ - { - messageId: 'preferCSSAttributeForEuiComponents', - }, - ], - output: ` - import React from 'react'; - - function TestComponent() { - return ( - This is a test - ) - }`, - }, -]; - -const valid: RuleTester.ValidTestCase[] = [ - { - name: invalid[0].name, - filename: invalid[0].filename, - code: invalid[0].output as string, - }, -]; - -for (const [name, tester] of [tsTester, babelTester]) { - describe(name, () => { - tester.run('@kbn/prefer_css_attribute_for_eui_components', PreferCSSAttributeForEuiComponents, { - valid, - invalid, - }); - }); -} diff --git a/packages/kbn-eslint-plugin-css/src/rules/prefer_css_attribute_for_eui_components.ts b/packages/kbn-eslint-plugin-css/src/rules/prefer_css_attribute_for_eui_components.ts deleted file mode 100644 index f2b8bfd7b7d7..000000000000 --- a/packages/kbn-eslint-plugin-css/src/rules/prefer_css_attribute_for_eui_components.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { Rule } from 'eslint'; -import type { TSESTree } from '@typescript-eslint/typescript-estree'; -import type { Identifier, Node } from 'estree'; - -export const PreferCSSAttributeForEuiComponents: Rule.RuleModule = { - meta: { - type: 'suggestion', - docs: { - description: 'Prefer the JSX css attribute for EUI components', - category: 'Best Practices', - recommended: true, - }, - messages: { - preferCSSAttributeForEuiComponents: 'Prefer the css attribute for EUI components', - }, - fixable: 'code', - schema: [], - }, - create(context) { - const isNamedEuiComponentRegex = /^Eui[A-Z]*/; - - return { - JSXOpeningElement(node: TSESTree.JSXOpeningElement) { - if (isNamedEuiComponentRegex.test((node.name as unknown as Identifier).name)) { - let styleAttrNode: TSESTree.JSXAttribute | undefined; - - if ( - // @ts-expect-error the returned result is somehow typed as a union of JSXAttribute and JSXAttributeSpread - (styleAttrNode = node.attributes.find( - (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'style' - )) - ) { - context.report({ - node: styleAttrNode?.parent! as Node, - messageId: 'preferCSSAttributeForEuiComponents', - fix(fixer) { - const cssAttr = node.attributes.find( - (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'css' - ); - - if (cssAttr) { - return null; - } - - return fixer.replaceTextRange(styleAttrNode?.name?.range!, 'css'); - }, - }); - } - } - }, - }; - }, -}; diff --git a/packages/kbn-eslint-plugin-css/tsconfig.json b/packages/kbn-eslint-plugin-css/tsconfig.json deleted file mode 100644 index a6dec1c1a62c..000000000000 --- a/packages/kbn-eslint-plugin-css/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": ["jest", "node"], - "lib": ["es2021"] - }, - "include": ["**/*.ts"], - "exclude": ["target/**/*"], - "kbn_references": [] -} diff --git a/src/platform/packages/shared/kbn-ui-theme/src/theme.ts b/src/platform/packages/shared/kbn-ui-theme/src/theme.ts index 2f3703f3f7e8..e22498671cfd 100644 --- a/src/platform/packages/shared/kbn-ui-theme/src/theme.ts +++ b/src/platform/packages/shared/kbn-ui-theme/src/theme.ts @@ -7,14 +7,10 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -/* eslint-disable-next-line @kbn/eslint/module_migration */ import { default as v8Light } from '@elastic/eui/dist/eui_theme_amsterdam_light.json'; -/* eslint-disable-next-line @kbn/eslint/module_migration */ import { default as v8Dark } from '@elastic/eui/dist/eui_theme_amsterdam_dark.json'; -/* eslint-disable-next-line @kbn/eslint/module_migration */ import { default as borealisLight } from '@elastic/eui/dist/eui_theme_borealis_light.json'; -/* eslint-disable-next-line @kbn/eslint/module_migration */ import { default as borealisDark } from '@elastic/eui/dist/eui_theme_borealis_dark.json'; const globals: any = typeof window === 'undefined' ? {} : window; diff --git a/tsconfig.base.json b/tsconfig.base.json index 76737f5afd46..cc0eb69e4cd8 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -860,8 +860,6 @@ "@kbn/es-ui-shared-plugin/*": ["src/platform/plugins/shared/es_ui_shared/*"], "@kbn/eslint-config": ["packages/kbn-eslint-config"], "@kbn/eslint-config/*": ["packages/kbn-eslint-config/*"], - "@kbn/eslint-plugin-css": ["packages/kbn-eslint-plugin-css"], - "@kbn/eslint-plugin-css/*": ["packages/kbn-eslint-plugin-css/*"], "@kbn/eslint-plugin-disable": ["packages/kbn-eslint-plugin-disable"], "@kbn/eslint-plugin-disable/*": ["packages/kbn-eslint-plugin-disable/*"], "@kbn/eslint-plugin-eslint": ["packages/kbn-eslint-plugin-eslint"], diff --git a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/results/attack_discovery_panel/tabs/attack_discovery_tab/attack/attack_chain/tactic/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/results/attack_discovery_panel/tabs/attack_discovery_tab/attack/attack_chain/tactic/index.tsx index 7399357d7104..44d6f6a961ae 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/results/attack_discovery_panel/tabs/attack_discovery_tab/attack/attack_chain/tactic/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/results/attack_discovery_panel/tabs/attack_discovery_tab/attack/attack_chain/tactic/index.tsx @@ -61,7 +61,7 @@ const TacticComponent: React.FC = ({ detected, tactic }) => { grow={false} >
= ({ detected, tactic }) => { data-test-subj="innerCircle" />