Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ The linter runs seven rules against a parsed DESIGN.md. Each rule produces findi
| `orphaned-tokens` | warning | Color tokens defined but never referenced by any component |
| `token-summary` | info | Summary of how many tokens are defined in each section |
| `missing-sections` | info | Optional sections (spacing, rounded) absent when other tokens exist |
| `missing-typography` | warning | Colors are defined but no typography tokens exist — agents will use default fonts |
| `section-order` | warning | Sections appear out of the canonical order defined by the spec |

### Programmatic API
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ describe('spec command', () => {
const output = JSON.parse(outputStr);
expect(output.spec).toBeDefined();
expect(output.rules).toBeDefined();
expect(output.rules.length).toBe(7);
expect(output.rules.length).toBe(8);
});
});
1 change: 1 addition & 0 deletions packages/cli/src/linter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export {
orphanedTokens,
tokenSummary,
missingSections,
missingTypography,
} from './linter/rules/index.js';
export { contrastRatio } from './model/handler.js';
export { TailwindEmitterHandler } from './tailwind/handler.js';
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/linter/linter/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { orphanedTokensRule } from './orphaned-tokens.js';
import { tokenSummaryRule } from './token-summary.js';
import { missingSectionsRule } from './missing-sections.js';
import { sectionOrderRule } from './section-order.js';
import { missingTypographyRule } from './missing-typography.js';

/** The default set of lint rule descriptors, in order. */
export const DEFAULT_RULE_DESCRIPTORS: RuleDescriptor[] = [
Expand All @@ -17,6 +18,7 @@ export const DEFAULT_RULE_DESCRIPTORS: RuleDescriptor[] = [
orphanedTokensRule,
tokenSummaryRule,
missingSectionsRule,
missingTypographyRule,
sectionOrderRule,
];

Expand All @@ -40,5 +42,6 @@ export { contrastCheck } from './contrast-ratio.js';
export { orphanedTokens } from './orphaned-tokens.js';
export { tokenSummary } from './token-summary.js';
export { missingSections } from './missing-sections.js';
export { missingTypography } from './missing-typography.js';
export { sectionOrder } from './section-order.js';
export type { LintRule } from './types.js';
34 changes: 34 additions & 0 deletions packages/cli/src/linter/linter/rules/missing-typography.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, it, expect } from 'bun:test';
import { missingTypography } from './missing-typography.js';
import { buildState } from './test-helpers.js';

describe('missingTypography', () => {
it('emits warning when colors exist but no typography defined', () => {
const state = buildState({
colors: { primary: '#ff0000' },
// no typography
});
const findings = missingTypography(state);
expect(findings.length).toBe(1);
expect(findings[0]!.path).toBe('typography');
expect(findings[0]!.message).toMatch(/typography/i);
});

it('returns empty when typography IS defined', () => {
const state = buildState({
colors: { primary: '#ff0000' },
typography: {
'body-md': {
fontFamily: 'Inter',
fontSize: '16px',
},
},
});
expect(missingTypography(state)).toEqual([]);
});

it('returns empty when no colors defined (nothing to compare against)', () => {
const state = buildState({});
expect(missingTypography(state)).toEqual([]);
});
});
24 changes: 24 additions & 0 deletions packages/cli/src/linter/linter/rules/missing-typography.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { DesignSystemState } from '../../model/spec.js';
import type { RuleDescriptor, RuleFinding } from './types.js';

/**
* Missing typography — warns when colors are defined but no typography tokens exist.
* Without typography tokens, agents will fall back to their own font choices,
* reducing the author's control over the design system's typographic identity.
*/
export function missingTypography(state: DesignSystemState): RuleFinding[] {
if (state.typography.size === 0 && state.colors.size > 0) {
return [{
path: 'typography',
message: "No typography tokens defined. Agents will use default font choices, reducing your control over the design system's typographic identity.",
}];
}
return [];
}

export const missingTypographyRule: RuleDescriptor = {
name: 'missing-typography',
severity: 'warning',
description: "Missing typography — warns when colors are defined but no typography tokens exist.",
run: missingTypography,
};
2 changes: 1 addition & 1 deletion packages/cli/src/linter/linter/rules/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('LintRule type', () => {
});

it('has all rules in DEFAULT_RULE_DESCRIPTORS', () => {
expect(DEFAULT_RULE_DESCRIPTORS.length).toBe(7);
expect(DEFAULT_RULE_DESCRIPTORS.length).toBe(8);
DEFAULT_RULE_DESCRIPTORS.forEach((rule: RuleDescriptor) => {
expect(rule.name).toBeTruthy();
expect(rule.severity).toBeTruthy();
Expand Down
Loading