Skip to content

Commit 6faf125

Browse files
authored
Handle theme get functions
1 parent ff30bb0 commit 6faf125

File tree

2 files changed

+118
-45
lines changed

2 files changed

+118
-45
lines changed

src/rules/__tests__/no-deprecated-colors.test.js

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ const testDeprecations = {
55
'text.primary': 'fg.default',
66
'bg.primary': 'canvas.default',
77
'auto.green.5': ['success.fg', 'success.emphasis'],
8-
'fade.fg10': null
8+
'fade.fg10': null,
9+
'autocomplete.shadow': 'shadow.medium'
910
}
1011

1112
jest.mock('@primer/primitives/dist/deprecations/colors_v2', () => testDeprecations)
@@ -23,12 +24,16 @@ const ruleTester = new RuleTester({
2324
ruleTester.run('no-deprecated-colors', rule, {
2425
valid: [
2526
`import {Box} from '@other/design-system'; <Box color="text.primary">Hello</Box>`,
26-
`import {Box} from '@primer/components'; <Box color="fg.default">Hello</Box>`
27+
`import {Box} from "@primer/components"; <Box color="fg.default">Hello</Box>`,
28+
`import {hello} from "@primer/components"; hello("colors.text.primary")`,
29+
`import {themeGet} from "@primer/components"; themeGet("space.text.primary")`,
30+
`import {themeGet} from "@other/design-system"; themeGet("colors.text.primary")`,
31+
`import {get} from "@other/constants"; get("space.text.primary")`
2732
],
2833
invalid: [
2934
{
30-
code: `import {Box} from '@primer/components'; function Example() { return <Box color="text.primary">Hello</Box> }`,
31-
output: `import {Box} from '@primer/components'; function Example() { return <Box color="fg.default">Hello</Box> }`,
35+
code: `import {Box} from "@primer/components"; function Example() { return <Box color="text.primary">Hello</Box> }`,
36+
output: `import {Box} from "@primer/components"; function Example() { return <Box color="fg.default">Hello</Box> }`,
3237
errors: [
3338
{
3439
message: '"text.primary" is deprecated. Use "fg.default" instead.'
@@ -45,43 +50,43 @@ ruleTester.run('no-deprecated-colors', rule, {
4550
]
4651
},
4752
{
48-
code: `import {Box} from '@primer/components'; const Example = () => <Box color="text.primary">Hello</Box>`,
49-
output: `import {Box} from '@primer/components'; const Example = () => <Box color="fg.default">Hello</Box>`,
53+
code: `import {Box} from "@primer/components"; const Example = () => <Box color="text.primary">Hello</Box>`,
54+
output: `import {Box} from "@primer/components"; const Example = () => <Box color="fg.default">Hello</Box>`,
5055
errors: [
5156
{
5257
message: '"text.primary" is deprecated. Use "fg.default" instead.'
5358
}
5459
]
5560
},
5661
{
57-
code: `import {Box} from '@primer/components'; <Box bg="bg.primary" m={1} />`,
58-
output: `import {Box} from '@primer/components'; <Box bg="canvas.default" m={1} />`,
62+
code: `import {Box} from "@primer/components"; <Box bg="bg.primary" m={1} />`,
63+
output: `import {Box} from "@primer/components"; <Box bg="canvas.default" m={1} />`,
5964
errors: [
6065
{
6166
message: '"bg.primary" is deprecated. Use "canvas.default" instead.'
6267
}
6368
]
6469
},
6570
{
66-
code: `import {Box} from '@primer/components'; <Box color="auto.green.5" />`,
71+
code: `import {Box} from "@primer/components"; <Box color="auto.green.5" />`,
6772
errors: [
6873
{
6974
message: '"auto.green.5" is deprecated.',
7075
suggestions: [
7176
{
7277
desc: 'Use "success.fg" instead.',
73-
output: `import {Box} from '@primer/components'; <Box color="success.fg" />`
78+
output: `import {Box} from "@primer/components"; <Box color="success.fg" />`
7479
},
7580
{
7681
desc: 'Use "success.emphasis" instead.',
77-
output: `import {Box} from '@primer/components'; <Box color="success.emphasis" />`
82+
output: `import {Box} from "@primer/components"; <Box color="success.emphasis" />`
7883
}
7984
]
8085
}
8186
]
8287
},
8388
{
84-
code: `import {Box} from '@primer/components'; <Box color="fade.fg10" />`,
89+
code: `import {Box} from "@primer/components"; <Box color="fade.fg10" />`,
8590
errors: [
8691
{
8792
message:
@@ -90,8 +95,8 @@ ruleTester.run('no-deprecated-colors', rule, {
9095
]
9196
},
9297
{
93-
code: `import {Box, Text} from '@primer/components'; <Box bg="bg.primary"><Text color="text.primary">Hello</Text></Box>`,
94-
output: `import {Box, Text} from '@primer/components'; <Box bg="canvas.default"><Text color="fg.default">Hello</Text></Box>`,
98+
code: `import {Box, Text} from "@primer/components"; <Box bg="bg.primary"><Text color="text.primary">Hello</Text></Box>`,
99+
output: `import {Box, Text} from "@primer/components"; <Box bg="canvas.default"><Text color="fg.default">Hello</Text></Box>`,
95100
errors: [
96101
{
97102
message: '"bg.primary" is deprecated. Use "canvas.default" instead.'
@@ -100,6 +105,42 @@ ruleTester.run('no-deprecated-colors', rule, {
100105
message: '"text.primary" is deprecated. Use "fg.default" instead.'
101106
}
102107
]
108+
},
109+
{
110+
code: `import {themeGet} from "@primer/components"; themeGet("colors.text.primary")`,
111+
output: `import {themeGet} from "@primer/components"; themeGet("colors.fg.default")`,
112+
errors: [
113+
{
114+
message: '"colors.text.primary" is deprecated. Use "colors.fg.default" instead.'
115+
}
116+
]
117+
},
118+
{
119+
code: `import {themeGet} from "@primer/components"; themeGet("shadows.autocomplete.shadow")`,
120+
output: `import {themeGet} from "@primer/components"; themeGet("shadows.shadow.medium")`,
121+
errors: [
122+
{
123+
message: '"shadows.autocomplete.shadow" is deprecated. Use "shadows.shadow.medium" instead.'
124+
}
125+
]
126+
},
127+
{
128+
code: `import {get} from "./constants"; get("colors.text.primary")`,
129+
output: `import {get} from "./constants"; get("colors.fg.default")`,
130+
errors: [
131+
{
132+
message: '"colors.text.primary" is deprecated. Use "colors.fg.default" instead.'
133+
}
134+
]
135+
},
136+
{
137+
code: `import {get} from "../constants"; get("colors.text.primary")`,
138+
output: `import {get} from "../constants"; get("colors.fg.default")`,
139+
errors: [
140+
{
141+
message: '"colors.text.primary" is deprecated. Use "colors.fg.default" instead.'
142+
}
143+
]
103144
}
104145
]
105146
})

src/rules/no-deprecated-colors.js

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module.exports = {
1010
return {
1111
JSXOpeningElement(node) {
1212
// Skip if component was not imported from @primer/components
13-
if (!isImportedFrom(/^@primer\/components/, node.name, context.getScope(node))) {
13+
if (!isPrimerComponent(node.name, context.getScope(node))) {
1414
return
1515
}
1616

@@ -22,40 +22,26 @@ module.exports = {
2222
const propName = attribute.name.name
2323
const propValue = attribute.value.value
2424

25+
// TODO: handle sx prop
26+
2527
// Check if styled-system color prop is using a deprecated color
2628
if (styledSystemColorProps.includes(propName) && Object.keys(deprecations).includes(propValue)) {
27-
const replacement = deprecations[propValue]
28-
29-
if (replacement === null) {
30-
// No replacement
31-
context.report({
32-
node: attribute.value,
33-
message: `"${propValue}" is deprecated. Go to https://primer.style/primitives or reach out in the #primer channel on Slack to find a suitable replacement.`
34-
})
35-
} else if (Array.isArray(replacement)) {
36-
// Multiple possible replacements
37-
context.report({
38-
node: attribute.value,
39-
message: `"${propValue}" is deprecated.`,
40-
suggest: replacement.map(replacementValue => ({
41-
desc: `Use "${replacementValue}" instead.`,
42-
fix(fixer) {
43-
return fixer.replaceText(attribute.value, JSON.stringify(replacementValue))
44-
}
45-
}))
46-
})
47-
} else {
48-
// One replacement
49-
context.report({
50-
node: attribute.value,
51-
message: `"${propValue}" is deprecated. Use "${replacement}" instead.`,
52-
fix(fixer) {
53-
return fixer.replaceText(attribute.value, JSON.stringify(replacement))
54-
}
55-
})
56-
}
29+
replaceDeprecatedColor(context, attribute.value, propValue)
5730
}
5831
}
32+
},
33+
CallExpression(node) {
34+
// Skip if not `themeGet` or `get` function
35+
if (!isThemeGet(node.callee, context.getScope(node)) && !isGet(node.callee, context.getScope(node))) {
36+
return
37+
}
38+
39+
const [key, ...path] = node.arguments[0].value.split('.')
40+
const name = path.join('.')
41+
42+
if (['colors', 'shadows'].includes(key) && Object.keys(deprecations).includes(name)) {
43+
replaceDeprecatedColor(context, node.arguments[0], name, str => [key, str].join('.'))
44+
}
5945
}
6046
}
6147
}
@@ -87,3 +73,49 @@ function isImportedFrom(moduleRegex, identifier, scope) {
8773
// Return true if the variable was imported from the given module
8874
return definition && definition.type == 'ImportBinding' && moduleRegex.test(definition.parent.source.value)
8975
}
76+
77+
function isPrimerComponent(identifier, scope) {
78+
return isImportedFrom(/^@primer\/components/, identifier, scope)
79+
}
80+
81+
function isThemeGet(identifier, scope) {
82+
return isImportedFrom(/^@primer\/components/, identifier, scope) && identifier.name === 'themeGet'
83+
}
84+
85+
function isGet(identifier, scope) {
86+
return isImportedFrom(/^\.\.?\/constants$/, identifier, scope) && identifier.name === 'get'
87+
}
88+
89+
function replaceDeprecatedColor(context, node, deprecatedName, transformName = str => str) {
90+
const replacement = deprecations[deprecatedName]
91+
const transformedDeprecatedName = transformName(deprecatedName)
92+
93+
if (replacement === null) {
94+
// No replacement
95+
context.report({
96+
node,
97+
message: `"${transformedDeprecatedName}" is deprecated. Go to https://primer.style/primitives or reach out in the #primer channel on Slack to find a suitable replacement.`
98+
})
99+
} else if (Array.isArray(replacement)) {
100+
// Multiple possible replacements
101+
context.report({
102+
node,
103+
message: `"${transformedDeprecatedName}" is deprecated.`,
104+
suggest: replacement.map(replacementValue => ({
105+
desc: `Use "${transformName(replacementValue)}" instead.`,
106+
fix(fixer) {
107+
return fixer.replaceText(node, JSON.stringify(transformName(replacementValue)))
108+
}
109+
}))
110+
})
111+
} else {
112+
// One replacement
113+
context.report({
114+
node,
115+
message: `"${transformedDeprecatedName}" is deprecated. Use "${transformName(replacement)}" instead.`,
116+
fix(fixer) {
117+
return fixer.replaceText(node, JSON.stringify(transformName(replacement)))
118+
}
119+
})
120+
}
121+
}

0 commit comments

Comments
 (0)