Skip to content

Commit

Permalink
Merge pull request #6 from primer/better-sx-prop-support
Browse files Browse the repository at this point in the history
[no-deprecated-colors] Handle `sx` prop edge cases
  • Loading branch information
colebemis authored Sep 20, 2021
2 parents 49bf748 + dd14594 commit 56557b4
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 20 deletions.
17 changes: 17 additions & 0 deletions .changeset/weak-tips-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"eslint-plugin-primer-react": patch
---

The `no-deprecated-colors` rule can now find deprecated colors in the following cases:

* Nested `sx` properties:

```jsx
<Box sx={{'&:hover': {bg: 'bg.primary'}}}>
```

* Functions in `sx` prop:

```jsx
<Box sx={{boxShadow: theme => `0 1px 2px ${theme.colors.text.primary}`}}>
```
7 changes: 6 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,8 @@
"eslint": ">=4.19.0",
"@primer/primitives": ">=4.6.2"
},
"prettier": "@github/prettier-config"
"prettier": "@github/prettier-config",
"dependencies": {
"eslint-traverse": "^1.0.0"
}
}
36 changes: 36 additions & 0 deletions src/rules/__tests__/no-deprecated-colors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,42 @@ ruleTester.run('no-deprecated-colors', rule, {
}
]
},
{
code: `import {Box} from "@primer/components"; <Box sx={{boxShadow: theme => theme.shadows.autocomplete.shadow}} />`,
output: `import {Box} from "@primer/components"; <Box sx={{boxShadow: theme => theme.shadows.shadow.medium}} />`,
errors: [
{
message: '"theme.shadows.autocomplete.shadow" is deprecated. Use "theme.shadows.shadow.medium" instead.'
}
]
},
{
code: `import {Box} from "@primer/components"; <Box sx={{boxShadow: theme => \`0 1px 2px \${theme.colors.text.primary}\`}} />`,
output: `import {Box} from "@primer/components"; <Box sx={{boxShadow: theme => \`0 1px 2px \${theme.colors.fg.default}\`}} />`,
errors: [
{
message: '"theme.colors.text.primary" is deprecated. Use "theme.colors.fg.default" instead.'
}
]
},
{
code: `import {Box} from "@primer/components"; <Box sx={{boxShadow: t => \`0 1px 2px \${t.colors.text.primary}\`}} />`,
output: `import {Box} from "@primer/components"; <Box sx={{boxShadow: t => \`0 1px 2px \${t.colors.fg.default}\`}} />`,
errors: [
{
message: '"t.colors.text.primary" is deprecated. Use "t.colors.fg.default" instead.'
}
]
},
{
code: `import {Box} from "@primer/components"; <Box sx={{"&:hover": {bg: "bg.primary"}}} />`,
output: `import {Box} from "@primer/components"; <Box sx={{"&:hover": {bg: "canvas.default"}}} />`,
errors: [
{
message: '"bg.primary" is deprecated. Use "canvas.default" instead.'
}
]
},
{
code: `import {Box} from "@primer/components"; <Box color="auto.green.5" />`,
errors: [
Expand Down
70 changes: 52 additions & 18 deletions src/rules/no-deprecated-colors.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const deprecations = require('@primer/primitives/dist/deprecations/colors')
const traverse = require('eslint-traverse')

const styledSystemColorProps = ['color', 'bg', 'backgroundColor', 'borderColor', 'textShadow', 'boxShadow']

Expand All @@ -25,19 +26,46 @@ module.exports = {

// Check for the sx prop
if (propName === 'sx' && attribute.value.expression.type === 'ObjectExpression') {
// Ignore non-literal properties
const sxProperties = attribute.value.expression.properties.filter(
property => property.type === 'Property' && property.value.type === 'Literal'
)

for (const prop of sxProperties) {
const propName = prop.key.name
const propValue = prop.value.value
// Search all properties of the sx object (even nested properties)
traverse(context, attribute.value, path => {
if (path.node.type === 'Property' && path.node.value.type === 'Literal') {
const prop = path.node
const propName = prop.key.name
const propValue = prop.value.value

if (styledSystemColorProps.includes(propName) && Object.keys(deprecations).includes(propValue)) {
replaceDeprecatedColor(context, prop.value, propValue)
}
}

if (styledSystemColorProps.includes(propName) && Object.keys(deprecations).includes(propValue)) {
replaceDeprecatedColor(context, prop.value, propValue)
// Check functions passed to sx object properties
// (e.g. boxShadow: theme => `0 1px 2px ${theme.colors.text.primary}` )
if (path.node.type === 'Property' && path.node.value.type === 'ArrowFunctionExpression') {
traverse(context, path.node.value.body, path => {
if (path.node.type === 'MemberExpression') {
// Convert MemberExpression AST to string
const code = context.getSourceCode().getText(path.node)

const [param, key, ...rest] = code.split('.')
const name = rest.join('.')

if (['colors', 'shadows'].includes(key) && Object.keys(deprecations).includes(name)) {
replaceDeprecatedColor(
context,
path.node,
name,
str => [param, key, str].join('.'),
str => str
)
}

// Don't traverse any nested member expressions.
// The root-level member expression gives us all the data we need.
return traverse.SKIP
}
})
}
}
})
}

// Check if styled-system color prop is using a deprecated color
Expand Down Expand Up @@ -103,36 +131,42 @@ function isGet(identifier, scope) {
return isImportedFrom(/^\.\.?\/constants$/, identifier, scope) && identifier.name === 'get'
}

function replaceDeprecatedColor(context, node, deprecatedName, getDisplayName = str => str) {
function replaceDeprecatedColor(
context,
node,
deprecatedName,
transformName = str => str,
transformReplacementValue = str => JSON.stringify(str)
) {
const replacement = deprecations[deprecatedName]

if (replacement === null) {
// No replacement
context.report({
node,
message: `"${getDisplayName(
message: `"${transformName(
deprecatedName
)}" is deprecated. Go to https://primer.style/primitives or reach out in the #primer channel on Slack to find a suitable replacement.`
})
} else if (Array.isArray(replacement)) {
// Multiple possible replacements
context.report({
node,
message: `"${getDisplayName(deprecatedName)}" is deprecated.`,
message: `"${transformName(deprecatedName)}" is deprecated.`,
suggest: replacement.map(replacementValue => ({
desc: `Use "${getDisplayName(replacementValue)}" instead.`,
desc: `Use "${transformName(replacementValue)}" instead.`,
fix(fixer) {
return fixer.replaceText(node, JSON.stringify(getDisplayName(replacementValue)))
return fixer.replaceText(node, transformReplacementValue(transformName(replacementValue)))
}
}))
})
} else {
// One replacement
context.report({
node,
message: `"${getDisplayName(deprecatedName)}" is deprecated. Use "${getDisplayName(replacement)}" instead.`,
message: `"${transformName(deprecatedName)}" is deprecated. Use "${transformName(replacement)}" instead.`,
fix(fixer) {
return fixer.replaceText(node, JSON.stringify(getDisplayName(replacement)))
return fixer.replaceText(node, transformReplacementValue(transformName(replacement)))
}
})
}
Expand Down

0 comments on commit 56557b4

Please sign in to comment.