feat: add new rules about usage of design tokens#626
Conversation
Brainstorm output covering two new rules (no-invalid-design-token, prefer-design-token), build-time token extraction from skill assets via culori, and per-rule configurable options.
…ings - Add `vapor/css/no-invalid-design-token` and `vapor/css/prefer-design-token` via @eslint/css (flat config language: css/css) for .css files. - Three-layer allowlist: settings.vapor.customTokens (global), rule option allowCustomTokens, and same-file LHS auto-detection. - Renumber sections; add CSS test cases; note SCSS/Less deferred.
17-task TDD plan covering build script, utils, JS/CSS rules, registration, README, and verification. References spec at docs/superpowers/specs/2026-06-22-eslint-token-rules-design.md.
- 3 rules: css/no-invalid-design-token, css/token-scope-mismatch, css/prefer-design-token - CSS-only scope (v1), JS/TS deferred - token assets copied from skills/token-lint/assets (manual sync) - supersedes 2026-06-22 + 2026-06-23 docs
…allowlist, value parsing
…ransposition case
… README and changeset
🦋 Changeset detectedLatest commit: 158d43f The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 54 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (11)
📝 WalkthroughWalkthroughAdds three new ESLint CSS design-token rules ( ChangesCSS Design-Token Rules
Accessibility Message Localization
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant CSSFile as CSS Source
participant Rule as ESLint Rule
participant Parser as parseDeclarationValue
participant Index as TOKEN_INDEX
participant Fix as Suggestion/Fixer
CSSFile->>Rule: Declaration node
Rule->>Parser: parse value parts (var/hex/dimension)
Parser-->>Rule: parsed parts
Rule->>Index: lookup canonicalTokens/byHex/byPx/tokenMeta
Index-->>Rule: token metadata / candidates
Rule->>Rule: evaluate scope/validity
alt mismatch or unknown/preferable token
Rule->>Fix: build suggestion with replacement
Fix-->>CSSFile: report + fixed text range
else valid usage
Rule-->>CSSFile: no report
end
Related PRs: None identified. Suggested labels: eslint-plugin-vapor, design-tokens, documentation Suggested reviewers: None identified. Poem: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (5)
packages/eslint-plugin-vapor/src/index.ts (1)
23-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueType assertion overstates completeness of
RuleNamekeys.
a11yRecommended/cssRecommendedare each cast toRecord<RuleName, 'error'|'warn'|'off'>but only cover 4 and 3 of the 7 totalRuleNamekeys respectively. ConsiderPartial<Record<RuleName, ...>>to keep the type honest about which rules are actually configured in each recommended set.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/eslint-plugin-vapor/src/index.ts` around lines 23 - 34, The a11yRecommended and cssRecommended objects are being asserted as complete Record<RuleName,...> maps even though they only define a subset of RuleName entries. Update the type annotation in the index.ts recommended rule sets to use a partial record shape so the types reflect that only some rules are configured in each preset, and keep the existing rule names/values in those objects unchanged.packages/eslint-plugin-vapor/src/rules/token-scope-mismatch.ts (1)
68-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftDuplicated candidate-gathering logic across hex/px branches and across rules.
The hex-match and px-match candidate blocks are nearly identical (build list, filter by kind+scope, slice to 3), and the same pattern is repeated again in
prefer-design-token.ts(lines 88-140). Consider extracting a shared helper (e.g.findScopedCandidates(index, key, kind, expectedScopes, limit)) used by both rules to reduce duplication and keep the hardcoded3limit in one place.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/eslint-plugin-vapor/src/rules/token-scope-mismatch.ts` around lines 68 - 95, The candidate collection logic in token-scope-mismatch is duplicated across the hex and px branches, and the same pattern also exists in prefer-design-token, so extract a shared helper to centralize it. Create a reusable function around TOKEN_INDEX lookups and filtering by token kind plus expected scope, then have both token-scope-mismatch and prefer-design-token call that helper with the appropriate index, key, kind, and limit so the hardcoded candidate cap is maintained in one place.packages/eslint-plugin-vapor/src/rules/prefer-design-token.ts (2)
26-32: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
.every()could silently under-match mixed-scope properties.
isColorScope/isFoundationScopeuse.every(), requiring all scopes of a property to belong to the bucket. Currently safe since everyPROPERTY_SCOPEentry is single-element, but if a property is ever mapped to multiple scopes spanning both buckets,.every()would incorrectly exclude it from raw-value suggestions where.some()would still correctly include it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/eslint-plugin-vapor/src/rules/prefer-design-token.ts` around lines 26 - 32, The scope classification helpers in prefer-design-token.ts are too strict because isColorScope and isFoundationScope use .every(), which can miss properties mapped to multiple scopes. Update these checks to use .some() so a property is treated as matching a bucket when any of its scopes fit, and keep the logic aligned with PROPERTY_SCOPE and the raw-value suggestion flow that depends on these helpers.
15-24: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueTrim the dead default ignore values
parseDeclarationValue()only producesvar,hex, anddimensionparts, so the default keyword entries (transparent,none,currentcolor,inherit,initial,unset, and bare0) never reach theignoreValuescheck. Remove them fromDEFAULT_IGNORE_VALUES, or extend the parser if raw keywords should be skippable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/eslint-plugin-vapor/src/rules/prefer-design-token.ts` around lines 15 - 24, Trim the dead entries from DEFAULT_IGNORE_VALUES in prefer-design-token.ts: parseDeclarationValue() only yields var, hex, and dimension parts, so the keyword/bare-zero defaults never affect the ignoreValues check. Update DEFAULT_IGNORE_VALUES to only include values that can actually be produced there, or else adjust parseDeclarationValue() and the rule’s matching logic if you intend to support raw keywords.packages/eslint-plugin-vapor/package.json (1)
32-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPeer dependency range for
@eslint/cssis wider than the devDependency range.devDependency pins
@eslint/cssto^1.3.0(< 2.0.0), but peerDependency allows>=1.3.0unbounded, permitting consumers to install a future major of@eslint/cssthat this plugin was never tested against. Consider capping the peer range consistently, e.g.>=1.3.0 <2.0.0.diff
"peerDependencies": { - "`@eslint/css`": ">=1.3.0", + "`@eslint/css`": ">=1.3.0 <2.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",Also applies to: 46-46
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/eslint-plugin-vapor/package.json` at line 32, The peer dependency range for `@eslint/css` is too broad compared with the devDependency, so update the package.json dependency declarations for `@eslint/css` to cap the peer range consistently with the tested major version. Make sure the peerDependencies entry matches the devDependencies intent by limiting it to the same supported 1.x range, and verify the change in the package.json fields for `@eslint/css`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/eslint-plugin-vapor-css-token-rules.md:
- Line 5: The changelog entry uses the wrong public rule IDs and preset name;
update the note in the changeset to match the actual exported names used
elsewhere in the PR. Replace the `css/*` rule references with the `vapor/*`
equivalents and change the preset mention from `vapor.configs.css` to
`vapor/flat/css`, so the release note points readers to the correct symbols.
In `@packages/eslint-plugin-vapor/src/data/tokens/semantic-color.light.json`:
- Around line 87-105: The `background.canvas.100` token in the semantic color
JSON is pointing at the non-primitive alias `{colors.background.canvas}`, which
prevents it from being resolved into `primitiveByName` and excluded it from
`byHex` suggestions. Update the `$value` for `canvas.100` to use the primitive
token reference `{colors.canvas}` so the token gets a hex value and participates
in the suggestion mapping.
In `@packages/eslint-plugin-vapor/src/data/tokens/shadow.json`:
- Around line 5-22: The shadow token entry is still being indexed even though it
is marked do-not-use, which lets deprecated tokens pass validation. Update the
token indexing path in the shadow walker (and match the same behavior used by
the semantic-color walkers) so `walkShadow` skips entries whose extension status
is do-not-use, or otherwise propagates that status into `canonicalTokens` and
`no-invalid-design-token`. Use the existing `walkShadow` and `canonicalTokens`
symbols to locate the fix and keep deprecated tokens out of the accepted set.
In `@packages/eslint-plugin-vapor/src/utils/css-value-parser.ts`:
- Around line 48-58: parseDeclarationValue() in css-value-parser.ts currently
returns immediately after recording the first var() identifier, which skips any
fallback tokens inside var(--x, fallback). Update the var-handling branch in the
walker so it still records the identifier but continues traversing node.children
(or specifically the fallback part) instead of returning early, ensuring nested
hex, dimension, and nested var() values are discovered by the downstream
matching logic.
In `@packages/eslint-plugin-vapor/src/utils/token-index.ts`:
- Around line 68-136: `buildTokenIndex()` is only indexing the light token
files, so dark-theme tokens never get into `byHex` and related lookups. Update
the indexing flow in `token-index.ts` (including `walkPrimitiveColors` and
`walkSemanticColors`) to ingest both light and dark variants from
`resolver.json`, or otherwise make the index theme-aware so dark primitive and
semantic tokens are included alongside the existing light ones. Ensure the token
names/scopes still use the existing `nameFromPath`, `scopeFromTokenName`, and
`pushIndex` paths consistently.
- Around line 46-66: The token index still includes deprecated semantic-color
and shadow entries marked with $extensions["io.goorm.vapor"].status equal to
do-not-use, which lets them pass validation. Update the indexing logic in
walkSemanticColors and walkShadow so they skip adding any token whose extension
status is do-not-use before inserting into TOKEN_INDEX.canonicalTokens or
related metadata. Leave walkFoundation unchanged since foundation tokens do not
use this marker.
---
Nitpick comments:
In `@packages/eslint-plugin-vapor/package.json`:
- Line 32: The peer dependency range for `@eslint/css` is too broad compared with
the devDependency, so update the package.json dependency declarations for
`@eslint/css` to cap the peer range consistently with the tested major version.
Make sure the peerDependencies entry matches the devDependencies intent by
limiting it to the same supported 1.x range, and verify the change in the
package.json fields for `@eslint/css`.
In `@packages/eslint-plugin-vapor/src/index.ts`:
- Around line 23-34: The a11yRecommended and cssRecommended objects are being
asserted as complete Record<RuleName,...> maps even though they only define a
subset of RuleName entries. Update the type annotation in the index.ts
recommended rule sets to use a partial record shape so the types reflect that
only some rules are configured in each preset, and keep the existing rule
names/values in those objects unchanged.
In `@packages/eslint-plugin-vapor/src/rules/prefer-design-token.ts`:
- Around line 26-32: The scope classification helpers in prefer-design-token.ts
are too strict because isColorScope and isFoundationScope use .every(), which
can miss properties mapped to multiple scopes. Update these checks to use
.some() so a property is treated as matching a bucket when any of its scopes
fit, and keep the logic aligned with PROPERTY_SCOPE and the raw-value suggestion
flow that depends on these helpers.
- Around line 15-24: Trim the dead entries from DEFAULT_IGNORE_VALUES in
prefer-design-token.ts: parseDeclarationValue() only yields var, hex, and
dimension parts, so the keyword/bare-zero defaults never affect the ignoreValues
check. Update DEFAULT_IGNORE_VALUES to only include values that can actually be
produced there, or else adjust parseDeclarationValue() and the rule’s matching
logic if you intend to support raw keywords.
In `@packages/eslint-plugin-vapor/src/rules/token-scope-mismatch.ts`:
- Around line 68-95: The candidate collection logic in token-scope-mismatch is
duplicated across the hex and px branches, and the same pattern also exists in
prefer-design-token, so extract a shared helper to centralize it. Create a
reusable function around TOKEN_INDEX lookups and filtering by token kind plus
expected scope, then have both token-scope-mismatch and prefer-design-token call
that helper with the appropriate index, key, kind, and limit so the hardcoded
candidate cap is maintained in one place.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e101d112-279a-4001-a1b8-33896bf5a611
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yaml
📒 Files selected for processing (42)
.changeset/eslint-plugin-vapor-css-token-rules.md.changeset/open-loops-hide.mddocs/superpowers/plans/2026-06-30-eslint-plugin-vapor-css-token-rules.mddocs/superpowers/specs/2026-06-30-css-token-rules-design.mdpackages/eslint-plugin-vapor/README.mdpackages/eslint-plugin-vapor/package.jsonpackages/eslint-plugin-vapor/src/data/property-scope-map.tspackages/eslint-plugin-vapor/src/data/tokens/border-radius.jsonpackages/eslint-plugin-vapor/src/data/tokens/dimension.jsonpackages/eslint-plugin-vapor/src/data/tokens/primitive-color.dark.jsonpackages/eslint-plugin-vapor/src/data/tokens/primitive-color.light.jsonpackages/eslint-plugin-vapor/src/data/tokens/resolver.jsonpackages/eslint-plugin-vapor/src/data/tokens/semantic-color.dark.jsonpackages/eslint-plugin-vapor/src/data/tokens/semantic-color.light.jsonpackages/eslint-plugin-vapor/src/data/tokens/shadow.jsonpackages/eslint-plugin-vapor/src/data/tokens/space.jsonpackages/eslint-plugin-vapor/src/data/tokens/text-style.jsonpackages/eslint-plugin-vapor/src/data/tokens/typography.jsonpackages/eslint-plugin-vapor/src/index.tspackages/eslint-plugin-vapor/src/rules/alt-text-on-avatar.tspackages/eslint-plugin-vapor/src/rules/aria-label-on-icon-button.tspackages/eslint-plugin-vapor/src/rules/aria-label-on-navigation.tspackages/eslint-plugin-vapor/src/rules/no-invalid-design-token.test.tspackages/eslint-plugin-vapor/src/rules/no-invalid-design-token.tspackages/eslint-plugin-vapor/src/rules/prefer-design-token.test.tspackages/eslint-plugin-vapor/src/rules/prefer-design-token.tspackages/eslint-plugin-vapor/src/rules/should-have-title-on-dialog.tspackages/eslint-plugin-vapor/src/rules/token-scope-mismatch.test.tspackages/eslint-plugin-vapor/src/rules/token-scope-mismatch.tspackages/eslint-plugin-vapor/src/utils/allowlist-matcher.test.tspackages/eslint-plugin-vapor/src/utils/allowlist-matcher.tspackages/eslint-plugin-vapor/src/utils/color-resolver.test.tspackages/eslint-plugin-vapor/src/utils/color-resolver.tspackages/eslint-plugin-vapor/src/utils/css-value-parser.test.tspackages/eslint-plugin-vapor/src/utils/css-value-parser.tspackages/eslint-plugin-vapor/src/utils/segment-distance.test.tspackages/eslint-plugin-vapor/src/utils/segment-distance.tspackages/eslint-plugin-vapor/src/utils/token-index.test.tspackages/eslint-plugin-vapor/src/utils/token-index.tspackages/eslint-plugin-vapor/src/utils/token-scope.test.tspackages/eslint-plugin-vapor/src/utils/token-scope.tspackages/eslint-plugin-vapor/tsconfig.json
| 'eslint-plugin-vapor': minor | ||
| --- | ||
|
|
||
| Add three CSS design-token rules: `css/no-invalid-design-token`, `css/token-scope-mismatch`, `css/prefer-design-token`. Opt in via the new `vapor.configs.css` preset. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the rule IDs and preset name in the changelog.
This note says css/* and vapor.configs.css, but the public names elsewhere in the PR are vapor/* and vapor/flat/css. Keep the release note aligned so readers don’t chase a nonexistent config.
Suggested fix
- Add three CSS design-token rules: `css/no-invalid-design-token`, `css/token-scope-mismatch`, `css/prefer-design-token`. Opt in via the new `vapor.configs.css` preset.
+ Add three CSS design-token rules: `vapor/no-invalid-design-token`, `vapor/token-scope-mismatch`, `vapor/prefer-design-token`. Opt in via the new `vapor/flat/css` preset.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Add three CSS design-token rules: `css/no-invalid-design-token`, `css/token-scope-mismatch`, `css/prefer-design-token`. Opt in via the new `vapor.configs.css` preset. | |
| Add three CSS design-token rules: `vapor/no-invalid-design-token`, `vapor/token-scope-mismatch`, `vapor/prefer-design-token`. Opt in via the new `vapor/flat/css` preset. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.changeset/eslint-plugin-vapor-css-token-rules.md at line 5, The changelog
entry uses the wrong public rule IDs and preset name; update the note in the
changeset to match the actual exported names used elsewhere in the PR. Replace
the `css/*` rule references with the `vapor/*` equivalents and change the preset
mention from `vapor.configs.css` to `vapor/flat/css`, so the release note points
readers to the correct symbols.
| "sm": { | ||
| "$description": "Do not use — no active use case defined.", | ||
| "$extensions": { | ||
| "io.goorm.vapor": { | ||
| "status": "do-not-use", | ||
| "reason": "No active use case defined.", | ||
| "when": [], | ||
| "avoid": [] | ||
| } | ||
| }, | ||
| "$value": { | ||
| "offsetX": { "value": 0, "unit": "px" }, | ||
| "offsetY": { "value": 1, "unit": "px" }, | ||
| "blur": { "value": 3, "unit": "px" }, | ||
| "spread": { "value": 0, "unit": "px" }, | ||
| "color": "rgba(0, 0, 0, 0.2)" | ||
| } | ||
| }, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
git ls-files 'packages/eslint-plugin-vapor/src/**' | sed -n '1,200p'Repository: goorm-dev/vapor-ui
Length of output: 2777
🏁 Script executed:
ast-grep outline packages/eslint-plugin-vapor/src/utils/token-index.ts --view expandedRepository: goorm-dev/vapor-ui
Length of output: 1445
🏁 Script executed:
ast-grep outline packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.ts --view expandedRepository: goorm-dev/vapor-ui
Length of output: 407
🏁 Script executed:
ast-grep outline packages/eslint-plugin-vapor/src/utils/token-index.test.ts --view expandedRepository: goorm-dev/vapor-ui
Length of output: 229
🏁 Script executed:
cat -n packages/eslint-plugin-vapor/src/utils/token-index.ts | sed -n '1,240p'Repository: goorm-dev/vapor-ui
Length of output: 8698
🏁 Script executed:
cat -n packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.ts | sed -n '1,220p'Repository: goorm-dev/vapor-ui
Length of output: 4807
🏁 Script executed:
cat -n packages/eslint-plugin-vapor/src/data/tokens/shadow.json | sed -n '1,120p'Repository: goorm-dev/vapor-ui
Length of output: 3903
🏁 Script executed:
rg -n --hidden -S '"do-not-use"|io\.goorm\.vapor|status' packages/eslint-plugin-vapor/srcRepository: goorm-dev/vapor-ui
Length of output: 14162
Exclude do-not-use tokens from the token index
walkShadow (and the semantic-color walkers) still add these entries to canonicalTokens, so no-invalid-design-token will accept deprecated tokens like --vapor-shadow-sm. Filter them out here or carry the status into the rule.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/eslint-plugin-vapor/src/data/tokens/shadow.json` around lines 5 -
22, The shadow token entry is still being indexed even though it is marked
do-not-use, which lets deprecated tokens pass validation. Update the token
indexing path in the shadow walker (and match the same behavior used by the
semantic-color walkers) so `walkShadow` skips entries whose extension status is
do-not-use, or otherwise propagates that status into `canonicalTokens` and
`no-invalid-design-token`. Use the existing `walkShadow` and `canonicalTokens`
symbols to locate the fix and keep deprecated tokens out of the accepted set.
| function walkFoundation(b: IndexBuilder, json: unknown, prefix: string, scope: Scope): void { | ||
| const visit = (node: unknown, path: string[]): void => { | ||
| if (!node || typeof node !== 'object') return; | ||
| const obj = node as Record<string, unknown>; | ||
| if ('$value' in obj && obj.$value && typeof obj.$value === 'object') { | ||
| const v = obj.$value as { value?: number; unit?: string }; | ||
| if (typeof v.value === 'number' && v.unit === 'px') { | ||
| const name = nameFromPath(prefix, path); | ||
| b.canonical.add(name); | ||
| b.meta.set(name, { name, kind: 'foundation', scope, px: v.value }); | ||
| pushIndex(b.pxIndex, v.value, name); | ||
| } | ||
| return; | ||
| } | ||
| for (const [k, v] of Object.entries(obj)) { | ||
| if (k.startsWith('$')) continue; | ||
| visit(v, [...path, k]); | ||
| } | ||
| }; | ||
| visit(json, []); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant file first, then inspect the specific sections and related token JSON files.
ast-grep outline packages/eslint-plugin-vapor/src/utils/token-index.ts --view expanded || true
printf '\n--- token-index.ts (relevant lines) ---\n'
sed -n '1,220p' packages/eslint-plugin-vapor/src/utils/token-index.ts | cat -n
printf '\n--- search for do-not-use handling ---\n'
rg -n 'do-not-use|io\.goorm\.vapor|status' packages/eslint-plugin-vapor/src packages -g '!**/dist/**' -g '!**/build/**' || true
printf '\n--- candidate JSON files ---\n'
fd -a 'semantic-color*.json|shadow.json|foundation*.json' packages/eslint-plugin-vapor -t f || trueRepository: goorm-dev/vapor-ui
Length of output: 38726
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect source JSON samples if present to confirm the marker shape.
for f in $(fd -a 'shadow.json|semantic-color.*\.json|foundation.*\.json' packages/eslint-plugin-vapor -t f 2>/dev/null); do
echo "--- $f ---"
python3 - <<'PY' "$f"
import json, sys
path=sys.argv[1]
with open(path, 'r', encoding='utf-8') as fh:
data=json.load(fh)
# Print only keys/paths where status exists to avoid large output.
def walk(node, path=()):
if isinstance(node, dict):
if '$extensions' in node or 'status' in node:
print(path, {k: node[k] for k in node.keys() if k in ('$extensions','status','value','unit')})
for k,v in node.items():
if isinstance(k,str) and k.startswith('$'):
continue
walk(v, path+(k,))
elif isinstance(node, list):
for i,v in enumerate(node):
walk(v, path+(str(i),))
walk(data)
PY
doneRepository: goorm-dev/vapor-ui
Length of output: 34942
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the rule that consumes TOKEN_INDEX to gauge impact.
ast-grep outline packages/eslint-plugin-vapor/src --view expanded || true
rg -n 'TOKEN_INDEX|isCanonicalToken|no-invalid-design-token|canonicalTokens|tokenMeta|byHex|byPx' packages/eslint-plugin-vapor/src -g '!**/dist/**' -g '!**/build/**' || trueRepository: goorm-dev/vapor-ui
Length of output: 9542
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the rule implementation if present.
fd -a 'no-invalid-design-token*|*design-token*' packages/eslint-plugin-vapor/src -t f || trueRepository: goorm-dev/vapor-ui
Length of output: 500
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the invalid-token rule and its tests to see how canonical tokens are used.
sed -n '1,220p' packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.ts | cat -n
printf '\n--- tests ---\n'
sed -n '1,220p' packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.test.ts | cat -n
printf '\n--- token-index tests ---\n'
sed -n '1,220p' packages/eslint-plugin-vapor/src/utils/token-index.test.ts | cat -nRepository: goorm-dev/vapor-ui
Length of output: 10009
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.ts | cat -n
printf '\n--- tests ---\n'
sed -n '1,220p' packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.test.ts | cat -n
printf '\n--- token-index tests ---\n'
sed -n '1,220p' packages/eslint-plugin-vapor/src/utils/token-index.test.ts | cat -nRepository: goorm-dev/vapor-ui
Length of output: 10009
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect token source files for do-not-use markers beyond shadow/semantic colors.
rg -n '"status"\s*:\s*"do-not-use"|io\.goorm\.vapor' packages/eslint-plugin-vapor/src/data/tokens \
-g 'dimension.json' -g 'space.json' -g 'border-radius.json' -g 'primitive-color.light.json' \
-g 'semantic-color.light.json' -g 'shadow.json' || trueRepository: goorm-dev/vapor-ui
Length of output: 5964
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the exact shapes around the marked entries in shadow and semantic colors.
python3 - <<'PY'
import json, pathlib
paths = [
pathlib.Path('packages/eslint-plugin-vapor/src/data/tokens/shadow.json'),
pathlib.Path('packages/eslint-plugin-vapor/src/data/tokens/semantic-color.light.json'),
]
for path in paths:
data = json.loads(path.read_text())
print(f'--- {path} ---')
def find(node, path_parts=()):
if isinstance(node, dict):
ext = node.get('$extensions', {})
if isinstance(ext, dict):
vap = ext.get('io.goorm.vapor')
if isinstance(vap, dict) and vap.get('status') == 'do-not-use':
print(path_parts, vap)
for k, v in node.items():
if isinstance(k, str) and k.startswith('$'):
continue
find(v, path_parts + (k,))
elif isinstance(node, list):
for i, v in enumerate(node):
find(v, path_parts + (str(i),))
find(data)
PYRepository: goorm-dev/vapor-ui
Length of output: 675
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- no-invalid-design-token.ts ---'
cat -n packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.ts | sed -n '1,220p'
echo
echo '--- no-invalid-design-token.test.ts ---'
cat -n packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.test.ts | sed -n '1,220p'
echo
echo '--- token-index.test.ts ---'
cat -n packages/eslint-plugin-vapor/src/utils/token-index.test.ts | sed -n '1,220p'Repository: goorm-dev/vapor-ui
Length of output: 10072
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Focus on the consumers of TOKEN_INDEX so the impact is clear.
sed -n '1,220p' packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.ts | cat -n
printf '\n---\n'
sed -n '1,220p' packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.test.ts | cat -n
printf '\n---\n'
sed -n '1,220p' packages/eslint-plugin-vapor/src/utils/token-index.test.ts | cat -nRepository: goorm-dev/vapor-ui
Length of output: 9977
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check whether foundation token sources include any do-not-use markers.
rg -n '"status"\s*:\s*"do-not-use"' packages/eslint-plugin-vapor/src/data/tokens/{dimension,space,border-radius,primitive-color.light}.json || trueRepository: goorm-dev/vapor-ui
Length of output: 156
Skip deprecated semantic-color and shadow tokens in the index
walkSemanticColors and walkShadow still add status: "do-not-use" entries to TOKEN_INDEX.canonicalTokens, so no-invalid-design-token accepts deprecated tokens as valid. Filter $extensions["io.goorm.vapor"].status === "do-not-use" here; foundation tokens don’t carry this marker.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/eslint-plugin-vapor/src/utils/token-index.ts` around lines 46 - 66,
The token index still includes deprecated semantic-color and shadow entries
marked with $extensions["io.goorm.vapor"].status equal to do-not-use, which lets
them pass validation. Update the indexing logic in walkSemanticColors and
walkShadow so they skip adding any token whose extension status is do-not-use
before inserting into TOKEN_INDEX.canonicalTokens or related metadata. Leave
walkFoundation unchanged since foundation tokens do not use this marker.
Description of Changes
Summary by CodeRabbit
New Features
Documentation
Bug Fixes
eslint-plugin-vaporpackage.@eslint/cssas a peer dependency to parse the Abstract Syntax Tree (AST) of CSS files.Checklist
Before submitting the PR, please make sure you have checked all of the following items.