mirror of
https://github.com/elastic/kibana.git
synced 2025-04-16 22:21:06 -04:00
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`  #### `prefer-css-attribute-for-eui-components` Example file: `x-pack/examples/alerting_example/public/alert_types/always_firing.tsx:166`
This commit is contained in:
parent
ecd83ce211
commit
7e46d2e756
18 changed files with 28 additions and 1085 deletions
|
@ -280,7 +280,7 @@ const RESTRICTED_IMPORTS = [
|
|||
module.exports = {
|
||||
root: true,
|
||||
|
||||
extends: ['plugin:@elastic/eui/recommended', '@kbn/eslint-config'],
|
||||
extends: ['@kbn/eslint-config'],
|
||||
|
||||
overrides: [
|
||||
/**
|
||||
|
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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 (
|
||||
<EuiText style={{ color: 'red' }}>You know, for search</EuiText>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
// 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 (
|
||||
<EuiText style={{ color: style.color }}>You know, for search</EuiText>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
// 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 (
|
||||
<EuiText style={{ color: colorValue }}>You know, for search</EuiText>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
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 (
|
||||
<EuiText css={css`color: '#dd4040' `}>You know, for search</EuiText>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
// Filename: /x-pack/plugins/observability_solution/observability/public/my_component.tsx
|
||||
|
||||
import React from 'react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
|
||||
function MyComponent() {
|
||||
return (
|
||||
<EuiText css={() => ({ color: '#dd4040' })}>You know, for search</EuiText>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
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 (
|
||||
<EuiText className={css`color: '#dd4040'`}>You know, for search</EuiText>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
@ -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,
|
||||
};
|
|
@ -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: ['<rootDir>/packages/kbn-eslint-plugin-css'],
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/eslint-plugin-css",
|
||||
"devOnly": true,
|
||||
"owner": "@elastic/appex-sharedux"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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 (
|
||||
<EuiCode style={{ color: '#dd4040' }}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode style={{ color: codeColor }}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode style={codeStyle}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode style={{ background: baseStyle.background }}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode style={codeStyle}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode style={{ background: '#dd4040' }}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode css={{ color: '#dd4040' }}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode css={css\` color: #dd4040; \`}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode css={codeCss}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode css={codeCss}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode css={() => ({ color: '#dd4040' })}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode css={function () { return { color: '#dd4040' }; }}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode className={css\` color: #dd4040; \`}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
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 (
|
||||
<EuiCode css={css\`
|
||||
border-top: none;
|
||||
border-radius: 0 0 6px 6px;
|
||||
\`}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const [name, tester] of [tsTester, babelTester]) {
|
||||
describe(name, () => {
|
||||
tester.run('@kbn/no_css_color', NoCssColor, {
|
||||
valid,
|
||||
invalid,
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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' };
|
||||
* <EuiCode style={codeStyle}>This is an example</EuiCode>
|
||||
*
|
||||
* @example
|
||||
* const codeStyle = { color: '#dd4040' };
|
||||
* <EuiCode css={codeStyle}>This is an example</EuiCode>
|
||||
*
|
||||
* @example
|
||||
* const codeStyle = css({ color: '#dd4040' });
|
||||
* <EuiCode css={codeStyle}>This is an example</EuiCode>
|
||||
*/
|
||||
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
|
||||
* <EuiCode style={{ color: '#dd4040' }}>This is an example</EuiCode>
|
||||
*
|
||||
* @example
|
||||
* <EuiCode css={{ color: '#dd4040' }}>This is an example</EuiCode>
|
||||
*
|
||||
* @example
|
||||
* const styleRules = { color: '#dd4040' };
|
||||
* <EuiCode style={{ color: styleRules.color }}>This is an example</EuiCode>
|
||||
*
|
||||
* @example
|
||||
* const styleRules = { color: '#dd4040' };
|
||||
* <EuiCode css={{ color: styleRules.color }}>This is an example</EuiCode>
|
||||
*/
|
||||
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
|
||||
* <EuiCode css={`{ color: '#dd4040' }`}>This is an example</EuiCode>
|
||||
*/
|
||||
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
|
||||
* <EuiCode css={() => ({ color: '#dd4040' })}>This is an example</EuiCode>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -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 (
|
||||
<EuiCode style={{ color: '#dd4040' }}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
errors: [
|
||||
{
|
||||
messageId: 'preferCSSAttributeForEuiComponents',
|
||||
},
|
||||
],
|
||||
output: `
|
||||
import React from 'react';
|
||||
|
||||
function TestComponent() {
|
||||
return (
|
||||
<EuiCode css={{ color: '#dd4040' }}>This is a test</EuiCode>
|
||||
)
|
||||
}`,
|
||||
},
|
||||
];
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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');
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": ["jest", "node"],
|
||||
"lib": ["es2021"]
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["target/**/*"],
|
||||
"kbn_references": []
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -61,7 +61,7 @@ const TacticComponent: React.FC<Props> = ({ detected, tactic }) => {
|
|||
grow={false}
|
||||
>
|
||||
<div
|
||||
// eslint-disable-next-line @kbn/css/no_css_color -- euiTheme.colors.danger is a string
|
||||
// eslint-disable-next-line @elastic/eui/no-css-color -- euiTheme.colors.danger is a string
|
||||
css={css`
|
||||
background: transparent;
|
||||
border: 2px solid ${color};
|
||||
|
@ -74,7 +74,7 @@ const TacticComponent: React.FC<Props> = ({ detected, tactic }) => {
|
|||
data-test-subj="innerCircle"
|
||||
/>
|
||||
<div
|
||||
// eslint-disable-next-line @kbn/css/no_css_color -- euiTheme.colors.danger is a string
|
||||
// eslint-disable-next-line @elastic/eui/no-css-color -- euiTheme.colors.danger is a string
|
||||
css={css`
|
||||
background: transparent;
|
||||
border: 2px solid ${color};
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -2162,10 +2162,10 @@
|
|||
semver "^7.6.3"
|
||||
topojson-client "^3.1.0"
|
||||
|
||||
"@elastic/eslint-plugin-eui@0.0.2":
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314"
|
||||
integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ==
|
||||
"@elastic/eslint-plugin-eui@0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.1.1.tgz#2829b0f3cc750c582baa683c2f503594676ba08a"
|
||||
integrity sha512-dCoXcCT8V1djwWrP0XSG0gXN5fYDtAw5aF88BPArVBbuCQSWPEBpR9avaWJ6uVjAsOteQ7tdwLMtzrvMKgKKIA==
|
||||
|
||||
"@elastic/eui-theme-borealis@0.1.0":
|
||||
version "0.1.0"
|
||||
|
@ -5410,10 +5410,6 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/eslint-plugin-css@link:packages/kbn-eslint-plugin-css":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/eslint-plugin-disable@link:packages/kbn-eslint-plugin-disable":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Reference in a new issue