eslint-plugin-primer-react 0.0.0-2021817225727

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ # Changesets
2
+
3
+ Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4
+ with multi-package repos, or single-package repos to help you version and publish your code. You can
5
+ find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6
+
7
+ We have a quick list of common questions to get you started engaging with this project in
8
+ [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json",
3
+ "changelog": ["@changesets/changelog-github", {"repo": "primer/eslint-plugin-primer-react"}],
4
+ "commit": false,
5
+ "linked": [],
6
+ "access": "public",
7
+ "baseBranch": "main",
8
+ "updateInternalDependencies": "patch",
9
+ "ignore": []
10
+ }
@@ -0,0 +1,28 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ build:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ strategy:
15
+ matrix:
16
+ node-version: [12.x, 14.x, 16.x]
17
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
18
+
19
+ steps:
20
+ - uses: actions/checkout@v2
21
+ - name: Use Node.js ${{ matrix.node-version }}
22
+ uses: actions/setup-node@v2
23
+ with:
24
+ node-version: ${{ matrix.node-version }}
25
+ cache: 'npm'
26
+ - run: npm ci
27
+ - run: npm run build --if-present
28
+ - run: npm test
@@ -0,0 +1,34 @@
1
+ name: Release
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ jobs:
7
+ release:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - name: Checkout repository
11
+ uses: actions/checkout@v2
12
+ with:
13
+ # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
14
+ fetch-depth: 0
15
+ persist-credentials: false
16
+
17
+ - name: Set up Node.js
18
+ uses: actions/setup-node@v2
19
+ with:
20
+ node-version: 14
21
+
22
+ - name: Install dependencies
23
+ run: npm ci
24
+
25
+ - name: Create release pull request or publish to npm
26
+ id: changesets
27
+ uses: changesets/action@master
28
+ with:
29
+ title: Release Tracking
30
+ # This expects you to have a script called release which does a build for your packages and calls changeset publish
31
+ publish: npm run release
32
+ env:
33
+ GITHUB_TOKEN: ${{ secrets.GPR_AUTH_TOKEN_SHARED }}
34
+ NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN_SHARED }}
@@ -0,0 +1,61 @@
1
+ name: Release (canary)
2
+ on:
3
+ push:
4
+ branches-ignore:
5
+ - 'main'
6
+ - 'changeset-release/main'
7
+
8
+ jobs:
9
+ release:
10
+ if: ${{ github.repository == 'primer/eslint-plugin-primer-react' }}
11
+
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Checkout repository
15
+ uses: actions/checkout@v2
16
+ with:
17
+ # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
18
+ fetch-depth: 0
19
+
20
+ - name: Set up Node.js
21
+ uses: actions/setup-node@v2
22
+ with:
23
+ node-version: 14.x
24
+
25
+ - name: Install dependencies
26
+ run: npm ci
27
+
28
+ - name: Build
29
+ run: npm run build --if-present
30
+
31
+ - name: Create .npmrc
32
+ run: |
33
+ cat << EOF > "$HOME/.npmrc"
34
+ //registry.npmjs.org/:_authToken=$NPM_TOKEN
35
+ EOF
36
+ env:
37
+ NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN_SHARED }}
38
+
39
+ - name: Publish canary version
40
+ run: |
41
+ echo "$( jq '.version = "0.0.0"' package.json )" > package.json
42
+ echo -e "---\n'eslint-plugin-primer-react': patch\n---\n\nFake entry to force publishing" > .changeset/force-snapshot-release.md
43
+ yarn changeset version --snapshot
44
+ yarn changeset publish --tag canary
45
+ env:
46
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47
+
48
+ - name: Output canary version number
49
+ uses: actions/github-script@v4.0.2
50
+ with:
51
+ script: |
52
+ const package = require(`${process.env.GITHUB_WORKSPACE}/package.json`)
53
+ github.repos.createCommitStatus({
54
+ owner: context.repo.owner,
55
+ repo: context.repo.repo,
56
+ sha: context.sha,
57
+ state: 'success',
58
+ context: `Published ${package.name}`,
59
+ description: package.version,
60
+ target_url: `https://unpkg.com/${package.name}@${package.version}/`
61
+ })
@@ -0,0 +1,59 @@
1
+ name: Release (candidate)
2
+ on:
3
+ push:
4
+ branches:
5
+ - 'changeset-release/main'
6
+
7
+ jobs:
8
+ release:
9
+ if: ${{ github.repository == 'primer/eslint-plugin-primer-react' }}
10
+
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout repository
14
+ uses: actions/checkout@v2
15
+ with:
16
+ # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
17
+ fetch-depth: 0
18
+
19
+ - name: Set up Node.js
20
+ uses: actions/setup-node@v2
21
+ with:
22
+ node-version: 14.x
23
+
24
+ - name: Install dependencies
25
+ run: npm ci
26
+
27
+ - name: Build
28
+ run: npm run build --if-present
29
+
30
+ - name: Create .npmrc
31
+ run: |
32
+ cat << EOF > "$HOME/.npmrc"
33
+ //registry.npmjs.org/:_authToken=$NPM_TOKEN
34
+ EOF
35
+ env:
36
+ NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN_SHARED }}
37
+
38
+ - name: Publish release candidate
39
+ run: |
40
+ version=$(jq -r .version package.json)
41
+ echo "$( jq ".version = \"$(echo $version)-rc.$(git rev-parse --short HEAD)\"" package.json )" > package.json
42
+ yarn publish --tag next
43
+ env:
44
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45
+
46
+ - name: Output release candidate version number
47
+ uses: actions/github-script@v4.0.2
48
+ with:
49
+ script: |
50
+ const package = require(`${process.env.GITHUB_WORKSPACE}/package.json`)
51
+ github.repos.createCommitStatus({
52
+ owner: context.repo.owner,
53
+ repo: context.repo.repo,
54
+ sha: context.sha,
55
+ state: 'success',
56
+ context: `Published ${package.name}`,
57
+ description: package.version,
58
+ target_url: `https://unpkg.com/${package.name}@${package.version}/`
59
+ })
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # eslint-plugin-primer-react
2
+
3
+ ## 0.0.0-2021817225727
4
+
5
+ ### Patch Changes
6
+
7
+ - Fake entry to force publishing
8
+
9
+ ## 0.4.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [#3](https://github.com/primer/eslint-plugin-primer-react/pull/3) [`8e9144f`](https://github.com/primer/eslint-plugin-primer-react/commit/8e9144fd7a9ff1bb99878dca61621351026ddc82) Thanks [@colebemis](https://github.com/colebemis)! - Add type metadata to no-deprecated-colors rule
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Primer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # eslint-plugin-primer-react
2
+
3
+ [![npm package](https://img.shields.io/npm/v/eslint-plugin-primer-react.svg)](https://www.npmjs.com/package/eslint-plugin-primer-react)
4
+
5
+ ESLint rules for Primer React
6
+
7
+ ## Installation
8
+
9
+ 1. Assuming you already have [ESLint](https://www.npmjs.com/package/eslint) and [Primer React](https://github.com/primer/react) installed, run:
10
+
11
+ ```shell
12
+ npm install --save-dev eslint-plugin-primer-react
13
+
14
+ # or
15
+
16
+ yarn add --dev eslint-plugin-primer-react
17
+ ```
18
+
19
+ 2. In your [ESLint configuration file](https://eslint.org/docs/user-guide/configuring/configuration-files), extend the recommended Primer React ESLint config:
20
+
21
+ ```js
22
+ {
23
+ "extends": [
24
+ // ...
25
+ "plugin:primer-react/recommended"
26
+ ]
27
+ }
28
+ ```
29
+
30
+ ## Rules
31
+
32
+ - [no-deprecated-colors](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/no-deprecated-colors.md)
@@ -0,0 +1,42 @@
1
+ # Disallow references to deprecated color variables (no-deprecated-colors)
2
+
3
+ 🔧 The `--fix` option on the [ESLint CLI](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
4
+
5
+ [Theming](https://primer.style/react/theming) in Primer React is made possible by a theme object that defines your application's colors, spacing, fonts, and more. The color variables in Primer React's [default theme object](https://primer.style/react/theme-reference) are pulled from [Primer Primitives](https://github.com/primer/primitives). When a color variable is deprecated in Primer Primitives, it's important to remove references to that color variable in your application before it's removed from the library.
6
+
7
+ ## Rule details
8
+
9
+ This rule disallows references to color variables that are deprecated in [Primer Primitives](https://github.com/primer/primitives).
10
+
11
+
12
+ 👎 Examples of **incorrect** code for this rule:
13
+
14
+ ```jsx
15
+ /* eslint primer-react/no-deprecated-colors: "error" */
16
+ import {Box, themeGet} from '@primer/components'
17
+ import styled from 'styled-components'
18
+
19
+ const SystemPropExample() = () => <Box color="some.deprecated.color">Incorrect</Box>
20
+
21
+ const SxPropExample() = () => <Box sx={{color: 'some.deprecated.color'}}>Incorrect</Box>
22
+
23
+ const ThemeGetExample = styled.div`
24
+ color: ${themeGet('some.deprecated.color')};
25
+ `
26
+ ```
27
+
28
+ 👍 Examples of **correct** code for this rule:
29
+
30
+ ```jsx
31
+ /* eslint primer-react/no-deprecated-colors: "error" */
32
+ import {Box, themeGet} from '@primer/components'
33
+ import styled from 'styled-components'
34
+
35
+ const SystemPropExample() = () => <Box color="some.color">Incorrect</Box>
36
+
37
+ const SxPropExample() = () => <Box sx={{color: 'some.color'}}>Incorrect</Box>
38
+
39
+ const ThemeGetExample = styled.div`
40
+ color: ${themeGet('some.color')};
41
+ `
42
+ ```
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "eslint-plugin-primer-react",
3
+ "version": "0.0.0-2021817225727",
4
+ "description": "ESLint rules for Primer React",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "jest",
8
+ "release": "changeset publish"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/primer/eslint-plugin-primer-react.git"
13
+ },
14
+ "keywords": [
15
+ "eslint",
16
+ "eslintplugin"
17
+ ],
18
+ "license": "MIT",
19
+ "bugs": {
20
+ "url": "https://github.com/primer/eslint-plugin-primer-react/issues"
21
+ },
22
+ "homepage": "https://github.com/primer/eslint-plugin-primer-react#readme",
23
+ "devDependencies": {
24
+ "@changesets/changelog-github": "^0.4.0",
25
+ "@changesets/cli": "^2.16.0",
26
+ "@github/prettier-config": "0.0.4",
27
+ "@primer/primitives": "^4.6.2",
28
+ "eslint": "^7.32.0",
29
+ "jest": "^27.0.6"
30
+ },
31
+ "peerDependencies": {
32
+ "eslint": ">=4.19.0",
33
+ "@primer/primitives": ">=4.6.2"
34
+ },
35
+ "prettier": "@github/prettier-config",
36
+ "dependencies": {
37
+ "eslint-traverse": "^1.0.0"
38
+ }
39
+ }
@@ -0,0 +1,12 @@
1
+ module.exports = {
2
+ parserOptions: {
3
+ sourceType: 'module',
4
+ ecmaFeatures: {
5
+ jsx: true
6
+ }
7
+ },
8
+ plugins: ['primer-react'],
9
+ rules: {
10
+ 'primer-react/no-deprecated-colors': 'warn'
11
+ }
12
+ }
package/src/index.js ADDED
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ rules: {
3
+ 'no-deprecated-colors': require('./rules/no-deprecated-colors')
4
+ },
5
+ configs: {
6
+ recommended: require('./configs/recommended')
7
+ }
8
+ }
@@ -0,0 +1,194 @@
1
+ const rule = require('../no-deprecated-colors')
2
+ const {RuleTester} = require('eslint')
3
+
4
+ const testDeprecations = {
5
+ 'text.primary': 'fg.default',
6
+ 'bg.primary': 'canvas.default',
7
+ 'auto.green.5': ['success.fg', 'success.emphasis'],
8
+ 'fade.fg10': null,
9
+ 'autocomplete.shadow': 'shadow.medium'
10
+ }
11
+
12
+ jest.mock('@primer/primitives/dist/deprecations/colors', () => testDeprecations)
13
+
14
+ const ruleTester = new RuleTester({
15
+ parserOptions: {
16
+ ecmaVersion: 'latest',
17
+ sourceType: 'module',
18
+ ecmaFeatures: {
19
+ jsx: true
20
+ }
21
+ }
22
+ })
23
+
24
+ ruleTester.run('no-deprecated-colors', rule, {
25
+ valid: [
26
+ `import {Box} from '@other/design-system'; <Box color="text.primary">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")`,
32
+ `import {Box} from '@primer/components'; <Box sx={styles}>Hello</Box>`,
33
+ `import {Box} from '@primer/components'; <Box sx={{color: text.primary}}>Hello</Box>`,
34
+ `import {Box} from '@primer/components'; <Box sx={{color: "fg.default"}}>Hello</Box>`
35
+ ],
36
+ invalid: [
37
+ {
38
+ code: `import {Box} from "@primer/components"; function Example() { return <Box color="text.primary">Hello</Box> }`,
39
+ output: `import {Box} from "@primer/components"; function Example() { return <Box color="fg.default">Hello</Box> }`,
40
+ errors: [
41
+ {
42
+ message: '"text.primary" is deprecated. Use "fg.default" instead.'
43
+ }
44
+ ]
45
+ },
46
+ {
47
+ code: `import Box from '@primer/components/lib-esm/Box'; function Example() { return <Box color="text.primary">Hello</Box> }`,
48
+ output: `import Box from '@primer/components/lib-esm/Box'; function Example() { return <Box color="fg.default">Hello</Box> }`,
49
+ errors: [
50
+ {
51
+ message: '"text.primary" is deprecated. Use "fg.default" instead.'
52
+ }
53
+ ]
54
+ },
55
+ {
56
+ code: `import {Box} from "@primer/components"; const Example = () => <Box color="text.primary">Hello</Box>`,
57
+ output: `import {Box} from "@primer/components"; const Example = () => <Box color="fg.default">Hello</Box>`,
58
+ errors: [
59
+ {
60
+ message: '"text.primary" is deprecated. Use "fg.default" instead.'
61
+ }
62
+ ]
63
+ },
64
+ {
65
+ code: `import {Box} from "@primer/components"; <Box bg="bg.primary" m={1} />`,
66
+ output: `import {Box} from "@primer/components"; <Box bg="canvas.default" m={1} />`,
67
+ errors: [
68
+ {
69
+ message: '"bg.primary" is deprecated. Use "canvas.default" instead.'
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ code: `import {Box} from "@primer/components"; <Box sx={{bg: "bg.primary", m: 1, ...rest}} />`,
75
+ output: `import {Box} from "@primer/components"; <Box sx={{bg: "canvas.default", m: 1, ...rest}} />`,
76
+ errors: [
77
+ {
78
+ message: '"bg.primary" is deprecated. Use "canvas.default" instead.'
79
+ }
80
+ ]
81
+ },
82
+ {
83
+ code: `import {Box} from "@primer/components"; <Box sx={{boxShadow: theme => theme.shadows.autocomplete.shadow}} />`,
84
+ output: `import {Box} from "@primer/components"; <Box sx={{boxShadow: theme => theme.shadows.shadow.medium}} />`,
85
+ errors: [
86
+ {
87
+ message: '"theme.shadows.autocomplete.shadow" is deprecated. Use "theme.shadows.shadow.medium" instead.'
88
+ }
89
+ ]
90
+ },
91
+ {
92
+ code: `import {Box} from "@primer/components"; <Box sx={{boxShadow: theme => \`0 1px 2px \${theme.colors.text.primary}\`}} />`,
93
+ output: `import {Box} from "@primer/components"; <Box sx={{boxShadow: theme => \`0 1px 2px \${theme.colors.fg.default}\`}} />`,
94
+ errors: [
95
+ {
96
+ message: '"theme.colors.text.primary" is deprecated. Use "theme.colors.fg.default" instead.'
97
+ }
98
+ ]
99
+ },
100
+ {
101
+ code: `import {Box} from "@primer/components"; <Box sx={{boxShadow: t => \`0 1px 2px \${t.colors.text.primary}\`}} />`,
102
+ output: `import {Box} from "@primer/components"; <Box sx={{boxShadow: t => \`0 1px 2px \${t.colors.fg.default}\`}} />`,
103
+ errors: [
104
+ {
105
+ message: '"t.colors.text.primary" is deprecated. Use "t.colors.fg.default" instead.'
106
+ }
107
+ ]
108
+ },
109
+ {
110
+ code: `import {Box} from "@primer/components"; <Box sx={{"&:hover": {bg: "bg.primary"}}} />`,
111
+ output: `import {Box} from "@primer/components"; <Box sx={{"&:hover": {bg: "canvas.default"}}} />`,
112
+ errors: [
113
+ {
114
+ message: '"bg.primary" is deprecated. Use "canvas.default" instead.'
115
+ }
116
+ ]
117
+ },
118
+ {
119
+ code: `import {Box} from "@primer/components"; <Box color="auto.green.5" />`,
120
+ errors: [
121
+ {
122
+ message: '"auto.green.5" is deprecated.',
123
+ suggestions: [
124
+ {
125
+ desc: 'Use "success.fg" instead.',
126
+ output: `import {Box} from "@primer/components"; <Box color="success.fg" />`
127
+ },
128
+ {
129
+ desc: 'Use "success.emphasis" instead.',
130
+ output: `import {Box} from "@primer/components"; <Box color="success.emphasis" />`
131
+ }
132
+ ]
133
+ }
134
+ ]
135
+ },
136
+ {
137
+ code: `import {Box} from "@primer/components"; <Box color="fade.fg10" />`,
138
+ errors: [
139
+ {
140
+ message:
141
+ '"fade.fg10" is deprecated. Go to https://primer.style/primitives or reach out in the #primer channel on Slack to find a suitable replacement.'
142
+ }
143
+ ]
144
+ },
145
+ {
146
+ code: `import {Box, Text} from "@primer/components"; <Box bg="bg.primary"><Text color="text.primary">Hello</Text></Box>`,
147
+ output: `import {Box, Text} from "@primer/components"; <Box bg="canvas.default"><Text color="fg.default">Hello</Text></Box>`,
148
+ errors: [
149
+ {
150
+ message: '"bg.primary" is deprecated. Use "canvas.default" instead.'
151
+ },
152
+ {
153
+ message: '"text.primary" is deprecated. Use "fg.default" instead.'
154
+ }
155
+ ]
156
+ },
157
+ {
158
+ code: `import {themeGet} from "@primer/components"; themeGet("colors.text.primary")`,
159
+ output: `import {themeGet} from "@primer/components"; themeGet("colors.fg.default")`,
160
+ errors: [
161
+ {
162
+ message: '"colors.text.primary" is deprecated. Use "colors.fg.default" instead.'
163
+ }
164
+ ]
165
+ },
166
+ {
167
+ code: `import {themeGet} from "@primer/components"; themeGet("shadows.autocomplete.shadow")`,
168
+ output: `import {themeGet} from "@primer/components"; themeGet("shadows.shadow.medium")`,
169
+ errors: [
170
+ {
171
+ message: '"shadows.autocomplete.shadow" is deprecated. Use "shadows.shadow.medium" instead.'
172
+ }
173
+ ]
174
+ },
175
+ {
176
+ code: `import {get} from "./constants"; get("colors.text.primary")`,
177
+ output: `import {get} from "./constants"; get("colors.fg.default")`,
178
+ errors: [
179
+ {
180
+ message: '"colors.text.primary" is deprecated. Use "colors.fg.default" instead.'
181
+ }
182
+ ]
183
+ },
184
+ {
185
+ code: `import {get} from "../constants"; get("colors.text.primary")`,
186
+ output: `import {get} from "../constants"; get("colors.fg.default")`,
187
+ errors: [
188
+ {
189
+ message: '"colors.text.primary" is deprecated. Use "colors.fg.default" instead.'
190
+ }
191
+ ]
192
+ }
193
+ ]
194
+ })
@@ -0,0 +1,173 @@
1
+ const deprecations = require('@primer/primitives/dist/deprecations/colors')
2
+ const traverse = require('eslint-traverse')
3
+
4
+ const styledSystemColorProps = ['color', 'bg', 'backgroundColor', 'borderColor', 'textShadow', 'boxShadow']
5
+
6
+ module.exports = {
7
+ meta: {
8
+ type: 'suggestion',
9
+ fixable: 'code'
10
+ },
11
+ create(context) {
12
+ return {
13
+ JSXOpeningElement(node) {
14
+ // Skip if component was not imported from @primer/components
15
+ if (!isPrimerComponent(node.name, context.getScope(node))) {
16
+ return
17
+ }
18
+
19
+ for (const attribute of node.attributes) {
20
+ if (!attribute.name || !attribute.value) {
21
+ continue
22
+ }
23
+
24
+ const propName = attribute.name.name
25
+ const propValue = attribute.value.value
26
+
27
+ // Check for the sx prop
28
+ if (propName === 'sx' && attribute.value.expression.type === 'ObjectExpression') {
29
+ // Search all properties of the sx object (even nested properties)
30
+ traverse(context, attribute.value, path => {
31
+ if (path.node.type === 'Property' && path.node.value.type === 'Literal') {
32
+ const prop = path.node
33
+ const propName = prop.key.name
34
+ const propValue = prop.value.value
35
+
36
+ if (styledSystemColorProps.includes(propName) && Object.keys(deprecations).includes(propValue)) {
37
+ replaceDeprecatedColor(context, prop.value, propValue)
38
+ }
39
+ }
40
+
41
+ // Check functions passed to sx object properties
42
+ // (e.g. boxShadow: theme => `0 1px 2px ${theme.colors.text.primary}` )
43
+ if (path.node.type === 'Property' && path.node.value.type === 'ArrowFunctionExpression') {
44
+ traverse(context, path.node.value.body, path => {
45
+ if (path.node.type === 'MemberExpression') {
46
+ // Convert MemberExpression AST to string
47
+ const code = context.getSourceCode().getText(path.node)
48
+
49
+ const [param, key, ...rest] = code.split('.')
50
+ const name = rest.join('.')
51
+
52
+ if (['colors', 'shadows'].includes(key) && Object.keys(deprecations).includes(name)) {
53
+ replaceDeprecatedColor(
54
+ context,
55
+ path.node,
56
+ name,
57
+ str => [param, key, str].join('.'),
58
+ str => str
59
+ )
60
+ }
61
+
62
+ // Don't traverse any nested member expressions.
63
+ // The root-level member expression gives us all the data we need.
64
+ return traverse.SKIP
65
+ }
66
+ })
67
+ }
68
+ })
69
+ }
70
+
71
+ // Check if styled-system color prop is using a deprecated color
72
+ if (styledSystemColorProps.includes(propName) && Object.keys(deprecations).includes(propValue)) {
73
+ replaceDeprecatedColor(context, attribute.value, propValue)
74
+ }
75
+ }
76
+ },
77
+ CallExpression(node) {
78
+ // Skip if not calling the `themeGet` or `get` function
79
+ // `get` is the internal version of `themeGet` that's used in the primer/react repository
80
+ if (!isThemeGet(node.callee, context.getScope(node)) && !isGet(node.callee, context.getScope(node))) {
81
+ return
82
+ }
83
+
84
+ const [key, ...path] = node.arguments[0].value.split('.')
85
+ const name = path.join('.')
86
+
87
+ if (['colors', 'shadows'].includes(key) && Object.keys(deprecations).includes(name)) {
88
+ replaceDeprecatedColor(context, node.arguments[0], name, str => [key, str].join('.'))
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Get the variable declaration for the given identifier
97
+ */
98
+ function getVariableDeclaration(scope, identifier) {
99
+ if (scope === null) {
100
+ return null
101
+ }
102
+
103
+ for (const variable of scope.variables) {
104
+ if (variable.name === identifier.name) {
105
+ return variable.defs[0]
106
+ }
107
+ }
108
+
109
+ return getVariableDeclaration(scope.upper, identifier)
110
+ }
111
+
112
+ /**
113
+ * Check if the given identifier is imported from the given module
114
+ */
115
+ function isImportedFrom(moduleRegex, identifier, scope) {
116
+ const definition = getVariableDeclaration(scope, identifier)
117
+
118
+ // Return true if the variable was imported from the given module
119
+ return definition && definition.type == 'ImportBinding' && moduleRegex.test(definition.parent.source.value)
120
+ }
121
+
122
+ function isPrimerComponent(identifier, scope) {
123
+ return isImportedFrom(/^@primer\/components/, identifier, scope)
124
+ }
125
+
126
+ function isThemeGet(identifier, scope) {
127
+ return isImportedFrom(/^@primer\/components/, identifier, scope) && identifier.name === 'themeGet'
128
+ }
129
+
130
+ function isGet(identifier, scope) {
131
+ return isImportedFrom(/^\.\.?\/constants$/, identifier, scope) && identifier.name === 'get'
132
+ }
133
+
134
+ function replaceDeprecatedColor(
135
+ context,
136
+ node,
137
+ deprecatedName,
138
+ transformName = str => str,
139
+ transformReplacementValue = str => JSON.stringify(str)
140
+ ) {
141
+ const replacement = deprecations[deprecatedName]
142
+
143
+ if (replacement === null) {
144
+ // No replacement
145
+ context.report({
146
+ node,
147
+ message: `"${transformName(
148
+ deprecatedName
149
+ )}" is deprecated. Go to https://primer.style/primitives or reach out in the #primer channel on Slack to find a suitable replacement.`
150
+ })
151
+ } else if (Array.isArray(replacement)) {
152
+ // Multiple possible replacements
153
+ context.report({
154
+ node,
155
+ message: `"${transformName(deprecatedName)}" is deprecated.`,
156
+ suggest: replacement.map(replacementValue => ({
157
+ desc: `Use "${transformName(replacementValue)}" instead.`,
158
+ fix(fixer) {
159
+ return fixer.replaceText(node, transformReplacementValue(transformName(replacementValue)))
160
+ }
161
+ }))
162
+ })
163
+ } else {
164
+ // One replacement
165
+ context.report({
166
+ node,
167
+ message: `"${transformName(deprecatedName)}" is deprecated. Use "${transformName(replacement)}" instead.`,
168
+ fix(fixer) {
169
+ return fixer.replaceText(node, transformReplacementValue(transformName(replacement)))
170
+ }
171
+ })
172
+ }
173
+ }