# How the broken-ref Rule Detects Unresolved Token References in DESIGN.md

> Learn how the broken ref rule in DESIGN.md detects unresolved token references by identifying failed lookups during component parsing and reporting them as lint errors.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: internals
- Published: 2026-06-28

---

**The `broken-ref` rule detects unresolved token references by collecting failed reference lookups during component parsing and then reporting them as lint errors after the model is fully built.**

The `broken-ref` rule in the google-labs-code/design.md linter validates that all token references within component definitions resolve to actual design tokens. When a property value like `{color.primary}` cannot be mapped to a defined token in the symbol table, the rule generates a specific lint error indicating the exact reference path and component location.

## How Reference Resolution Works During Parsing

During the linting process, the `ModelHandler.execute` method in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts) processes the `components` section of the DESIGN.md file. For each property value that matches the token reference pattern (`{…}`), the handler invokes `resolveReference` to traverse the reference chain against the symbol table.

When `resolveReference` encounters a missing token or detects a circular dependency, it returns `null`. The handler captures these failures by pushing the raw reference string onto the component's `unresolvedRefs` array, which is stored alongside the component's properties in the internal model.

### Tracking Unresolved References in ModelHandler

The following TypeScript implementation from [`model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/model/handler.ts) demonstrates how unresolved references are collected during component processing:

```typescript
// Inside ModelHandler.execute – building component definitions
for (const [compName, props] of Object.entries(input.components)) {
  const properties = new Map<string, ResolvedValue>();
  const unresolvedRefs: string[] = [];

  for (const [propName, rawValue] of Object.entries(props)) {
    if (isTokenReference(rawValue)) {
      const refPath = rawValue.slice(1, -1);
      const resolved = resolveReference(symbolTable, refPath, new Set());
      if (resolved !== null) {
        properties.set(propName, resolved);
      } else {
        // <-- reference cannot be resolved → track it
        unresolvedRefs.push(rawValue);
        properties.set(propName, rawValue);
      }
    }
    // … handling of numbers, booleans, colors, dimensions …
  }

  components.set(compName, { properties, unresolvedRefs });
}

```

## Reporting Broken References

After the model is constructed, the `brokenRef` function defined in [`packages/cli/src/linter/linter/rules/broken-ref.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/rules/broken-ref.ts) receives the `DesignSystemState` containing the populated `components` map. The rule iterates through each component's `unresolvedRefs` array and generates a `RuleFinding` for every entry.

Each finding includes the specific path `components.<componentName>` and a descriptive message identifying the unresolvable reference. This approach ensures that detection is decoupled from reporting—the parsing stage captures all resolution failures, while the rule simply surfaces them as lint errors.

### The brokenRef Rule Implementation

The implementation in [`broken-ref.ts`](https://github.com/google-labs-code/design.md/blob/main/broken-ref.ts) processes the pre-collected unresolved references:

```typescript
export function brokenRef(state: DesignSystemState): RuleFinding[] {
  const findings: RuleFinding[] = [];
  for (const [compName, comp] of state.components) {
    // Unresolved references → generate error findings
    for (const ref of comp.unresolvedRefs) {
      findings.push({
        path: `components.${compName}`,
        message: `Reference ${ref} does not resolve to any defined token.`,
      });
    }

    // … additional check for unknown component sub‑tokens …
  }
  return findings;
}

```

## Key Files and Functions

The detection and reporting logic spans two primary files in the google-labs-code/design.md repository:

- **[`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts)**: Parses DESIGN.md components, resolves token references via `resolveReference`, and populates the `unresolvedRefs` arrays when references fail to resolve.
- **[`packages/cli/src/linter/linter/rules/broken-ref.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/rules/broken-ref.ts)**: Contains the `brokenRef` function that transforms unresolved references into lint findings.
- **[`packages/cli/src/linter/linter/rules/index.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/rules/index.ts)**: Registers the `broken-ref` rule within the default linting configuration.

## Summary

- The `broken-ref` rule detects unresolved token references by analyzing pre-collected data from the parsing phase rather than performing live lookups.
- During component processing in `ModelHandler.execute`, failed reference resolutions (returning `null` from `resolveReference`) are stored in component-specific `unresolvedRefs` arrays.
- The `brokenRef` rule in [`packages/cli/src/linter/linter/rules/broken-ref.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/rules/broken-ref.ts) iterates these arrays to generate lint errors with paths like `components.Button` and messages identifying the specific broken reference.
- This architecture separates reference resolution from error reporting, ensuring consistent and accurate detection of missing tokens in DESIGN.md files.

## Frequently Asked Questions

### What triggers the broken-ref rule to report an error?

The `broken-ref` rule reports an error when a component property contains a token reference string (enclosed in curly braces) that cannot be resolved to a defined token in the symbol table. This occurs when `resolveReference` returns `null` during the parsing phase, indicating either a missing token definition or a circular reference chain.

### How does the linter distinguish between valid and broken references?

The linter validates references in `ModelHandler.execute` by calling `resolveReference` with the reference path and symbol table. If the function successfully traverses the reference chain and returns a resolved value, the reference is valid. If it returns `null`, the reference is considered broken and is added to the `unresolvedRefs` collection for later reporting.

### Where are broken references stored before the rule reports them?

Broken references are stored in the `unresolvedRefs` array property of each component object within the `DesignSystemState.components` map. This data structure is populated during the model building phase in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts) and consumed by the `brokenRef` function after the entire model has been constructed.

### Can circular token references trigger the broken-ref rule?

Yes. The `resolveReference` function tracks visited references using a `Set` to detect cycles. When a circular reference is detected, the function returns `null`, causing the reference to be added to `unresolvedRefs` and subsequently reported by the `broken-ref` rule as an unresolved token reference.