@buoy-design/core 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/audit.d.ts +47 -0
- package/dist/analysis/audit.d.ts.map +1 -0
- package/dist/analysis/audit.js +168 -0
- package/dist/analysis/audit.js.map +1 -0
- package/dist/analysis/index.d.ts +1 -0
- package/dist/analysis/index.d.ts.map +1 -1
- package/dist/analysis/index.js +2 -0
- package/dist/analysis/index.js.map +1 -1
- package/dist/analysis/semantic-diff.d.ts +15 -0
- package/dist/analysis/semantic-diff.d.ts.map +1 -1
- package/dist/analysis/semantic-diff.js +204 -1
- package/dist/analysis/semantic-diff.js.map +1 -1
- package/dist/analysis/token-suggestions.d.ts +8 -1
- package/dist/analysis/token-suggestions.d.ts.map +1 -1
- package/dist/analysis/token-suggestions.js +116 -10
- package/dist/analysis/token-suggestions.js.map +1 -1
- package/dist/extraction/css-parser.d.ts +51 -0
- package/dist/extraction/css-parser.d.ts.map +1 -0
- package/dist/extraction/css-parser.js +303 -0
- package/dist/extraction/css-parser.js.map +1 -0
- package/dist/extraction/index.d.ts +2 -0
- package/dist/extraction/index.d.ts.map +1 -0
- package/dist/extraction/index.js +2 -0
- package/dist/extraction/index.js.map +1 -0
- package/dist/graph/builder.d.ts +118 -0
- package/dist/graph/builder.d.ts.map +1 -0
- package/dist/graph/builder.js +328 -0
- package/dist/graph/builder.js.map +1 -0
- package/dist/graph/collectors/git.d.ts +90 -0
- package/dist/graph/collectors/git.d.ts.map +1 -0
- package/dist/graph/collectors/git.js +253 -0
- package/dist/graph/collectors/git.js.map +1 -0
- package/dist/graph/collectors/imports.d.ts +55 -0
- package/dist/graph/collectors/imports.d.ts.map +1 -0
- package/dist/graph/collectors/imports.js +230 -0
- package/dist/graph/collectors/imports.js.map +1 -0
- package/dist/graph/collectors/index.d.ts +9 -0
- package/dist/graph/collectors/index.d.ts.map +1 -0
- package/dist/graph/collectors/index.js +12 -0
- package/dist/graph/collectors/index.js.map +1 -0
- package/dist/graph/collectors/usages.d.ts +68 -0
- package/dist/graph/collectors/usages.d.ts.map +1 -0
- package/dist/graph/collectors/usages.js +244 -0
- package/dist/graph/collectors/usages.js.map +1 -0
- package/dist/graph/index.d.ts +11 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +29 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/queries.d.ts +81 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +379 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/types.d.ts +184 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +8 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/models/drift.d.ts +20 -4
- package/dist/models/drift.d.ts.map +1 -1
- package/dist/models/drift.js +55 -1
- package/dist/models/drift.js.map +1 -1
- package/dist/models/index.d.ts +3 -3
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -2
- package/dist/models/index.js.map +1 -1
- package/dist/models/token.d.ts +57 -0
- package/dist/models/token.d.ts.map +1 -1
- package/dist/models/token.js +9 -0
- package/dist/models/token.js.map +1 -1
- package/dist/tokenization/generator.d.ts +49 -0
- package/dist/tokenization/generator.d.ts.map +1 -0
- package/dist/tokenization/generator.js +886 -0
- package/dist/tokenization/generator.js.map +1 -0
- package/dist/tokenization/index.d.ts +2 -0
- package/dist/tokenization/index.d.ts.map +1 -0
- package/dist/tokenization/index.js +2 -0
- package/dist/tokenization/index.js.map +1 -0
- package/dist/tokens/comparison.d.ts +30 -0
- package/dist/tokens/comparison.d.ts.map +1 -0
- package/dist/tokens/comparison.js +142 -0
- package/dist/tokens/comparison.js.map +1 -0
- package/dist/tokens/index.d.ts +3 -0
- package/dist/tokens/index.d.ts.map +1 -0
- package/dist/tokens/index.js +5 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/tokens/parser.d.ts +11 -0
- package/dist/tokens/parser.d.ts.map +1 -0
- package/dist/tokens/parser.js +268 -0
- package/dist/tokens/parser.js.map +1 -0
- package/package.json +14 -10
- package/LICENSE +0 -21
- package/dist/analysis/semantic-diff.test.d.ts +0 -2
- package/dist/analysis/semantic-diff.test.d.ts.map +0 -1
- package/dist/analysis/semantic-diff.test.js +0 -188
- package/dist/analysis/semantic-diff.test.js.map +0 -1
- package/dist/models/component.test.d.ts +0 -2
- package/dist/models/component.test.d.ts.map +0 -1
- package/dist/models/component.test.js +0 -55
- package/dist/models/component.test.js.map +0 -1
- package/dist/models/drift.test.d.ts +0 -2
- package/dist/models/drift.test.d.ts.map +0 -1
- package/dist/models/drift.test.js +0 -38
- package/dist/models/drift.test.js.map +0 -1
- package/dist/models/token.test.d.ts +0 -2
- package/dist/models/token.test.d.ts.map +0 -1
- package/dist/models/token.test.js +0 -168
- package/dist/models/token.test.js.map +0 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface AuditValue {
|
|
2
|
+
category: 'color' | 'spacing' | 'typography' | 'radius';
|
|
3
|
+
value: string;
|
|
4
|
+
file: string;
|
|
5
|
+
line: number;
|
|
6
|
+
}
|
|
7
|
+
export interface CategoryStats {
|
|
8
|
+
uniqueCount: number;
|
|
9
|
+
totalUsages: number;
|
|
10
|
+
mostCommon: Array<{
|
|
11
|
+
value: string;
|
|
12
|
+
count: number;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export interface FileIssue {
|
|
16
|
+
file: string;
|
|
17
|
+
issueCount: number;
|
|
18
|
+
}
|
|
19
|
+
export interface CloseMatch {
|
|
20
|
+
value: string;
|
|
21
|
+
closeTo: string;
|
|
22
|
+
distance: number;
|
|
23
|
+
}
|
|
24
|
+
export interface AuditReport {
|
|
25
|
+
categories: Record<string, CategoryStats>;
|
|
26
|
+
worstFiles: FileIssue[];
|
|
27
|
+
totals: {
|
|
28
|
+
uniqueValues: number;
|
|
29
|
+
totalUsages: number;
|
|
30
|
+
filesAffected: number;
|
|
31
|
+
};
|
|
32
|
+
closeMatches: CloseMatch[];
|
|
33
|
+
score: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generate an audit report from extracted values
|
|
37
|
+
*/
|
|
38
|
+
export declare function generateAuditReport(values: AuditValue[]): AuditReport;
|
|
39
|
+
/**
|
|
40
|
+
* Find values that are close to design tokens (likely typos)
|
|
41
|
+
*/
|
|
42
|
+
export declare function findCloseMatches(foundValues: string[], designTokens: string[], category: 'color' | 'spacing' | 'typography' | 'radius'): CloseMatch[];
|
|
43
|
+
/**
|
|
44
|
+
* Calculate health score (0-100) from audit report
|
|
45
|
+
*/
|
|
46
|
+
export declare function calculateHealthScore(report: AuditReport): number;
|
|
47
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/analysis/audit.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC1C,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,MAAM,EAAE;QACN,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,WAAW,CAoErE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EAAE,EACrB,YAAY,EAAE,MAAM,EAAE,EACtB,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,QAAQ,GACtD,UAAU,EAAE,CAoCd;AA0DD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CA6BhE"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Audit report generation - analyzes codebase for design system health
|
|
2
|
+
/**
|
|
3
|
+
* Generate an audit report from extracted values
|
|
4
|
+
*/
|
|
5
|
+
export function generateAuditReport(values) {
|
|
6
|
+
if (values.length === 0) {
|
|
7
|
+
return {
|
|
8
|
+
categories: {},
|
|
9
|
+
worstFiles: [],
|
|
10
|
+
totals: { uniqueValues: 0, totalUsages: 0, filesAffected: 0 },
|
|
11
|
+
closeMatches: [],
|
|
12
|
+
score: 100,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
// Group by category
|
|
16
|
+
const byCategory = new Map();
|
|
17
|
+
const byFile = new Map();
|
|
18
|
+
const allFiles = new Set();
|
|
19
|
+
for (const v of values) {
|
|
20
|
+
// Category stats
|
|
21
|
+
if (!byCategory.has(v.category)) {
|
|
22
|
+
byCategory.set(v.category, new Map());
|
|
23
|
+
}
|
|
24
|
+
const catMap = byCategory.get(v.category);
|
|
25
|
+
catMap.set(v.value, (catMap.get(v.value) || 0) + 1);
|
|
26
|
+
// File stats
|
|
27
|
+
byFile.set(v.file, (byFile.get(v.file) || 0) + 1);
|
|
28
|
+
allFiles.add(v.file);
|
|
29
|
+
}
|
|
30
|
+
// Build category stats
|
|
31
|
+
const categories = {};
|
|
32
|
+
let totalUnique = 0;
|
|
33
|
+
for (const [category, valueMap] of byCategory) {
|
|
34
|
+
const entries = [...valueMap.entries()];
|
|
35
|
+
const uniqueCount = entries.length;
|
|
36
|
+
const totalUsages = entries.reduce((sum, [, count]) => sum + count, 0);
|
|
37
|
+
// Sort by count descending for mostCommon
|
|
38
|
+
const mostCommon = entries
|
|
39
|
+
.map(([value, count]) => ({ value, count }))
|
|
40
|
+
.sort((a, b) => b.count - a.count)
|
|
41
|
+
.slice(0, 10);
|
|
42
|
+
categories[category] = { uniqueCount, totalUsages, mostCommon };
|
|
43
|
+
totalUnique += uniqueCount;
|
|
44
|
+
}
|
|
45
|
+
// Build worst files list
|
|
46
|
+
const worstFiles = [...byFile.entries()]
|
|
47
|
+
.map(([file, issueCount]) => ({ file, issueCount }))
|
|
48
|
+
.sort((a, b) => b.issueCount - a.issueCount)
|
|
49
|
+
.slice(0, 10);
|
|
50
|
+
const report = {
|
|
51
|
+
categories,
|
|
52
|
+
worstFiles,
|
|
53
|
+
totals: {
|
|
54
|
+
uniqueValues: totalUnique,
|
|
55
|
+
totalUsages: values.length,
|
|
56
|
+
filesAffected: allFiles.size,
|
|
57
|
+
},
|
|
58
|
+
closeMatches: [],
|
|
59
|
+
score: 0,
|
|
60
|
+
};
|
|
61
|
+
report.score = calculateHealthScore(report);
|
|
62
|
+
return report;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Find values that are close to design tokens (likely typos)
|
|
66
|
+
*/
|
|
67
|
+
export function findCloseMatches(foundValues, designTokens, category) {
|
|
68
|
+
const matches = [];
|
|
69
|
+
const tokenSet = new Set(designTokens.map((t) => t.toLowerCase()));
|
|
70
|
+
for (const value of foundValues) {
|
|
71
|
+
const valueLower = value.toLowerCase();
|
|
72
|
+
// Skip exact matches
|
|
73
|
+
if (tokenSet.has(valueLower)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Find closest token
|
|
77
|
+
let closestToken = null;
|
|
78
|
+
let closestDistance = Infinity;
|
|
79
|
+
for (const token of designTokens) {
|
|
80
|
+
const distance = getDistance(value, token, category);
|
|
81
|
+
if (distance < closestDistance && distance > 0) {
|
|
82
|
+
closestDistance = distance;
|
|
83
|
+
closestToken = token;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Only include if close enough (threshold depends on category)
|
|
87
|
+
const threshold = category === 'color' ? 5 : 2;
|
|
88
|
+
if (closestToken && closestDistance <= threshold) {
|
|
89
|
+
matches.push({
|
|
90
|
+
value,
|
|
91
|
+
closeTo: closestToken,
|
|
92
|
+
distance: closestDistance,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return matches;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Calculate distance between two values
|
|
100
|
+
*/
|
|
101
|
+
function getDistance(a, b, category) {
|
|
102
|
+
if (category === 'color') {
|
|
103
|
+
return colorDistance(a, b);
|
|
104
|
+
}
|
|
105
|
+
if (category === 'spacing' || category === 'radius') {
|
|
106
|
+
return numericDistance(a, b);
|
|
107
|
+
}
|
|
108
|
+
// For typography, use simple string comparison
|
|
109
|
+
return a.toLowerCase() === b.toLowerCase() ? 0 : Infinity;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Calculate color distance (simple hex comparison)
|
|
113
|
+
*/
|
|
114
|
+
function colorDistance(a, b) {
|
|
115
|
+
const hexA = a.replace('#', '').toLowerCase();
|
|
116
|
+
const hexB = b.replace('#', '').toLowerCase();
|
|
117
|
+
if (hexA.length !== 6 || hexB.length !== 6) {
|
|
118
|
+
return Infinity;
|
|
119
|
+
}
|
|
120
|
+
// Count differing characters
|
|
121
|
+
let diff = 0;
|
|
122
|
+
for (let i = 0; i < 6; i++) {
|
|
123
|
+
if (hexA[i] !== hexB[i]) {
|
|
124
|
+
diff++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return diff;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Calculate numeric distance for spacing/radius
|
|
131
|
+
*/
|
|
132
|
+
function numericDistance(a, b) {
|
|
133
|
+
const numA = parseFloat(a);
|
|
134
|
+
const numB = parseFloat(b);
|
|
135
|
+
if (isNaN(numA) || isNaN(numB)) {
|
|
136
|
+
return Infinity;
|
|
137
|
+
}
|
|
138
|
+
return Math.abs(numA - numB);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Calculate health score (0-100) from audit report
|
|
142
|
+
*/
|
|
143
|
+
export function calculateHealthScore(report) {
|
|
144
|
+
// Perfect score if no issues
|
|
145
|
+
if (report.totals.uniqueValues === 0) {
|
|
146
|
+
return 100;
|
|
147
|
+
}
|
|
148
|
+
let score = 100;
|
|
149
|
+
// Penalize for too many unique values
|
|
150
|
+
// Expect roughly: colors ~12, spacing ~8, typography ~6, radius ~4 = ~30 total
|
|
151
|
+
const expectedMaxUnique = 30;
|
|
152
|
+
const uniqueRatio = report.totals.uniqueValues / expectedMaxUnique;
|
|
153
|
+
if (uniqueRatio > 1) {
|
|
154
|
+
// Heavy penalty for exceeding expected unique values
|
|
155
|
+
// 50 unique = ratio 1.67, penalty = 0.67 * 80 = 53
|
|
156
|
+
score -= Math.min(80, (uniqueRatio - 1) * 80);
|
|
157
|
+
}
|
|
158
|
+
// Penalize for close matches (typos) - 6 points each
|
|
159
|
+
score -= report.closeMatches.length * 6;
|
|
160
|
+
// Penalize for concentration in worst files
|
|
161
|
+
const worstFile = report.worstFiles[0];
|
|
162
|
+
if (worstFile && worstFile.issueCount > 20) {
|
|
163
|
+
score -= Math.min(20, (worstFile.issueCount - 20) * 0.5);
|
|
164
|
+
}
|
|
165
|
+
// Clamp to 0-100
|
|
166
|
+
return Math.max(0, Math.min(100, Math.round(score)));
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/analysis/audit.ts"],"names":[],"mappings":"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"}
|
package/dist/analysis/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { SemanticDiffEngine, type ComponentMatch, type ComponentDifference, type
|
|
|
2
2
|
export { TokenSuggestionService, type TokenSuggestion, } from "./token-suggestions.js";
|
|
3
3
|
export { stringSimilarity, levenshteinDistance, normalizeForComparison, } from "./string-utils.js";
|
|
4
4
|
export { MATCHING_CONFIG, NAMING_CONFIG, TOKEN_SUGGESTION_CONFIG, SCANNER_CONFIG, getOutlierThreshold, } from "./config.js";
|
|
5
|
+
export { generateAuditReport, findCloseMatches, calculateHealthScore, type AuditValue, type AuditReport, type CategoryStats, type FileIssue, type CloseMatch, } from "./audit.js";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,sBAAsB,EACtB,KAAK,eAAe,GACrB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,eAAe,EACf,aAAa,EACb,uBAAuB,EACvB,cAAc,EACd,mBAAmB,GACpB,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,sBAAsB,EACtB,KAAK,eAAe,GACrB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,eAAe,EACf,aAAa,EACb,uBAAuB,EACvB,cAAc,EACd,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC"}
|
package/dist/analysis/index.js
CHANGED
|
@@ -5,4 +5,6 @@ export { TokenSuggestionService, } from "./token-suggestions.js";
|
|
|
5
5
|
export { stringSimilarity, levenshteinDistance, normalizeForComparison, } from "./string-utils.js";
|
|
6
6
|
// Analysis configuration
|
|
7
7
|
export { MATCHING_CONFIG, NAMING_CONFIG, TOKEN_SUGGESTION_CONFIG, SCANNER_CONFIG, getOutlierThreshold, } from "./config.js";
|
|
8
|
+
// Audit report
|
|
9
|
+
export { generateAuditReport, findCloseMatches, calculateHealthScore, } from "./audit.js";
|
|
8
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAOnB,MAAM,oBAAoB,CAAC;AAE5B,6BAA6B;AAC7B,OAAO,EACL,sBAAsB,GAEvB,MAAM,wBAAwB,CAAC;AAEhC,mBAAmB;AACnB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,yBAAyB;AACzB,OAAO,EACL,eAAe,EACf,aAAa,EACb,uBAAuB,EACvB,cAAc,EACd,mBAAmB,GACpB,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAOnB,MAAM,oBAAoB,CAAC;AAE5B,6BAA6B;AAC7B,OAAO,EACL,sBAAsB,GAEvB,MAAM,wBAAwB,CAAC;AAEhC,mBAAmB;AACnB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,yBAAyB;AACzB,OAAO,EACL,eAAe,EACf,aAAa,EACb,uBAAuB,EACvB,cAAc,EACd,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAErB,eAAe;AACf,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,GAMrB,MAAM,YAAY,CAAC"}
|
|
@@ -122,6 +122,21 @@ export declare class SemanticDiffEngine {
|
|
|
122
122
|
private tokenToDriftSource;
|
|
123
123
|
private getHighestSeverity;
|
|
124
124
|
private checkAccessibility;
|
|
125
|
+
/**
|
|
126
|
+
* Check for color contrast issues in component
|
|
127
|
+
* Detects foreground/background color pairs that fail WCAG contrast ratios
|
|
128
|
+
*/
|
|
129
|
+
private checkColorContrast;
|
|
130
|
+
/**
|
|
131
|
+
* Check for unused components
|
|
132
|
+
* Components that are defined but never imported/used elsewhere
|
|
133
|
+
*/
|
|
134
|
+
checkUnusedComponents(components: Component[], usageMap: Map<string, number>): DriftSignal[];
|
|
135
|
+
/**
|
|
136
|
+
* Check for unused tokens
|
|
137
|
+
* Design tokens that are defined but never referenced
|
|
138
|
+
*/
|
|
139
|
+
checkUnusedTokens(tokens: DesignToken[], usageMap: Map<string, number>): DriftSignal[];
|
|
125
140
|
/**
|
|
126
141
|
* Find token suggestions for a hardcoded color value
|
|
127
142
|
* Delegates to TokenSuggestionService
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-diff.d.ts","sourceRoot":"","sources":["../../src/analysis/semantic-diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,WAAW,EACX,QAAQ,EAET,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAC;AAShC,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"semantic-diff.d.ts","sourceRoot":"","sources":["../../src/analysis/semantic-diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,84 @@ import { createDriftId, normalizeComponentName, normalizeTokenName, tokensMatch,
|
|
|
2
2
|
import { TokenSuggestionService, } from "./token-suggestions.js";
|
|
3
3
|
import { stringSimilarity as calcStringSimilarity } from "./string-utils.js";
|
|
4
4
|
import { MATCHING_CONFIG, NAMING_CONFIG, getOutlierThreshold, } from "./config.js";
|
|
5
|
+
/**
|
|
6
|
+
* Calculate relative luminance for WCAG contrast ratio
|
|
7
|
+
* https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
|
8
|
+
*/
|
|
9
|
+
function getRelativeLuminance(r, g, b) {
|
|
10
|
+
const normalize = (c) => {
|
|
11
|
+
const sRGB = c / 255;
|
|
12
|
+
return sRGB <= 0.03928 ? sRGB / 12.92 : Math.pow((sRGB + 0.055) / 1.055, 2.4);
|
|
13
|
+
};
|
|
14
|
+
const rs = normalize(r);
|
|
15
|
+
const gs = normalize(g);
|
|
16
|
+
const bs = normalize(b);
|
|
17
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Calculate WCAG contrast ratio between two colors
|
|
21
|
+
* https://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
|
22
|
+
*/
|
|
23
|
+
function getContrastRatio(color1, color2) {
|
|
24
|
+
const l1 = getRelativeLuminance(color1.r, color1.g, color1.b);
|
|
25
|
+
const l2 = getRelativeLuminance(color2.r, color2.g, color2.b);
|
|
26
|
+
const lighter = Math.max(l1, l2);
|
|
27
|
+
const darker = Math.min(l1, l2);
|
|
28
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse hex color to RGB
|
|
32
|
+
*/
|
|
33
|
+
function hexToRgb(hex) {
|
|
34
|
+
const normalized = hex.replace(/^#/, '');
|
|
35
|
+
// Handle 3-digit hex
|
|
36
|
+
if (normalized.length === 3) {
|
|
37
|
+
const [r, g, b] = normalized.split('').map(c => parseInt(c + c, 16));
|
|
38
|
+
return { r: r, g: g, b: b };
|
|
39
|
+
}
|
|
40
|
+
// Handle 6-digit hex
|
|
41
|
+
if (normalized.length === 6) {
|
|
42
|
+
const r = parseInt(normalized.substring(0, 2), 16);
|
|
43
|
+
const g = parseInt(normalized.substring(2, 4), 16);
|
|
44
|
+
const b = parseInt(normalized.substring(4, 6), 16);
|
|
45
|
+
return { r, g, b };
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Parse rgb/rgba string to RGB
|
|
51
|
+
*/
|
|
52
|
+
function parseRgbString(rgbString) {
|
|
53
|
+
const match = rgbString.match(/rgba?\((\d+),?\s*(\d+),?\s*(\d+)/);
|
|
54
|
+
if (!match)
|
|
55
|
+
return null;
|
|
56
|
+
return {
|
|
57
|
+
r: parseInt(match[1]),
|
|
58
|
+
g: parseInt(match[2]),
|
|
59
|
+
b: parseInt(match[3]),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Convert color value to RGB
|
|
64
|
+
*/
|
|
65
|
+
function colorToRgb(color) {
|
|
66
|
+
if (color.startsWith('#')) {
|
|
67
|
+
return hexToRgb(color);
|
|
68
|
+
}
|
|
69
|
+
if (color.startsWith('rgb')) {
|
|
70
|
+
return parseRgbString(color);
|
|
71
|
+
}
|
|
72
|
+
// Named colors (common ones)
|
|
73
|
+
const namedColors = {
|
|
74
|
+
'white': { r: 255, g: 255, b: 255 },
|
|
75
|
+
'black': { r: 0, g: 0, b: 0 },
|
|
76
|
+
'red': { r: 255, g: 0, b: 0 },
|
|
77
|
+
'green': { r: 0, g: 128, b: 0 },
|
|
78
|
+
'blue': { r: 0, g: 0, b: 255 },
|
|
79
|
+
'transparent': { r: 255, g: 255, b: 255 }, // Treat as white for contrast purposes
|
|
80
|
+
};
|
|
81
|
+
return namedColors[color.toLowerCase()] || null;
|
|
82
|
+
}
|
|
5
83
|
/**
|
|
6
84
|
* Semantic suffixes that represent legitimate separate components.
|
|
7
85
|
* Components with these suffixes are NOT duplicates of their base component.
|
|
@@ -473,6 +551,11 @@ export class SemanticDiffEngine {
|
|
|
473
551
|
});
|
|
474
552
|
}
|
|
475
553
|
}
|
|
554
|
+
// Check for color contrast issues
|
|
555
|
+
if (options.checkAccessibility) {
|
|
556
|
+
const contrastIssues = this.checkColorContrast(component);
|
|
557
|
+
drifts.push(...contrastIssues);
|
|
558
|
+
}
|
|
476
559
|
}
|
|
477
560
|
// Cross-component checks
|
|
478
561
|
// Check for potential duplicate components
|
|
@@ -705,7 +788,8 @@ export class SemanticDiffEngine {
|
|
|
705
788
|
if (lowerName.endsWith(suffix) &&
|
|
706
789
|
lowerName.length > suffix.length &&
|
|
707
790
|
// Ensure the suffix is at a word boundary (e.g., "ButtonGroup" not "Buttong")
|
|
708
|
-
|
|
791
|
+
// Use [a-z0-9] to handle names like "Button2Group" where digit precedes suffix
|
|
792
|
+
lowerName[lowerName.length - suffix.length - 1]?.match(/[a-z0-9]/)) {
|
|
709
793
|
// This is a compound component, not a duplicate candidate
|
|
710
794
|
// Return the full name as the base (it's distinct)
|
|
711
795
|
return { baseName: lowerName, hasVersionSuffix: false };
|
|
@@ -932,6 +1016,125 @@ export class SemanticDiffEngine {
|
|
|
932
1016
|
}
|
|
933
1017
|
return issues;
|
|
934
1018
|
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Check for color contrast issues in component
|
|
1021
|
+
* Detects foreground/background color pairs that fail WCAG contrast ratios
|
|
1022
|
+
*/
|
|
1023
|
+
checkColorContrast(component) {
|
|
1024
|
+
const drifts = [];
|
|
1025
|
+
if (!component.metadata.hardcodedValues) {
|
|
1026
|
+
return drifts;
|
|
1027
|
+
}
|
|
1028
|
+
const colorValues = component.metadata.hardcodedValues.filter((h) => h.type === "color");
|
|
1029
|
+
// Look for color/background-color pairs
|
|
1030
|
+
const colorsByProperty = new Map();
|
|
1031
|
+
for (const cv of colorValues) {
|
|
1032
|
+
const prop = cv.property.toLowerCase();
|
|
1033
|
+
if (!colorsByProperty.has(prop)) {
|
|
1034
|
+
colorsByProperty.set(prop, []);
|
|
1035
|
+
}
|
|
1036
|
+
colorsByProperty.get(prop).push(cv);
|
|
1037
|
+
}
|
|
1038
|
+
const foregroundColors = colorsByProperty.get("color") || [];
|
|
1039
|
+
const backgroundColors = colorsByProperty.get("background-color") ||
|
|
1040
|
+
colorsByProperty.get("background") ||
|
|
1041
|
+
[];
|
|
1042
|
+
// Check contrast ratio for each foreground/background pair
|
|
1043
|
+
for (const fg of foregroundColors) {
|
|
1044
|
+
for (const bg of backgroundColors) {
|
|
1045
|
+
const fgRgb = colorToRgb(fg.value);
|
|
1046
|
+
const bgRgb = colorToRgb(bg.value);
|
|
1047
|
+
if (!fgRgb || !bgRgb)
|
|
1048
|
+
continue;
|
|
1049
|
+
const ratio = getContrastRatio(fgRgb, bgRgb);
|
|
1050
|
+
// WCAG AA requires 4.5:1 for normal text, 3:1 for large text
|
|
1051
|
+
// WCAG AAA requires 7:1 for normal text, 4.5:1 for large text
|
|
1052
|
+
const minRatioAA = 4.5;
|
|
1053
|
+
const minRatioAAA = 7;
|
|
1054
|
+
if (ratio < minRatioAA) {
|
|
1055
|
+
const level = ratio < 3 ? "WCAG AA and AAA" : "WCAG AAA";
|
|
1056
|
+
drifts.push({
|
|
1057
|
+
id: createDriftId("color-contrast", component.id, `${fg.value}-${bg.value}`),
|
|
1058
|
+
type: "color-contrast",
|
|
1059
|
+
severity: "critical",
|
|
1060
|
+
source: this.componentToDriftSource(component),
|
|
1061
|
+
message: `Component "${component.name}" has insufficient color contrast: ${fg.value} on ${bg.value} (ratio: ${ratio.toFixed(2)}:1)`,
|
|
1062
|
+
details: {
|
|
1063
|
+
expected: `Minimum ${minRatioAA}:1 for WCAG AA, ${minRatioAAA}:1 for AAA`,
|
|
1064
|
+
actual: `${ratio.toFixed(2)}:1`,
|
|
1065
|
+
suggestions: [
|
|
1066
|
+
`Fails ${level} - adjust colors to meet minimum ${minRatioAA}:1 ratio`,
|
|
1067
|
+
"Use a contrast checker tool to find accessible color combinations",
|
|
1068
|
+
"Consider using design system color tokens with built-in contrast ratios",
|
|
1069
|
+
],
|
|
1070
|
+
affectedFiles: [
|
|
1071
|
+
`${fg.location}: ${fg.property}: ${fg.value}`,
|
|
1072
|
+
`${bg.location}: ${bg.property}: ${bg.value}`,
|
|
1073
|
+
],
|
|
1074
|
+
},
|
|
1075
|
+
detectedAt: new Date(),
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return drifts;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Check for unused components
|
|
1084
|
+
* Components that are defined but never imported/used elsewhere
|
|
1085
|
+
*/
|
|
1086
|
+
checkUnusedComponents(components, usageMap) {
|
|
1087
|
+
const drifts = [];
|
|
1088
|
+
for (const component of components) {
|
|
1089
|
+
const usageCount = usageMap.get(component.id) || usageMap.get(component.name) || 0;
|
|
1090
|
+
if (usageCount === 0) {
|
|
1091
|
+
drifts.push({
|
|
1092
|
+
id: createDriftId("unused-component", component.id),
|
|
1093
|
+
type: "unused-component",
|
|
1094
|
+
severity: "warning",
|
|
1095
|
+
source: this.componentToDriftSource(component),
|
|
1096
|
+
message: `Component "${component.name}" is defined but never used`,
|
|
1097
|
+
details: {
|
|
1098
|
+
suggestions: [
|
|
1099
|
+
"Remove component if no longer needed",
|
|
1100
|
+
"Export component if it's part of the public API",
|
|
1101
|
+
"Add usage in tests or documentation",
|
|
1102
|
+
],
|
|
1103
|
+
},
|
|
1104
|
+
detectedAt: new Date(),
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return drifts;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Check for unused tokens
|
|
1112
|
+
* Design tokens that are defined but never referenced
|
|
1113
|
+
*/
|
|
1114
|
+
checkUnusedTokens(tokens, usageMap) {
|
|
1115
|
+
const drifts = [];
|
|
1116
|
+
for (const token of tokens) {
|
|
1117
|
+
const usageCount = usageMap.get(token.id) || usageMap.get(token.name) || 0;
|
|
1118
|
+
if (usageCount === 0) {
|
|
1119
|
+
drifts.push({
|
|
1120
|
+
id: createDriftId("unused-token", token.id),
|
|
1121
|
+
type: "unused-token",
|
|
1122
|
+
severity: "info",
|
|
1123
|
+
source: this.tokenToDriftSource(token),
|
|
1124
|
+
message: `Token "${token.name}" is defined but never used`,
|
|
1125
|
+
details: {
|
|
1126
|
+
suggestions: [
|
|
1127
|
+
"Remove token if no longer needed",
|
|
1128
|
+
"Document token for future use",
|
|
1129
|
+
"Check if token is referenced by name in CSS/JS",
|
|
1130
|
+
],
|
|
1131
|
+
},
|
|
1132
|
+
detectedAt: new Date(),
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return drifts;
|
|
1137
|
+
}
|
|
935
1138
|
/**
|
|
936
1139
|
* Find token suggestions for a hardcoded color value
|
|
937
1140
|
* Delegates to TokenSuggestionService
|