Skip to content

feat(linter): token name collision detection for flat vs grouped keys #149

Description

@Arshgill01

Following up on @davideast's suggestion in #68:

One thing your PR has that #103 does not: collision detection for cases where a flat key and a grouped key produce the same name. That's a good idea and worth tracking. If you're interested, filing a separate issue for collision detection would be welcome and we can work on it from there. Create an issue and tag me in it.

Problem

Since #103 merged forEachLeaf and arbitrary-depth nesting for colors, rounded, and spacing, it is now possible to author a DESIGN.md that produces silent name collisions in the symbol table. Two distinct cases:

Case 1 — Duplicate dot path (a flat key whose literal text mirrors an existing nested token's dot-path):

colors:
  utility-info:
    50: "#EEF7FC"
  utility-info.50: "#FFFFFF"   # same symbol table key: colors.utility-info.50

Case 2 — Flat/grouped name collision (a nested token that normalizes, dots → hyphens, to the same string as an already-registered flat key):

colors:
  utility-info-50: "#EEF7FC"   # registers as utility-info-50
  utility-info:
    50: "#FFFFFF"               # also normalizes to utility-info-50 in flat output

In both cases the second value silently overwrites the first in the symbol table. The Tailwind v4 emitter then exports whichever value happened to win the race — with no diagnostic emitted to the user.

The same issue applies to rounded and spacing since they also use forEachLeaf as of #103.

Proposed solution

During ModelHandler Phase 1 (primitive token resolution), track two sets per token category (colors, rounded, spacing):

  • seenKeys: Set<string> — exact dot-separated path, to detect Case 1
  • seenNormalized: Map<string, string> — hyphen-normalized name, to detect Case 2

Emit an error-severity finding for each violation before writing to the symbol table, and skip the offending token so the winning value is deterministic and the user sees the diagnostic at lint time.

@davideast — tagging you as requested in #68.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions