Skip to content

feat: add design.md linter#1

Merged
davideast merged 6 commits into
mainfrom
feat/linter
Apr 10, 2026
Merged

feat: add design.md linter#1
davideast merged 6 commits into
mainfrom
feat/linter

Conversation

@davideast

Copy link
Copy Markdown
Collaborator

Introduces the DESIGN.md linter. Follows a "Parse, don't validate" architecture where structural errors are surfaced as actionable diagnostics.

Public API

The primary entry point for the library is the lint() function. It accepts a raw YAML string (extracted from markdown or a standalone file) and returns a comprehensive report.

import { lint } from '@google/design.md';

const markdownResult = `---
name: Custom Theme
colors:
  primary: '#ff0000'
  accent: 'red' # Invalid color
---
# My Design System
`;

const report = lint(markdownResult);

// Check if the model was built successfully
console.log(report.designSystem.name); // "Custom Theme"

// Inspect diagnostics (both modeler errors and linter warnings)
report.diagnostics.forEach(diagnostic => {
  console.log(`[${diagnostic.severity.toUpperCase()}] ${diagnostic.path}: ${diagnostic.message}`);
});

// Quick summary counts
console.log(`Errors: ${report.summary.errors}`);
console.log(`Warnings: ${report.summary.warnings}`);
console.log(`Infos: ${report.summary.infos}`);

Advanced Usage: Custom Rules

You can also run the linter with a custom set of rules or pre-evaluate them for TUI applications:

import { runLinter, preEvaluate, DEFAULT_RULES } from '@google-labs-code/design.md-linter';

// Run with a subset of rules
const customResult = runLinter(designSystemState, [myCustomRule]);

// For interactive menus (groups by Fixes, Improvements, Suggestions)
const graded = preEvaluate(designSystemState);
console.log(graded.fixes); // Errors that must be fixed

Modeler-Emitted Diagnostics

The ModelHandler now exhaustively validates all properties during the resolution phase:

  • Colors: Must be valid hex format (#RGB or #RRGGBB).
  • Typography: fontFamily cannot be a color, fontWeight must be a number, and dimensions (fontSize, lineHeight, letterSpacing) are strictly restricted to px and rem units.
  • Dimensions: rounded and spacing tokens are also restricted to px and rem.

Any invalid value is recorded as an error diagnostic rather than being silently dropped, ensuring the generated state is always valid.

Pluggable Linter Architecture

The linter has been refactored into a pure functional core with standalone rules:

  • Rules: brokenRef, contrastCheck, missingPrimary, orphanedTokens, tokenSummary, and missingSections.
  • Redundant rules (invalidColor, nonStandardUnit) were removed as they are now handled natively by the modeler's structural validation.

@davideast
davideast merged commit 83c0933 into main Apr 10, 2026
3 checks passed
@davideast
davideast deleted the feat/linter branch April 10, 2026 23:50
davideast added a commit that referenced this pull request Apr 21, 2026
- Remove console.log/console.error from loadSpecConfig() that corrupted
  stdout JSON output for CLI and programmatic consumers
- Introduce getSpecConfig() lazy singleton to cache YAML file reads
- Add 3 new tests: no-stdout-pollution, lazy-load validity, cache identity

Fixes Critical Blockers #1 and #2 from pre-publish analysis.
davideast added a commit that referenced this pull request Apr 21, 2026
* chore: remove unused TUI dependencies

* fix: remove debug logging and add lazy-loaded config singleton

- Remove console.log/console.error from loadSpecConfig() that corrupted
  stdout JSON output for CLI and programmatic consumers
- Introduce getSpecConfig() lazy singleton to cache YAML file reads
- Add 3 new tests: no-stdout-pollution, lazy-load validity, cache identity

Fixes Critical Blockers #1 and #2 from pre-publish analysis.

* fix: address high-risk publishing issues

- Add engines field to package.json (node >= 18)
- Add prepublishOnly script to enforce build before publish
- Add Node.js smoke test to CI workflow
- Clean up unused SPEC_VERSION import in test

* fix: derive version from package.json and simplify spec.md resolution

Version:
- Create src/version.ts that reads ../package.json using import.meta.url
- Path is stable across source, bundle, installed, and npx contexts
  because npm always includes package.json in the published tarball
- Replace hardcoded '0.1.0' in index.ts with dynamic VERSION import
- Add 3 tests: matches package.json, valid semver, not fallback

spec.md path resolution:
- Reduce 5 shotgun candidate paths to 2 deterministic strategies:
  1. Bundle path: ./spec.md (build copies it alongside entry points)
  2. Dev path: ../../../../../docs/spec.md (relative to source)
- Add explicit specPath parameter for callers who know the path
- Update build script to copy spec.md to both dist/ and dist/linter/
- Add 3 tests: consistency, content length, explicit path contract

* fix: pre-publish hardening — 5 items

1. Move build-only deps to devDependencies (keep zod for type compat)
   - citty, mdast, remark-*, unified, unist-util-visit, yaml all bundled
   - Zero runtime require() calls to any npm dependency
2. Add 'designmd' bin alias for Windows shell compatibility
   - cmd.exe confuses .md extension with file association
3. Add ./package.json to exports map
   - Prevents ERR_PACKAGE_PATH_NOT_EXPORTED in strict ESM bundlers
4. Remove stale @types/react from devDependencies
5. Add tarball smoke test to CI workflow
   - Packs, installs in clean dir, runs CLI + programmatic import
   - Catches 'works in monorepo, breaks when installed' bugs
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.

2 participants