Skip to content

No way to declare intentional component sub-tokens, so every recipe warns #171

Description

@yangcheng

Custom component sub-tokens already work: they parse, their token references resolve, and they survive into the resolved model, so a project can build on them. design.md lint exits 0. The gap is that there's no way to tell the linter which ones are intentional, so broken-ref warns once per recipe, forever.

Our design system records two sub-tokens per recipe that the exporter schema doesn't define: owner, the canonical code module implementing the recipe, and gap, a resolved spacing value for compositions that own the spacing between their children. Our build step reads the linter's resolved model and generates a typed contract from both.

That's 49 of our 62 warnings today, growing with every component. The cost isn't the volume, it's that a misspelled sub-token produces a warning that reads identically to the 48 expected ones — the check's real value is buried by its own false positives.

Blanket suppression isn't what we want, and it isn't really available anyway: the sub-token check lives inside broken-ref alongside dangling-reference errors, so deselecting the rule through LintOptions.rules would silence genuine errors. Since 0.4.0 we post-process the report instead, matching finding.rule plus the token path against a list of extensions we declare ourselves — which works, but means we maintain the typo defense the linter could be providing.

What would solve it, in order of preference:

  1. Per-rule options. A way to pass configuration to a rule — for broken-ref, the set of sub-token names the project owns. Validation then runs against the union, and anything unlisted still warns. This generalizes past our case; sub-tokens just happen to be where we hit it first.
  2. A frontmatter declaration, so the extension names live next to the recipes that use them and travel with the document.
  3. A documented extension prefix such as x-owner, treated as always-valid. Cheapest to implement, but it renames keys in every recipe and in the resolved model.

Any of these lets a project keep the typo detection instead of reimplementing it. Happy to send a PR in whichever direction you prefer.

Checked in 0.3.0 and 0.4.0: VALID_COMPONENT_SUB_TOKENS, the broken-ref implementation, and LintOptions are identical. loadSpecConfig(filePath?) accepts a path but getSpecConfig() calls it with no argument and it isn't exported. The 0.4.0 omitted: key covers section-level omissions, not sub-tokens.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions