@backstage/eslint-plugin 0.3.0-next.0 → 0.3.1-next.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @backstage/eslint-plugin
2
2
 
3
+ ## 0.3.1-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 5d80f77: Adds a new `@backstage/no-deprecated-bui-tokens` lint rule that warns when a deprecated `@backstage/ui` CSS token is referenced in a JavaScript or TypeScript file (including CSS-in-JS patterns and template literals). The rule is included in the `recommended` config, so plugin authors using `plugin:@backstage/recommended` will receive warnings automatically when using tokens that have been superseded by the new semantic color families. Note that plain CSS and CSS module files are outside ESLint's scope and are not covered by this rule.
8
+
9
+ ## 0.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - ab1cdbb: Added a new `no-self-package-imports` lint rule, enabled as `error` in the recommended config, that reports when a package imports itself by its own name instead of using a relative path. This pattern causes circular initialization errors in bundled ESM and with `jest.requireActual`.
14
+
3
15
  ## 0.3.0-next.0
4
16
 
5
17
  ### Minor Changes
@@ -0,0 +1,108 @@
1
+ # no-deprecated-bui-tokens
2
+
3
+ Warns when a deprecated `@backstage/ui` CSS token is referenced inside a JS/TS string or template literal.
4
+
5
+ The `@backstage/ui` design token system has been updated with a new set of semantic color families. The old tokens still work for backward compatibility, but they are scheduled for removal in a future major version.
6
+
7
+ ## Rule Details
8
+
9
+ This rule reports a warning for any string value that contains a reference to a deprecated BUI CSS custom property, such as `var(--bui-bg-solid)` or `var(--bui-fg-danger)`.
10
+
11
+ Examples of **incorrect** code for this rule:
12
+
13
+ ```js
14
+ // Deprecated background token
15
+ const style = { background: 'var(--bui-bg-solid)' };
16
+
17
+ // Deprecated foreground token
18
+ const style = { color: 'var(--bui-fg-danger)' };
19
+
20
+ // Deprecated token in a template literal
21
+ const css = `border: 1px solid var(--bui-border-danger)`;
22
+ ```
23
+
24
+ Examples of **correct** code for this rule:
25
+
26
+ ```js
27
+ // New accent family
28
+ const style = { background: 'var(--bui-accent-bg)' };
29
+
30
+ // New semantic negative family
31
+ const style = { color: 'var(--bui-negative-fg-subdued)' };
32
+
33
+ // New semantic positive family
34
+ const style = { borderColor: 'var(--bui-positive-border)' };
35
+ ```
36
+
37
+ ## Migration Guide
38
+
39
+ Replace deprecated tokens with their equivalents from the new semantic families.
40
+
41
+ ### Neutral backgrounds
42
+
43
+ The neutral background tokens (`--bui-bg-app`, `--bui-bg-neutral-1..4`) are now the active semantic tokens with updated solid-color values. The `-hover`, `-pressed`, and `-disabled` interaction variants remain deprecated:
44
+
45
+ | Deprecated | Replacement |
46
+ | ----------------------------- | --------------------- |
47
+ | `--bui-bg-neutral-1-hover` | _(remove or restyle)_ |
48
+ | `--bui-bg-neutral-1-pressed` | _(remove or restyle)_ |
49
+ | `--bui-bg-neutral-1-disabled` | _(remove or restyle)_ |
50
+ | `--bui-bg-neutral-2-hover` | _(remove or restyle)_ |
51
+ | `--bui-bg-neutral-2-pressed` | _(remove or restyle)_ |
52
+ | `--bui-bg-neutral-2-disabled` | _(remove or restyle)_ |
53
+ | `--bui-bg-neutral-3-hover` | _(remove or restyle)_ |
54
+ | `--bui-bg-neutral-3-pressed` | _(remove or restyle)_ |
55
+ | `--bui-bg-neutral-3-disabled` | _(remove or restyle)_ |
56
+ | `--bui-bg-neutral-4-hover` | _(remove or restyle)_ |
57
+ | `--bui-bg-neutral-4-pressed` | _(remove or restyle)_ |
58
+ | `--bui-bg-neutral-4-disabled` | _(remove or restyle)_ |
59
+
60
+ ### Foreground
61
+
62
+ | Deprecated | Replacement |
63
+ | ------------------ | ----------------------- |
64
+ | `--bui-fg-danger` | `--bui-fg-negative` |
65
+ | `--bui-fg-success` | `--bui-fg-positive` |
66
+ | `--bui-fg-info` | `--bui-fg-announcement` |
67
+
68
+ ### Accent
69
+
70
+ | Deprecated | Replacement |
71
+ | ------------------------- | -------------------------- |
72
+ | `--bui-bg-solid` | `--bui-accent-bg` |
73
+ | `--bui-bg-solid-hover` | `--bui-accent-bg-hover` |
74
+ | `--bui-bg-solid-disabled` | `--bui-accent-bg-disabled` |
75
+ | `--bui-fg-solid` | `--bui-accent-fg` |
76
+ | `--bui-fg-solid-disabled` | `--bui-accent-fg-disabled` |
77
+
78
+ ### Positive
79
+
80
+ | Deprecated | Replacement |
81
+ | ------------------------ | --------------------------- |
82
+ | `--bui-bg-success` | `--bui-positive-bg-subdued` |
83
+ | `--bui-fg-success-on-bg` | `--bui-positive-fg-subdued` |
84
+ | `--bui-border-success` | `--bui-positive-border` |
85
+
86
+ ### Negative
87
+
88
+ | Deprecated | Replacement |
89
+ | ----------------------- | --------------------------- |
90
+ | `--bui-bg-danger` | `--bui-negative-bg-subdued` |
91
+ | `--bui-fg-danger-on-bg` | `--bui-negative-fg-subdued` |
92
+ | `--bui-border-danger` | `--bui-negative-border` |
93
+
94
+ ### Warning
95
+
96
+ | Deprecated | Replacement |
97
+ | ------------------------ | -------------------------- |
98
+ | `--bui-bg-warning` | `--bui-warning-bg-subdued` |
99
+ | `--bui-fg-warning-on-bg` | `--bui-warning-fg-subdued` |
100
+ | `--bui-border-warning` | `--bui-warning-border` |
101
+
102
+ ### Announcement
103
+
104
+ | Deprecated | Replacement |
105
+ | --------------------- | ------------------------------- |
106
+ | `--bui-bg-info` | `--bui-announcement-bg-subdued` |
107
+ | `--bui-fg-info-on-bg` | `--bui-announcement-fg-subdued` |
108
+ | `--bui-border-info` | `--bui-announcement-border` |
package/index.js CHANGED
@@ -25,6 +25,7 @@ module.exports = {
25
25
  '@backstage/no-mixed-plugin-imports': 'warn',
26
26
  '@backstage/no-ui-css-imports-in-non-frontend': 'error',
27
27
  '@backstage/no-self-package-imports': 'error',
28
+ '@backstage/no-deprecated-bui-tokens': 'warn',
28
29
  },
29
30
  },
30
31
  },
@@ -36,5 +37,6 @@ module.exports = {
36
37
  'no-mixed-plugin-imports': require('./rules/no-mixed-plugin-imports'),
37
38
  'no-ui-css-imports-in-non-frontend': require('./rules/no-ui-css-imports-in-non-frontend'),
38
39
  'no-self-package-imports': require('./rules/no-self-package-imports'),
40
+ 'no-deprecated-bui-tokens': require('./rules/no-deprecated-bui-tokens'),
39
41
  },
40
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/eslint-plugin",
3
- "version": "0.3.0-next.0",
3
+ "version": "0.3.1-next.0",
4
4
  "description": "Backstage ESLint plugin",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -22,7 +22,7 @@
22
22
  "minimatch": "^10.2.1"
23
23
  },
24
24
  "devDependencies": {
25
- "@backstage/cli": "0.36.2-next.1",
25
+ "@backstage/cli": "0.36.3-next.1",
26
26
  "@types/estree": "^1.0.5",
27
27
  "eslint": "^8.33.0"
28
28
  }
@@ -0,0 +1,117 @@
1
+ /*
2
+ * Copyright 2025 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ // @ts-check
18
+
19
+ /** @type {string[]} */
20
+ const DEPRECATED_TOKENS = [
21
+ '--bui-bg-solid',
22
+ '--bui-bg-solid-hover',
23
+ '--bui-bg-solid-pressed',
24
+ '--bui-bg-solid-disabled',
25
+ '--bui-bg-neutral-1-hover',
26
+ '--bui-bg-neutral-1-pressed',
27
+ '--bui-bg-neutral-1-disabled',
28
+ '--bui-bg-neutral-2-hover',
29
+ '--bui-bg-neutral-2-pressed',
30
+ '--bui-bg-neutral-2-disabled',
31
+ '--bui-bg-neutral-3-hover',
32
+ '--bui-bg-neutral-3-pressed',
33
+ '--bui-bg-neutral-3-disabled',
34
+ '--bui-bg-neutral-4-hover',
35
+ '--bui-bg-neutral-4-pressed',
36
+ '--bui-bg-neutral-4-disabled',
37
+ '--bui-bg-danger',
38
+ '--bui-bg-warning',
39
+ '--bui-bg-success',
40
+ '--bui-bg-info',
41
+ '--bui-fg-solid',
42
+ '--bui-fg-solid-disabled',
43
+ '--bui-fg-danger-on-bg',
44
+ '--bui-fg-warning-on-bg',
45
+ '--bui-fg-success-on-bg',
46
+ '--bui-fg-info-on-bg',
47
+ '--bui-fg-danger',
48
+ '--bui-fg-success',
49
+ '--bui-fg-info',
50
+ '--bui-border-info',
51
+ '--bui-border-danger',
52
+ '--bui-border-warning',
53
+ '--bui-border-success',
54
+ '--bui-shadow',
55
+ ];
56
+
57
+ const DEPRECATED_SET = new Set(DEPRECATED_TOKENS);
58
+
59
+ /**
60
+ * Extracts all CSS custom property names referenced inside a string value,
61
+ * e.g. "var(--bui-bg-solid)" → ["--bui-bg-solid"]
62
+ * @param {string} value
63
+ * @returns {string[]}
64
+ */
65
+ function extractTokenNames(value) {
66
+ const matches = value.match(/--bui-[\w-]+/g);
67
+ return matches ?? [];
68
+ }
69
+
70
+ /** @type {import('eslint').Rule.RuleModule} */
71
+ module.exports = {
72
+ meta: {
73
+ type: 'suggestion',
74
+ docs: {
75
+ description:
76
+ 'Warn when deprecated Backstage UI CSS tokens are referenced in JS/TS string literals.',
77
+ url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md',
78
+ },
79
+ messages: {
80
+ deprecated:
81
+ "'{{token}}' is a deprecated BUI token. Replace it with the appropriate current BUI token for its usage (for example, a semantic intent, surface/background, or shadow token); see this rule's migration guide for the correct mapping.",
82
+ },
83
+ schema: [],
84
+ },
85
+
86
+ create(context) {
87
+ /**
88
+ * @param {import('estree').Node} node
89
+ * @param {string} value
90
+ */
91
+ function checkValue(node, value) {
92
+ for (const token of extractTokenNames(value)) {
93
+ if (DEPRECATED_SET.has(token)) {
94
+ context.report({
95
+ node,
96
+ messageId: 'deprecated',
97
+ data: { token },
98
+ });
99
+ }
100
+ }
101
+ }
102
+
103
+ return {
104
+ Literal(node) {
105
+ if (typeof node.value === 'string') {
106
+ checkValue(node, node.value);
107
+ }
108
+ },
109
+
110
+ TemplateElement(node) {
111
+ if (node.value?.cooked) {
112
+ checkValue(node, node.value.cooked);
113
+ }
114
+ },
115
+ };
116
+ },
117
+ };
@@ -0,0 +1,257 @@
1
+ /*
2
+ * Copyright 2025 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { RuleTester } from 'eslint';
18
+ import rule from '../rules/no-deprecated-bui-tokens';
19
+
20
+ const ruleTester = new RuleTester({
21
+ parserOptions: {
22
+ sourceType: 'module',
23
+ ecmaVersion: 2021,
24
+ },
25
+ });
26
+
27
+ ruleTester.run('no-deprecated-bui-tokens', rule, {
28
+ valid: [
29
+ // Non-deprecated tokens — should not warn
30
+ { code: `const s = 'var(--bui-fg-primary)'` },
31
+ { code: `const s = 'var(--bui-fg-secondary)'` },
32
+ { code: `const s = 'var(--bui-fg-disabled)'` },
33
+ { code: `const s = 'var(--bui-fg-warning)'` },
34
+ { code: `const s = 'var(--bui-fg-positive)'` },
35
+ { code: `const s = 'var(--bui-fg-negative)'` },
36
+ { code: `const s = 'var(--bui-fg-announcement)'` },
37
+ { code: `const s = 'var(--bui-border-1)'` },
38
+ { code: `const s = 'var(--bui-border-2)'` },
39
+ { code: `const s = 'var(--bui-accent-bg)'` },
40
+ { code: `const s = 'var(--bui-accent-bg-hover)'` },
41
+ { code: `const s = 'var(--bui-accent-bg-disabled)'` },
42
+ { code: `const s = 'var(--bui-accent-fg)'` },
43
+ { code: `const s = 'var(--bui-accent-fg-disabled)'` },
44
+ { code: `const s = 'var(--bui-negative-bg)'` },
45
+ { code: `const s = 'var(--bui-positive-bg)'` },
46
+ { code: `const s = 'var(--bui-warning-bg)'` },
47
+ { code: `const s = 'var(--bui-announcement-bg)'` },
48
+ { code: `const s = 'var(--bui-gray-1)'` },
49
+ { code: `const s = 'var(--bui-bg-app)'` },
50
+ { code: `const s = 'var(--bui-bg-neutral-1)'` },
51
+ { code: `const s = 'var(--bui-bg-neutral-2)'` },
52
+ { code: `const s = 'var(--bui-bg-neutral-3)'` },
53
+ { code: `const s = 'var(--bui-bg-neutral-4)'` },
54
+ // Unrelated strings — should not warn
55
+ { code: `const s = 'some-other-string'` },
56
+ { code: `const n = 42` },
57
+ ],
58
+
59
+ invalid: [
60
+ // Plain string literals
61
+ {
62
+ code: `const s = 'var(--bui-bg-solid)'`,
63
+ errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-solid' } }],
64
+ },
65
+ {
66
+ code: `const s = 'var(--bui-bg-solid-hover)'`,
67
+ errors: [
68
+ { messageId: 'deprecated', data: { token: '--bui-bg-solid-hover' } },
69
+ ],
70
+ },
71
+ {
72
+ code: `const s = 'var(--bui-bg-solid-pressed)'`,
73
+ errors: [
74
+ { messageId: 'deprecated', data: { token: '--bui-bg-solid-pressed' } },
75
+ ],
76
+ },
77
+ {
78
+ code: `const s = 'var(--bui-bg-solid-disabled)'`,
79
+ errors: [
80
+ {
81
+ messageId: 'deprecated',
82
+ data: { token: '--bui-bg-solid-disabled' },
83
+ },
84
+ ],
85
+ },
86
+ {
87
+ code: `const s = 'var(--bui-bg-neutral-2-hover)'`,
88
+ errors: [
89
+ {
90
+ messageId: 'deprecated',
91
+ data: { token: '--bui-bg-neutral-2-hover' },
92
+ },
93
+ ],
94
+ },
95
+ {
96
+ code: `const s = 'var(--bui-bg-neutral-3-pressed)'`,
97
+ errors: [
98
+ {
99
+ messageId: 'deprecated',
100
+ data: { token: '--bui-bg-neutral-3-pressed' },
101
+ },
102
+ ],
103
+ },
104
+ {
105
+ code: `const s = 'var(--bui-bg-neutral-4-disabled)'`,
106
+ errors: [
107
+ {
108
+ messageId: 'deprecated',
109
+ data: { token: '--bui-bg-neutral-4-disabled' },
110
+ },
111
+ ],
112
+ },
113
+ {
114
+ code: `const s = 'var(--bui-bg-danger)'`,
115
+ errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-danger' } }],
116
+ },
117
+ {
118
+ code: `const s = 'var(--bui-bg-warning)'`,
119
+ errors: [
120
+ { messageId: 'deprecated', data: { token: '--bui-bg-warning' } },
121
+ ],
122
+ },
123
+ {
124
+ code: `const s = 'var(--bui-bg-success)'`,
125
+ errors: [
126
+ { messageId: 'deprecated', data: { token: '--bui-bg-success' } },
127
+ ],
128
+ },
129
+ {
130
+ code: `const s = 'var(--bui-bg-info)'`,
131
+ errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-info' } }],
132
+ },
133
+ {
134
+ code: `const s = 'var(--bui-fg-solid)'`,
135
+ errors: [{ messageId: 'deprecated', data: { token: '--bui-fg-solid' } }],
136
+ },
137
+ {
138
+ code: `const s = 'var(--bui-fg-solid-disabled)'`,
139
+ errors: [
140
+ { messageId: 'deprecated', data: { token: '--bui-fg-solid-disabled' } },
141
+ ],
142
+ },
143
+ {
144
+ code: `const s = 'var(--bui-fg-danger-on-bg)'`,
145
+ errors: [
146
+ { messageId: 'deprecated', data: { token: '--bui-fg-danger-on-bg' } },
147
+ ],
148
+ },
149
+ {
150
+ code: `const s = 'var(--bui-fg-warning-on-bg)'`,
151
+ errors: [
152
+ { messageId: 'deprecated', data: { token: '--bui-fg-warning-on-bg' } },
153
+ ],
154
+ },
155
+ {
156
+ code: `const s = 'var(--bui-fg-success-on-bg)'`,
157
+ errors: [
158
+ { messageId: 'deprecated', data: { token: '--bui-fg-success-on-bg' } },
159
+ ],
160
+ },
161
+ {
162
+ code: `const s = 'var(--bui-fg-info-on-bg)'`,
163
+ errors: [
164
+ { messageId: 'deprecated', data: { token: '--bui-fg-info-on-bg' } },
165
+ ],
166
+ },
167
+ {
168
+ code: `const s = 'var(--bui-fg-danger)'`,
169
+ errors: [{ messageId: 'deprecated', data: { token: '--bui-fg-danger' } }],
170
+ },
171
+ {
172
+ code: `const s = 'var(--bui-fg-success)'`,
173
+ errors: [
174
+ { messageId: 'deprecated', data: { token: '--bui-fg-success' } },
175
+ ],
176
+ },
177
+ {
178
+ code: `const s = 'var(--bui-fg-info)'`,
179
+ errors: [{ messageId: 'deprecated', data: { token: '--bui-fg-info' } }],
180
+ },
181
+ {
182
+ code: `const s = 'var(--bui-border-info)'`,
183
+ errors: [
184
+ { messageId: 'deprecated', data: { token: '--bui-border-info' } },
185
+ ],
186
+ },
187
+ {
188
+ code: `const s = 'var(--bui-border-danger)'`,
189
+ errors: [
190
+ { messageId: 'deprecated', data: { token: '--bui-border-danger' } },
191
+ ],
192
+ },
193
+ {
194
+ code: `const s = 'var(--bui-border-warning)'`,
195
+ errors: [
196
+ { messageId: 'deprecated', data: { token: '--bui-border-warning' } },
197
+ ],
198
+ },
199
+ {
200
+ code: `const s = 'var(--bui-border-success)'`,
201
+ errors: [
202
+ { messageId: 'deprecated', data: { token: '--bui-border-success' } },
203
+ ],
204
+ },
205
+ {
206
+ code: `const s = 'var(--bui-shadow)'`,
207
+ errors: [{ messageId: 'deprecated', data: { token: '--bui-shadow' } }],
208
+ },
209
+
210
+ // Template literals
211
+ {
212
+ code: 'const s = `var(--bui-bg-solid)`',
213
+ errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-solid' } }],
214
+ },
215
+ {
216
+ code: 'const s = `var(--bui-fg-danger)`',
217
+ errors: [{ messageId: 'deprecated', data: { token: '--bui-fg-danger' } }],
218
+ },
219
+ {
220
+ code: 'const s = `border: 1px solid var(--bui-border-danger)`',
221
+ errors: [
222
+ { messageId: 'deprecated', data: { token: '--bui-border-danger' } },
223
+ ],
224
+ },
225
+
226
+ // Multiple deprecated tokens in one string — one error per token
227
+ {
228
+ code: `const s = 'background: var(--bui-bg-solid); color: var(--bui-fg-solid)'`,
229
+ errors: [
230
+ { messageId: 'deprecated', data: { token: '--bui-bg-solid' } },
231
+ { messageId: 'deprecated', data: { token: '--bui-fg-solid' } },
232
+ ],
233
+ },
234
+
235
+ // Inline style usage patterns (JSX)
236
+ {
237
+ code: `const el = <div style={{ background: 'var(--bui-bg-danger)' }} />`,
238
+ parserOptions: {
239
+ sourceType: 'module',
240
+ ecmaVersion: 2021,
241
+ ecmaFeatures: { jsx: true },
242
+ },
243
+ errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-danger' } }],
244
+ },
245
+ {
246
+ code: `const el = <div style={{ color: 'var(--bui-fg-success)' }} />`,
247
+ parserOptions: {
248
+ sourceType: 'module',
249
+ ecmaVersion: 2021,
250
+ ecmaFeatures: { jsx: true },
251
+ },
252
+ errors: [
253
+ { messageId: 'deprecated', data: { token: '--bui-fg-success' } },
254
+ ],
255
+ },
256
+ ],
257
+ });