@kong/eslint-plugin-design-tokens 0.0.1
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/LICENSE +201 -0
- package/README.md +174 -0
- package/index.mjs +35 -0
- package/package.json +47 -0
- package/rules/token-constant-requires-css-var/__tests__/index.test.mjs +1017 -0
- package/rules/token-constant-requires-css-var/index.mjs +500 -0
- package/utilities/index.mjs +18 -0
|
@@ -0,0 +1,1017 @@
|
|
|
1
|
+
import { describe, it } from 'vitest'
|
|
2
|
+
import { RuleTester } from 'eslint'
|
|
3
|
+
import vueParser from 'vue-eslint-parser'
|
|
4
|
+
import * as tsParser from '@typescript-eslint/parser'
|
|
5
|
+
import rule from '../index.mjs'
|
|
6
|
+
|
|
7
|
+
// Wire up vitest's describe/it so RuleTester integrates with the vitest reporter
|
|
8
|
+
RuleTester.describe = describe
|
|
9
|
+
RuleTester.it = it
|
|
10
|
+
RuleTester.itOnly = it.only
|
|
11
|
+
|
|
12
|
+
const tester = new RuleTester({
|
|
13
|
+
languageOptions: {
|
|
14
|
+
parser: vueParser,
|
|
15
|
+
parserOptions: {
|
|
16
|
+
ecmaVersion: 2020,
|
|
17
|
+
sourceType: 'module',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const RULE_NAME = '@kong/design-tokens/token-constant-requires-css-var'
|
|
23
|
+
|
|
24
|
+
// SFC source helpers
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Builds a minimal `<script setup>` + `<template>` SFC string.
|
|
28
|
+
* @param {object} [parts]
|
|
29
|
+
* @param {string} [parts.script] - `<script setup>` body
|
|
30
|
+
* @param {string} [parts.template] - `<template>` body
|
|
31
|
+
* @param {string} [parts.lang] - Optional `lang` attribute (e.g. `'ts'`)
|
|
32
|
+
*/
|
|
33
|
+
function sfc({ script = '', template = '', lang } = {}) {
|
|
34
|
+
return [
|
|
35
|
+
`<script setup${lang ? ` lang="${lang}"` : ''}>`,
|
|
36
|
+
script.trim(),
|
|
37
|
+
'</script>',
|
|
38
|
+
'<template>',
|
|
39
|
+
template.trim(),
|
|
40
|
+
'</template>',
|
|
41
|
+
].join('\n')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Shorthand for an SFC that imports one token from `@kong/design-tokens`.
|
|
46
|
+
* @param {string} varName - The exported constant name (e.g. `KUI_COLOR_TEXT_INVERSE`)
|
|
47
|
+
* @param {string} template - The `<template>` body
|
|
48
|
+
* @param {string} [alias] - Optional local alias (`import { varName as alias }`)
|
|
49
|
+
* @param {string} [lang] - Optional `<script setup>` lang (e.g. `'ts'`)
|
|
50
|
+
*/
|
|
51
|
+
function withImport(varName, template, alias, lang) {
|
|
52
|
+
const specifier = alias ? `${varName} as ${alias}` : varName
|
|
53
|
+
return sfc({
|
|
54
|
+
script: `import { ${specifier} } from '@kong/design-tokens'`,
|
|
55
|
+
template,
|
|
56
|
+
lang,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** TypeScript variant of {@link sfc} (`<script setup lang="ts">`). */
|
|
61
|
+
function sfcTs({ script = '', template = '' } = {}) {
|
|
62
|
+
return sfc({ script, template, lang: 'ts' })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* TypeScript variant of {@link withImport} (`<script setup lang="ts">`).
|
|
67
|
+
* @param {string} varName - The exported constant name (e.g. `KUI_COLOR_TEXT_INVERSE`)
|
|
68
|
+
* @param {string} template - The `<template>` body
|
|
69
|
+
* @param {string} [alias] - Optional local alias (`import { varName as alias }`)
|
|
70
|
+
*/
|
|
71
|
+
function withImportTs(varName, template, alias) {
|
|
72
|
+
return withImport(varName, template, alias, 'ts')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
tester.run(RULE_NAME, rule, {
|
|
76
|
+
valid: [
|
|
77
|
+
// Already properly wrapped — idempotency check
|
|
78
|
+
{
|
|
79
|
+
filename: 'test.vue',
|
|
80
|
+
code: withImport(
|
|
81
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
82
|
+
'<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
83
|
+
),
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// Already wrapped inside a ternary branch
|
|
87
|
+
{
|
|
88
|
+
filename: 'test.vue',
|
|
89
|
+
code: withImport(
|
|
90
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
91
|
+
"<div :color=\"cond ? `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` : 'red'\" />",
|
|
92
|
+
),
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// Identifier not imported from @kong/design-tokens
|
|
96
|
+
{
|
|
97
|
+
filename: 'test.vue',
|
|
98
|
+
code: sfc({
|
|
99
|
+
script: "const myColor = '#fff'",
|
|
100
|
+
template: '<div :color="myColor" />',
|
|
101
|
+
}),
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// Import from a different package — not tracked
|
|
105
|
+
{
|
|
106
|
+
filename: 'test.vue',
|
|
107
|
+
code: sfc({
|
|
108
|
+
script: "import { KUI_COLOR_TEXT_INVERSE } from 'other-package'",
|
|
109
|
+
template: '<div :color="KUI_COLOR_TEXT_INVERSE" />',
|
|
110
|
+
}),
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
// KUI token in v-if — not a v-bind style value
|
|
114
|
+
{
|
|
115
|
+
filename: 'test.vue',
|
|
116
|
+
code: withImport(
|
|
117
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
118
|
+
'<div v-if="KUI_COLOR_TEXT_INVERSE" />',
|
|
119
|
+
),
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// KUI token in mustache interpolation — not a v-bind
|
|
123
|
+
{
|
|
124
|
+
filename: 'test.vue',
|
|
125
|
+
code: withImport(
|
|
126
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
127
|
+
'<div>{{ KUI_COLOR_TEXT_INVERSE }}</div>',
|
|
128
|
+
),
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// Namespace import — not individually tracked
|
|
132
|
+
{
|
|
133
|
+
filename: 'test.vue',
|
|
134
|
+
code: sfc({
|
|
135
|
+
script: "import * as tokens from '@kong/design-tokens'",
|
|
136
|
+
template: '<div :color="tokens.KUI_COLOR_TEXT_INVERSE" />',
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// Static (non-binding) attribute — no colon
|
|
141
|
+
{
|
|
142
|
+
filename: 'test.vue',
|
|
143
|
+
code: withImport(
|
|
144
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
145
|
+
'<div color="someStaticValue" />',
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// v-on directive — not a v-bind, never visited by the rule
|
|
150
|
+
{
|
|
151
|
+
filename: 'test.vue',
|
|
152
|
+
code: withImport(
|
|
153
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
154
|
+
'<div @click="handler(KUI_COLOR_TEXT_INVERSE)" />',
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* MemberExpression property name — KUI token is the key, not the value reference.
|
|
160
|
+
* `walkExpression` only walks the object side of a MemberExpression, not the property.
|
|
161
|
+
*/
|
|
162
|
+
{
|
|
163
|
+
filename: 'test.vue',
|
|
164
|
+
code: withImport(
|
|
165
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
166
|
+
'<div :color="theme.KUI_COLOR_TEXT_INVERSE" />',
|
|
167
|
+
),
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// Partially wrapped ternary — the already-wrapped branch is idempotent (no re-report)
|
|
171
|
+
{
|
|
172
|
+
filename: 'test.vue',
|
|
173
|
+
code: withImport(
|
|
174
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
175
|
+
'<div :color="cond ? `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` : `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
176
|
+
),
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Idempotency with import alias — the CSS var is derived from the canonical imported
|
|
181
|
+
* name, not the local alias, so the already-wrapped form must be recognised as valid.
|
|
182
|
+
*/
|
|
183
|
+
{
|
|
184
|
+
filename: 'test.vue',
|
|
185
|
+
code: withImport(
|
|
186
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
187
|
+
'<div :color="`var(--kui-color-text-inverse, ${myColor})`" />',
|
|
188
|
+
'myColor',
|
|
189
|
+
),
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Idempotency is whitespace-tolerant — extra spaces inside var() are valid CSS
|
|
194
|
+
* and must not cause a false re-report after a developer manually writes the binding.
|
|
195
|
+
*/
|
|
196
|
+
{
|
|
197
|
+
filename: 'test.vue',
|
|
198
|
+
code: withImport(
|
|
199
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
200
|
+
'<div :color="`var( --kui-color-text-inverse , ${KUI_COLOR_TEXT_INVERSE} )`" />',
|
|
201
|
+
),
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Options API `<script>` (no setup attribute) — module-scope KUI aliases must not
|
|
206
|
+
* be tracked because they are not directly accessible in the template; the template
|
|
207
|
+
* `myColor` refers to a prop/data/computed property, not the module-level const.
|
|
208
|
+
*/
|
|
209
|
+
{
|
|
210
|
+
filename: 'test.vue',
|
|
211
|
+
code: [
|
|
212
|
+
'<script>',
|
|
213
|
+
"import { KUI_COLOR_TEXT_INVERSE } from '@kong/design-tokens'",
|
|
214
|
+
'const myColor = KUI_COLOR_TEXT_INVERSE',
|
|
215
|
+
"export default { props: ['myColor'] }",
|
|
216
|
+
'</script>',
|
|
217
|
+
'<template>',
|
|
218
|
+
'<div :color="myColor" />',
|
|
219
|
+
'</template>',
|
|
220
|
+
].join('\n'),
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* KUI_BREAKPOINT_* tokens are excluded — they are viewport pixel widths, not CSS
|
|
225
|
+
* custom properties, so DOM-level theming does not apply.
|
|
226
|
+
*/
|
|
227
|
+
{
|
|
228
|
+
filename: 'test.vue',
|
|
229
|
+
code: withImport(
|
|
230
|
+
'KUI_BREAKPOINT_PHABLET',
|
|
231
|
+
'<div :style="{ maxWidth: KUI_BREAKPOINT_PHABLET }" />',
|
|
232
|
+
),
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
// KUI_BREAKPOINT_* with alias — exclusion covers the canonical imported name
|
|
236
|
+
{
|
|
237
|
+
filename: 'test.vue',
|
|
238
|
+
code: withImport(
|
|
239
|
+
'KUI_BREAKPOINT_PHABLET',
|
|
240
|
+
'<div :style="{ maxWidth: bp }" />',
|
|
241
|
+
'bp',
|
|
242
|
+
),
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Shadowing: a `v-for` item that happens to share a token's name resolves to
|
|
247
|
+
* the loop variable, not the import, so it must not be flagged.
|
|
248
|
+
*/
|
|
249
|
+
{
|
|
250
|
+
filename: 'test.vue',
|
|
251
|
+
code: withImport(
|
|
252
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
253
|
+
'<div v-for="KUI_COLOR_TEXT_INVERSE in colors" :key="KUI_COLOR_TEXT_INVERSE" :color="KUI_COLOR_TEXT_INVERSE" />',
|
|
254
|
+
),
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Shadowing: a scoped-slot prop that shares a token's name resolves to the
|
|
259
|
+
* slot variable, not the import.
|
|
260
|
+
*/
|
|
261
|
+
{
|
|
262
|
+
filename: 'test.vue',
|
|
263
|
+
code: withImport(
|
|
264
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
265
|
+
'<Comp><template #default="{ KUI_COLOR_TEXT_INVERSE }"><div :color="KUI_COLOR_TEXT_INVERSE" /></template></Comp>',
|
|
266
|
+
),
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Script-setup alias already wrapped with correct CSS var — theming works,
|
|
271
|
+
* so this is idempotent and must not be reported.
|
|
272
|
+
*/
|
|
273
|
+
{
|
|
274
|
+
filename: 'test.vue',
|
|
275
|
+
code: sfc({
|
|
276
|
+
script: [
|
|
277
|
+
"import { KUI_COLOR_TEXT_INVERSE } from '@kong/design-tokens'",
|
|
278
|
+
'const myColor = KUI_COLOR_TEXT_INVERSE',
|
|
279
|
+
].join('\n'),
|
|
280
|
+
template: '<div :color="`var(--kui-color-text-inverse, ${myColor})`" />',
|
|
281
|
+
}),
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Function-scoped `const local = KUI_X` must not be tracked — template binding
|
|
286
|
+
* uses a different `myColor` (e.g. from a prop) and this would be a false positive.
|
|
287
|
+
*/
|
|
288
|
+
{
|
|
289
|
+
filename: 'test.vue',
|
|
290
|
+
code: sfc({
|
|
291
|
+
script: [
|
|
292
|
+
"import { KUI_COLOR_TEXT_INVERSE } from '@kong/design-tokens'",
|
|
293
|
+
'function helper() { const myColor = KUI_COLOR_TEXT_INVERSE; return myColor }',
|
|
294
|
+
].join('\n'),
|
|
295
|
+
template: '<div :color="myColor" />',
|
|
296
|
+
}),
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// Idempotency across token families — the rule must not re-report already-fixed bindings
|
|
300
|
+
{
|
|
301
|
+
filename: 'test.vue',
|
|
302
|
+
code: withImport(
|
|
303
|
+
'KUI_FONT_SIZE_30',
|
|
304
|
+
'<div :style="{ fontSize: `var(--kui-font-size-30, ${KUI_FONT_SIZE_30})` }" />',
|
|
305
|
+
),
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
// Idempotency for multiple tokens in one object — both slots already wrapped
|
|
309
|
+
{
|
|
310
|
+
filename: 'test.vue',
|
|
311
|
+
code: sfc({
|
|
312
|
+
script: "import { KUI_SPACE_40, KUI_BORDER_RADIUS_20 } from '@kong/design-tokens'",
|
|
313
|
+
template: '<div :style="{ padding: `var(--kui-space-40, ${KUI_SPACE_40})`, borderRadius: `var(--kui-border-radius-20, ${KUI_BORDER_RADIUS_20})` }" />',
|
|
314
|
+
}),
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
|
|
318
|
+
invalid: [
|
|
319
|
+
// Simple: bare identifier as the whole binding expression
|
|
320
|
+
{
|
|
321
|
+
filename: 'test.vue',
|
|
322
|
+
code: withImport(
|
|
323
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
324
|
+
'<div :color="KUI_COLOR_TEXT_INVERSE" />',
|
|
325
|
+
),
|
|
326
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
327
|
+
output: withImport(
|
|
328
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
329
|
+
'<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
330
|
+
),
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
// Import alias: CSS var uses canonical name, fallback uses local alias
|
|
334
|
+
{
|
|
335
|
+
filename: 'test.vue',
|
|
336
|
+
code: withImport(
|
|
337
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
338
|
+
'<div :color="myColor" />',
|
|
339
|
+
'myColor',
|
|
340
|
+
),
|
|
341
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'myColor', cssVar: 'kui-color-text-inverse' } }],
|
|
342
|
+
output: withImport(
|
|
343
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
344
|
+
'<div :color="`var(--kui-color-text-inverse, ${myColor})`" />',
|
|
345
|
+
'myColor',
|
|
346
|
+
),
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
// Ternary: both branches are the same KUI token — two separate fixes
|
|
350
|
+
{
|
|
351
|
+
filename: 'test.vue',
|
|
352
|
+
code: withImport(
|
|
353
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
354
|
+
'<div :color="cond ? KUI_COLOR_TEXT_INVERSE : KUI_COLOR_TEXT_INVERSE" />',
|
|
355
|
+
),
|
|
356
|
+
errors: [{ messageId: 'wrapInVar' }, { messageId: 'wrapInVar' }],
|
|
357
|
+
output: withImport(
|
|
358
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
359
|
+
'<div :color="cond ? `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` : `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
360
|
+
),
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
// Ternary: two DIFFERENT KUI tokens in consequent and alternate — independent CSS vars
|
|
364
|
+
{
|
|
365
|
+
filename: 'test.vue',
|
|
366
|
+
code: sfc({
|
|
367
|
+
script: "import { KUI_COLOR_TEXT_INVERSE, KUI_COLOR_BACKGROUND_PRIMARY } from '@kong/design-tokens'",
|
|
368
|
+
template: '<div :color="cond ? KUI_COLOR_TEXT_INVERSE : KUI_COLOR_BACKGROUND_PRIMARY" />',
|
|
369
|
+
}),
|
|
370
|
+
errors: [
|
|
371
|
+
{ messageId: 'wrapInVar', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } },
|
|
372
|
+
{ messageId: 'wrapInVar', data: { local: 'KUI_COLOR_BACKGROUND_PRIMARY', cssVar: 'kui-color-background-primary' } },
|
|
373
|
+
],
|
|
374
|
+
output: sfc({
|
|
375
|
+
script: "import { KUI_COLOR_TEXT_INVERSE, KUI_COLOR_BACKGROUND_PRIMARY } from '@kong/design-tokens'",
|
|
376
|
+
template: '<div :color="cond ? `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` : `var(--kui-color-background-primary, ${KUI_COLOR_BACKGROUND_PRIMARY})`" />',
|
|
377
|
+
}),
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
// Partially wrapped ternary: only the unwrapped branch is reported and fixed
|
|
381
|
+
{
|
|
382
|
+
filename: 'test.vue',
|
|
383
|
+
code: withImport(
|
|
384
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
385
|
+
'<div :color="cond ? `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` : KUI_COLOR_TEXT_INVERSE" />',
|
|
386
|
+
),
|
|
387
|
+
errors: [{ messageId: 'wrapInVar' }],
|
|
388
|
+
output: withImport(
|
|
389
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
390
|
+
'<div :color="cond ? `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` : `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
391
|
+
),
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
// Object: `:style="{ color: KUI_X }"`
|
|
395
|
+
{
|
|
396
|
+
filename: 'test.vue',
|
|
397
|
+
code: withImport(
|
|
398
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
399
|
+
'<div :style="{ color: KUI_COLOR_TEXT_INVERSE }" />',
|
|
400
|
+
),
|
|
401
|
+
errors: [{ messageId: 'wrapInVar' }],
|
|
402
|
+
output: withImport(
|
|
403
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
404
|
+
'<div :style="{ color: `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` }" />',
|
|
405
|
+
),
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
// Object with two KUI values — both fixed in a single pass
|
|
409
|
+
{
|
|
410
|
+
filename: 'test.vue',
|
|
411
|
+
code: sfc({
|
|
412
|
+
script: "import { KUI_COLOR_TEXT_INVERSE, KUI_COLOR_BACKGROUND_PRIMARY } from '@kong/design-tokens'",
|
|
413
|
+
template: '<div :style="{ color: KUI_COLOR_TEXT_INVERSE, background: KUI_COLOR_BACKGROUND_PRIMARY }" />',
|
|
414
|
+
}),
|
|
415
|
+
errors: [{ messageId: 'wrapInVar' }, { messageId: 'wrapInVar' }],
|
|
416
|
+
output: sfc({
|
|
417
|
+
script: "import { KUI_COLOR_TEXT_INVERSE, KUI_COLOR_BACKGROUND_PRIMARY } from '@kong/design-tokens'",
|
|
418
|
+
template: '<div :style="{ color: `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`, background: `var(--kui-color-background-primary, ${KUI_COLOR_BACKGROUND_PRIMARY})` }" />',
|
|
419
|
+
}),
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
// Array element
|
|
423
|
+
{
|
|
424
|
+
filename: 'test.vue',
|
|
425
|
+
code: withImport(
|
|
426
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
427
|
+
'<div :style="[baseStyle, KUI_COLOR_TEXT_INVERSE]" />',
|
|
428
|
+
),
|
|
429
|
+
errors: [{ messageId: 'wrapInVar' }],
|
|
430
|
+
output: withImport(
|
|
431
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
432
|
+
'<div :style="[baseStyle, `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`]" />',
|
|
433
|
+
),
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
// LogicalExpression (??) — autofix the right-hand operand
|
|
437
|
+
{
|
|
438
|
+
filename: 'test.vue',
|
|
439
|
+
code: withImport(
|
|
440
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
441
|
+
'<div :color="theme.color ?? KUI_COLOR_TEXT_INVERSE" />',
|
|
442
|
+
),
|
|
443
|
+
errors: [{ messageId: 'wrapInVar' }],
|
|
444
|
+
output: withImport(
|
|
445
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
446
|
+
'<div :color="theme.color ?? `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
447
|
+
),
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
// LogicalExpression (||) — autofix the right-hand operand
|
|
451
|
+
{
|
|
452
|
+
filename: 'test.vue',
|
|
453
|
+
code: withImport(
|
|
454
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
455
|
+
'<div :color="theme.color || KUI_COLOR_TEXT_INVERSE" />',
|
|
456
|
+
),
|
|
457
|
+
errors: [{ messageId: 'wrapInVar' }],
|
|
458
|
+
output: withImport(
|
|
459
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
460
|
+
'<div :color="theme.color || `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
461
|
+
),
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
// Dynamic argument (:[propName])
|
|
465
|
+
{
|
|
466
|
+
filename: 'test.vue',
|
|
467
|
+
code: withImport(
|
|
468
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
469
|
+
'<div :[propName]="KUI_COLOR_TEXT_INVERSE" />',
|
|
470
|
+
),
|
|
471
|
+
errors: [{ messageId: 'wrapInVar' }],
|
|
472
|
+
output: withImport(
|
|
473
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
474
|
+
'<div :[propName]="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
475
|
+
),
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
// v-bind without argument (object spread syntax)
|
|
479
|
+
{
|
|
480
|
+
filename: 'test.vue',
|
|
481
|
+
code: withImport(
|
|
482
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
483
|
+
'<div v-bind="{ color: KUI_COLOR_TEXT_INVERSE }" />',
|
|
484
|
+
),
|
|
485
|
+
errors: [{ messageId: 'wrapInVar' }],
|
|
486
|
+
output: withImport(
|
|
487
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
488
|
+
'<div v-bind="{ color: `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` }" />',
|
|
489
|
+
),
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Object shorthand property { KUI_X } — fixer must expand to { KUI_X: `var(...)` }
|
|
494
|
+
* because replacing only the identifier drops the key and yields invalid JS.
|
|
495
|
+
*/
|
|
496
|
+
{
|
|
497
|
+
filename: 'test.vue',
|
|
498
|
+
code: withImport(
|
|
499
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
500
|
+
'<div v-bind="{ KUI_COLOR_TEXT_INVERSE }" />',
|
|
501
|
+
),
|
|
502
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
503
|
+
output: withImport(
|
|
504
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
505
|
+
'<div v-bind="{ KUI_COLOR_TEXT_INVERSE: `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` }" />',
|
|
506
|
+
),
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
// SequenceExpression — last element gets AUTOFIX context; earlier ones are REPORT_ONLY
|
|
510
|
+
{
|
|
511
|
+
filename: 'test.vue',
|
|
512
|
+
code: withImport(
|
|
513
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
514
|
+
'<div :color="(sideEffect(), KUI_COLOR_TEXT_INVERSE)" />',
|
|
515
|
+
),
|
|
516
|
+
errors: [{ messageId: 'wrapInVar' }],
|
|
517
|
+
output: withImport(
|
|
518
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
519
|
+
'<div :color="(sideEffect(), `var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`)" />',
|
|
520
|
+
),
|
|
521
|
+
},
|
|
522
|
+
|
|
523
|
+
// AssignmentExpression — always REPORT_ONLY (wrapping the assigned value changes semantics)
|
|
524
|
+
{
|
|
525
|
+
filename: 'test.vue',
|
|
526
|
+
code: withImport(
|
|
527
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
528
|
+
'<div :color="(x = KUI_COLOR_TEXT_INVERSE)" />',
|
|
529
|
+
),
|
|
530
|
+
errors: [{ messageId: 'wrapInVarNoFix', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
531
|
+
output: null,
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
// Token family coverage — same autofix transform applies to all KUI_ prefixes
|
|
535
|
+
|
|
536
|
+
// KUI_FONT_SIZE — typography scale tokens
|
|
537
|
+
{
|
|
538
|
+
filename: 'test.vue',
|
|
539
|
+
code: withImport(
|
|
540
|
+
'KUI_FONT_SIZE_30',
|
|
541
|
+
'<div :style="{ fontSize: KUI_FONT_SIZE_30 }" />',
|
|
542
|
+
),
|
|
543
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_FONT_SIZE_30', cssVar: 'kui-font-size-30' } }],
|
|
544
|
+
output: withImport(
|
|
545
|
+
'KUI_FONT_SIZE_30',
|
|
546
|
+
'<div :style="{ fontSize: `var(--kui-font-size-30, ${KUI_FONT_SIZE_30})` }" />',
|
|
547
|
+
),
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
// KUI_BORDER_RADIUS — shape tokens
|
|
551
|
+
{
|
|
552
|
+
filename: 'test.vue',
|
|
553
|
+
code: withImport(
|
|
554
|
+
'KUI_BORDER_RADIUS_20',
|
|
555
|
+
'<div :style="{ borderRadius: KUI_BORDER_RADIUS_20 }" />',
|
|
556
|
+
),
|
|
557
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_BORDER_RADIUS_20', cssVar: 'kui-border-radius-20' } }],
|
|
558
|
+
output: withImport(
|
|
559
|
+
'KUI_BORDER_RADIUS_20',
|
|
560
|
+
'<div :style="{ borderRadius: `var(--kui-border-radius-20, ${KUI_BORDER_RADIUS_20})` }" />',
|
|
561
|
+
),
|
|
562
|
+
},
|
|
563
|
+
|
|
564
|
+
// KUI_FONT_WEIGHT — weight scale tokens
|
|
565
|
+
{
|
|
566
|
+
filename: 'test.vue',
|
|
567
|
+
code: withImport(
|
|
568
|
+
'KUI_FONT_WEIGHT_BOLD',
|
|
569
|
+
'<div :style="{ fontWeight: KUI_FONT_WEIGHT_BOLD }" />',
|
|
570
|
+
),
|
|
571
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_FONT_WEIGHT_BOLD', cssVar: 'kui-font-weight-bold' } }],
|
|
572
|
+
output: withImport(
|
|
573
|
+
'KUI_FONT_WEIGHT_BOLD',
|
|
574
|
+
'<div :style="{ fontWeight: `var(--kui-font-weight-bold, ${KUI_FONT_WEIGHT_BOLD})` }" />',
|
|
575
|
+
),
|
|
576
|
+
},
|
|
577
|
+
|
|
578
|
+
// KUI_LINE_HEIGHT — line height tokens
|
|
579
|
+
{
|
|
580
|
+
filename: 'test.vue',
|
|
581
|
+
code: withImport(
|
|
582
|
+
'KUI_LINE_HEIGHT_40',
|
|
583
|
+
'<div :style="{ lineHeight: KUI_LINE_HEIGHT_40 }" />',
|
|
584
|
+
),
|
|
585
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_LINE_HEIGHT_40', cssVar: 'kui-line-height-40' } }],
|
|
586
|
+
output: withImport(
|
|
587
|
+
'KUI_LINE_HEIGHT_40',
|
|
588
|
+
'<div :style="{ lineHeight: `var(--kui-line-height-40, ${KUI_LINE_HEIGHT_40})` }" />',
|
|
589
|
+
),
|
|
590
|
+
},
|
|
591
|
+
|
|
592
|
+
// KUI_Z_INDEX — z-index tokens
|
|
593
|
+
{
|
|
594
|
+
filename: 'test.vue',
|
|
595
|
+
code: withImport(
|
|
596
|
+
'KUI_Z_INDEX_10',
|
|
597
|
+
'<div :style="{ zIndex: KUI_Z_INDEX_10 }" />',
|
|
598
|
+
),
|
|
599
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_Z_INDEX_10', cssVar: 'kui-z-index-10' } }],
|
|
600
|
+
output: withImport(
|
|
601
|
+
'KUI_Z_INDEX_10',
|
|
602
|
+
'<div :style="{ zIndex: `var(--kui-z-index-10, ${KUI_Z_INDEX_10})` }" />',
|
|
603
|
+
),
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
// KUI_SPACE — spacing tokens (also confirms numeric suffix handling)
|
|
607
|
+
{
|
|
608
|
+
filename: 'test.vue',
|
|
609
|
+
code: withImport(
|
|
610
|
+
'KUI_SPACE_40',
|
|
611
|
+
'<div :style="{ padding: KUI_SPACE_40 }" />',
|
|
612
|
+
),
|
|
613
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_SPACE_40', cssVar: 'kui-space-40' } }],
|
|
614
|
+
output: withImport(
|
|
615
|
+
'KUI_SPACE_40',
|
|
616
|
+
'<div :style="{ padding: `var(--kui-space-40, ${KUI_SPACE_40})` }" />',
|
|
617
|
+
),
|
|
618
|
+
},
|
|
619
|
+
|
|
620
|
+
// REPORT_ONLY — no autofix because rewriting would change expression semantics
|
|
621
|
+
|
|
622
|
+
// Inside TemplateLiteral (no autofix: would nest backticks)
|
|
623
|
+
{
|
|
624
|
+
filename: 'test.vue',
|
|
625
|
+
code: withImport(
|
|
626
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
627
|
+
'<div :color="`${KUI_COLOR_TEXT_INVERSE}`" />',
|
|
628
|
+
),
|
|
629
|
+
errors: [{ messageId: 'wrapInVarNoFix', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
630
|
+
output: null,
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Multi-token TemplateLiteral — both tokens reported, no autofix. Adjacent slots
|
|
635
|
+
* share quasi boundaries (quasis[1] is simultaneously the suffix of slot 0 and the
|
|
636
|
+
* prefix of slot 1), so per-token replacement ranges overlap. ESLint rejects
|
|
637
|
+
* overlapping fixes; a whole-template rewrite would be needed instead.
|
|
638
|
+
* Manual fix: `:padding="'var(--kui-space-0, ' + KUI_SPACE_0 + ') var(--kui-space-70, ' + KUI_SPACE_70 + ')'"`.
|
|
639
|
+
*/
|
|
640
|
+
{
|
|
641
|
+
filename: 'test.vue',
|
|
642
|
+
code: sfc({
|
|
643
|
+
script: "import { KUI_SPACE_0, KUI_SPACE_70 } from '@kong/design-tokens'",
|
|
644
|
+
template: '<div :padding="`${KUI_SPACE_0} ${KUI_SPACE_70}`" />',
|
|
645
|
+
}),
|
|
646
|
+
errors: [
|
|
647
|
+
{ messageId: 'wrapInVarNoFix', data: { local: 'KUI_SPACE_0', cssVar: 'kui-space-0' } },
|
|
648
|
+
{ messageId: 'wrapInVarNoFix', data: { local: 'KUI_SPACE_70', cssVar: 'kui-space-70' } },
|
|
649
|
+
],
|
|
650
|
+
output: null,
|
|
651
|
+
},
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Mismatched CSS var: wraps the token with the WRONG custom property.
|
|
655
|
+
* `var(--kui-color-text-primary, ${KUI_COLOR_TEXT_INVERSE})` must be caught
|
|
656
|
+
* because the theme override targets the wrong property and theming will not work.
|
|
657
|
+
*/
|
|
658
|
+
{
|
|
659
|
+
filename: 'test.vue',
|
|
660
|
+
code: withImport(
|
|
661
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
662
|
+
'<div :color="`var(--kui-color-text-primary, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
663
|
+
),
|
|
664
|
+
errors: [{ messageId: 'wrapInVarNoFix', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
665
|
+
output: null,
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Mismatched CSS var with import alias — alias resolves to the canonical imported name,
|
|
670
|
+
* so the wrong var is still caught even when a local alias is used.
|
|
671
|
+
*/
|
|
672
|
+
{
|
|
673
|
+
filename: 'test.vue',
|
|
674
|
+
code: withImport(
|
|
675
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
676
|
+
'<div :color="`var(--kui-color-text-primary, ${myColor})`" />',
|
|
677
|
+
'myColor',
|
|
678
|
+
),
|
|
679
|
+
errors: [{ messageId: 'wrapInVarNoFix', data: { local: 'myColor', cssVar: 'kui-color-text-inverse' } }],
|
|
680
|
+
output: null,
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Token nested inside a TemplateLiteral slot expression (not a direct Identifier).
|
|
685
|
+
* `asDirectIdentifier` returns null for the ConditionalExpression, so the slot
|
|
686
|
+
* context is NOT passed and idempotency is not checked — the nested token is always reported.
|
|
687
|
+
*/
|
|
688
|
+
{
|
|
689
|
+
filename: 'test.vue',
|
|
690
|
+
code: withImport(
|
|
691
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
692
|
+
'<div :color="`var(--kui-color-text-inverse, ${cond ? KUI_COLOR_TEXT_INVERSE : \'red\'})`" />',
|
|
693
|
+
),
|
|
694
|
+
errors: [{ messageId: 'wrapInVarNoFix', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
695
|
+
output: null,
|
|
696
|
+
},
|
|
697
|
+
|
|
698
|
+
// BinaryExpression (no autofix: changes string semantics)
|
|
699
|
+
{
|
|
700
|
+
filename: 'test.vue',
|
|
701
|
+
code: withImport(
|
|
702
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
703
|
+
"<div :color=\"KUI_COLOR_TEXT_INVERSE + '!important'\" />",
|
|
704
|
+
),
|
|
705
|
+
errors: [{ messageId: 'wrapInVarNoFix', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
706
|
+
output: null,
|
|
707
|
+
},
|
|
708
|
+
|
|
709
|
+
// CallExpression argument (no autofix: could break color helpers like darken/rgba)
|
|
710
|
+
{
|
|
711
|
+
filename: 'test.vue',
|
|
712
|
+
code: withImport(
|
|
713
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
714
|
+
'<div :color="darken(KUI_COLOR_TEXT_INVERSE)" />',
|
|
715
|
+
),
|
|
716
|
+
errors: [{ messageId: 'wrapInVarNoFix', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
717
|
+
output: null,
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
// Script-setup variable (`const c = KUI_X`) — detected, no autofix
|
|
721
|
+
{
|
|
722
|
+
filename: 'test.vue',
|
|
723
|
+
code: sfc({
|
|
724
|
+
script: [
|
|
725
|
+
"import { KUI_COLOR_TEXT_INVERSE } from '@kong/design-tokens'",
|
|
726
|
+
'const myColor = KUI_COLOR_TEXT_INVERSE',
|
|
727
|
+
].join('\n'),
|
|
728
|
+
template: '<div :color="myColor" />',
|
|
729
|
+
}),
|
|
730
|
+
errors: [{ messageId: 'wrapInVarScriptSetup', data: { imported: 'KUI_COLOR_TEXT_INVERSE', local: 'myColor', cssVar: 'kui-color-text-inverse' } }],
|
|
731
|
+
output: null,
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
})
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* TypeScript SFCs — requires a second RuleTester that delegates <script lang="ts">
|
|
738
|
+
* to @typescript-eslint/parser, which produces TS-specific AST nodes (TSAsExpression,
|
|
739
|
+
* TSNonNullExpression, importKind) that the rule handles explicitly.
|
|
740
|
+
*/
|
|
741
|
+
const tsTester = new RuleTester({
|
|
742
|
+
languageOptions: {
|
|
743
|
+
parser: vueParser,
|
|
744
|
+
parserOptions: {
|
|
745
|
+
parser: tsParser,
|
|
746
|
+
ecmaVersion: 2020,
|
|
747
|
+
sourceType: 'module',
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
tsTester.run(`${RULE_NAME} (TypeScript)`, rule, {
|
|
753
|
+
valid: [
|
|
754
|
+
// `import type { KUI_X }` — declaration-level type-only import is not tracked
|
|
755
|
+
{
|
|
756
|
+
filename: 'test.vue',
|
|
757
|
+
code: sfcTs({
|
|
758
|
+
script: "import type { KUI_COLOR_TEXT_INVERSE } from '@kong/design-tokens'",
|
|
759
|
+
template: '<div :color="KUI_COLOR_TEXT_INVERSE" />',
|
|
760
|
+
}),
|
|
761
|
+
},
|
|
762
|
+
|
|
763
|
+
// `${KUI_X as string}` in an already-correct var() slot — idempotency through a TS cast
|
|
764
|
+
{
|
|
765
|
+
filename: 'test.vue',
|
|
766
|
+
code: withImportTs(
|
|
767
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
768
|
+
'<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE as string})`" />',
|
|
769
|
+
),
|
|
770
|
+
},
|
|
771
|
+
|
|
772
|
+
// `${KUI_X satisfies T}` in an already-correct var() slot — idempotency through `satisfies`
|
|
773
|
+
{
|
|
774
|
+
filename: 'test.vue',
|
|
775
|
+
code: withImportTs(
|
|
776
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
777
|
+
'<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE satisfies string})`" />',
|
|
778
|
+
),
|
|
779
|
+
},
|
|
780
|
+
],
|
|
781
|
+
|
|
782
|
+
invalid: [
|
|
783
|
+
// TSAsExpression — autofix unwraps the cast to reach the identifier; cast is preserved in output
|
|
784
|
+
{
|
|
785
|
+
filename: 'test.vue',
|
|
786
|
+
code: withImportTs(
|
|
787
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
788
|
+
'<div :color="KUI_COLOR_TEXT_INVERSE as string" />',
|
|
789
|
+
),
|
|
790
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
791
|
+
output: withImportTs(
|
|
792
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
793
|
+
'<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` as string" />',
|
|
794
|
+
),
|
|
795
|
+
},
|
|
796
|
+
|
|
797
|
+
// TSNonNullExpression — autofix unwraps `!`; non-null assertion is preserved in output
|
|
798
|
+
{
|
|
799
|
+
filename: 'test.vue',
|
|
800
|
+
code: withImportTs(
|
|
801
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
802
|
+
'<div :color="KUI_COLOR_TEXT_INVERSE!" />',
|
|
803
|
+
),
|
|
804
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
805
|
+
output: withImportTs(
|
|
806
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
807
|
+
'<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`!" />',
|
|
808
|
+
),
|
|
809
|
+
},
|
|
810
|
+
|
|
811
|
+
// TSSatisfiesExpression — autofix unwraps `satisfies`; the satisfies clause is preserved
|
|
812
|
+
{
|
|
813
|
+
filename: 'test.vue',
|
|
814
|
+
code: withImportTs(
|
|
815
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
816
|
+
'<div :color="KUI_COLOR_TEXT_INVERSE satisfies string" />',
|
|
817
|
+
),
|
|
818
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
819
|
+
output: withImportTs(
|
|
820
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
821
|
+
'<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})` satisfies string" />',
|
|
822
|
+
),
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* One-hop alias with a TS `satisfies` initializer — the wrapper must be unwrapped
|
|
827
|
+
* so the alias is tracked and the template binding is reported.
|
|
828
|
+
*/
|
|
829
|
+
{
|
|
830
|
+
filename: 'test.vue',
|
|
831
|
+
code: sfcTs({
|
|
832
|
+
script: [
|
|
833
|
+
"import { KUI_COLOR_TEXT_INVERSE } from '@kong/design-tokens'",
|
|
834
|
+
'const myColor = KUI_COLOR_TEXT_INVERSE satisfies string',
|
|
835
|
+
].join('\n'),
|
|
836
|
+
template: '<div :color="myColor" />',
|
|
837
|
+
}),
|
|
838
|
+
errors: [{ messageId: 'wrapInVarScriptSetup', data: { imported: 'KUI_COLOR_TEXT_INVERSE', local: 'myColor', cssVar: 'kui-color-text-inverse' } }],
|
|
839
|
+
output: null,
|
|
840
|
+
},
|
|
841
|
+
|
|
842
|
+
// `${KUI_X as string}` in a var() slot with the WRONG custom property — still caught
|
|
843
|
+
{
|
|
844
|
+
filename: 'test.vue',
|
|
845
|
+
code: withImportTs(
|
|
846
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
847
|
+
'<div :color="`var(--kui-color-text-primary, ${KUI_COLOR_TEXT_INVERSE as string})`" />',
|
|
848
|
+
),
|
|
849
|
+
errors: [{ messageId: 'wrapInVarNoFix', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
850
|
+
output: null,
|
|
851
|
+
},
|
|
852
|
+
|
|
853
|
+
// Mixed import `{ KUI_SPACE_40, type KUI_COLOR_TEXT_INVERSE }` — only the value specifier is reported
|
|
854
|
+
{
|
|
855
|
+
filename: 'test.vue',
|
|
856
|
+
code: sfcTs({
|
|
857
|
+
script: "import { KUI_SPACE_40, type KUI_COLOR_TEXT_INVERSE } from '@kong/design-tokens'",
|
|
858
|
+
template: '<div :style="{ padding: KUI_SPACE_40, color: KUI_COLOR_TEXT_INVERSE }" />',
|
|
859
|
+
}),
|
|
860
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_SPACE_40', cssVar: 'kui-space-40' } }],
|
|
861
|
+
output: sfcTs({
|
|
862
|
+
script: "import { KUI_SPACE_40, type KUI_COLOR_TEXT_INVERSE } from '@kong/design-tokens'",
|
|
863
|
+
template: '<div :style="{ padding: `var(--kui-space-40, ${KUI_SPACE_40})`, color: KUI_COLOR_TEXT_INVERSE }" />',
|
|
864
|
+
}),
|
|
865
|
+
},
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* One-hop alias with a TS `as` cast initializer — the wrapper must be unwrapped
|
|
869
|
+
* so the alias is still tracked and the template binding is reported.
|
|
870
|
+
*/
|
|
871
|
+
{
|
|
872
|
+
filename: 'test.vue',
|
|
873
|
+
code: sfcTs({
|
|
874
|
+
script: [
|
|
875
|
+
"import { KUI_COLOR_TEXT_INVERSE } from '@kong/design-tokens'",
|
|
876
|
+
'const myColor = KUI_COLOR_TEXT_INVERSE as string',
|
|
877
|
+
].join('\n'),
|
|
878
|
+
template: '<div :color="myColor" />',
|
|
879
|
+
}),
|
|
880
|
+
errors: [{ messageId: 'wrapInVarScriptSetup', data: { imported: 'KUI_COLOR_TEXT_INVERSE', local: 'myColor', cssVar: 'kui-color-text-inverse' } }],
|
|
881
|
+
output: null,
|
|
882
|
+
},
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* One-hop alias with a TS non-null `!` initializer — same unwrapping requirement.
|
|
886
|
+
*/
|
|
887
|
+
{
|
|
888
|
+
filename: 'test.vue',
|
|
889
|
+
code: sfcTs({
|
|
890
|
+
script: [
|
|
891
|
+
"import { KUI_COLOR_TEXT_INVERSE } from '@kong/design-tokens'",
|
|
892
|
+
'const myColor = KUI_COLOR_TEXT_INVERSE!',
|
|
893
|
+
].join('\n'),
|
|
894
|
+
template: '<div :color="myColor" />',
|
|
895
|
+
}),
|
|
896
|
+
errors: [{ messageId: 'wrapInVarScriptSetup', data: { imported: 'KUI_COLOR_TEXT_INVERSE', local: 'myColor', cssVar: 'kui-color-text-inverse' } }],
|
|
897
|
+
output: null,
|
|
898
|
+
},
|
|
899
|
+
],
|
|
900
|
+
})
|
|
901
|
+
|
|
902
|
+
// Note: TSTypeAssertion (`<Type>expr` syntax) is NOT tested. In a Vue template
|
|
903
|
+
// attribute expression `:<prop>="<Type>expr"`, the parser sees `<Type>` as an
|
|
904
|
+
// HTML open-tag rather than a cast, making this syntax unreachable in the binding
|
|
905
|
+
// context where the rule runs. The TSAsExpression and TSNonNullExpression cases
|
|
906
|
+
// above already exercise the shared unwrap code path in both walkExpression and
|
|
907
|
+
// asDirectIdentifier.
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Shorthand for an SFC that imports one token from `@kong/portal-design-tokens`.
|
|
911
|
+
* @param {string} varName - The exported constant name (e.g. `KUI_COLOR_TEXT_INVERSE`)
|
|
912
|
+
* @param {string} template - The `<template>` body
|
|
913
|
+
*/
|
|
914
|
+
function withPortalImport(varName, template) {
|
|
915
|
+
return sfc({
|
|
916
|
+
script: `import { ${varName} } from '@kong/portal-design-tokens'`,
|
|
917
|
+
template,
|
|
918
|
+
})
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* importSources option — controls which packages the rule tracks token imports from.
|
|
923
|
+
* Default: ['@kong/design-tokens', '@kong/portal-design-tokens']
|
|
924
|
+
*/
|
|
925
|
+
tester.run(`${RULE_NAME} (importSources option)`, rule, {
|
|
926
|
+
valid: [
|
|
927
|
+
// Default options: import from @kong/portal-design-tokens IS tracked — already wrapped is valid
|
|
928
|
+
{
|
|
929
|
+
filename: 'test.vue',
|
|
930
|
+
code: withPortalImport(
|
|
931
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
932
|
+
'<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
933
|
+
),
|
|
934
|
+
},
|
|
935
|
+
|
|
936
|
+
// importSources restricted to @kong/design-tokens only — portal import is NOT tracked (no error)
|
|
937
|
+
{
|
|
938
|
+
filename: 'test.vue',
|
|
939
|
+
options: [{ importSources: ['@kong/design-tokens'] }],
|
|
940
|
+
code: withPortalImport(
|
|
941
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
942
|
+
'<div :color="KUI_COLOR_TEXT_INVERSE" />',
|
|
943
|
+
),
|
|
944
|
+
},
|
|
945
|
+
|
|
946
|
+
// importSources restricted to @kong/portal-design-tokens only — design-tokens import is NOT tracked
|
|
947
|
+
{
|
|
948
|
+
filename: 'test.vue',
|
|
949
|
+
options: [{ importSources: ['@kong/portal-design-tokens'] }],
|
|
950
|
+
code: withImport(
|
|
951
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
952
|
+
'<div :color="KUI_COLOR_TEXT_INVERSE" />',
|
|
953
|
+
),
|
|
954
|
+
},
|
|
955
|
+
],
|
|
956
|
+
|
|
957
|
+
invalid: [
|
|
958
|
+
// Default options: import from @kong/portal-design-tokens IS flagged (same as @kong/design-tokens)
|
|
959
|
+
{
|
|
960
|
+
filename: 'test.vue',
|
|
961
|
+
code: withPortalImport(
|
|
962
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
963
|
+
'<div :color="KUI_COLOR_TEXT_INVERSE" />',
|
|
964
|
+
),
|
|
965
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
966
|
+
output: withPortalImport(
|
|
967
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
968
|
+
'<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
969
|
+
),
|
|
970
|
+
},
|
|
971
|
+
|
|
972
|
+
// importSources: ['@kong/portal-design-tokens'] — portal import IS flagged, design-tokens is NOT
|
|
973
|
+
{
|
|
974
|
+
filename: 'test.vue',
|
|
975
|
+
options: [{ importSources: ['@kong/portal-design-tokens'] }],
|
|
976
|
+
code: withPortalImport(
|
|
977
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
978
|
+
'<div :color="KUI_COLOR_TEXT_INVERSE" />',
|
|
979
|
+
),
|
|
980
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
981
|
+
output: withPortalImport(
|
|
982
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
983
|
+
'<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
984
|
+
),
|
|
985
|
+
},
|
|
986
|
+
|
|
987
|
+
// Fully custom importSources: ['my-company/design-tokens'] — only that source is tracked
|
|
988
|
+
{
|
|
989
|
+
filename: 'test.vue',
|
|
990
|
+
options: [{ importSources: ['my-company/design-tokens'] }],
|
|
991
|
+
code: sfc({
|
|
992
|
+
script: "import { KUI_COLOR_TEXT_INVERSE } from 'my-company/design-tokens'",
|
|
993
|
+
template: '<div :color="KUI_COLOR_TEXT_INVERSE" />',
|
|
994
|
+
}),
|
|
995
|
+
errors: [{ messageId: 'wrapInVar', data: { local: 'KUI_COLOR_TEXT_INVERSE', cssVar: 'kui-color-text-inverse' } }],
|
|
996
|
+
output: sfc({
|
|
997
|
+
script: "import { KUI_COLOR_TEXT_INVERSE } from 'my-company/design-tokens'",
|
|
998
|
+
template: '<div :color="`var(--kui-color-text-inverse, ${KUI_COLOR_TEXT_INVERSE})`" />',
|
|
999
|
+
}),
|
|
1000
|
+
},
|
|
1001
|
+
],
|
|
1002
|
+
})
|
|
1003
|
+
|
|
1004
|
+
// Fully custom importSources: ['my-company/design-tokens'] — @kong/design-tokens is NOT tracked
|
|
1005
|
+
tester.run(`${RULE_NAME} (fully custom importSources — kong package not tracked)`, rule, {
|
|
1006
|
+
valid: [
|
|
1007
|
+
{
|
|
1008
|
+
filename: 'test.vue',
|
|
1009
|
+
options: [{ importSources: ['my-company/design-tokens'] }],
|
|
1010
|
+
code: withImport(
|
|
1011
|
+
'KUI_COLOR_TEXT_INVERSE',
|
|
1012
|
+
'<div :color="KUI_COLOR_TEXT_INVERSE" />',
|
|
1013
|
+
),
|
|
1014
|
+
},
|
|
1015
|
+
],
|
|
1016
|
+
invalid: [],
|
|
1017
|
+
})
|