Skip to content

feat: add new rules about usage of design tokens#626

Open
noahchoii wants to merge 30 commits into
mainfrom
eslint-plugin-css-tokens
Open

feat: add new rules about usage of design tokens#626
noahchoii wants to merge 30 commits into
mainfrom
eslint-plugin-css-tokens

Conversation

@noahchoii

@noahchoii noahchoii commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Description of Changes

Summary by CodeRabbit

  • New Features

    • Added three new CSS lint rules for design tokens: detecting invalid token names, token/property scope mismatches, and raw values that should use tokens.
    • Expanded the CSS lint preset so these rules can be enabled together.
  • Documentation

    • Added setup and usage guidance for CSS/SCSS linting, including rule options, limits, and token-sync notes.
  • Bug Fixes

    • Improved accessibility rule messages and localized several lint messages for clearer feedback.
  • Added linting rules to inspect the usage of design tokens within the eslint-plugin-vapor package.
  • Since this targets CSS files, it utilizes @eslint/css as a peer dependency to parse the Abstract Syntax Tree (AST) of CSS files.
  • Based on this, the plugin inspects the following:
    • Use of Raw Values when a matching design token is available within the same scope.
    • Use of Primitive tokens when a Semantic token is available within the same scope.
    • Use of non-existent or unregistered tokens.
  • It currently relies on design token JSON files, which will be replaced once a dedicated tokens package is created in the future.

Checklist

Before submitting the PR, please make sure you have checked all of the following items.

  • The PR title follows the Conventional Commits convention. (e.g., feat, fix, docs, style, refactor, test, chore)
  • I have added tests for my changes.
  • I have updated the Storybook or relevant documentation.
  • I have added a changeset for this change. (e.g., for any changes that affect users, such as component prop changes or new features).
  • I have performed a self-code review.
  • I have followed the project's coding conventions and component patterns.

noahchoii added 22 commits June 22, 2026 17:44
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
@noahchoii noahchoii requested a review from MaxLee-dev as a code owner July 6, 2026 06:24
@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 158d43f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
eslint-plugin-vapor Minor

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

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vapor-ui Ready Ready Preview, Comment Jul 7, 2026 12:11am

Request Review

@noahchoii noahchoii changed the title feat: add token-lint-rule feat: add new rules about usage of design tokens Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@noahchoii, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 54 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 57c55607-5ff5-4a66-8ea0-22df9fd34cd2

📥 Commits

Reviewing files that changed from the base of the PR and between 2c41462 and 158d43f.

📒 Files selected for processing (11)
  • packages/eslint-plugin-vapor/package.json
  • packages/eslint-plugin-vapor/src/data/tokens/semantic-color.dark.json
  • packages/eslint-plugin-vapor/src/data/tokens/semantic-color.light.json
  • packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.ts
  • packages/eslint-plugin-vapor/src/utils/allowlist-matcher.ts
  • packages/eslint-plugin-vapor/src/utils/color-resolver.ts
  • packages/eslint-plugin-vapor/src/utils/css-value-parser.test.ts
  • packages/eslint-plugin-vapor/src/utils/css-value-parser.ts
  • packages/eslint-plugin-vapor/src/utils/segment-distance.ts
  • packages/eslint-plugin-vapor/src/utils/token-index.ts
  • packages/eslint-plugin-vapor/src/utils/token-scope.ts
📝 Walkthrough

Walkthrough

Adds three new ESLint CSS design-token rules (no-invalid-design-token, token-scope-mismatch, prefer-design-token) to eslint-plugin-vapor, backed by new token JSON data assets, a token index, parsing/distance/color utilities, plugin config wiring, README documentation, design/plan docs, and changesets. Also localizes several existing accessibility rule messages to Korean.

Changes

CSS Design-Token Rules

Layer / File(s) Summary
Token data assets and scope mapping
src/data/property-scope-map.ts, src/data/tokens/*.json, tsconfig.json
Adds Scope/PROPERTY_SCOPE mapping and JSON token assets for border-radius, dimension, space, typography, text-style, shadow, primitive/semantic colors (light/dark), and resolver config, plus JSON import support.
Token utilities
src/utils/color-resolver.ts, src/utils/segment-distance.ts, src/utils/allowlist-matcher.ts, src/utils/css-value-parser.ts, src/utils/token-scope.ts, src/utils/token-index.ts, plus *.test.ts
Implements OKLCH-to-hex conversion, Damerau-Levenshtein segment distance, wildcard allowlist matching, CSS value parsing (var/hex/dimension), token-scope inference, and the TOKEN_INDEX builder with unit tests.
no-invalid-design-token rule
src/rules/no-invalid-design-token.ts, ...test.ts
Detects unknown --vapor-* tokens in var() usages and reports with distance-based fix suggestions.
token-scope-mismatch rule
src/rules/token-scope-mismatch.ts, ...test.ts
Validates token scope against the CSS property's expected scope and suggests matching replacements.
prefer-design-token rule
src/rules/prefer-design-token.ts, ...test.ts
Suggests semantic/foundation token replacements for primitive tokens and raw hex/px values.
Plugin wiring, package config, and docs
src/index.ts, package.json, README.md, docs/superpowers/*, .changeset/*
Registers new rules and cssRecommended/flat/css config, adds culori/@eslint/css dependencies, and documents usage, design, and plan.

Accessibility Message Localization

Layer / File(s) Summary
Korean error messages
src/rules/alt-text-on-avatar.ts, src/rules/aria-label-on-icon-button.ts, src/rules/aria-label-on-navigation.ts, src/rules/should-have-title-on-dialog.ts
Updates four accessibility rule error message strings from English to Korean.

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
Loading

Related PRs: None identified.

Suggested labels: eslint-plugin-vapor, design-tokens, documentation

Suggested reviewers: None identified.

Poem:
A rabbit hopped through CSS trees,
Chasing tokens, scopes, and keys,
Hex to px, and var() unwound,
Three new rules now stand their ground,
Korean words for a11y ease. 🐰🎨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding new design-token usage rules to the plugin.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch eslint-plugin-css-tokens

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (5)
packages/eslint-plugin-vapor/src/index.ts (1)

23-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Type assertion overstates completeness of RuleName keys.

a11yRecommended/cssRecommended are each cast to Record<RuleName, 'error'|'warn'|'off'> but only cover 4 and 3 of the 7 total RuleName keys respectively. Consider Partial<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 lift

Duplicated 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 hardcoded 3 limit 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/isFoundationScope use .every(), requiring all scopes of a property to belong to the bucket. Currently safe since every PROPERTY_SCOPE entry 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 value

Trim the dead default ignore values

parseDeclarationValue() only produces var, hex, and dimension parts, so the default keyword entries (transparent, none, currentcolor, inherit, initial, unset, and bare 0) never reach the ignoreValues check. Remove them from DEFAULT_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 win

Peer dependency range for @eslint/css is wider than the devDependency range.

devDependency pins @eslint/css to ^1.3.0 (< 2.0.0), but peerDependency allows >=1.3.0 unbounded, permitting consumers to install a future major of @eslint/css that 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3a87c86 and 2c41462.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is 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.md
  • docs/superpowers/plans/2026-06-30-eslint-plugin-vapor-css-token-rules.md
  • docs/superpowers/specs/2026-06-30-css-token-rules-design.md
  • packages/eslint-plugin-vapor/README.md
  • packages/eslint-plugin-vapor/package.json
  • packages/eslint-plugin-vapor/src/data/property-scope-map.ts
  • packages/eslint-plugin-vapor/src/data/tokens/border-radius.json
  • packages/eslint-plugin-vapor/src/data/tokens/dimension.json
  • packages/eslint-plugin-vapor/src/data/tokens/primitive-color.dark.json
  • packages/eslint-plugin-vapor/src/data/tokens/primitive-color.light.json
  • packages/eslint-plugin-vapor/src/data/tokens/resolver.json
  • packages/eslint-plugin-vapor/src/data/tokens/semantic-color.dark.json
  • packages/eslint-plugin-vapor/src/data/tokens/semantic-color.light.json
  • packages/eslint-plugin-vapor/src/data/tokens/shadow.json
  • packages/eslint-plugin-vapor/src/data/tokens/space.json
  • packages/eslint-plugin-vapor/src/data/tokens/text-style.json
  • packages/eslint-plugin-vapor/src/data/tokens/typography.json
  • packages/eslint-plugin-vapor/src/index.ts
  • packages/eslint-plugin-vapor/src/rules/alt-text-on-avatar.ts
  • packages/eslint-plugin-vapor/src/rules/aria-label-on-icon-button.ts
  • packages/eslint-plugin-vapor/src/rules/aria-label-on-navigation.ts
  • packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.test.ts
  • packages/eslint-plugin-vapor/src/rules/no-invalid-design-token.ts
  • packages/eslint-plugin-vapor/src/rules/prefer-design-token.test.ts
  • packages/eslint-plugin-vapor/src/rules/prefer-design-token.ts
  • packages/eslint-plugin-vapor/src/rules/should-have-title-on-dialog.ts
  • packages/eslint-plugin-vapor/src/rules/token-scope-mismatch.test.ts
  • packages/eslint-plugin-vapor/src/rules/token-scope-mismatch.ts
  • packages/eslint-plugin-vapor/src/utils/allowlist-matcher.test.ts
  • packages/eslint-plugin-vapor/src/utils/allowlist-matcher.ts
  • packages/eslint-plugin-vapor/src/utils/color-resolver.test.ts
  • packages/eslint-plugin-vapor/src/utils/color-resolver.ts
  • packages/eslint-plugin-vapor/src/utils/css-value-parser.test.ts
  • packages/eslint-plugin-vapor/src/utils/css-value-parser.ts
  • packages/eslint-plugin-vapor/src/utils/segment-distance.test.ts
  • packages/eslint-plugin-vapor/src/utils/segment-distance.ts
  • packages/eslint-plugin-vapor/src/utils/token-index.test.ts
  • packages/eslint-plugin-vapor/src/utils/token-index.ts
  • packages/eslint-plugin-vapor/src/utils/token-scope.test.ts
  • packages/eslint-plugin-vapor/src/utils/token-scope.ts
  • packages/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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
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.

Comment on lines +5 to +22
"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)"
}
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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 expanded

Repository: 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 expanded

Repository: goorm-dev/vapor-ui

Length of output: 407


🏁 Script executed:

ast-grep outline packages/eslint-plugin-vapor/src/utils/token-index.test.ts --view expanded

Repository: 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/src

Repository: 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.

Comment thread packages/eslint-plugin-vapor/src/utils/css-value-parser.ts Outdated
Comment on lines +46 to +66
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, []);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 || true

Repository: 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
done

Repository: 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/**' || true

Repository: 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 || true

Repository: 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 -n

Repository: 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 -n

Repository: 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' || true

Repository: 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)
PY

Repository: 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 -n

Repository: 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 || true

Repository: 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.

Comment thread packages/eslint-plugin-vapor/src/utils/token-index.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant