diff --git a/.changeset/curvy-pears-perform.md b/.changeset/curvy-pears-perform.md new file mode 100644 index 00000000..5895b4c9 --- /dev/null +++ b/.changeset/curvy-pears-perform.md @@ -0,0 +1,20 @@ +--- +"eslint-plugin-primer-react": minor +--- + +Add a `checkAllStrings` option to the `no-deprecated-colors` rule. + +If `checkAllStrings` is set to `true`, the `no-deprecated-colors` rule will check for deprecated colors in all strings. This is useful for catching uses of deprecated colors outside system props and the `sx` prop. + + ```js + /* eslint primer-react/no-deprecated-colors: ["warn", {"checkAllStrings": true}] */ + import {Box} from '@primer/components' + + function ExampleComponent() { + const styles = { + // Enabling `checkAllStrings` will find deprecated colors used like this: + color: 'text.primary' + } + return Hello + } + ``` diff --git a/docs/rules/no-deprecated-colors.md b/docs/rules/no-deprecated-colors.md index f85806bb..98468d9f 100644 --- a/docs/rules/no-deprecated-colors.md +++ b/docs/rules/no-deprecated-colors.md @@ -19,6 +19,8 @@ const SystemPropExample() = () => Incorrect Incorrect +const SxPropExample2() = () => `0 1px 2px ${theme.colors.some.deprecated.color}`}}>Incorrect + const ThemeGetExample = styled.div` color: ${themeGet('colors.some.deprecated.color')}; ` @@ -31,9 +33,11 @@ const ThemeGetExample = styled.div` import {Box, themeGet} from '@primer/components' import styled from 'styled-components' -const SystemPropExample() = () => Incorrect +const SystemPropExample() = () => Correct + +const SxPropExample() = () => Correct -const SxPropExample() = () => Incorrect +const SxPropExample2() = () => `0 1px 2px ${theme.colors.some.color}`}}>Correct const ThemeGetExample = styled.div` color: ${themeGet('colors.some.color')}; @@ -46,7 +50,33 @@ const ThemeGetExample = styled.div` By default, the `no-deprecated-colors` rule will only check for deprecated colors used in functions and components that are imported from `@primer/components`. You can disable this behavior by setting `skipImportCheck` to `true`. This is useful for linting custom components that pass color-related props down to Primer React components. + ```js + /* eslint primer-react/no-deprecated-colors: ["warn", {"skipImportCheck": true}] */ + import {Box} from '@primer/components' + function MyBox({color, children}) { + return {children} + } + + function App() { + // Enabling `skipImportCheck` will find deprecated colors used like this: + return Hello + } ``` - "primer-react/no-deprecated-colors": ["warn", {"skipImportCheck": true}] + +- `checkAllStrings` (default: `false`) + + If `checkAllStrings` is set to `true`, the `no-deprecated-colors` rule will check for deprecated colors in all strings. This is useful for catching uses of deprecated colors outside system props and the `sx` prop. + + ```js + /* eslint primer-react/no-deprecated-colors: ["warn", {"checkAllStrings": true}] */ + import {Box} from '@primer/components' + + function ExampleComponent() { + const styles = { + // Enabling `checkAllStrings` will find deprecated colors used like this: + color: 'text.primary' + } + return Hello + } ``` diff --git a/src/rules/__tests__/no-deprecated-colors.test.js b/src/rules/__tests__/no-deprecated-colors.test.js index 9973a599..fdd8ed00 100644 --- a/src/rules/__tests__/no-deprecated-colors.test.js +++ b/src/rules/__tests__/no-deprecated-colors.test.js @@ -31,9 +31,20 @@ ruleTester.run('no-deprecated-colors', rule, { `import {get} from "@other/constants"; get("space.text.primary")`, `import {Box} from '@primer/components'; Hello`, `import {Box} from '@primer/components'; Hello`, - `import {Box} from '@primer/components'; Hello` + `import {Box} from '@primer/components'; Hello`, + `{color: 'text.primary'}` ], invalid: [ + { + code: `{color: 'text.primary'}`, + output: `{color: "fg.default"}`, + options: [{checkAllStrings: true}], + errors: [ + { + message: '"text.primary" is deprecated. Use "fg.default" instead.' + } + ] + }, { code: `import {Box} from "@primer/components"; function Example() { return Hello }`, output: `import {Box} from "@primer/components"; function Example() { return Hello }`, diff --git a/src/rules/no-deprecated-colors.js b/src/rules/no-deprecated-colors.js index d23c068f..c4509ced 100644 --- a/src/rules/no-deprecated-colors.js +++ b/src/rules/no-deprecated-colors.js @@ -13,6 +13,9 @@ module.exports = { properties: { skipImportCheck: { type: 'boolean' + }, + checkAllStrings: { + type: 'boolean' } }, additionalProperties: false @@ -24,7 +27,17 @@ module.exports = { // used in any components (not just ones that are imported from `@primer/components`). const skipImportCheck = context.options[0] ? context.options[0].skipImportCheck : false + const checkAllStrings = context.options[0] ? context.options[0].checkAllStrings : false + + // Track visited string literals to avoid reporting the same string multiple times + const visitedStrings = new Set() + return { + Literal(node) { + if (checkAllStrings && Object.keys(deprecations).includes(node.value) && !visitedStrings.has(node)) { + replaceDeprecatedColor(context, node, node.value) + } + }, JSXOpeningElement(node) { // Skip if component was not imported from @primer/components if (!skipImportCheck && !isPrimerComponent(node.name, context.getScope(node))) { @@ -50,6 +63,7 @@ module.exports = { if (styledSystemColorProps.includes(propName) && Object.keys(deprecations).includes(propValue)) { replaceDeprecatedColor(context, prop.value, propValue) + visitedStrings.add(prop.value) } } @@ -86,6 +100,7 @@ module.exports = { // Check if styled-system color prop is using a deprecated color if (styledSystemColorProps.includes(propName) && Object.keys(deprecations).includes(propValue)) { replaceDeprecatedColor(context, attribute.value, propValue) + visitedStrings.add(attribute.value) } } },