@schemyx/mcp 0.1.0 → 0.1.2
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/README.md +79 -2
- package/dist/backend-client.d.ts +1 -17
- package/dist/backend-client.js +15 -91
- package/dist/backend-client.js.map +1 -1
- package/dist/client/backend-client.d.ts +17 -0
- package/dist/client/backend-client.js +97 -0
- package/dist/client/backend-client.js.map +1 -0
- package/dist/client/local-theme-source.d.ts +19 -0
- package/dist/client/local-theme-source.js +737 -0
- package/dist/client/local-theme-source.js.map +1 -0
- package/dist/client/mcp-client.d.ts +15 -0
- package/dist/client/mcp-client.js +46 -0
- package/dist/client/mcp-client.js.map +1 -0
- package/dist/codebase-scanner/backend.d.ts +12 -0
- package/dist/codebase-scanner/backend.js +814 -0
- package/dist/codebase-scanner/backend.js.map +1 -0
- package/dist/codebase-scanner/bundle.d.ts +315 -0
- package/dist/codebase-scanner/bundle.js +5195 -0
- package/dist/codebase-scanner/bundle.js.map +1 -0
- package/dist/codebase-scanner/constants.d.ts +26 -0
- package/dist/codebase-scanner/constants.js +231 -0
- package/dist/codebase-scanner/constants.js.map +1 -0
- package/dist/codebase-scanner/database.d.ts +8 -0
- package/dist/codebase-scanner/database.js +1252 -0
- package/dist/codebase-scanner/database.js.map +1 -0
- package/dist/codebase-scanner/extractors.d.ts +241 -0
- package/dist/codebase-scanner/extractors.js +3513 -0
- package/dist/codebase-scanner/extractors.js.map +1 -0
- package/dist/codebase-scanner/files.d.ts +16 -0
- package/dist/codebase-scanner/files.js +250 -0
- package/dist/codebase-scanner/files.js.map +1 -0
- package/dist/codebase-scanner/index.d.ts +217 -0
- package/dist/codebase-scanner/index.js +387 -0
- package/dist/codebase-scanner/index.js.map +1 -0
- package/dist/codebase-scanner/recipes.d.ts +74 -0
- package/dist/codebase-scanner/recipes.js +743 -0
- package/dist/codebase-scanner/recipes.js.map +1 -0
- package/dist/codebase-scanner/storage.d.ts +19 -0
- package/dist/codebase-scanner/storage.js +103 -0
- package/dist/codebase-scanner/storage.js.map +1 -0
- package/dist/codebase-scanner/types.d.ts +743 -0
- package/dist/codebase-scanner/types.js +3 -0
- package/dist/codebase-scanner/types.js.map +1 -0
- package/dist/codebase-scanner/utils.d.ts +37 -0
- package/dist/codebase-scanner/utils.js +259 -0
- package/dist/codebase-scanner/utils.js.map +1 -0
- package/dist/codebase-scanner.d.ts +1 -0
- package/dist/codebase-scanner.js +18 -0
- package/dist/codebase-scanner.js.map +1 -0
- package/dist/config.d.ts +1 -2
- package/dist/config.js +15 -37
- package/dist/config.js.map +1 -1
- package/dist/local-theme-source.d.ts +1 -0
- package/dist/local-theme-source.js +18 -0
- package/dist/local-theme-source.js.map +1 -0
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/mcp-client.d.ts +1 -0
- package/dist/mcp-client.js +18 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/prompts.d.ts +1 -7
- package/dist/prompts.js +15 -52
- package/dist/prompts.js.map +1 -1
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +163 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/prompts.d.ts +7 -0
- package/dist/server/prompts.js +55 -0
- package/dist/server/prompts.js.map +1 -0
- package/dist/server/tool-definitions.d.ts +22 -0
- package/dist/server/tool-definitions.js +531 -0
- package/dist/server/tool-definitions.js.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.js +33 -0
- package/dist/server.js.map +1 -1
- package/dist/shared/config.d.ts +2 -0
- package/dist/shared/config.js +54 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/text.d.ts +14 -0
- package/dist/shared/text.js +33 -0
- package/dist/shared/text.js.map +1 -0
- package/dist/shared/types.d.ts +118 -0
- package/dist/shared/types.js +3 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared/uri.d.ts +6 -0
- package/dist/shared/uri.js +24 -0
- package/dist/shared/uri.js.map +1 -0
- package/dist/style-recipes.d.ts +1 -0
- package/dist/style-recipes.js +18 -0
- package/dist/style-recipes.js.map +1 -0
- package/dist/text.d.ts +1 -14
- package/dist/text.js +15 -30
- package/dist/text.js.map +1 -1
- package/dist/theme/style-recipes.d.ts +26 -0
- package/dist/theme/style-recipes.js +129 -0
- package/dist/theme/style-recipes.js.map +1 -0
- package/dist/tool-definitions.d.ts +1 -11
- package/dist/tool-definitions.js +15 -127
- package/dist/tool-definitions.js.map +1 -1
- package/dist/types.d.ts +1 -106
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -1
- package/dist/uri.d.ts +1 -6
- package/dist/uri.js +15 -21
- package/dist/uri.js.map +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,3513 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractImports = extractImports;
|
|
4
|
+
exports.extractExports = extractExports;
|
|
5
|
+
exports.extractSymbols = extractSymbols;
|
|
6
|
+
exports.extractFunctions = extractFunctions;
|
|
7
|
+
exports.extractClasses = extractClasses;
|
|
8
|
+
exports.extractTypes = extractTypes;
|
|
9
|
+
exports.extractConstants = extractConstants;
|
|
10
|
+
exports.extractComponents = extractComponents;
|
|
11
|
+
exports.extractComponentProps = extractComponentProps;
|
|
12
|
+
exports.extractHooks = extractHooks;
|
|
13
|
+
exports.extractJsxElements = extractJsxElements;
|
|
14
|
+
exports.extractRoutes = extractRoutes;
|
|
15
|
+
exports.extractApiHandlers = extractApiHandlers;
|
|
16
|
+
exports.extractDecoratedClasses = extractDecoratedClasses;
|
|
17
|
+
exports.extractControllers = extractControllers;
|
|
18
|
+
exports.extractServices = extractServices;
|
|
19
|
+
exports.extractModules = extractModules;
|
|
20
|
+
exports.extractNamedClasses = extractNamedClasses;
|
|
21
|
+
exports.extractPrismaModels = extractPrismaModels;
|
|
22
|
+
exports.extractSqlModels = extractSqlModels;
|
|
23
|
+
exports.extractOrmModels = extractOrmModels;
|
|
24
|
+
exports.extractModelFields = extractModelFields;
|
|
25
|
+
exports.extractCssVariables = extractCssVariables;
|
|
26
|
+
exports.extractCssVariableValues = extractCssVariableValues;
|
|
27
|
+
exports.extractCssRules = extractCssRules;
|
|
28
|
+
exports.extractCssAtRuleBlocks = extractCssAtRuleBlocks;
|
|
29
|
+
exports.extractCssClasses = extractCssClasses;
|
|
30
|
+
exports.extractCssIds = extractCssIds;
|
|
31
|
+
exports.extractCssVariableReferences = extractCssVariableReferences;
|
|
32
|
+
exports.extractClassReferences = extractClassReferences;
|
|
33
|
+
exports.extractClassExpressions = extractClassExpressions;
|
|
34
|
+
exports.extractComponentStyleDefinitions = extractComponentStyleDefinitions;
|
|
35
|
+
exports.extractThemeTokens = extractThemeTokens;
|
|
36
|
+
exports.extractInlineStyles = extractInlineStyles;
|
|
37
|
+
exports.nearestCssSelector = nearestCssSelector;
|
|
38
|
+
exports.parseCssDeclarations = parseCssDeclarations;
|
|
39
|
+
exports.parseJsStyleObject = parseJsStyleObject;
|
|
40
|
+
exports.readTagBefore = readTagBefore;
|
|
41
|
+
exports.readBalancedCall = readBalancedCall;
|
|
42
|
+
exports.readBalancedBlock = readBalancedBlock;
|
|
43
|
+
exports.readBalancedRange = readBalancedRange;
|
|
44
|
+
exports.skipQuoted = skipQuoted;
|
|
45
|
+
exports.isInsideQuotedString = isInsideQuotedString;
|
|
46
|
+
exports.extractClassListsFromStrings = extractClassListsFromStrings;
|
|
47
|
+
exports.readPrimaryClassHelperUsage = readPrimaryClassHelperUsage;
|
|
48
|
+
exports.readClassHelperUsages = readClassHelperUsages;
|
|
49
|
+
exports.parseHelperVariantProps = parseHelperVariantProps;
|
|
50
|
+
exports.analyzeClassSource = analyzeClassSource;
|
|
51
|
+
exports.toClassSourceAnalysis = toClassSourceAnalysis;
|
|
52
|
+
exports.isConditionalClassLiteral = isConditionalClassLiteral;
|
|
53
|
+
exports.readStringLiteralRanges = readStringLiteralRanges;
|
|
54
|
+
exports.directClassCandidate = directClassCandidate;
|
|
55
|
+
exports.readStringLiterals = readStringLiterals;
|
|
56
|
+
exports.looksLikeClassList = looksLikeClassList;
|
|
57
|
+
exports.isLikelyStyleClass = isLikelyStyleClass;
|
|
58
|
+
exports.firstStringLiteral = firstStringLiteral;
|
|
59
|
+
exports.extractNamedObjectBlock = extractNamedObjectBlock;
|
|
60
|
+
exports.parseVariantGroups = parseVariantGroups;
|
|
61
|
+
exports.parseStringValueMap = parseStringValueMap;
|
|
62
|
+
exports.parseClassValueMap = parseClassValueMap;
|
|
63
|
+
exports.parseTopLevelObjectEntries = parseTopLevelObjectEntries;
|
|
64
|
+
exports.stripOuterBraces = stripOuterBraces;
|
|
65
|
+
exports.skipSeparators = skipSeparators;
|
|
66
|
+
exports.skipWhitespace = skipWhitespace;
|
|
67
|
+
exports.readObjectKey = readObjectKey;
|
|
68
|
+
exports.readObjectValueEnd = readObjectValueEnd;
|
|
69
|
+
exports.splitTopLevel = splitTopLevel;
|
|
70
|
+
exports.cleanupBareValue = cleanupBareValue;
|
|
71
|
+
exports.classifyThemeToken = classifyThemeToken;
|
|
72
|
+
exports.flattenJsonTokens = flattenJsonTokens;
|
|
73
|
+
exports.readOpeningTags = readOpeningTags;
|
|
74
|
+
exports.annotateOpeningTags = annotateOpeningTags;
|
|
75
|
+
exports.findClosingTagEnd = findClosingTagEnd;
|
|
76
|
+
exports.isVoidHtmlElement = isVoidHtmlElement;
|
|
77
|
+
exports.extractElementLabel = extractElementLabel;
|
|
78
|
+
exports.readClassSource = readClassSource;
|
|
79
|
+
exports.readUiProps = readUiProps;
|
|
80
|
+
exports.readVariantProps = readVariantProps;
|
|
81
|
+
exports.readAttrSource = readAttrSource;
|
|
82
|
+
exports.readAttrValueSource = readAttrValueSource;
|
|
83
|
+
exports.cleanupAttrValue = cleanupAttrValue;
|
|
84
|
+
exports.hasBooleanAttr = hasBooleanAttr;
|
|
85
|
+
exports.groupTailwindClasses = groupTailwindClasses;
|
|
86
|
+
exports.addClassGroup = addClassGroup;
|
|
87
|
+
exports.mergeClassGroups = mergeClassGroups;
|
|
88
|
+
exports.mergeVariantProps = mergeVariantProps;
|
|
89
|
+
exports.isCompositionElement = isCompositionElement;
|
|
90
|
+
exports.filterClasses = filterClasses;
|
|
91
|
+
exports.isResponsiveClass = isResponsiveClass;
|
|
92
|
+
exports.isSpacingClass = isSpacingClass;
|
|
93
|
+
exports.isLayoutClass = isLayoutClass;
|
|
94
|
+
exports.isSurfaceClass = isSurfaceClass;
|
|
95
|
+
exports.isDecorativeAccentClass = isDecorativeAccentClass;
|
|
96
|
+
exports.isTypographyClass = isTypographyClass;
|
|
97
|
+
exports.isTypographyScaleBase = isTypographyScaleBase;
|
|
98
|
+
exports.isLayoutSafetyBase = isLayoutSafetyBase;
|
|
99
|
+
exports.isSizingClass = isSizingClass;
|
|
100
|
+
exports.isVisibilityClass = isVisibilityClass;
|
|
101
|
+
exports.isLineHeightClass = isLineHeightClass;
|
|
102
|
+
exports.isMaxWidthClass = isMaxWidthClass;
|
|
103
|
+
exports.createUiLayoutSafety = createUiLayoutSafety;
|
|
104
|
+
exports.createUiResponsiveProfile = createUiResponsiveProfile;
|
|
105
|
+
exports.createUiScaleProfile = createUiScaleProfile;
|
|
106
|
+
exports.responsivePrefix = responsivePrefix;
|
|
107
|
+
exports.groupClassExpressionsByTarget = groupClassExpressionsByTarget;
|
|
108
|
+
exports.buildComponentSlots = buildComponentSlots;
|
|
109
|
+
exports.createUiCompositionGuidance = createUiCompositionGuidance;
|
|
110
|
+
exports.createUiPatternGuidance = createUiPatternGuidance;
|
|
111
|
+
exports.extractTailwindUtilities = extractTailwindUtilities;
|
|
112
|
+
exports.extractPackageDependencies = extractPackageDependencies;
|
|
113
|
+
exports.extractUiElements = extractUiElements;
|
|
114
|
+
exports.summarizeDirectUiChildren = summarizeDirectUiChildren;
|
|
115
|
+
exports.createUiSemanticProfile = createUiSemanticProfile;
|
|
116
|
+
exports.createUiRoleSignature = createUiRoleSignature;
|
|
117
|
+
exports.exactRoleClassFacts = exactRoleClassFacts;
|
|
118
|
+
exports.createRoleScaleKey = createRoleScaleKey;
|
|
119
|
+
exports.createRoleDensityKey = createRoleDensityKey;
|
|
120
|
+
exports.createRoleSurfaceKey = createRoleSurfaceKey;
|
|
121
|
+
exports.createRoleLayoutKey = createRoleLayoutKey;
|
|
122
|
+
exports.isCompactHeroEyebrow = isCompactHeroEyebrow;
|
|
123
|
+
exports.isOverlayPill = isOverlayPill;
|
|
124
|
+
exports.isHeroHeadingScale = isHeroHeadingScale;
|
|
125
|
+
exports.isOversizedHeadingScale = isOversizedHeadingScale;
|
|
126
|
+
exports.isMetricValueScale = isMetricValueScale;
|
|
127
|
+
exports.isCompactStatValue = isCompactStatValue;
|
|
128
|
+
exports.isLargeMetricValue = isLargeMetricValue;
|
|
129
|
+
exports.isCompactStatCard = isCompactStatCard;
|
|
130
|
+
exports.isInteractiveServiceCard = isInteractiveServiceCard;
|
|
131
|
+
exports.isStaticArticleCard = isStaticArticleCard;
|
|
132
|
+
exports.isMediaPanelCard = isMediaPanelCard;
|
|
133
|
+
exports.isQuietAction = isQuietAction;
|
|
134
|
+
exports.isCompactAction = isCompactAction;
|
|
135
|
+
exports.roleClassSort = roleClassSort;
|
|
136
|
+
exports.deriveUiChildSemanticRole = deriveUiChildSemanticRole;
|
|
137
|
+
exports.deriveLayoutRole = deriveLayoutRole;
|
|
138
|
+
exports.isStateClass = isStateClass;
|
|
139
|
+
exports.extractApiCalls = extractApiCalls;
|
|
140
|
+
exports.extractEnvVars = extractEnvVars;
|
|
141
|
+
exports.extractTestNames = extractTestNames;
|
|
142
|
+
exports.extractDocsHeadings = extractDocsHeadings;
|
|
143
|
+
exports.extractConfigKeys = extractConfigKeys;
|
|
144
|
+
exports.extractTemplateIncludes = extractTemplateIncludes;
|
|
145
|
+
exports.extractAuthHints = extractAuthHints;
|
|
146
|
+
exports.extractMiddleware = extractMiddleware;
|
|
147
|
+
exports.extractJobs = extractJobs;
|
|
148
|
+
exports.extractEvents = extractEvents;
|
|
149
|
+
exports.detectKind = detectKind;
|
|
150
|
+
exports.detectLayer = detectLayer;
|
|
151
|
+
exports.detectLanguage = detectLanguage;
|
|
152
|
+
exports.summarizeFile = summarizeFile;
|
|
153
|
+
exports.isNextPageFile = isNextPageFile;
|
|
154
|
+
exports.isNextLayoutFile = isNextLayoutFile;
|
|
155
|
+
exports.isNextRouteFile = isNextRouteFile;
|
|
156
|
+
exports.isPagesRouterPageFile = isPagesRouterPageFile;
|
|
157
|
+
exports.isSvelteKitPageFile = isSvelteKitPageFile;
|
|
158
|
+
exports.isAstroPageFile = isAstroPageFile;
|
|
159
|
+
exports.isNuxtPageFile = isNuxtPageFile;
|
|
160
|
+
exports.isRawPageFile = isRawPageFile;
|
|
161
|
+
exports.toNextRoutePath = toNextRoutePath;
|
|
162
|
+
exports.toPagesRoutePath = toPagesRoutePath;
|
|
163
|
+
exports.toSvelteKitRoutePath = toSvelteKitRoutePath;
|
|
164
|
+
exports.toAstroRoutePath = toAstroRoutePath;
|
|
165
|
+
exports.toNuxtRoutePath = toNuxtRoutePath;
|
|
166
|
+
exports.toRawPageRoutePath = toRawPageRoutePath;
|
|
167
|
+
exports.extractFrameworkRoutePaths = extractFrameworkRoutePaths;
|
|
168
|
+
exports.normalizeRoutePath = normalizeRoutePath;
|
|
169
|
+
exports.shouldIgnoreDirectory = shouldIgnoreDirectory;
|
|
170
|
+
exports.isExcluded = isExcluded;
|
|
171
|
+
exports.shouldIgnoreNonCanonicalPath = shouldIgnoreNonCanonicalPath;
|
|
172
|
+
exports.isScannableFile = isScannableFile;
|
|
173
|
+
exports.shouldIgnoreFileName = shouldIgnoreFileName;
|
|
174
|
+
exports.isUiFile = isUiFile;
|
|
175
|
+
exports.isTemplateFile = isTemplateFile;
|
|
176
|
+
exports.isTestFile = isTestFile;
|
|
177
|
+
exports.isConfigFile = isConfigFile;
|
|
178
|
+
exports.routeExtractorForFile = routeExtractorForFile;
|
|
179
|
+
exports.routeConfidenceForFile = routeConfidenceForFile;
|
|
180
|
+
exports.apiExtractorForFile = apiExtractorForFile;
|
|
181
|
+
exports.controllerExtractorForFile = controllerExtractorForFile;
|
|
182
|
+
exports.serviceExtractorForFile = serviceExtractorForFile;
|
|
183
|
+
exports.moduleExtractorForFile = moduleExtractorForFile;
|
|
184
|
+
exports.modelExtractorForFile = modelExtractorForFile;
|
|
185
|
+
exports.styleExtractorForFile = styleExtractorForFile;
|
|
186
|
+
exports.styleConfidenceForFile = styleConfidenceForFile;
|
|
187
|
+
exports.entryMatchesConcept = entryMatchesConcept;
|
|
188
|
+
exports.splitClassList = splitClassList;
|
|
189
|
+
exports.classifyUiElement = classifyUiElement;
|
|
190
|
+
exports.looksLikeSurfaceContainer = looksLikeSurfaceContainer;
|
|
191
|
+
exports.looksLikePillBadge = looksLikePillBadge;
|
|
192
|
+
exports.stripTags = stripTags;
|
|
193
|
+
const path = require("node:path");
|
|
194
|
+
const constants_1 = require("./constants");
|
|
195
|
+
const utils_1 = require("./utils");
|
|
196
|
+
function extractImports(content) {
|
|
197
|
+
const imports = new Set();
|
|
198
|
+
const importPattern = /import(?:\s+type)?[\s\S]*?\sfrom\s+['"]([^'"]+)['"]/g;
|
|
199
|
+
const sideEffectPattern = /import\s+['"]([^'"]+)['"]/g;
|
|
200
|
+
const requirePattern = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
201
|
+
const cssImportPattern = /@import\s+(?:url\()?['"]?([^'");]+)['"]?\)?/g;
|
|
202
|
+
const phpIncludePattern = /\b(?:require|require_once|include|include_once)\s*(?:\(?\s*)['"]([^'"]+)['"]/g;
|
|
203
|
+
const phpUsePattern = /^\s*use\s+([A-Za-z_\\][A-Za-z0-9_\\]*)\s*;/gm;
|
|
204
|
+
const pythonImportPattern = /^\s*(?:from\s+([A-Za-z_][\w.]*)(?:\s+import\s+[\w*,\s]+)|import\s+([A-Za-z_][\w.]*))/gm;
|
|
205
|
+
const rubyRequirePattern = /\brequire(?:_relative)?\s+['"]([^'"]+)['"]/g;
|
|
206
|
+
const javaLikeImportPattern = /^\s*import\s+(?:static\s+)?([A-Za-z_][\w.*]*);/gm;
|
|
207
|
+
const goImportPattern = /^\s*import\s+(?:\(\s*([\s\S]*?)\s*\)|"([^"]+)")/gm;
|
|
208
|
+
for (const pattern of [
|
|
209
|
+
importPattern,
|
|
210
|
+
sideEffectPattern,
|
|
211
|
+
requirePattern,
|
|
212
|
+
cssImportPattern,
|
|
213
|
+
phpIncludePattern,
|
|
214
|
+
phpUsePattern,
|
|
215
|
+
pythonImportPattern,
|
|
216
|
+
rubyRequirePattern,
|
|
217
|
+
javaLikeImportPattern,
|
|
218
|
+
goImportPattern,
|
|
219
|
+
]) {
|
|
220
|
+
for (const match of content.matchAll(pattern)) {
|
|
221
|
+
if (pattern === goImportPattern && match[1]) {
|
|
222
|
+
for (const importMatch of match[1].matchAll(/"([^"]+)"/g)) {
|
|
223
|
+
imports.add(importMatch[1]);
|
|
224
|
+
}
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
imports.add(match[1] || match[2]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return Array.from(imports).filter(Boolean).sort();
|
|
231
|
+
}
|
|
232
|
+
function extractExports(content) {
|
|
233
|
+
const exports = new Set();
|
|
234
|
+
const patterns = [
|
|
235
|
+
/export\s+(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/g,
|
|
236
|
+
/export\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)/g,
|
|
237
|
+
/export\s+class\s+([A-Za-z_$][\w$]*)/g,
|
|
238
|
+
/export\s+default\s+(?:async\s+)?function\s+([A-Za-z_$][\w$]*)?/g,
|
|
239
|
+
/module\.exports\s*=\s*([A-Za-z_$][\w$]*)/g,
|
|
240
|
+
/exports\.([A-Za-z_$][\w$]*)\s*=/g,
|
|
241
|
+
];
|
|
242
|
+
for (const pattern of patterns) {
|
|
243
|
+
for (const match of content.matchAll(pattern)) {
|
|
244
|
+
exports.add(match[1] || 'default');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return Array.from(exports).sort();
|
|
248
|
+
}
|
|
249
|
+
function extractSymbols(content, relPath) {
|
|
250
|
+
return (0, utils_1.unique)([
|
|
251
|
+
...extractExports(content),
|
|
252
|
+
...extractFunctions(content, relPath),
|
|
253
|
+
...extractClasses(content, relPath),
|
|
254
|
+
...extractTypes(content, relPath),
|
|
255
|
+
...extractConstants(content, relPath),
|
|
256
|
+
]).sort();
|
|
257
|
+
}
|
|
258
|
+
function extractFunctions(content, relPath) {
|
|
259
|
+
const names = new Set();
|
|
260
|
+
const language = detectLanguage(relPath);
|
|
261
|
+
const patterns = [
|
|
262
|
+
/(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/g,
|
|
263
|
+
/(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?[^=]*?\)?\s*=>/g,
|
|
264
|
+
/function\s+([A-Za-z_][\w]*)\s*\(/g,
|
|
265
|
+
/def\s+([A-Za-z_][\w]*)\s*\(/g,
|
|
266
|
+
/func\s+(?:\([^)]+\)\s*)?([A-Za-z_][\w]*)\s*\(/g,
|
|
267
|
+
];
|
|
268
|
+
for (const pattern of patterns) {
|
|
269
|
+
for (const match of content.matchAll(pattern)) {
|
|
270
|
+
names.add(match[1]);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (language === 'rb') {
|
|
274
|
+
for (const match of content.matchAll(/^\s*def\s+([A-Za-z_][\w!?=]*)/gm)) {
|
|
275
|
+
names.add(match[1]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return Array.from(names).sort();
|
|
279
|
+
}
|
|
280
|
+
function extractClasses(content, relPath) {
|
|
281
|
+
const names = new Set();
|
|
282
|
+
for (const pattern of [
|
|
283
|
+
/(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/g,
|
|
284
|
+
/(?:class|interface|enum)\s+([A-Z][A-Za-z0-9_]*)/g,
|
|
285
|
+
/class\s+([A-Za-z_][\w]*)\s*(?:\(|:|<|{|extends)?/g,
|
|
286
|
+
/module\s+([A-Z][A-Za-z0-9_:]*)/g,
|
|
287
|
+
]) {
|
|
288
|
+
for (const match of content.matchAll(pattern)) {
|
|
289
|
+
names.add(match[1]);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (/\.(java|kt|kts|cs)$/.test(relPath)) {
|
|
293
|
+
for (const match of content.matchAll(/\b(?:class|interface|record|enum|object)\s+([A-Z][A-Za-z0-9_]*)/g)) {
|
|
294
|
+
names.add(match[1]);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return Array.from(names).sort();
|
|
298
|
+
}
|
|
299
|
+
function extractTypes(content, relPath) {
|
|
300
|
+
const names = new Set();
|
|
301
|
+
for (const pattern of [
|
|
302
|
+
/(?:export\s+)?(?:type|interface|enum)\s+([A-Za-z_$][\w$]*)/g,
|
|
303
|
+
/type\s+([A-Za-z_][\w]*)\s+(?:struct|interface)/g,
|
|
304
|
+
]) {
|
|
305
|
+
for (const match of content.matchAll(pattern)) {
|
|
306
|
+
names.add(match[1]);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (/\.graphql$/.test(relPath)) {
|
|
310
|
+
for (const match of content.matchAll(/\b(?:type|interface|enum|input)\s+([A-Za-z_][\w]*)/g)) {
|
|
311
|
+
names.add(match[1]);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return Array.from(names).sort();
|
|
315
|
+
}
|
|
316
|
+
function extractConstants(content, relPath) {
|
|
317
|
+
const names = new Set();
|
|
318
|
+
for (const pattern of [
|
|
319
|
+
/(?:export\s+)?(?:const|let|var)\s+([A-Z][A-Z0-9_]*)\b/g,
|
|
320
|
+
/^\s*([A-Z][A-Z0-9_]+)\s*=/gm,
|
|
321
|
+
]) {
|
|
322
|
+
for (const match of content.matchAll(pattern)) {
|
|
323
|
+
names.add(match[1]);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (/\.(go|rs)$/.test(relPath)) {
|
|
327
|
+
for (const match of content.matchAll(/\bconst\s+([A-Za-z_][\w]*)/g)) {
|
|
328
|
+
names.add(match[1]);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return Array.from(names).sort();
|
|
332
|
+
}
|
|
333
|
+
function extractComponents(content, filePath, relPath) {
|
|
334
|
+
const names = new Set();
|
|
335
|
+
const extension = path.extname(filePath);
|
|
336
|
+
if (!isUiFile(relPath)) {
|
|
337
|
+
return [];
|
|
338
|
+
}
|
|
339
|
+
for (const exported of extractExports(content)) {
|
|
340
|
+
if ((0, utils_1.isPascalCase)(exported)) {
|
|
341
|
+
names.add(exported);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const pattern of [
|
|
345
|
+
/function\s+([A-Z][A-Za-z0-9_]*)\s*\(/g,
|
|
346
|
+
/const\s+([A-Z][A-Za-z0-9_]*)\s*=\s*(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>/g,
|
|
347
|
+
/name\s*:\s*['"]([A-Z][A-Za-z0-9_]*)['"]/g,
|
|
348
|
+
]) {
|
|
349
|
+
for (const match of content.matchAll(pattern)) {
|
|
350
|
+
names.add(match[1]);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (['.vue', '.svelte', '.astro'].includes(extension) || isTemplateFile(relPath)) {
|
|
354
|
+
const fileStem = (0, utils_1.toPascalCase)(path.basename(relPath).replace(/\.[^.]+(?:\.[^.]+)?$/, ''));
|
|
355
|
+
if (fileStem && !['Index', 'Page', 'Layout'].includes(fileStem)) {
|
|
356
|
+
names.add(fileStem);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return Array.from(names).sort();
|
|
360
|
+
}
|
|
361
|
+
function extractComponentProps(content, components) {
|
|
362
|
+
const propsByComponent = {};
|
|
363
|
+
for (const component of components) {
|
|
364
|
+
const props = new Set();
|
|
365
|
+
const paramPattern = new RegExp(`(?:function\\s+${(0, utils_1.escapeRegExp)(component)}|const\\s+${(0, utils_1.escapeRegExp)(component)}\\s*=)\\s*\\(?\\s*\\{([^}]+)\\}`);
|
|
366
|
+
const paramMatch = content.match(paramPattern);
|
|
367
|
+
if (paramMatch) {
|
|
368
|
+
for (const prop of paramMatch[1]
|
|
369
|
+
.split(',')
|
|
370
|
+
.map((item) => item.trim().replace(/[:=].*$/, ''))) {
|
|
371
|
+
if (/^[A-Za-z_$][\w$]*$/.test(prop)) {
|
|
372
|
+
props.add(prop);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const typePattern = new RegExp(`(?:type|interface)\\s+${(0, utils_1.escapeRegExp)(component)}Props\\b[\\s\\S]*?[{=]([\\s\\S]*?)[};]`, 'm');
|
|
377
|
+
const typeMatch = content.match(typePattern);
|
|
378
|
+
if (typeMatch) {
|
|
379
|
+
for (const propMatch of typeMatch[1].matchAll(/([A-Za-z_$][\w$]*)\??\s*:/g)) {
|
|
380
|
+
props.add(propMatch[1]);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
propsByComponent[component] = Array.from(props).sort();
|
|
384
|
+
}
|
|
385
|
+
return propsByComponent;
|
|
386
|
+
}
|
|
387
|
+
function extractHooks(content) {
|
|
388
|
+
const hooks = new Set();
|
|
389
|
+
for (const pattern of [
|
|
390
|
+
/export\s+function\s+(use[A-Z][A-Za-z0-9_]*)\s*\(/g,
|
|
391
|
+
/export\s+const\s+(use[A-Z][A-Za-z0-9_]*)\s*=/g,
|
|
392
|
+
/function\s+(use[A-Z][A-Za-z0-9_]*)\s*\(/g,
|
|
393
|
+
/const\s+(use[A-Z][A-Za-z0-9_]*)\s*=/g,
|
|
394
|
+
]) {
|
|
395
|
+
for (const match of content.matchAll(pattern)) {
|
|
396
|
+
hooks.add(match[1]);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return Array.from(hooks).sort();
|
|
400
|
+
}
|
|
401
|
+
function extractJsxElements(content) {
|
|
402
|
+
const elements = new Set();
|
|
403
|
+
for (const match of content.matchAll(/<([A-Z][A-Za-z0-9_.]*)\b/g)) {
|
|
404
|
+
elements.add(match[1].split('.')[0]);
|
|
405
|
+
}
|
|
406
|
+
return Array.from(elements).sort();
|
|
407
|
+
}
|
|
408
|
+
function extractRoutes(relPath, content) {
|
|
409
|
+
const routes = new Set();
|
|
410
|
+
if (isNextPageFile(relPath) || isNextLayoutFile(relPath)) {
|
|
411
|
+
routes.add(toNextRoutePath(relPath));
|
|
412
|
+
}
|
|
413
|
+
if (isPagesRouterPageFile(relPath)) {
|
|
414
|
+
routes.add(toPagesRoutePath(relPath));
|
|
415
|
+
}
|
|
416
|
+
if (isSvelteKitPageFile(relPath)) {
|
|
417
|
+
routes.add(toSvelteKitRoutePath(relPath));
|
|
418
|
+
}
|
|
419
|
+
if (isAstroPageFile(relPath)) {
|
|
420
|
+
routes.add(toAstroRoutePath(relPath));
|
|
421
|
+
}
|
|
422
|
+
if (isNuxtPageFile(relPath)) {
|
|
423
|
+
routes.add(toNuxtRoutePath(relPath));
|
|
424
|
+
}
|
|
425
|
+
if (isRawPageFile(relPath)) {
|
|
426
|
+
routes.add(toRawPageRoutePath(relPath));
|
|
427
|
+
}
|
|
428
|
+
for (const route of extractFrameworkRoutePaths(relPath, content)) {
|
|
429
|
+
routes.add(route);
|
|
430
|
+
}
|
|
431
|
+
return Array.from(routes).sort();
|
|
432
|
+
}
|
|
433
|
+
function extractApiHandlers(relPath, content) {
|
|
434
|
+
const handlers = new Set();
|
|
435
|
+
const routePath = isNextRouteFile(relPath) ? toNextRoutePath(relPath) : null;
|
|
436
|
+
if (routePath) {
|
|
437
|
+
for (const method of ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']) {
|
|
438
|
+
if (new RegExp(`export\\s+(?:async\\s+)?(?:function\\s+${method}|const\\s+${method})\\b`).test(content)) {
|
|
439
|
+
handlers.add(`${method} ${routePath}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
for (const match of content.matchAll(/\b(?:router|app|server|route)\.(get|post|put|patch|delete|options|head)\(\s*['"`]([^'"`]+)/g)) {
|
|
444
|
+
handlers.add(`${match[1].toUpperCase()} ${match[2]}`);
|
|
445
|
+
}
|
|
446
|
+
for (const match of content.matchAll(/\b(?:fastify|hono|fiber|mux)\.(get|post|put|patch|delete|options|head)\(\s*['"`]([^'"`]+)/gi)) {
|
|
447
|
+
handlers.add(`${match[1].toUpperCase()} ${match[2]}`);
|
|
448
|
+
}
|
|
449
|
+
for (const match of content.matchAll(/@(Get|Post|Put|Patch|Delete)\(\s*['"`]?([^'"`)]*)/g)) {
|
|
450
|
+
handlers.add(`${match[1].toUpperCase()} /${match[2]}`.replace(/\/+$/, '/'));
|
|
451
|
+
}
|
|
452
|
+
for (const match of content.matchAll(/Route::(get|post|put|patch|delete|options|any)\(\s*['"`]([^'"`]+)/gi)) {
|
|
453
|
+
handlers.add(`${match[1].toUpperCase()} ${normalizeRoutePath(match[2])}`);
|
|
454
|
+
}
|
|
455
|
+
for (const match of content.matchAll(/^\s*(get|post|put|patch|delete|resources?)\s+['"]([^'"]+)/gim)) {
|
|
456
|
+
const method = match[1].toLowerCase() === 'resources' ? 'RESOURCE' : match[1].toUpperCase();
|
|
457
|
+
handlers.add(`${method} ${normalizeRoutePath(match[2])}`);
|
|
458
|
+
}
|
|
459
|
+
for (const match of content.matchAll(/@(app|router|api)\.(get|post|put|patch|delete|route)\(\s*['"]([^'"]+)/gi)) {
|
|
460
|
+
const method = match[2].toLowerCase() === 'route' ? 'ANY' : match[2].toUpperCase();
|
|
461
|
+
handlers.add(`${method} ${normalizeRoutePath(match[3])}`);
|
|
462
|
+
}
|
|
463
|
+
for (const match of content.matchAll(/\bpath\(\s*['"]([^'"]+)['"]/g)) {
|
|
464
|
+
handlers.add(`ANY ${normalizeRoutePath(match[1])}`);
|
|
465
|
+
}
|
|
466
|
+
for (const match of content.matchAll(/\burl\(r?['"]\^?([^'"]+)['"]/g)) {
|
|
467
|
+
handlers.add(`ANY ${normalizeRoutePath(match[1].replace(/\$$/, ''))}`);
|
|
468
|
+
}
|
|
469
|
+
return Array.from(handlers).sort();
|
|
470
|
+
}
|
|
471
|
+
function extractDecoratedClasses(content, decoratorName) {
|
|
472
|
+
const names = new Set();
|
|
473
|
+
const pattern = new RegExp(`@${decoratorName}\\([^)]*\\)\\s*(?:export\\s+)?class\\s+([A-Za-z_$][\\w$]*)`, 'g');
|
|
474
|
+
for (const match of content.matchAll(pattern)) {
|
|
475
|
+
names.add(match[1]);
|
|
476
|
+
}
|
|
477
|
+
return Array.from(names).sort();
|
|
478
|
+
}
|
|
479
|
+
function extractControllers(content, relPath) {
|
|
480
|
+
const names = new Set();
|
|
481
|
+
for (const className of extractClasses(content, relPath)) {
|
|
482
|
+
if (/Controller$/.test(className) || /controllers?\//i.test(relPath)) {
|
|
483
|
+
names.add(className);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return Array.from(names).sort();
|
|
487
|
+
}
|
|
488
|
+
function extractServices(content, relPath) {
|
|
489
|
+
return (0, utils_1.unique)([
|
|
490
|
+
...extractNamedClasses(content, /Service$/),
|
|
491
|
+
...extractClasses(content, relPath).filter((name) => /(Service|Repository|Client|Gateway|Provider|Manager)$/.test(name)),
|
|
492
|
+
]).sort();
|
|
493
|
+
}
|
|
494
|
+
function extractModules(content, relPath) {
|
|
495
|
+
return (0, utils_1.unique)([
|
|
496
|
+
...extractNamedClasses(content, /Module$/),
|
|
497
|
+
...extractClasses(content, relPath).filter((name) => /(Module|Package|Bundle)$/.test(name)),
|
|
498
|
+
]).sort();
|
|
499
|
+
}
|
|
500
|
+
function extractNamedClasses(content, suffix) {
|
|
501
|
+
const names = new Set();
|
|
502
|
+
for (const match of content.matchAll(/(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/g)) {
|
|
503
|
+
if (suffix.test(match[1])) {
|
|
504
|
+
names.add(match[1]);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return Array.from(names).sort();
|
|
508
|
+
}
|
|
509
|
+
function extractPrismaModels(content) {
|
|
510
|
+
const models = new Set();
|
|
511
|
+
for (const match of content.matchAll(/^model\s+([A-Za-z_$][\w$]*)/gm)) {
|
|
512
|
+
models.add(match[1]);
|
|
513
|
+
}
|
|
514
|
+
return Array.from(models).sort();
|
|
515
|
+
}
|
|
516
|
+
function extractSqlModels(content) {
|
|
517
|
+
const models = new Set();
|
|
518
|
+
for (const match of content.matchAll(/\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"[]?([A-Za-z_][\w.]*)[`"\]]?/gi)) {
|
|
519
|
+
models.add(match[1].split('.').pop() ?? match[1]);
|
|
520
|
+
}
|
|
521
|
+
return Array.from(models).sort();
|
|
522
|
+
}
|
|
523
|
+
function extractOrmModels(content, relPath) {
|
|
524
|
+
const models = new Set();
|
|
525
|
+
for (const match of content.matchAll(/@Entity\([^)]*\)\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/g)) {
|
|
526
|
+
models.add(match[1]);
|
|
527
|
+
}
|
|
528
|
+
for (const match of content.matchAll(/mongoose\.model\(\s*['"]([^'"]+)['"]/g)) {
|
|
529
|
+
models.add(match[1]);
|
|
530
|
+
}
|
|
531
|
+
for (const match of content.matchAll(/\b(?:sequelize|db)\.define\(\s*['"]([^'"]+)['"]/g)) {
|
|
532
|
+
models.add((0, utils_1.toPascalCase)(match[1]));
|
|
533
|
+
}
|
|
534
|
+
for (const match of content.matchAll(/class\s+([A-Z][A-Za-z0-9_]*)\s*<\s*(?:ApplicationRecord|ActiveRecord::Base|Model)\b/g)) {
|
|
535
|
+
models.add(match[1]);
|
|
536
|
+
}
|
|
537
|
+
for (const match of content.matchAll(/class\s+([A-Z][A-Za-z0-9_]*)\s*\([^)]*models\.Model[^)]*\)/g)) {
|
|
538
|
+
models.add(match[1]);
|
|
539
|
+
}
|
|
540
|
+
for (const match of content.matchAll(/class\s+([A-Z][A-Za-z0-9_]*)\s+extends\s+Model\b/g)) {
|
|
541
|
+
models.add(match[1]);
|
|
542
|
+
}
|
|
543
|
+
if (/models?\//.test(relPath) || /\/models?\./.test(relPath)) {
|
|
544
|
+
for (const className of extractClasses(content, relPath)) {
|
|
545
|
+
if ((0, utils_1.isPascalCase)(className)) {
|
|
546
|
+
models.add(className);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return Array.from(models).sort();
|
|
551
|
+
}
|
|
552
|
+
function extractModelFields(content, relPath) {
|
|
553
|
+
const fieldsByModel = {};
|
|
554
|
+
for (const match of content.matchAll(/^model\s+([A-Za-z_$][\w$]*)\s*\{([\s\S]*?)^}/gm)) {
|
|
555
|
+
fieldsByModel[match[1]] = match[2]
|
|
556
|
+
.split('\n')
|
|
557
|
+
.map((line) => line.trim().match(/^([A-Za-z_$][\w$]*)\s+/)?.[1])
|
|
558
|
+
.filter((field) => Boolean(field))
|
|
559
|
+
.slice(0, 80);
|
|
560
|
+
}
|
|
561
|
+
for (const match of content.matchAll(/\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"[]?([A-Za-z_][\w.]*)[`"\]]?\s*\(([\s\S]*?)\);/gi)) {
|
|
562
|
+
const modelName = match[1].split('.').pop() ?? match[1];
|
|
563
|
+
fieldsByModel[modelName] = match[2]
|
|
564
|
+
.split(',')
|
|
565
|
+
.map((line) => line.trim().match(/^[`"[]?([A-Za-z_][\w]*)[`"\]]?\s+/)?.[1] ?? '')
|
|
566
|
+
.filter((field) => Boolean(field) && !/^(PRIMARY|FOREIGN|UNIQUE|KEY|CONSTRAINT)$/i.test(field))
|
|
567
|
+
.slice(0, 80);
|
|
568
|
+
}
|
|
569
|
+
for (const model of extractOrmModels(content, relPath)) {
|
|
570
|
+
const fields = new Set();
|
|
571
|
+
for (const match of content.matchAll(/(?:this\.|self\.|attr_accessor\s+:|property\s+:|field\s+:)([A-Za-z_][\w]*)/g)) {
|
|
572
|
+
fields.add(match[1]);
|
|
573
|
+
}
|
|
574
|
+
for (const match of content.matchAll(/([A-Za-z_][\w]*)\s*=\s*(?:models\.|Column\(|db\.Column|fields\.)/g)) {
|
|
575
|
+
fields.add(match[1]);
|
|
576
|
+
}
|
|
577
|
+
if (fields.size) {
|
|
578
|
+
fieldsByModel[model] = Array.from(fields).sort().slice(0, 80);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return fieldsByModel;
|
|
582
|
+
}
|
|
583
|
+
function extractCssVariables(content) {
|
|
584
|
+
return (0, utils_1.unique)(Array.from(content.matchAll(/--[a-zA-Z0-9-_]+/g)).map((match) => match[0])).slice(0, 80);
|
|
585
|
+
}
|
|
586
|
+
function extractCssVariableValues(content) {
|
|
587
|
+
const values = [];
|
|
588
|
+
for (const match of content.matchAll(/(--[a-zA-Z0-9-_]+)\s*:\s*([^;{}]+);/g)) {
|
|
589
|
+
const selector = nearestCssSelector(content, match.index ?? 0);
|
|
590
|
+
values.push({
|
|
591
|
+
name: match[1],
|
|
592
|
+
value: match[2].trim(),
|
|
593
|
+
...(selector ? { selector } : {}),
|
|
594
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
return (0, utils_1.uniqueBy)(values, (value) => `${value.name}:${value.value}:${value.selector ?? ''}`).slice(0, 240);
|
|
598
|
+
}
|
|
599
|
+
function extractCssRules(content, relPath) {
|
|
600
|
+
if (!/\.(css|scss|vue|svelte|astro|html|htm|php|phtml|twig|erb)$/.test(relPath)) {
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
const rules = [];
|
|
604
|
+
const mediaBlocks = extractCssAtRuleBlocks(content, 'media');
|
|
605
|
+
const mediaRanges = mediaBlocks.map((block) => ({ start: block.start, end: block.end }));
|
|
606
|
+
for (const match of content.matchAll(/([^{}@][^{}]{0,240})\{([^{}]{1,2400})\}/g)) {
|
|
607
|
+
const matchIndex = match.index ?? 0;
|
|
608
|
+
if (mediaRanges.some((range) => matchIndex >= range.start && matchIndex <= range.end)) {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const selector = match[1].trim().replace(/\s+/g, ' ');
|
|
612
|
+
if (!selector || selector.includes(';') || selector.startsWith('import')) {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
const declarations = parseCssDeclarations(match[2]).slice(0, 40);
|
|
616
|
+
if (!declarations.length) {
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
rules.push({
|
|
620
|
+
selector,
|
|
621
|
+
declarations,
|
|
622
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
for (const block of mediaBlocks) {
|
|
626
|
+
for (const match of block.body.matchAll(/([^{}@][^{}]{0,240})\{([^{}]{1,2400})\}/g)) {
|
|
627
|
+
const selector = match[1].trim().replace(/\s+/g, ' ');
|
|
628
|
+
if (!selector || selector.includes(';') || selector.startsWith('import')) {
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const declarations = parseCssDeclarations(match[2]).slice(0, 40);
|
|
632
|
+
if (!declarations.length) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
rules.push({
|
|
636
|
+
selector,
|
|
637
|
+
declarations,
|
|
638
|
+
line: (0, utils_1.lineNumberAt)(content, block.bodyStart + (match.index ?? 0)),
|
|
639
|
+
media: block.prelude,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return (0, utils_1.uniqueBy)(rules, (rule) => `${rule.selector}:${rule.line}:${rule.media ?? ''}`).slice(0, 320);
|
|
644
|
+
}
|
|
645
|
+
function extractCssAtRuleBlocks(content, atRule) {
|
|
646
|
+
const blocks = [];
|
|
647
|
+
const pattern = new RegExp(`@${atRule}\\s+([^{}]+)\\{`, 'g');
|
|
648
|
+
for (const match of content.matchAll(pattern)) {
|
|
649
|
+
const start = match.index ?? 0;
|
|
650
|
+
const openIndex = start + match[0].length - 1;
|
|
651
|
+
let depth = 0;
|
|
652
|
+
let end = -1;
|
|
653
|
+
for (let cursor = openIndex; cursor < content.length; cursor += 1) {
|
|
654
|
+
const char = content[cursor];
|
|
655
|
+
if (char === '{') {
|
|
656
|
+
depth += 1;
|
|
657
|
+
}
|
|
658
|
+
else if (char === '}') {
|
|
659
|
+
depth -= 1;
|
|
660
|
+
if (depth === 0) {
|
|
661
|
+
end = cursor;
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (end <= openIndex) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
blocks.push({
|
|
670
|
+
prelude: match[1].trim().replace(/\s+/g, ' '),
|
|
671
|
+
body: content.slice(openIndex + 1, end),
|
|
672
|
+
start,
|
|
673
|
+
end,
|
|
674
|
+
bodyStart: openIndex + 1,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
return blocks;
|
|
678
|
+
}
|
|
679
|
+
function extractCssClasses(content) {
|
|
680
|
+
return (0, utils_1.unique)(Array.from(content.matchAll(/\.([a-zA-Z][a-zA-Z0-9_-]+)\b/g)).map((match) => match[1])).slice(0, 80);
|
|
681
|
+
}
|
|
682
|
+
function extractCssIds(content) {
|
|
683
|
+
return (0, utils_1.unique)(Array.from(content.matchAll(/#([a-zA-Z][a-zA-Z0-9_-]+)\b/g)).map((match) => match[1])).slice(0, 80);
|
|
684
|
+
}
|
|
685
|
+
function extractCssVariableReferences(content) {
|
|
686
|
+
return (0, utils_1.unique)(Array.from(content.matchAll(/var\(\s*(--[a-zA-Z0-9-_]+)/g)).map((match) => match[1])).slice(0, 80);
|
|
687
|
+
}
|
|
688
|
+
function extractClassReferences(content, classExpressions = extractClassExpressions(content)) {
|
|
689
|
+
const classes = new Set(classExpressions.flatMap((expression) => expression.classes));
|
|
690
|
+
const patterns = [
|
|
691
|
+
/\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/g,
|
|
692
|
+
/\bclass(?:Name)?\s*=\s*\{\s*["'`]([^"'`]+)["'`]\s*\}/g,
|
|
693
|
+
/\bclass:\s*["'`]([^"'`]+)["'`]/g,
|
|
694
|
+
/\bclassList\.(?:add|remove|toggle)\(([^)]*)\)/g,
|
|
695
|
+
];
|
|
696
|
+
for (const pattern of patterns) {
|
|
697
|
+
for (const match of content.matchAll(pattern)) {
|
|
698
|
+
for (const className of splitClassList(match[1])) {
|
|
699
|
+
classes.add(className);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return Array.from(classes).sort().slice(0, 500);
|
|
704
|
+
}
|
|
705
|
+
function extractClassExpressions(content) {
|
|
706
|
+
const expressions = [];
|
|
707
|
+
for (const pattern of [
|
|
708
|
+
/\bclass(?:Name)?\s*=\s*(["'`])([\s\S]*?)\1/g,
|
|
709
|
+
/\bclass(?:Name)?\s*=\s*\{\s*(["'`])([\s\S]*?)\1\s*\}/g,
|
|
710
|
+
/\bclass:\s*(["'`])([\s\S]*?)\1/g,
|
|
711
|
+
]) {
|
|
712
|
+
for (const match of content.matchAll(pattern)) {
|
|
713
|
+
const raw = match[0];
|
|
714
|
+
const classValue = match[2] ?? match[1] ?? '';
|
|
715
|
+
const classes = splitClassList(classValue);
|
|
716
|
+
if (classes.length) {
|
|
717
|
+
const helperUsage = readPrimaryClassHelperUsage(classValue);
|
|
718
|
+
const analysis = analyzeClassSource(classValue);
|
|
719
|
+
expressions.push({
|
|
720
|
+
kind: 'attribute',
|
|
721
|
+
raw: (0, utils_1.truncate)(raw, 500),
|
|
722
|
+
classes,
|
|
723
|
+
defaultClasses: analysis.defaultClasses,
|
|
724
|
+
conditionalClasses: analysis.conditionalClasses,
|
|
725
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
726
|
+
target: readTagBefore(content, match.index ?? 0),
|
|
727
|
+
...(helperUsage ? { styleHelper: helperUsage.name } : {}),
|
|
728
|
+
...(helperUsage && Object.keys(helperUsage.variantProps).length
|
|
729
|
+
? { styleHelperVariants: helperUsage.variantProps }
|
|
730
|
+
: {}),
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
for (const helperName of ['cn', 'clsx', 'classNames', 'twMerge', 'cva']) {
|
|
736
|
+
const helperPattern = new RegExp(`\\b${helperName}\\s*\\(`, 'g');
|
|
737
|
+
for (const match of content.matchAll(helperPattern)) {
|
|
738
|
+
if (isInsideQuotedString(content, match.index ?? 0)) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
const call = readBalancedCall(content, match.index ?? 0);
|
|
742
|
+
if (!call) {
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
const analysis = analyzeClassSource(call);
|
|
746
|
+
const classes = analysis.classes;
|
|
747
|
+
const helperUsage = readPrimaryClassHelperUsage(call);
|
|
748
|
+
if (classes.length || helperUsage) {
|
|
749
|
+
expressions.push({
|
|
750
|
+
kind: helperName === 'cva' ? 'variant-definition' : 'helper',
|
|
751
|
+
raw: (0, utils_1.truncate)(call, 900),
|
|
752
|
+
classes,
|
|
753
|
+
defaultClasses: analysis.defaultClasses,
|
|
754
|
+
conditionalClasses: analysis.conditionalClasses,
|
|
755
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
756
|
+
target: helperName,
|
|
757
|
+
...(helperUsage ? { styleHelper: helperUsage.name } : {}),
|
|
758
|
+
...(helperUsage && Object.keys(helperUsage.variantProps).length
|
|
759
|
+
? { styleHelperVariants: helperUsage.variantProps }
|
|
760
|
+
: {}),
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
for (const match of content.matchAll(/\b([A-Za-z_$][\w$]*(?:Variants?|ClassNames?|Styles?))\s*\(/g)) {
|
|
766
|
+
const helperName = match[1];
|
|
767
|
+
if (/^(?:cn|clsx|classNames|twMerge|cva)$/i.test(helperName)) {
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (isInsideQuotedString(content, match.index ?? 0)) {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const call = readBalancedCall(content, match.index ?? 0);
|
|
774
|
+
if (!call) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
const helperUsage = readPrimaryClassHelperUsage(call);
|
|
778
|
+
if (!helperUsage) {
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
expressions.push({
|
|
782
|
+
kind: 'helper',
|
|
783
|
+
raw: (0, utils_1.truncate)(call, 900),
|
|
784
|
+
classes: [],
|
|
785
|
+
defaultClasses: [],
|
|
786
|
+
conditionalClasses: [],
|
|
787
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
788
|
+
target: helperName,
|
|
789
|
+
styleHelper: helperUsage.name,
|
|
790
|
+
...(Object.keys(helperUsage.variantProps).length
|
|
791
|
+
? { styleHelperVariants: helperUsage.variantProps }
|
|
792
|
+
: {}),
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
for (const match of content.matchAll(/(["'`])([^"'`]{5,900})\1/g)) {
|
|
796
|
+
const classes = splitClassList(match[2]);
|
|
797
|
+
if (classes.length >= 2 && looksLikeClassList(classes)) {
|
|
798
|
+
expressions.push({
|
|
799
|
+
kind: 'string-literal',
|
|
800
|
+
raw: (0, utils_1.truncate)(match[0], 500),
|
|
801
|
+
classes,
|
|
802
|
+
defaultClasses: classes,
|
|
803
|
+
conditionalClasses: [],
|
|
804
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return (0, utils_1.uniqueBy)(expressions, (expression) => `${expression.kind}:${expression.line}:${expression.raw}`).slice(0, 240);
|
|
809
|
+
}
|
|
810
|
+
function extractComponentStyleDefinitions(content) {
|
|
811
|
+
const definitions = [];
|
|
812
|
+
const cvaPattern = /(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*cva\s*\(/g;
|
|
813
|
+
for (const match of content.matchAll(cvaPattern)) {
|
|
814
|
+
if (isInsideQuotedString(content, match.index ?? 0)) {
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
const call = readBalancedCall(content, (match.index ?? 0) + match[0].lastIndexOf('cva'));
|
|
818
|
+
if (!call) {
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
const baseClasses = splitClassList(firstStringLiteral(call) ?? '');
|
|
822
|
+
const variantsBlock = extractNamedObjectBlock(call, 'variants');
|
|
823
|
+
const defaultVariantsBlock = extractNamedObjectBlock(call, 'defaultVariants');
|
|
824
|
+
const variants = variantsBlock ? parseVariantGroups(variantsBlock) : {};
|
|
825
|
+
const defaultVariants = defaultVariantsBlock ? parseStringValueMap(defaultVariantsBlock) : {};
|
|
826
|
+
const classes = (0, utils_1.unique)([
|
|
827
|
+
...baseClasses,
|
|
828
|
+
...Object.values(variants).flatMap((variant) => Object.values(variant).flat()),
|
|
829
|
+
]);
|
|
830
|
+
definitions.push({
|
|
831
|
+
name: match[1],
|
|
832
|
+
kind: 'cva',
|
|
833
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
834
|
+
baseClasses,
|
|
835
|
+
variants,
|
|
836
|
+
defaultVariants,
|
|
837
|
+
classes,
|
|
838
|
+
source: (0, utils_1.truncate)(call, 2200),
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
for (const match of content.matchAll(/(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*(?:Classes|ClassNames|Styles|Variants|Config|Map))\s*=\s*\{/g)) {
|
|
842
|
+
if (isInsideQuotedString(content, match.index ?? 0)) {
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
const block = readBalancedBlock(content, (match.index ?? 0) + match[0].lastIndexOf('{'));
|
|
846
|
+
if (!block) {
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
const variants = { values: parseClassValueMap(block) };
|
|
850
|
+
const classes = (0, utils_1.unique)(Object.values(variants.values).flat());
|
|
851
|
+
if (!classes.length) {
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
definitions.push({
|
|
855
|
+
name: match[1],
|
|
856
|
+
kind: /variant/i.test(match[1]) ? 'variant-object' : 'class-map',
|
|
857
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
858
|
+
baseClasses: [],
|
|
859
|
+
variants,
|
|
860
|
+
defaultVariants: {},
|
|
861
|
+
classes,
|
|
862
|
+
source: (0, utils_1.truncate)(block, 2200),
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
return (0, utils_1.uniqueBy)(definitions, (definition) => `${definition.name}:${definition.line}`).slice(0, 80);
|
|
866
|
+
}
|
|
867
|
+
function extractThemeTokens(relPath, content) {
|
|
868
|
+
const shouldExtract = /(?:theme|token|tailwind|config|globals|styles?|css|scss)/i.test(relPath) ||
|
|
869
|
+
/--[a-zA-Z0-9-_]+\s*:/.test(content);
|
|
870
|
+
if (!shouldExtract) {
|
|
871
|
+
return [];
|
|
872
|
+
}
|
|
873
|
+
const tokens = [];
|
|
874
|
+
for (const variable of extractCssVariableValues(content)) {
|
|
875
|
+
tokens.push({
|
|
876
|
+
path: variable.name,
|
|
877
|
+
value: variable.value,
|
|
878
|
+
type: classifyThemeToken(variable.name, variable.value),
|
|
879
|
+
line: variable.line,
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
if (/\.json$/.test(relPath)) {
|
|
883
|
+
try {
|
|
884
|
+
flattenJsonTokens(JSON.parse(content), [], tokens);
|
|
885
|
+
}
|
|
886
|
+
catch {
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
for (const match of content.matchAll(/["']?([A-Za-z_$][\w$-]*(?:Color|Radius|Spacing|Shadow|Font|Size|Height|Width|Padding|Gap|Background|Foreground|Border|Ring|Surface|Text)?|--[a-zA-Z0-9-_]+)["']?\s*:\s*(["'`])([^"'`]{1,220})\2/g)) {
|
|
890
|
+
tokens.push({
|
|
891
|
+
path: match[1],
|
|
892
|
+
value: match[3],
|
|
893
|
+
type: classifyThemeToken(match[1], match[3]),
|
|
894
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
return (0, utils_1.uniqueBy)(tokens, (token) => `${token.path}:${token.value}`).slice(0, 300);
|
|
898
|
+
}
|
|
899
|
+
function extractInlineStyles(content) {
|
|
900
|
+
const styles = [];
|
|
901
|
+
for (const match of content.matchAll(/\bstyle\s*=\s*(["'])([^"']+)\1/g)) {
|
|
902
|
+
styles.push({
|
|
903
|
+
tag: readTagBefore(content, match.index ?? 0),
|
|
904
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
905
|
+
raw: (0, utils_1.truncate)(match[0], 500),
|
|
906
|
+
properties: parseCssDeclarations(match[2]),
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
for (const match of content.matchAll(/\bstyle\s*=\s*\{\{([\s\S]{1,1200}?)\}\}/g)) {
|
|
910
|
+
styles.push({
|
|
911
|
+
tag: readTagBefore(content, match.index ?? 0),
|
|
912
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
913
|
+
raw: (0, utils_1.truncate)(match[0], 700),
|
|
914
|
+
properties: parseJsStyleObject(match[1]),
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
return styles.slice(0, 120);
|
|
918
|
+
}
|
|
919
|
+
function nearestCssSelector(content, index) {
|
|
920
|
+
const openIndex = content.lastIndexOf('{', index);
|
|
921
|
+
if (openIndex < 0) {
|
|
922
|
+
return undefined;
|
|
923
|
+
}
|
|
924
|
+
const previousClose = content.lastIndexOf('}', openIndex);
|
|
925
|
+
const selector = content
|
|
926
|
+
.slice(previousClose + 1, openIndex)
|
|
927
|
+
.replace(/\/\*[\s\S]*?\*\//g, ' ')
|
|
928
|
+
.trim()
|
|
929
|
+
.replace(/\s+/g, ' ');
|
|
930
|
+
return selector ? (0, utils_1.truncate)(selector, 240) : undefined;
|
|
931
|
+
}
|
|
932
|
+
function parseCssDeclarations(block) {
|
|
933
|
+
return splitTopLevel(block, ';')
|
|
934
|
+
.map((declaration) => {
|
|
935
|
+
const separatorIndex = declaration.indexOf(':');
|
|
936
|
+
if (separatorIndex <= 0) {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
const property = declaration.slice(0, separatorIndex).trim();
|
|
940
|
+
const value = declaration.slice(separatorIndex + 1).trim();
|
|
941
|
+
if (!property || !value || property.startsWith('@')) {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
return { property, value };
|
|
945
|
+
})
|
|
946
|
+
.filter((item) => Boolean(item));
|
|
947
|
+
}
|
|
948
|
+
function parseJsStyleObject(block) {
|
|
949
|
+
return parseTopLevelObjectEntries(`{${block}}`)
|
|
950
|
+
.map((entry) => {
|
|
951
|
+
const value = firstStringLiteral(entry.rawValue) ?? cleanupBareValue(entry.rawValue);
|
|
952
|
+
if (!value) {
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
955
|
+
return {
|
|
956
|
+
property: (0, utils_1.toKebabCase)(entry.key),
|
|
957
|
+
value,
|
|
958
|
+
};
|
|
959
|
+
})
|
|
960
|
+
.filter((item) => Boolean(item));
|
|
961
|
+
}
|
|
962
|
+
function readTagBefore(content, index) {
|
|
963
|
+
const start = content.lastIndexOf('<', index);
|
|
964
|
+
if (start < 0 || ['/', '!', '?'].includes(content[start + 1] ?? '')) {
|
|
965
|
+
return undefined;
|
|
966
|
+
}
|
|
967
|
+
const match = content.slice(start + 1, index).match(/^([A-Za-z][A-Za-z0-9:._-]*)/);
|
|
968
|
+
return match?.[1];
|
|
969
|
+
}
|
|
970
|
+
function readBalancedCall(content, startIndex) {
|
|
971
|
+
const openIndex = content.indexOf('(', startIndex);
|
|
972
|
+
if (openIndex < 0) {
|
|
973
|
+
return undefined;
|
|
974
|
+
}
|
|
975
|
+
const args = readBalancedRange(content, openIndex, '(', ')');
|
|
976
|
+
return args ? content.slice(startIndex, openIndex) + args : undefined;
|
|
977
|
+
}
|
|
978
|
+
function readBalancedBlock(content, openIndex) {
|
|
979
|
+
return readBalancedRange(content, openIndex, '{', '}');
|
|
980
|
+
}
|
|
981
|
+
function readBalancedRange(content, openIndex, openChar, closeChar) {
|
|
982
|
+
if (content[openIndex] !== openChar) {
|
|
983
|
+
return undefined;
|
|
984
|
+
}
|
|
985
|
+
let depth = 0;
|
|
986
|
+
for (let index = openIndex; index < content.length; index += 1) {
|
|
987
|
+
const char = content[index];
|
|
988
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
989
|
+
index = skipQuoted(content, index, char);
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (char === '/' && content[index + 1] === '/') {
|
|
993
|
+
const lineEnd = content.indexOf('\n', index + 2);
|
|
994
|
+
index = lineEnd >= 0 ? lineEnd : content.length;
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
if (char === '/' && content[index + 1] === '*') {
|
|
998
|
+
const commentEnd = content.indexOf('*/', index + 2);
|
|
999
|
+
index = commentEnd >= 0 ? commentEnd + 1 : content.length;
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if (char === openChar) {
|
|
1003
|
+
depth += 1;
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
if (char === closeChar) {
|
|
1007
|
+
depth -= 1;
|
|
1008
|
+
if (depth === 0) {
|
|
1009
|
+
return content.slice(openIndex, index + 1);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return undefined;
|
|
1014
|
+
}
|
|
1015
|
+
function skipQuoted(content, quoteIndex, quote) {
|
|
1016
|
+
for (let index = quoteIndex + 1; index < content.length; index += 1) {
|
|
1017
|
+
if (content[index] === '\\') {
|
|
1018
|
+
index += 1;
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
if (content[index] === quote) {
|
|
1022
|
+
return index;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return content.length - 1;
|
|
1026
|
+
}
|
|
1027
|
+
function isInsideQuotedString(content, targetIndex) {
|
|
1028
|
+
let quote = null;
|
|
1029
|
+
for (let index = 0; index < targetIndex; index += 1) {
|
|
1030
|
+
const char = content[index];
|
|
1031
|
+
if (quote) {
|
|
1032
|
+
if (char === '\\') {
|
|
1033
|
+
index += 1;
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
if (char === quote) {
|
|
1037
|
+
quote = null;
|
|
1038
|
+
}
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (char === '/' && content[index + 1] === '/') {
|
|
1042
|
+
const lineEnd = content.indexOf('\n', index + 2);
|
|
1043
|
+
index = lineEnd >= 0 ? lineEnd : targetIndex;
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
if (char === '/' && content[index + 1] === '*') {
|
|
1047
|
+
const commentEnd = content.indexOf('*/', index + 2);
|
|
1048
|
+
index = commentEnd >= 0 ? commentEnd + 1 : targetIndex;
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
1052
|
+
quote = char;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return Boolean(quote);
|
|
1056
|
+
}
|
|
1057
|
+
function extractClassListsFromStrings(source) {
|
|
1058
|
+
return analyzeClassSource(source).classes;
|
|
1059
|
+
}
|
|
1060
|
+
function readPrimaryClassHelperUsage(source) {
|
|
1061
|
+
const usages = readClassHelperUsages(source);
|
|
1062
|
+
return (usages.find((usage) => /button|cta|action/i.test(usage.name)) ??
|
|
1063
|
+
usages.find((usage) => Object.keys(usage.variantProps).length > 0) ??
|
|
1064
|
+
usages[0]);
|
|
1065
|
+
}
|
|
1066
|
+
function readClassHelperUsages(source) {
|
|
1067
|
+
const usages = [];
|
|
1068
|
+
const helperPattern = /\b([A-Za-z_$][\w$]*(?:Variants?|ClassNames?|Styles?))\s*\(/g;
|
|
1069
|
+
for (const match of source.matchAll(helperPattern)) {
|
|
1070
|
+
const name = match[1];
|
|
1071
|
+
if (/^(?:cn|clsx|classNames|twMerge|cva)$/i.test(name)) {
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
const call = readBalancedCall(source, match.index ?? 0);
|
|
1075
|
+
if (!call) {
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
usages.push({
|
|
1079
|
+
name,
|
|
1080
|
+
variantProps: parseHelperVariantProps(call),
|
|
1081
|
+
call: (0, utils_1.truncate)(call, 700),
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
return (0, utils_1.uniqueBy)(usages, (usage) => `${usage.name}:${JSON.stringify(usage.variantProps)}`).slice(0, 12);
|
|
1085
|
+
}
|
|
1086
|
+
function parseHelperVariantProps(call) {
|
|
1087
|
+
const props = {};
|
|
1088
|
+
const openIndex = call.indexOf('{');
|
|
1089
|
+
if (openIndex < 0) {
|
|
1090
|
+
return props;
|
|
1091
|
+
}
|
|
1092
|
+
const block = readBalancedBlock(call, openIndex);
|
|
1093
|
+
if (!block) {
|
|
1094
|
+
return props;
|
|
1095
|
+
}
|
|
1096
|
+
for (const entry of parseTopLevelObjectEntries(block)) {
|
|
1097
|
+
const literalValues = readStringLiterals(entry.rawValue)
|
|
1098
|
+
.map((value) => value.trim())
|
|
1099
|
+
.filter(Boolean)
|
|
1100
|
+
.slice(0, 4);
|
|
1101
|
+
const value = literalValues.length > 1
|
|
1102
|
+
? literalValues.join('|')
|
|
1103
|
+
: (literalValues[0] ?? cleanupBareValue(entry.rawValue));
|
|
1104
|
+
if (value) {
|
|
1105
|
+
props[entry.key] = value;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return props;
|
|
1109
|
+
}
|
|
1110
|
+
function analyzeClassSource(source) {
|
|
1111
|
+
const classes = new Set();
|
|
1112
|
+
const defaultClasses = new Set();
|
|
1113
|
+
const conditionalClasses = new Set();
|
|
1114
|
+
const directCandidate = directClassCandidate(source);
|
|
1115
|
+
const directClasses = directCandidate ? splitClassList(directCandidate) : [];
|
|
1116
|
+
if (directCandidate && looksLikeClassList(directClasses)) {
|
|
1117
|
+
for (const className of directClasses) {
|
|
1118
|
+
classes.add(className);
|
|
1119
|
+
defaultClasses.add(className);
|
|
1120
|
+
}
|
|
1121
|
+
return toClassSourceAnalysis(classes, defaultClasses, conditionalClasses);
|
|
1122
|
+
}
|
|
1123
|
+
for (const literal of readStringLiteralRanges(source)) {
|
|
1124
|
+
const literalClasses = splitClassList(literal.value);
|
|
1125
|
+
if (!looksLikeClassList(literalClasses)) {
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
const targetSet = isConditionalClassLiteral(source, literal.start)
|
|
1129
|
+
? conditionalClasses
|
|
1130
|
+
: defaultClasses;
|
|
1131
|
+
for (const className of literalClasses) {
|
|
1132
|
+
classes.add(className);
|
|
1133
|
+
targetSet.add(className);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return toClassSourceAnalysis(classes, defaultClasses, conditionalClasses);
|
|
1137
|
+
}
|
|
1138
|
+
function toClassSourceAnalysis(classes, defaultClasses, conditionalClasses) {
|
|
1139
|
+
const allClasses = Array.from(classes).sort();
|
|
1140
|
+
const defaultClassList = Array.from(defaultClasses).sort();
|
|
1141
|
+
const conditionalClassList = Array.from(conditionalClasses).sort();
|
|
1142
|
+
return {
|
|
1143
|
+
classes: allClasses,
|
|
1144
|
+
defaultClasses: defaultClassList,
|
|
1145
|
+
conditionalClasses: conditionalClassList,
|
|
1146
|
+
decorativeAccentClasses: allClasses.filter(isDecorativeAccentClass).sort(),
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
function isConditionalClassLiteral(source, literalStart) {
|
|
1150
|
+
const openIndex = source.indexOf('(');
|
|
1151
|
+
const before = source.slice(Math.max(openIndex + 1, literalStart - 140), literalStart);
|
|
1152
|
+
const sanitizedBefore = before.replace(/(["'`])(?:\\.|(?!\1)[\s\S])*\1/g, ' ');
|
|
1153
|
+
const segmentStart = Math.max(sanitizedBefore.lastIndexOf(','), sanitizedBefore.lastIndexOf('('), sanitizedBefore.lastIndexOf('{'), sanitizedBefore.lastIndexOf('['));
|
|
1154
|
+
const segment = sanitizedBefore.slice(segmentStart + 1);
|
|
1155
|
+
return /(?:&&|\?|:)\s*[^"'`]*$/.test(segment);
|
|
1156
|
+
}
|
|
1157
|
+
function readStringLiteralRanges(source) {
|
|
1158
|
+
const literals = [];
|
|
1159
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1160
|
+
const char = source[index];
|
|
1161
|
+
if (char !== '"' && char !== "'" && char !== '`') {
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
const end = skipQuoted(source, index, char);
|
|
1165
|
+
literals.push({
|
|
1166
|
+
value: source.slice(index + 1, end),
|
|
1167
|
+
start: index,
|
|
1168
|
+
end,
|
|
1169
|
+
});
|
|
1170
|
+
index = end;
|
|
1171
|
+
}
|
|
1172
|
+
return literals;
|
|
1173
|
+
}
|
|
1174
|
+
function directClassCandidate(source) {
|
|
1175
|
+
const trimmed = source.trim();
|
|
1176
|
+
if (!trimmed ||
|
|
1177
|
+
trimmed.startsWith('{') ||
|
|
1178
|
+
/\b(?:cn|clsx|classNames|twMerge|cva|buttonVariants)\s*\(/.test(trimmed)) {
|
|
1179
|
+
return undefined;
|
|
1180
|
+
}
|
|
1181
|
+
const withoutTemplateExpressions = trimmed.replace(/\$\{[\s\S]*?\}/g, ' ');
|
|
1182
|
+
const withoutArbitraryValues = withoutTemplateExpressions.replace(/\[[^\]]+\]/g, '[arbitrary]');
|
|
1183
|
+
if (/[{}"'`,;]/.test(withoutArbitraryValues)) {
|
|
1184
|
+
return undefined;
|
|
1185
|
+
}
|
|
1186
|
+
return withoutTemplateExpressions;
|
|
1187
|
+
}
|
|
1188
|
+
function readStringLiterals(source) {
|
|
1189
|
+
return readStringLiteralRanges(source).map((literal) => literal.value);
|
|
1190
|
+
}
|
|
1191
|
+
function looksLikeClassList(classes) {
|
|
1192
|
+
if (!classes.length) {
|
|
1193
|
+
return false;
|
|
1194
|
+
}
|
|
1195
|
+
const likelyCount = classes.filter(isLikelyStyleClass).length;
|
|
1196
|
+
return likelyCount >= Math.min(2, classes.length) || likelyCount / classes.length >= 0.5;
|
|
1197
|
+
}
|
|
1198
|
+
function isLikelyStyleClass(className) {
|
|
1199
|
+
const base = className.split(':').pop() ?? className;
|
|
1200
|
+
return (/^(?:-?m[trblxy]?|-?p[trblxy]?|flex|grid|block|inline|hidden|items-|justify-|content-|self-|gap-|space-|w-|h-|min-|max-|size-|rounded|border|bg-|from-|via-|to-|text-|font-|leading-|tracking-|shadow|ring|outline|opacity|transition|duration|ease|animate-|hover|focus|active|disabled|dark|sm|md|lg|xl|2xl|container|sr-only|absolute|relative|fixed|sticky|inset-|top-|right-|bottom-|left-|z-|overflow-|object-|aspect-|backdrop-|blur|scale-|translate-|rotate-)/.test(className) ||
|
|
1201
|
+
/^(?:btn(?:-.+)?|card(?:-.+)?|badge(?:-.+)?|form(?:-.+)?|input-group(?:-.+)?|navbar(?:-.+)?|nav(?:-.+)?|row|col(?:-.+)?|container(?:-.+)?|d-(?:sm-|md-|lg-|xl-|xxl-)?|justify-content-|align-items-|align-self-|fw-|fs-|lh-|display-|lead|small|list-group(?:-.+)?|modal(?:-.+)?|alert(?:-.+)?|rounded-(?:pill|circle|\d)|shadow(?:-.+)?|w-\d+|h-\d+|mw-\d+|mh-\d+|min-vh-|min-vw-|vw-|vh-)/.test(className) ||
|
|
1202
|
+
/^(?:button|btn|card|panel|surface|badge|pill|chip|nav|field|input|dialog|modal|drawer|sheet|container|wrapper|section|hero|header|footer|sidebar|menu)$/.test(base) ||
|
|
1203
|
+
className.includes('[') ||
|
|
1204
|
+
className.includes(']'));
|
|
1205
|
+
}
|
|
1206
|
+
function firstStringLiteral(source) {
|
|
1207
|
+
return readStringLiterals(source)[0];
|
|
1208
|
+
}
|
|
1209
|
+
function extractNamedObjectBlock(source, name) {
|
|
1210
|
+
const pattern = new RegExp(`\\b${(0, utils_1.escapeRegExp)(name)}\\s*:`, 'g');
|
|
1211
|
+
for (const match of source.matchAll(pattern)) {
|
|
1212
|
+
const openIndex = source.indexOf('{', (match.index ?? 0) + match[0].length);
|
|
1213
|
+
if (openIndex < 0) {
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
return readBalancedBlock(source, openIndex);
|
|
1217
|
+
}
|
|
1218
|
+
return undefined;
|
|
1219
|
+
}
|
|
1220
|
+
function parseVariantGroups(block) {
|
|
1221
|
+
const variants = {};
|
|
1222
|
+
for (const entry of parseTopLevelObjectEntries(block)) {
|
|
1223
|
+
if (!entry.rawValue.trim().startsWith('{')) {
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
const values = parseClassValueMap(entry.rawValue);
|
|
1227
|
+
if (Object.keys(values).length) {
|
|
1228
|
+
variants[entry.key] = values;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return variants;
|
|
1232
|
+
}
|
|
1233
|
+
function parseStringValueMap(block) {
|
|
1234
|
+
const values = {};
|
|
1235
|
+
for (const entry of parseTopLevelObjectEntries(block)) {
|
|
1236
|
+
const value = firstStringLiteral(entry.rawValue) ?? cleanupBareValue(entry.rawValue);
|
|
1237
|
+
if (value) {
|
|
1238
|
+
values[entry.key] = value;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return values;
|
|
1242
|
+
}
|
|
1243
|
+
function parseClassValueMap(block) {
|
|
1244
|
+
const values = {};
|
|
1245
|
+
for (const entry of parseTopLevelObjectEntries(block)) {
|
|
1246
|
+
const classes = extractClassListsFromStrings(entry.rawValue);
|
|
1247
|
+
if (classes.length) {
|
|
1248
|
+
values[entry.key] = classes;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return values;
|
|
1252
|
+
}
|
|
1253
|
+
function parseTopLevelObjectEntries(block) {
|
|
1254
|
+
const source = stripOuterBraces(block);
|
|
1255
|
+
const entries = [];
|
|
1256
|
+
let index = 0;
|
|
1257
|
+
while (index < source.length) {
|
|
1258
|
+
index = skipSeparators(source, index);
|
|
1259
|
+
if (index >= source.length) {
|
|
1260
|
+
break;
|
|
1261
|
+
}
|
|
1262
|
+
const keyResult = readObjectKey(source, index);
|
|
1263
|
+
if (!keyResult) {
|
|
1264
|
+
index += 1;
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
index = skipWhitespace(source, keyResult.nextIndex);
|
|
1268
|
+
if (source[index] !== ':') {
|
|
1269
|
+
index += 1;
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
index = skipWhitespace(source, index + 1);
|
|
1273
|
+
const valueEnd = readObjectValueEnd(source, index);
|
|
1274
|
+
const rawValue = source.slice(index, valueEnd).trim();
|
|
1275
|
+
if (rawValue) {
|
|
1276
|
+
entries.push({ key: keyResult.key, rawValue });
|
|
1277
|
+
}
|
|
1278
|
+
index = valueEnd + 1;
|
|
1279
|
+
}
|
|
1280
|
+
return entries;
|
|
1281
|
+
}
|
|
1282
|
+
function stripOuterBraces(block) {
|
|
1283
|
+
const trimmed = block.trim();
|
|
1284
|
+
return trimmed.startsWith('{') && trimmed.endsWith('}') ? trimmed.slice(1, -1) : trimmed;
|
|
1285
|
+
}
|
|
1286
|
+
function skipSeparators(source, index) {
|
|
1287
|
+
let cursor = index;
|
|
1288
|
+
while (cursor < source.length && /[\s,]/.test(source[cursor])) {
|
|
1289
|
+
cursor += 1;
|
|
1290
|
+
}
|
|
1291
|
+
return cursor;
|
|
1292
|
+
}
|
|
1293
|
+
function skipWhitespace(source, index) {
|
|
1294
|
+
let cursor = index;
|
|
1295
|
+
while (cursor < source.length && /\s/.test(source[cursor])) {
|
|
1296
|
+
cursor += 1;
|
|
1297
|
+
}
|
|
1298
|
+
return cursor;
|
|
1299
|
+
}
|
|
1300
|
+
function readObjectKey(source, index) {
|
|
1301
|
+
const char = source[index];
|
|
1302
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
1303
|
+
const end = skipQuoted(source, index, char);
|
|
1304
|
+
return { key: source.slice(index + 1, end), nextIndex: end + 1 };
|
|
1305
|
+
}
|
|
1306
|
+
const match = source.slice(index).match(/^([A-Za-z_$][\w$-]*|true|false|\d+)/);
|
|
1307
|
+
return match ? { key: match[1], nextIndex: index + match[0].length } : undefined;
|
|
1308
|
+
}
|
|
1309
|
+
function readObjectValueEnd(source, index) {
|
|
1310
|
+
let cursor = index;
|
|
1311
|
+
let parenDepth = 0;
|
|
1312
|
+
let blockDepth = 0;
|
|
1313
|
+
let arrayDepth = 0;
|
|
1314
|
+
while (cursor < source.length) {
|
|
1315
|
+
const char = source[cursor];
|
|
1316
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
1317
|
+
cursor = skipQuoted(source, cursor, char) + 1;
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
if (char === '(') {
|
|
1321
|
+
parenDepth += 1;
|
|
1322
|
+
}
|
|
1323
|
+
else if (char === ')') {
|
|
1324
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
1325
|
+
}
|
|
1326
|
+
else if (char === '{') {
|
|
1327
|
+
blockDepth += 1;
|
|
1328
|
+
}
|
|
1329
|
+
else if (char === '}') {
|
|
1330
|
+
if (blockDepth === 0) {
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
1333
|
+
blockDepth -= 1;
|
|
1334
|
+
}
|
|
1335
|
+
else if (char === '[') {
|
|
1336
|
+
arrayDepth += 1;
|
|
1337
|
+
}
|
|
1338
|
+
else if (char === ']') {
|
|
1339
|
+
arrayDepth = Math.max(0, arrayDepth - 1);
|
|
1340
|
+
}
|
|
1341
|
+
else if (char === ',' && parenDepth === 0 && blockDepth === 0 && arrayDepth === 0) {
|
|
1342
|
+
break;
|
|
1343
|
+
}
|
|
1344
|
+
cursor += 1;
|
|
1345
|
+
}
|
|
1346
|
+
return cursor;
|
|
1347
|
+
}
|
|
1348
|
+
function splitTopLevel(source, delimiter) {
|
|
1349
|
+
const parts = [];
|
|
1350
|
+
let start = 0;
|
|
1351
|
+
let parenDepth = 0;
|
|
1352
|
+
let blockDepth = 0;
|
|
1353
|
+
let arrayDepth = 0;
|
|
1354
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1355
|
+
const char = source[index];
|
|
1356
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
1357
|
+
index = skipQuoted(source, index, char);
|
|
1358
|
+
continue;
|
|
1359
|
+
}
|
|
1360
|
+
if (char === '(') {
|
|
1361
|
+
parenDepth += 1;
|
|
1362
|
+
}
|
|
1363
|
+
else if (char === ')') {
|
|
1364
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
1365
|
+
}
|
|
1366
|
+
else if (char === '{') {
|
|
1367
|
+
blockDepth += 1;
|
|
1368
|
+
}
|
|
1369
|
+
else if (char === '}') {
|
|
1370
|
+
blockDepth = Math.max(0, blockDepth - 1);
|
|
1371
|
+
}
|
|
1372
|
+
else if (char === '[') {
|
|
1373
|
+
arrayDepth += 1;
|
|
1374
|
+
}
|
|
1375
|
+
else if (char === ']') {
|
|
1376
|
+
arrayDepth = Math.max(0, arrayDepth - 1);
|
|
1377
|
+
}
|
|
1378
|
+
else if (char === delimiter && parenDepth === 0 && blockDepth === 0 && arrayDepth === 0) {
|
|
1379
|
+
parts.push(source.slice(start, index));
|
|
1380
|
+
start = index + 1;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
parts.push(source.slice(start));
|
|
1384
|
+
return parts;
|
|
1385
|
+
}
|
|
1386
|
+
function cleanupBareValue(value) {
|
|
1387
|
+
const cleaned = value
|
|
1388
|
+
.trim()
|
|
1389
|
+
.replace(/,$/, '')
|
|
1390
|
+
.replace(/^['"`]|['"`]$/g, '');
|
|
1391
|
+
return cleaned && cleaned.length < 220 ? cleaned : undefined;
|
|
1392
|
+
}
|
|
1393
|
+
function classifyThemeToken(key, value) {
|
|
1394
|
+
const haystack = `${key} ${value}`.toLowerCase();
|
|
1395
|
+
if (/(#[0-9a-f]{3,8}\b|rgb\(|rgba\(|hsl\(|hsla\(|oklch\(|color|foreground|background|surface|border|ring|accent|primary|secondary|muted|destructive)/.test(haystack)) {
|
|
1396
|
+
return 'color';
|
|
1397
|
+
}
|
|
1398
|
+
if (/(radius|rounded|border-radius)/.test(haystack)) {
|
|
1399
|
+
return 'radius';
|
|
1400
|
+
}
|
|
1401
|
+
if (/(spacing|space|gap|padding|margin|inset)/.test(haystack)) {
|
|
1402
|
+
return 'spacing';
|
|
1403
|
+
}
|
|
1404
|
+
if (/(font|family|weight|leading|tracking|letter|line-height)/.test(haystack)) {
|
|
1405
|
+
return 'font';
|
|
1406
|
+
}
|
|
1407
|
+
if (/(shadow|elevation|drop-shadow)/.test(haystack)) {
|
|
1408
|
+
return 'shadow';
|
|
1409
|
+
}
|
|
1410
|
+
if (/(size|width|height|text-|rem|px|vh|vw|%)/.test(haystack)) {
|
|
1411
|
+
return 'size';
|
|
1412
|
+
}
|
|
1413
|
+
return 'unknown';
|
|
1414
|
+
}
|
|
1415
|
+
function flattenJsonTokens(value, pathParts, tokens) {
|
|
1416
|
+
if (value === null || value === undefined) {
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
1420
|
+
const pathValue = pathParts.join('.');
|
|
1421
|
+
const stringValue = String(value);
|
|
1422
|
+
if (pathValue) {
|
|
1423
|
+
tokens.push({
|
|
1424
|
+
path: pathValue,
|
|
1425
|
+
value: stringValue,
|
|
1426
|
+
type: classifyThemeToken(pathValue, stringValue),
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if (Array.isArray(value)) {
|
|
1432
|
+
value
|
|
1433
|
+
.slice(0, 80)
|
|
1434
|
+
.forEach((item, index) => flattenJsonTokens(item, [...pathParts, String(index)], tokens));
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
if (typeof value === 'object') {
|
|
1438
|
+
for (const [key, childValue] of Object.entries(value).slice(0, 200)) {
|
|
1439
|
+
flattenJsonTokens(childValue, [...pathParts, key], tokens);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
function readOpeningTags(content) {
|
|
1444
|
+
const tags = [];
|
|
1445
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
1446
|
+
if (content[index] !== '<' || !/[A-Za-z]/.test(content[index + 1] ?? '')) {
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
const start = index;
|
|
1450
|
+
let cursor = index + 1;
|
|
1451
|
+
while (cursor < content.length && /[A-Za-z0-9:._-]/.test(content[cursor])) {
|
|
1452
|
+
cursor += 1;
|
|
1453
|
+
}
|
|
1454
|
+
const originalTag = content.slice(index + 1, cursor);
|
|
1455
|
+
if (!originalTag) {
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
let braceDepth = 0;
|
|
1459
|
+
let end = -1;
|
|
1460
|
+
for (; cursor < content.length; cursor += 1) {
|
|
1461
|
+
const char = content[cursor];
|
|
1462
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
1463
|
+
cursor = skipQuoted(content, cursor, char);
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
if (char === '{') {
|
|
1467
|
+
braceDepth += 1;
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
if (char === '}') {
|
|
1471
|
+
braceDepth = Math.max(0, braceDepth - 1);
|
|
1472
|
+
continue;
|
|
1473
|
+
}
|
|
1474
|
+
if (char === '>' && braceDepth === 0) {
|
|
1475
|
+
end = cursor;
|
|
1476
|
+
break;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
if (end < 0) {
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
const attrs = content.slice(start + originalTag.length + 1, end);
|
|
1483
|
+
tags.push({
|
|
1484
|
+
originalTag,
|
|
1485
|
+
attrs,
|
|
1486
|
+
start,
|
|
1487
|
+
end,
|
|
1488
|
+
selfClosing: /\/\s*$/.test(attrs),
|
|
1489
|
+
});
|
|
1490
|
+
index = end;
|
|
1491
|
+
}
|
|
1492
|
+
annotateOpeningTags(content, tags);
|
|
1493
|
+
return tags;
|
|
1494
|
+
}
|
|
1495
|
+
function annotateOpeningTags(content, tags) {
|
|
1496
|
+
for (const tag of tags) {
|
|
1497
|
+
tag.closeEnd = findClosingTagEnd(content, tag);
|
|
1498
|
+
}
|
|
1499
|
+
for (const [index, tag] of tags.entries()) {
|
|
1500
|
+
let parentIndex;
|
|
1501
|
+
let parentStart = -1;
|
|
1502
|
+
for (let candidateIndex = 0; candidateIndex < index; candidateIndex += 1) {
|
|
1503
|
+
const candidate = tags[candidateIndex];
|
|
1504
|
+
const candidateCloseEnd = candidate.closeEnd ?? candidate.end;
|
|
1505
|
+
if (candidate.start < tag.start &&
|
|
1506
|
+
candidateCloseEnd > tag.end &&
|
|
1507
|
+
candidate.start > parentStart) {
|
|
1508
|
+
parentIndex = candidateIndex;
|
|
1509
|
+
parentStart = candidate.start;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
tag.parentIndex = parentIndex;
|
|
1513
|
+
tag.depth = parentIndex === undefined ? 0 : Math.min(24, (tags[parentIndex].depth ?? 0) + 1);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function findClosingTagEnd(content, tagInfo) {
|
|
1517
|
+
if (tagInfo.selfClosing || isVoidHtmlElement(tagInfo.originalTag)) {
|
|
1518
|
+
return tagInfo.end;
|
|
1519
|
+
}
|
|
1520
|
+
const tagPattern = new RegExp(`<\\/?${(0, utils_1.escapeRegExp)(tagInfo.originalTag)}(?=\\s|>|\\/)[\\s\\S]*?>`, 'g');
|
|
1521
|
+
tagPattern.lastIndex = tagInfo.end + 1;
|
|
1522
|
+
let depth = 1;
|
|
1523
|
+
for (const match of content.matchAll(tagPattern)) {
|
|
1524
|
+
const raw = match[0];
|
|
1525
|
+
if (raw.startsWith('</')) {
|
|
1526
|
+
depth -= 1;
|
|
1527
|
+
if (depth === 0) {
|
|
1528
|
+
return (match.index ?? 0) + raw.length;
|
|
1529
|
+
}
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
if (!/\/\s*>$/.test(raw)) {
|
|
1533
|
+
depth += 1;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
return undefined;
|
|
1537
|
+
}
|
|
1538
|
+
function isVoidHtmlElement(tag) {
|
|
1539
|
+
return /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/i.test(tag);
|
|
1540
|
+
}
|
|
1541
|
+
function extractElementLabel(content, tagInfo) {
|
|
1542
|
+
if (tagInfo.selfClosing || tagInfo.originalTag.includes('.')) {
|
|
1543
|
+
return undefined;
|
|
1544
|
+
}
|
|
1545
|
+
const closeTag = `</${tagInfo.originalTag}>`;
|
|
1546
|
+
const closeIndex = content.indexOf(closeTag, tagInfo.end + 1);
|
|
1547
|
+
if (closeIndex < 0 || closeIndex - tagInfo.end > 1800) {
|
|
1548
|
+
return undefined;
|
|
1549
|
+
}
|
|
1550
|
+
const label = stripTags(content.slice(tagInfo.end + 1, closeIndex))
|
|
1551
|
+
.replace(/\{[\s\S]*?\}/g, ' ')
|
|
1552
|
+
.trim()
|
|
1553
|
+
.replace(/\s+/g, ' ');
|
|
1554
|
+
return label ? (0, utils_1.truncate)(label, 120) : undefined;
|
|
1555
|
+
}
|
|
1556
|
+
function readClassSource(attrs) {
|
|
1557
|
+
const classNames = ['className', 'class', 'class:list', 'classList', ':class', 'ngClass'];
|
|
1558
|
+
const sources = [];
|
|
1559
|
+
for (const className of classNames) {
|
|
1560
|
+
const source = readAttrSource(attrs, className);
|
|
1561
|
+
if (source) {
|
|
1562
|
+
sources.push(source);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return sources.length ? sources.join(' ') : undefined;
|
|
1566
|
+
}
|
|
1567
|
+
function readUiProps(attrs) {
|
|
1568
|
+
const props = {};
|
|
1569
|
+
const names = [
|
|
1570
|
+
'variant',
|
|
1571
|
+
'size',
|
|
1572
|
+
'intent',
|
|
1573
|
+
'tone',
|
|
1574
|
+
'color',
|
|
1575
|
+
'appearance',
|
|
1576
|
+
'type',
|
|
1577
|
+
'role',
|
|
1578
|
+
'href',
|
|
1579
|
+
'disabled',
|
|
1580
|
+
'checked',
|
|
1581
|
+
'aria-label',
|
|
1582
|
+
'data-slot',
|
|
1583
|
+
'asChild',
|
|
1584
|
+
];
|
|
1585
|
+
for (const name of names) {
|
|
1586
|
+
const value = (0, utils_1.readAttr)(attrs, name) ?? readAttrSource(attrs, name);
|
|
1587
|
+
if (value) {
|
|
1588
|
+
props[name] = (0, utils_1.truncate)(cleanupAttrValue(value), 160);
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (hasBooleanAttr(attrs, name)) {
|
|
1592
|
+
props[name] = 'true';
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return props;
|
|
1596
|
+
}
|
|
1597
|
+
function readVariantProps(props) {
|
|
1598
|
+
const variantProps = {};
|
|
1599
|
+
for (const name of ['variant', 'size', 'intent', 'tone', 'color', 'appearance', 'type']) {
|
|
1600
|
+
const value = props[name];
|
|
1601
|
+
if (value) {
|
|
1602
|
+
variantProps[name] = value;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
return variantProps;
|
|
1606
|
+
}
|
|
1607
|
+
function readAttrSource(attrs, name) {
|
|
1608
|
+
const pattern = new RegExp(`(?:^|\\s)${(0, utils_1.escapeRegExp)(name)}\\s*=`, 'g');
|
|
1609
|
+
const match = pattern.exec(attrs);
|
|
1610
|
+
if (!match) {
|
|
1611
|
+
return undefined;
|
|
1612
|
+
}
|
|
1613
|
+
return readAttrValueSource(attrs, match.index + match[0].length);
|
|
1614
|
+
}
|
|
1615
|
+
function readAttrValueSource(attrs, index) {
|
|
1616
|
+
const cursor = skipWhitespace(attrs, index);
|
|
1617
|
+
const char = attrs[cursor];
|
|
1618
|
+
if (!char) {
|
|
1619
|
+
return undefined;
|
|
1620
|
+
}
|
|
1621
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
1622
|
+
const end = skipQuoted(attrs, cursor, char);
|
|
1623
|
+
return attrs.slice(cursor + 1, end);
|
|
1624
|
+
}
|
|
1625
|
+
if (char === '{') {
|
|
1626
|
+
return readBalancedBlock(attrs, cursor);
|
|
1627
|
+
}
|
|
1628
|
+
const match = attrs.slice(cursor).match(/^[^\s>]+/);
|
|
1629
|
+
return match?.[0];
|
|
1630
|
+
}
|
|
1631
|
+
function cleanupAttrValue(value) {
|
|
1632
|
+
let cleaned = value.trim();
|
|
1633
|
+
if (cleaned.startsWith('{') && cleaned.endsWith('}')) {
|
|
1634
|
+
cleaned = cleaned.slice(1, -1).trim();
|
|
1635
|
+
}
|
|
1636
|
+
const first = cleaned[0];
|
|
1637
|
+
const last = cleaned[cleaned.length - 1];
|
|
1638
|
+
if ((first === '"' || first === "'" || first === '`') && first === last) {
|
|
1639
|
+
cleaned = cleaned.slice(1, -1).trim();
|
|
1640
|
+
}
|
|
1641
|
+
return cleaned;
|
|
1642
|
+
}
|
|
1643
|
+
function hasBooleanAttr(attrs, name) {
|
|
1644
|
+
return new RegExp(`(?:^|\\s)${(0, utils_1.escapeRegExp)(name)}(?:\\s|$|>)`).test(attrs);
|
|
1645
|
+
}
|
|
1646
|
+
function groupTailwindClasses(classes) {
|
|
1647
|
+
const groups = {};
|
|
1648
|
+
for (const className of classes) {
|
|
1649
|
+
const variants = className.split(':');
|
|
1650
|
+
const base = variants[variants.length - 1];
|
|
1651
|
+
if (variants.length > 1) {
|
|
1652
|
+
const prefixes = variants.slice(0, -1);
|
|
1653
|
+
if (prefixes.some((prefix) => /^(sm|md|lg|xl|2xl|max-|min-)/.test(prefix))) {
|
|
1654
|
+
addClassGroup(groups, 'responsive', className);
|
|
1655
|
+
}
|
|
1656
|
+
if (prefixes.some((prefix) => /^(hover|focus|active|disabled|visited|dark|group|peer|aria|data)/.test(prefix))) {
|
|
1657
|
+
addClassGroup(groups, 'state', className);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (/^(flex|grid|block|inline|hidden|items-|justify-|content-|self-|place-|order-|col-|row-|container|d-|justify-content-|align-items-|align-self-)/.test(base)) {
|
|
1661
|
+
addClassGroup(groups, 'layout', className);
|
|
1662
|
+
}
|
|
1663
|
+
else if (/^(?:-?m[trblxy]?|-?p[trblxy]?|gap-|space-|inset-|top-|right-|bottom-|left-)/.test(base)) {
|
|
1664
|
+
addClassGroup(groups, 'spacing', className);
|
|
1665
|
+
}
|
|
1666
|
+
else if (/^(w-|h-|mw-|mh-|vw-|vh-|min-|max-|size-|aspect-|basis-|grow|shrink)/.test(base)) {
|
|
1667
|
+
addClassGroup(groups, 'sizing', className);
|
|
1668
|
+
}
|
|
1669
|
+
else if (isTypographyScaleBase(base)) {
|
|
1670
|
+
addClassGroup(groups, 'typography', className);
|
|
1671
|
+
}
|
|
1672
|
+
else if (isLayoutSafetyBase(base)) {
|
|
1673
|
+
addClassGroup(groups, 'layout-safety', className);
|
|
1674
|
+
}
|
|
1675
|
+
else if (/^(bg-|from-|via-|to-|text-|decoration-|accent-|caret-|fill-|stroke-)/.test(base)) {
|
|
1676
|
+
addClassGroup(groups, 'color', className);
|
|
1677
|
+
}
|
|
1678
|
+
else if (/^(rounded|border|divide-|outline|ring)/.test(base)) {
|
|
1679
|
+
addClassGroup(groups, 'border', className);
|
|
1680
|
+
}
|
|
1681
|
+
else if (/^(list-)/.test(base)) {
|
|
1682
|
+
addClassGroup(groups, 'typography', className);
|
|
1683
|
+
}
|
|
1684
|
+
else if (/^(shadow|opacity|blur|backdrop|mix-|bg-blend|drop-shadow|filter)/.test(base)) {
|
|
1685
|
+
addClassGroup(groups, 'effects', className);
|
|
1686
|
+
}
|
|
1687
|
+
else if (/^(absolute|relative|fixed|sticky|z-|overflow-|object-)/.test(base)) {
|
|
1688
|
+
addClassGroup(groups, 'position', className);
|
|
1689
|
+
}
|
|
1690
|
+
else if (/^(transition|duration|ease|delay|animate-|transform|scale-|rotate-|translate-)/.test(base)) {
|
|
1691
|
+
addClassGroup(groups, 'motion', className);
|
|
1692
|
+
}
|
|
1693
|
+
else {
|
|
1694
|
+
addClassGroup(groups, 'other', className);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
return Object.fromEntries(Object.entries(groups).map(([group, values]) => [group, (0, utils_1.unique)(values).slice(0, 80)]));
|
|
1698
|
+
}
|
|
1699
|
+
function addClassGroup(groups, group, className) {
|
|
1700
|
+
groups[group] = [...(groups[group] ?? []), className];
|
|
1701
|
+
}
|
|
1702
|
+
function mergeClassGroups(groups) {
|
|
1703
|
+
const merged = {};
|
|
1704
|
+
for (const group of groups) {
|
|
1705
|
+
for (const [name, values] of Object.entries(group)) {
|
|
1706
|
+
merged[name] = (0, utils_1.unique)([...(merged[name] ?? []), ...values]).slice(0, 120);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
return merged;
|
|
1710
|
+
}
|
|
1711
|
+
function mergeVariantProps(variants) {
|
|
1712
|
+
const merged = {};
|
|
1713
|
+
for (const variant of variants) {
|
|
1714
|
+
for (const [name, value] of Object.entries(variant)) {
|
|
1715
|
+
merged[name] = (0, utils_1.unique)([...(merged[name] ?? []), value]).slice(0, 40);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return merged;
|
|
1719
|
+
}
|
|
1720
|
+
function isCompositionElement(element) {
|
|
1721
|
+
return ([
|
|
1722
|
+
'section',
|
|
1723
|
+
'layout',
|
|
1724
|
+
'card',
|
|
1725
|
+
'form',
|
|
1726
|
+
'nav',
|
|
1727
|
+
'table',
|
|
1728
|
+
'heading',
|
|
1729
|
+
'badge',
|
|
1730
|
+
'button',
|
|
1731
|
+
'input',
|
|
1732
|
+
].includes(element.kind) ||
|
|
1733
|
+
Boolean(element.layoutRole) ||
|
|
1734
|
+
element.classes.some((className) => /^(?:section-frame|context-surface|brand-spectrum-card|load-in-section|reveal-up)$/.test(className.replace(/^[a-z0-9-]+:/, ''))));
|
|
1735
|
+
}
|
|
1736
|
+
function filterClasses(classes, predicate) {
|
|
1737
|
+
return (0, utils_1.unique)(classes.filter(predicate));
|
|
1738
|
+
}
|
|
1739
|
+
function isResponsiveClass(className) {
|
|
1740
|
+
return (/^(?:sm|md|lg|xl|2xl|min-[^:]+|max-[^:]+):/.test(className) ||
|
|
1741
|
+
/^(?:d|col|offset|order|m[stebxy]?|p[stebxy]?|gap|text|float|flex|justify-content|align-items|align-self)-(?:sm|md|lg|xl|xxl)-/.test(className));
|
|
1742
|
+
}
|
|
1743
|
+
function isSpacingClass(className) {
|
|
1744
|
+
const base = (0, utils_1.classBase)(className);
|
|
1745
|
+
return /^(?:-?m[trblxy]?|-?p[trblxy]?|gap-|space-|inset-|top-|right-|bottom-|left-)/.test(base);
|
|
1746
|
+
}
|
|
1747
|
+
function isLayoutClass(className) {
|
|
1748
|
+
const base = (0, utils_1.classBase)(className);
|
|
1749
|
+
return /^(?:flex|grid|block|inline|hidden|items-|justify-|content-|self-|place-|order-|col-|row-|container|d-|justify-content-|align-items-|align-self-)/.test(base);
|
|
1750
|
+
}
|
|
1751
|
+
function isSurfaceClass(className) {
|
|
1752
|
+
const base = (0, utils_1.classBase)(className);
|
|
1753
|
+
return (/^(?:bg-|border|rounded|shadow|ring|outline|divide-|opacity|backdrop|blur|drop-shadow)/.test(base) ||
|
|
1754
|
+
['section-frame', 'context-surface', 'brand-spectrum-card', 'brand-spectrum-line'].includes(base));
|
|
1755
|
+
}
|
|
1756
|
+
function isDecorativeAccentClass(className) {
|
|
1757
|
+
const base = (0, utils_1.classBase)(className);
|
|
1758
|
+
return (['brand-spectrum-card', 'brand-spectrum-line'].includes(base) ||
|
|
1759
|
+
base === 'bg-[var(--brand-spectrum-gradient)]' ||
|
|
1760
|
+
/^(?:shadow|drop-shadow)-\[.*rgba\(96,48,232/.test(base) ||
|
|
1761
|
+
/^(?:bg|border)-\[rgba\(96,48,232/.test(base));
|
|
1762
|
+
}
|
|
1763
|
+
function isTypographyClass(className) {
|
|
1764
|
+
const base = (0, utils_1.classBase)(className);
|
|
1765
|
+
return isTypographyScaleBase(base);
|
|
1766
|
+
}
|
|
1767
|
+
function isTypographyScaleBase(base) {
|
|
1768
|
+
return (/^(?:font-|leading-|tracking-|line-clamp|whitespace-|break-|truncate|text-balance|text-pretty|text-nowrap|text-wrap)/.test(base) ||
|
|
1769
|
+
/^(?:fs-|fw-|lh-|display-|lead|small|text-uppercase|text-lowercase|text-capitalize)/.test(base) ||
|
|
1770
|
+
/^text-(?:xs|sm|base|lg|xl|[2-9]xl|\[[^\]]+\])/.test(base) ||
|
|
1771
|
+
/^max-w-\[(?:\d+(?:\.\d+)?)?ch\]/.test(base));
|
|
1772
|
+
}
|
|
1773
|
+
function isLayoutSafetyBase(base) {
|
|
1774
|
+
return /^(?:overflow-|min-w-|min-h-|max-w-|max-h-|w-fit|h-fit|w-max|h-max|w-min|h-min|basis-|grow|shrink|flex-1|flex-none|self-|whitespace-|break-|truncate|line-clamp|text-ellipsis|text-nowrap|text-balance|text-pretty)/.test(base);
|
|
1775
|
+
}
|
|
1776
|
+
function isSizingClass(className) {
|
|
1777
|
+
const base = (0, utils_1.classBase)(className);
|
|
1778
|
+
return /^(?:w-|h-|mw-|mh-|vw-|vh-|min-|max-|size-|aspect-|basis-|grow|shrink|flex-1|flex-none)/.test(base);
|
|
1779
|
+
}
|
|
1780
|
+
function isVisibilityClass(className) {
|
|
1781
|
+
const base = (0, utils_1.classBase)(className);
|
|
1782
|
+
return /^(?:hidden|block|inline|inline-block|flex|inline-flex|grid|inline-grid|table|contents)$/.test(base);
|
|
1783
|
+
}
|
|
1784
|
+
function isLineHeightClass(className) {
|
|
1785
|
+
return (0, utils_1.classBase)(className).startsWith('leading-');
|
|
1786
|
+
}
|
|
1787
|
+
function isMaxWidthClass(className) {
|
|
1788
|
+
return (0, utils_1.classBase)(className).startsWith('max-w-');
|
|
1789
|
+
}
|
|
1790
|
+
function createUiLayoutSafety(classes) {
|
|
1791
|
+
const wrapClasses = filterClasses(classes, (className) => /^(?:whitespace-|break-|truncate|line-clamp|text-ellipsis|text-nowrap|text-balance|text-pretty|text-wrap)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1792
|
+
const overflowClasses = filterClasses(classes, (className) => /^(?:overflow-|line-clamp|truncate|text-ellipsis)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1793
|
+
const minSizeClasses = filterClasses(classes, (className) => /^(?:min-w-|min-h-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1794
|
+
const maxSizeClasses = filterClasses(classes, (className) => /^(?:max-w-|max-h-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1795
|
+
const fitClasses = filterClasses(classes, (className) => /^(?:w-fit|h-fit|w-max|h-max|w-min|h-min|basis-|self-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1796
|
+
const flexBehaviorClasses = filterClasses(classes, (className) => /^(?:grow|shrink|flex-1|flex-none|basis-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1797
|
+
const layoutSafetyClasses = (0, utils_1.unique)([
|
|
1798
|
+
...wrapClasses,
|
|
1799
|
+
...overflowClasses,
|
|
1800
|
+
...minSizeClasses,
|
|
1801
|
+
...maxSizeClasses,
|
|
1802
|
+
...fitClasses,
|
|
1803
|
+
...flexBehaviorClasses,
|
|
1804
|
+
]).slice(0, 80);
|
|
1805
|
+
if (!layoutSafetyClasses.length) {
|
|
1806
|
+
return undefined;
|
|
1807
|
+
}
|
|
1808
|
+
return {
|
|
1809
|
+
wrapClasses,
|
|
1810
|
+
overflowClasses,
|
|
1811
|
+
minSizeClasses,
|
|
1812
|
+
maxSizeClasses,
|
|
1813
|
+
fitClasses,
|
|
1814
|
+
flexBehaviorClasses,
|
|
1815
|
+
layoutSafetyClasses,
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
function createUiResponsiveProfile(classes) {
|
|
1819
|
+
const responsiveClasses = filterClasses(classes, isResponsiveClass).slice(0, 120);
|
|
1820
|
+
if (!responsiveClasses.length) {
|
|
1821
|
+
return undefined;
|
|
1822
|
+
}
|
|
1823
|
+
const breakpoints = {};
|
|
1824
|
+
for (const className of responsiveClasses) {
|
|
1825
|
+
const prefix = responsivePrefix(className);
|
|
1826
|
+
if (!prefix) {
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
breakpoints[prefix] = (0, utils_1.unique)([...(breakpoints[prefix] ?? []), className]).slice(0, 60);
|
|
1830
|
+
}
|
|
1831
|
+
return {
|
|
1832
|
+
breakpoints,
|
|
1833
|
+
layoutClasses: filterClasses(responsiveClasses, isLayoutClass).slice(0, 60),
|
|
1834
|
+
typographyClasses: filterClasses(responsiveClasses, isTypographyClass).slice(0, 60),
|
|
1835
|
+
spacingClasses: filterClasses(responsiveClasses, isSpacingClass).slice(0, 60),
|
|
1836
|
+
sizingClasses: filterClasses(responsiveClasses, isSizingClass).slice(0, 60),
|
|
1837
|
+
visibilityClasses: filterClasses(responsiveClasses, isVisibilityClass).slice(0, 60),
|
|
1838
|
+
stateClasses: filterClasses(responsiveClasses, isStateClass).slice(0, 60),
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
function createUiScaleProfile(classes) {
|
|
1842
|
+
const typographyClasses = filterClasses(classes, isTypographyClass).slice(0, 80);
|
|
1843
|
+
const spacingClasses = filterClasses(classes, isSpacingClass).slice(0, 80);
|
|
1844
|
+
const sizingClasses = filterClasses(classes, isSizingClass).slice(0, 80);
|
|
1845
|
+
const gapClasses = filterClasses(classes, (className) => /^(?:gap-|space-)/.test((0, utils_1.classBase)(className))).slice(0, 60);
|
|
1846
|
+
const maxWidthClasses = filterClasses(classes, isMaxWidthClass).slice(0, 60);
|
|
1847
|
+
const lineHeightClasses = filterClasses(classes, isLineHeightClass).slice(0, 60);
|
|
1848
|
+
if (!typographyClasses.length &&
|
|
1849
|
+
!spacingClasses.length &&
|
|
1850
|
+
!sizingClasses.length &&
|
|
1851
|
+
!gapClasses.length &&
|
|
1852
|
+
!maxWidthClasses.length &&
|
|
1853
|
+
!lineHeightClasses.length) {
|
|
1854
|
+
return undefined;
|
|
1855
|
+
}
|
|
1856
|
+
return {
|
|
1857
|
+
typographyClasses,
|
|
1858
|
+
spacingClasses,
|
|
1859
|
+
sizingClasses,
|
|
1860
|
+
gapClasses,
|
|
1861
|
+
maxWidthClasses,
|
|
1862
|
+
lineHeightClasses,
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
function responsivePrefix(className) {
|
|
1866
|
+
return (className.split(':').find((part) => /^(?:sm|md|lg|xl|2xl|min-|max-)/.test(part)) ??
|
|
1867
|
+
className.match(/^(?:d|col|offset|order|m[stebxy]?|p[stebxy]?|gap|text|float|flex|justify-content|align-items|align-self)-(sm|md|lg|xl|xxl)-/)?.[1]);
|
|
1868
|
+
}
|
|
1869
|
+
function groupClassExpressionsByTarget(expressions) {
|
|
1870
|
+
const grouped = {};
|
|
1871
|
+
for (const expression of expressions) {
|
|
1872
|
+
const target = expression.target ?? expression.kind;
|
|
1873
|
+
grouped[target] = [
|
|
1874
|
+
...(grouped[target] ?? []),
|
|
1875
|
+
{
|
|
1876
|
+
kind: expression.kind,
|
|
1877
|
+
line: expression.line,
|
|
1878
|
+
classes: expression.classes,
|
|
1879
|
+
defaultClasses: expression.defaultClasses,
|
|
1880
|
+
conditionalClasses: expression.conditionalClasses,
|
|
1881
|
+
decorativeAccentClasses: expression.classes.filter(isDecorativeAccentClass),
|
|
1882
|
+
classGroups: groupTailwindClasses(expression.classes),
|
|
1883
|
+
raw: (0, utils_1.truncate)(expression.raw, 600),
|
|
1884
|
+
},
|
|
1885
|
+
].slice(0, 40);
|
|
1886
|
+
}
|
|
1887
|
+
return grouped;
|
|
1888
|
+
}
|
|
1889
|
+
function buildComponentSlots(elements) {
|
|
1890
|
+
return elements
|
|
1891
|
+
.filter((element) => /^(Card|CardHeader|CardContent|CardFooter|CardTitle|CardDescription|Container|Section)$/i.test(element.originalTag))
|
|
1892
|
+
.slice(0, 80)
|
|
1893
|
+
.map((element) => ({
|
|
1894
|
+
index: element.index,
|
|
1895
|
+
tag: element.originalTag,
|
|
1896
|
+
kind: element.kind,
|
|
1897
|
+
layoutRole: element.layoutRole,
|
|
1898
|
+
line: element.line,
|
|
1899
|
+
classes: element.classes,
|
|
1900
|
+
defaultClasses: element.defaultClasses,
|
|
1901
|
+
conditionalClasses: element.conditionalClasses,
|
|
1902
|
+
decorativeAccentClasses: element.decorativeAccentClasses,
|
|
1903
|
+
classGroups: element.classGroups,
|
|
1904
|
+
parentIndex: element.parentIndex,
|
|
1905
|
+
parentTag: element.parentTag,
|
|
1906
|
+
childSummary: element.childSummary,
|
|
1907
|
+
label: element.label,
|
|
1908
|
+
}));
|
|
1909
|
+
}
|
|
1910
|
+
function createUiCompositionGuidance(file) {
|
|
1911
|
+
const classes = new Set(file.classReferences);
|
|
1912
|
+
const guidance = [];
|
|
1913
|
+
if (classes.has('sm:section-frame') && classes.has('bg-transparent')) {
|
|
1914
|
+
guidance.push('Preserve mobile-first shell behavior: transparent/unframed on mobile, section-frame at sm and above.');
|
|
1915
|
+
}
|
|
1916
|
+
if (classes.has('p-[var(--panel-padding)]') &&
|
|
1917
|
+
(classes.has('p-3') || classes.has('p-4') || classes.has('sm:p-6') || classes.has('sm:p-8'))) {
|
|
1918
|
+
guidance.push('Do not apply panel padding everywhere; card and section padding is slot-specific and responsive.');
|
|
1919
|
+
}
|
|
1920
|
+
if (classes.has('hidden') &&
|
|
1921
|
+
(classes.has('sm:block') || classes.has('md:block') || classes.has('sm:inline-flex'))) {
|
|
1922
|
+
guidance.push('Preserve responsive visibility rules; some secondary copy, cards, and CTAs are intentionally hidden on small screens.');
|
|
1923
|
+
}
|
|
1924
|
+
if (classes.has('max-w-[11ch]') ||
|
|
1925
|
+
classes.has('leading-[0.98]') ||
|
|
1926
|
+
classes.has('sm:text-balance')) {
|
|
1927
|
+
guidance.push('Hero headline typography is exact: keep narrow mobile measure, tight leading, and desktop balancing classes.');
|
|
1928
|
+
}
|
|
1929
|
+
if (file.imports.some((item) => item.includes('/ui/card'))) {
|
|
1930
|
+
guidance.push('Card is a shell primitive; use CardHeader/CardContent/CardFooter usage classes for padding and hierarchy.');
|
|
1931
|
+
}
|
|
1932
|
+
if (classes.has('section-frame') || classes.has('context-surface')) {
|
|
1933
|
+
guidance.push('Use section-frame/context-surface only for framed technical panels, not every repeated card.');
|
|
1934
|
+
}
|
|
1935
|
+
if (classes.has('section-frame')) {
|
|
1936
|
+
guidance.push('section-frame owns the clipped section-frame::before grid overlay for that surface; do not treat it as a simple border/background token.');
|
|
1937
|
+
}
|
|
1938
|
+
if (classes.has('section-frame') && classes.has('context-surface')) {
|
|
1939
|
+
guidance.push('Keep section-frame and context-surface paired for technical surfaces when child content must sit above the parent grid overlay.');
|
|
1940
|
+
}
|
|
1941
|
+
if (classes.has('sm:section-frame') || classes.has('bg-transparent')) {
|
|
1942
|
+
guidance.push('Preserve transparent mobile surfaces and only add framed behavior at the same responsive breakpoint as the source.');
|
|
1943
|
+
}
|
|
1944
|
+
if (file.classReferences.some(isDecorativeAccentClass)) {
|
|
1945
|
+
guidance.push('Treat brand-spectrum-card and purple glow shadow classes as accent/highlight-only; do not use them for default repeated cards unless the source element is explicitly current, highlighted, or featured.');
|
|
1946
|
+
}
|
|
1947
|
+
return guidance;
|
|
1948
|
+
}
|
|
1949
|
+
function createUiPatternGuidance(kind, decorativeAccentClasses) {
|
|
1950
|
+
const guidance = [];
|
|
1951
|
+
if (kind === 'card') {
|
|
1952
|
+
guidance.push('Use defaultClasses for ordinary cards; decorativeAccentClasses are opt-in highlight treatments.');
|
|
1953
|
+
}
|
|
1954
|
+
if (decorativeAccentClasses.length) {
|
|
1955
|
+
guidance.push('Do not apply decorativeAccentClasses by default. They are reserved for explicitly featured/current/highlighted states.');
|
|
1956
|
+
}
|
|
1957
|
+
return guidance;
|
|
1958
|
+
}
|
|
1959
|
+
function extractTailwindUtilities(classReferences) {
|
|
1960
|
+
return classReferences
|
|
1961
|
+
.filter((className) => /^(?:-?m[trblxy]?|-?p[trblxy]?|flex|grid|block|inline|hidden|items-|justify-|gap-|space-|w-|h-|min-|max-|size-|basis-|grow|shrink|aspect-|overflow-|object-|whitespace-|break-|truncate|line-clamp|text-balance|text-pretty|text-nowrap|rounded|border|bg-|text-|font-|leading-|tracking-|shadow|ring|opacity|transition|duration|ease|hover:|focus:|active:|disabled:|dark:|sm:|md:|lg:|xl:|2xl:|min-[^:]+:|max-[^:]+:|container|sr-only)/.test(className))
|
|
1962
|
+
.slice(0, 160);
|
|
1963
|
+
}
|
|
1964
|
+
function extractPackageDependencies(relPath, content) {
|
|
1965
|
+
const baseName = path.basename(relPath);
|
|
1966
|
+
if (baseName === 'package.json' || baseName === 'composer.json') {
|
|
1967
|
+
try {
|
|
1968
|
+
const parsed = JSON.parse(content);
|
|
1969
|
+
return (0, utils_1.unique)([
|
|
1970
|
+
...Object.keys(parsed.dependencies ?? {}),
|
|
1971
|
+
...Object.keys(parsed.devDependencies ?? {}),
|
|
1972
|
+
...Object.keys(parsed.require ?? {}),
|
|
1973
|
+
...Object.keys(parsed.requireDev ?? {}),
|
|
1974
|
+
...Object.keys(parsed['require-dev'] ?? {}),
|
|
1975
|
+
]).sort();
|
|
1976
|
+
}
|
|
1977
|
+
catch {
|
|
1978
|
+
return [];
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
if (baseName === 'requirements.txt') {
|
|
1982
|
+
return (0, utils_1.unique)(content
|
|
1983
|
+
.split('\n')
|
|
1984
|
+
.map((line) => line
|
|
1985
|
+
.trim()
|
|
1986
|
+
.replace(/\s*#.*$/, '')
|
|
1987
|
+
.split(/[<>=~!]/)[0])
|
|
1988
|
+
.filter((line) => Boolean(line) && !line.startsWith('-'))).sort();
|
|
1989
|
+
}
|
|
1990
|
+
if (baseName === 'Gemfile') {
|
|
1991
|
+
return (0, utils_1.unique)(Array.from(content.matchAll(/^\s*gem\s+['"]([^'"]+)['"]/gm)).map((match) => match[1])).sort();
|
|
1992
|
+
}
|
|
1993
|
+
if (baseName === 'go.mod') {
|
|
1994
|
+
return (0, utils_1.unique)(Array.from(content.matchAll(/^\s*(?:require\s+)?([a-zA-Z0-9_.\-/]+)\s+v\d/gm)).map((match) => match[1])).sort();
|
|
1995
|
+
}
|
|
1996
|
+
if (baseName === 'Cargo.toml' || baseName === 'pyproject.toml') {
|
|
1997
|
+
return (0, utils_1.unique)(Array.from(content.matchAll(/^\s*([A-Za-z0-9_.-]+)\s*=\s*["{]/gm)).map((match) => match[1])).sort();
|
|
1998
|
+
}
|
|
1999
|
+
return [];
|
|
2000
|
+
}
|
|
2001
|
+
function extractUiElements(content, relPath) {
|
|
2002
|
+
if (!isUiFile(relPath) &&
|
|
2003
|
+
!isTemplateFile(relPath) &&
|
|
2004
|
+
!/\.(html|htm|php|phtml|erb|twig|blade\.php)$/.test(relPath)) {
|
|
2005
|
+
return [];
|
|
2006
|
+
}
|
|
2007
|
+
const elements = [];
|
|
2008
|
+
const tags = readOpeningTags(content);
|
|
2009
|
+
for (const [index, tagInfo] of tags.entries()) {
|
|
2010
|
+
const tag = tagInfo.originalTag.toLowerCase();
|
|
2011
|
+
const attrs = tagInfo.attrs;
|
|
2012
|
+
const label = extractElementLabel(content, tagInfo);
|
|
2013
|
+
const classSource = readClassSource(attrs);
|
|
2014
|
+
const classAnalysis = classSource ? analyzeClassSource(classSource) : undefined;
|
|
2015
|
+
const styleHelper = classSource ? readPrimaryClassHelperUsage(classSource) : undefined;
|
|
2016
|
+
const classes = classAnalysis?.classes ?? [];
|
|
2017
|
+
const role = (0, utils_1.readAttr)(attrs, 'role');
|
|
2018
|
+
const props = {
|
|
2019
|
+
...readUiProps(attrs),
|
|
2020
|
+
...(styleHelper ? { styleHelper: styleHelper.name } : {}),
|
|
2021
|
+
};
|
|
2022
|
+
const variants = {
|
|
2023
|
+
...readVariantProps(props),
|
|
2024
|
+
...(styleHelper?.variantProps ?? {}),
|
|
2025
|
+
};
|
|
2026
|
+
const kind = classifyUiElement(tagInfo.originalTag, attrs, classes, role);
|
|
2027
|
+
const parentTagInfo = tagInfo.parentIndex === undefined ? undefined : tags[tagInfo.parentIndex];
|
|
2028
|
+
const parentClassSource = parentTagInfo ? readClassSource(parentTagInfo.attrs) : undefined;
|
|
2029
|
+
const parentClasses = parentClassSource ? analyzeClassSource(parentClassSource).classes : [];
|
|
2030
|
+
const parentRole = parentTagInfo ? (0, utils_1.readAttr)(parentTagInfo.attrs, 'role') : undefined;
|
|
2031
|
+
const parentKind = parentTagInfo
|
|
2032
|
+
? classifyUiElement(parentTagInfo.originalTag, parentTagInfo.attrs, parentClasses, parentRole)
|
|
2033
|
+
: undefined;
|
|
2034
|
+
const layoutRole = deriveLayoutRole(tagInfo.originalTag, kind, classes, label);
|
|
2035
|
+
const responsiveClasses = classes.filter(isResponsiveClass);
|
|
2036
|
+
const stateClasses = classes.filter(isStateClass);
|
|
2037
|
+
const layoutSafety = createUiLayoutSafety(classes);
|
|
2038
|
+
const responsiveProfile = createUiResponsiveProfile(classes);
|
|
2039
|
+
const scaleProfile = createUiScaleProfile(classes);
|
|
2040
|
+
const childSummary = summarizeDirectUiChildren(content, tags, index);
|
|
2041
|
+
const semanticProfile = createUiSemanticProfile(kind, tagInfo.originalTag, classes, childSummary);
|
|
2042
|
+
const roleSignature = createUiRoleSignature({
|
|
2043
|
+
kind,
|
|
2044
|
+
tag: tagInfo.originalTag,
|
|
2045
|
+
classes,
|
|
2046
|
+
children: childSummary,
|
|
2047
|
+
label,
|
|
2048
|
+
layoutRole,
|
|
2049
|
+
parentKind,
|
|
2050
|
+
parentClasses,
|
|
2051
|
+
});
|
|
2052
|
+
if (kind === 'unknown') {
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
elements.push({
|
|
2056
|
+
index,
|
|
2057
|
+
kind,
|
|
2058
|
+
tag,
|
|
2059
|
+
originalTag: tagInfo.originalTag,
|
|
2060
|
+
line: (0, utils_1.lineNumberAt)(content, tagInfo.start),
|
|
2061
|
+
...(tagInfo.depth ? { depth: tagInfo.depth } : {}),
|
|
2062
|
+
...(tagInfo.parentIndex !== undefined ? { parentIndex: tagInfo.parentIndex } : {}),
|
|
2063
|
+
...(parentKind && parentKind !== 'unknown' ? { parentKind } : {}),
|
|
2064
|
+
...(parentTagInfo ? { parentTag: parentTagInfo.originalTag } : {}),
|
|
2065
|
+
...(parentClasses.length ? { parentClasses: parentClasses.slice(0, 40) } : {}),
|
|
2066
|
+
...(parentClasses.length ? { parentClassGroups: groupTailwindClasses(parentClasses) } : {}),
|
|
2067
|
+
...(layoutRole ? { layoutRole } : {}),
|
|
2068
|
+
...(label ? { label } : {}),
|
|
2069
|
+
classes,
|
|
2070
|
+
...(classAnalysis?.defaultClasses.length
|
|
2071
|
+
? { defaultClasses: classAnalysis.defaultClasses.slice(0, 80) }
|
|
2072
|
+
: {}),
|
|
2073
|
+
...(classAnalysis?.conditionalClasses.length
|
|
2074
|
+
? { conditionalClasses: classAnalysis.conditionalClasses.slice(0, 80) }
|
|
2075
|
+
: {}),
|
|
2076
|
+
...(classAnalysis?.decorativeAccentClasses.length
|
|
2077
|
+
? { decorativeAccentClasses: classAnalysis.decorativeAccentClasses.slice(0, 40) }
|
|
2078
|
+
: {}),
|
|
2079
|
+
...(classSource ? { classSource: (0, utils_1.truncate)(classSource, 700) } : {}),
|
|
2080
|
+
classGroups: groupTailwindClasses(classes),
|
|
2081
|
+
...(responsiveClasses.length ? { responsiveClasses } : {}),
|
|
2082
|
+
...(stateClasses.length ? { stateClasses } : {}),
|
|
2083
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
2084
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
2085
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
2086
|
+
...(semanticProfile ? { semanticProfile } : {}),
|
|
2087
|
+
...(roleSignature ? { roleSignature } : {}),
|
|
2088
|
+
...(childSummary.length ? { childSummary } : {}),
|
|
2089
|
+
...((0, utils_1.readAttr)(attrs, 'id') ? { id: (0, utils_1.readAttr)(attrs, 'id') } : {}),
|
|
2090
|
+
...(role ? { role } : {}),
|
|
2091
|
+
...((0, utils_1.readAttr)(attrs, 'type') ? { type: (0, utils_1.readAttr)(attrs, 'type') } : {}),
|
|
2092
|
+
...((0, utils_1.readAttr)(attrs, 'href') ? { href: (0, utils_1.readAttr)(attrs, 'href') } : {}),
|
|
2093
|
+
...((0, utils_1.readAttr)(attrs, 'action') ? { action: (0, utils_1.readAttr)(attrs, 'action') } : {}),
|
|
2094
|
+
...((0, utils_1.readAttr)(attrs, 'method') ? { method: (0, utils_1.readAttr)(attrs, 'method')?.toUpperCase() } : {}),
|
|
2095
|
+
...(Object.keys(props).length ? { props } : {}),
|
|
2096
|
+
...(Object.keys(variants).length ? { variants } : {}),
|
|
2097
|
+
...(styleHelper ? { styleHelper: styleHelper.name } : {}),
|
|
2098
|
+
...(styleHelper && Object.keys(styleHelper.variantProps).length
|
|
2099
|
+
? { styleHelperVariants: styleHelper.variantProps }
|
|
2100
|
+
: {}),
|
|
2101
|
+
evidence: `<${tagInfo.originalTag}${classes.length ? ` class="${classes.slice(0, 6).join(' ')}"` : ''}>`,
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
return elements.slice(0, 240);
|
|
2105
|
+
}
|
|
2106
|
+
function summarizeDirectUiChildren(content, tags, parentIndex) {
|
|
2107
|
+
return tags
|
|
2108
|
+
.map((tagInfo, index) => ({ tagInfo, index }))
|
|
2109
|
+
.filter(({ tagInfo }) => tagInfo.parentIndex === parentIndex)
|
|
2110
|
+
.map(({ tagInfo, index }) => {
|
|
2111
|
+
const classSource = readClassSource(tagInfo.attrs);
|
|
2112
|
+
const classAnalysis = classSource ? analyzeClassSource(classSource) : undefined;
|
|
2113
|
+
const styleHelper = classSource ? readPrimaryClassHelperUsage(classSource) : undefined;
|
|
2114
|
+
const classes = classAnalysis?.classes ?? [];
|
|
2115
|
+
const role = (0, utils_1.readAttr)(tagInfo.attrs, 'role');
|
|
2116
|
+
const kind = classifyUiElement(tagInfo.originalTag, tagInfo.attrs, classes, role);
|
|
2117
|
+
const label = extractElementLabel(content, tagInfo);
|
|
2118
|
+
const layoutRole = deriveLayoutRole(tagInfo.originalTag, kind, classes, label);
|
|
2119
|
+
const layoutSafety = createUiLayoutSafety(classes);
|
|
2120
|
+
const responsiveProfile = createUiResponsiveProfile(classes);
|
|
2121
|
+
const scaleProfile = createUiScaleProfile(classes);
|
|
2122
|
+
const semanticRole = deriveUiChildSemanticRole(tagInfo.originalTag, kind, classes, label);
|
|
2123
|
+
const roleSignature = createUiRoleSignature({
|
|
2124
|
+
kind,
|
|
2125
|
+
tag: tagInfo.originalTag,
|
|
2126
|
+
classes,
|
|
2127
|
+
label,
|
|
2128
|
+
layoutRole,
|
|
2129
|
+
});
|
|
2130
|
+
const props = {
|
|
2131
|
+
...readUiProps(tagInfo.attrs),
|
|
2132
|
+
...(styleHelper ? { styleHelper: styleHelper.name } : {}),
|
|
2133
|
+
};
|
|
2134
|
+
const variants = {
|
|
2135
|
+
...readVariantProps(props),
|
|
2136
|
+
...(styleHelper?.variantProps ?? {}),
|
|
2137
|
+
};
|
|
2138
|
+
return {
|
|
2139
|
+
index,
|
|
2140
|
+
kind,
|
|
2141
|
+
tag: tagInfo.originalTag.toLowerCase(),
|
|
2142
|
+
originalTag: tagInfo.originalTag,
|
|
2143
|
+
...(layoutRole ? { layoutRole } : {}),
|
|
2144
|
+
classes: classes.slice(0, 32),
|
|
2145
|
+
...(classAnalysis?.defaultClasses.length
|
|
2146
|
+
? { defaultClasses: classAnalysis.defaultClasses.slice(0, 32) }
|
|
2147
|
+
: {}),
|
|
2148
|
+
...(classAnalysis?.conditionalClasses.length
|
|
2149
|
+
? { conditionalClasses: classAnalysis.conditionalClasses.slice(0, 32) }
|
|
2150
|
+
: {}),
|
|
2151
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
2152
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
2153
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
2154
|
+
...(semanticRole ? { semanticRole } : {}),
|
|
2155
|
+
...(roleSignature ? { roleSignature } : {}),
|
|
2156
|
+
...(Object.keys(props).length ? { props } : {}),
|
|
2157
|
+
...(Object.keys(variants).length ? { variants } : {}),
|
|
2158
|
+
...(styleHelper ? { styleHelper: styleHelper.name } : {}),
|
|
2159
|
+
...(styleHelper && Object.keys(styleHelper.variantProps).length
|
|
2160
|
+
? { styleHelperVariants: styleHelper.variantProps }
|
|
2161
|
+
: {}),
|
|
2162
|
+
...(label ? { label } : {}),
|
|
2163
|
+
};
|
|
2164
|
+
})
|
|
2165
|
+
.filter((child) => child.kind !== 'unknown' ||
|
|
2166
|
+
child.classes.length ||
|
|
2167
|
+
child.label ||
|
|
2168
|
+
Boolean(child.semanticRole))
|
|
2169
|
+
.slice(0, 16);
|
|
2170
|
+
}
|
|
2171
|
+
function createUiSemanticProfile(kind, tag, classes, children) {
|
|
2172
|
+
const roles = (0, utils_1.unique)(children.flatMap((child) => child.semanticRole ?? [])).slice(0, 24);
|
|
2173
|
+
const textStyleSignatures = (0, utils_1.unique)(children
|
|
2174
|
+
.filter((child) => ['text', 'heading', 'badge'].includes(child.kind) || child.semanticRole)
|
|
2175
|
+
.map((child) => JSON.stringify(groupTailwindClasses(child.defaultClasses?.length ? child.defaultClasses : child.classes)))).filter(Boolean);
|
|
2176
|
+
const hasIcon = roles.includes('icon') || roles.includes('component-icon') || roles.includes('image');
|
|
2177
|
+
const hasDivider = roles.includes('divider');
|
|
2178
|
+
const hasImage = roles.includes('image');
|
|
2179
|
+
const hasMultipleTextStyles = textStyleSignatures.length > 1;
|
|
2180
|
+
const isCompound = children.length > 1 &&
|
|
2181
|
+
(hasIcon ||
|
|
2182
|
+
hasDivider ||
|
|
2183
|
+
hasMultipleTextStyles ||
|
|
2184
|
+
roles.includes('primary-text') ||
|
|
2185
|
+
roles.includes('secondary-text'));
|
|
2186
|
+
if (!isCompound && !roles.length) {
|
|
2187
|
+
return undefined;
|
|
2188
|
+
}
|
|
2189
|
+
return {
|
|
2190
|
+
roles,
|
|
2191
|
+
isCompound,
|
|
2192
|
+
hasIcon,
|
|
2193
|
+
hasDivider,
|
|
2194
|
+
hasImage,
|
|
2195
|
+
hasMultipleTextStyles,
|
|
2196
|
+
textStyleCount: textStyleSignatures.length,
|
|
2197
|
+
childCount: children.length,
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
function createUiRoleSignature(input) {
|
|
2201
|
+
const { kind, tag, classes } = input;
|
|
2202
|
+
const children = input.children ?? [];
|
|
2203
|
+
const tagName = tag.toLowerCase();
|
|
2204
|
+
const bases = classes.map(utils_1.classBase);
|
|
2205
|
+
const baseSet = new Set(bases);
|
|
2206
|
+
const childRoles = (0, utils_1.unique)(children.flatMap((child) => [child.semanticRole, child.roleSignature?.role].filter((value) => typeof value === 'string' && Boolean(value)))).slice(0, 24);
|
|
2207
|
+
const childKinds = (0, utils_1.unique)(children.map((child) => child.kind).filter(Boolean)).slice(0, 24);
|
|
2208
|
+
const hasIcon = childRoles.some((role) => /(?:icon|image|logo|mark)/.test(role));
|
|
2209
|
+
const hasDivider = childRoles.includes('divider');
|
|
2210
|
+
const hasMultipleTextSegments = childRoles.filter((role) => /(?:primary|secondary|text|segment)/.test(role)).length > 1;
|
|
2211
|
+
const exactClassFacts = exactRoleClassFacts(classes);
|
|
2212
|
+
const flags = new Set();
|
|
2213
|
+
const layout = createRoleLayoutKey(kind, tagName, classes, children, input.parentClasses ?? []);
|
|
2214
|
+
const scale = createRoleScaleKey(classes, children);
|
|
2215
|
+
const density = createRoleDensityKey(classes, children);
|
|
2216
|
+
const surface = createRoleSurfaceKey(classes);
|
|
2217
|
+
let roleGroup = kind;
|
|
2218
|
+
let role = `${kind}-${input.layoutRole ?? 'element'}`;
|
|
2219
|
+
if (input.layoutRole) {
|
|
2220
|
+
flags.add(`layout-role:${input.layoutRole}`);
|
|
2221
|
+
}
|
|
2222
|
+
if (hasIcon)
|
|
2223
|
+
flags.add('has-icon');
|
|
2224
|
+
if (hasDivider)
|
|
2225
|
+
flags.add('has-divider');
|
|
2226
|
+
if (hasMultipleTextSegments)
|
|
2227
|
+
flags.add('multiple-text-segments');
|
|
2228
|
+
if (baseSet.has('uppercase'))
|
|
2229
|
+
flags.add('uppercase');
|
|
2230
|
+
if (classes.some((className) => className.includes('hover:')))
|
|
2231
|
+
flags.add('has-hover-state');
|
|
2232
|
+
if (baseSet.has('group'))
|
|
2233
|
+
flags.add('group-context');
|
|
2234
|
+
if (classes.some((className) => (0, utils_1.classBase)(className).startsWith('backdrop-blur'))) {
|
|
2235
|
+
flags.add('backdrop-blur');
|
|
2236
|
+
}
|
|
2237
|
+
if (kind === 'section') {
|
|
2238
|
+
roleGroup = 'section';
|
|
2239
|
+
if (baseSet.has('min-h-screen')) {
|
|
2240
|
+
role = 'hero-section-fullscreen';
|
|
2241
|
+
flags.add('full-screen-hero');
|
|
2242
|
+
}
|
|
2243
|
+
else if (layout === 'split-media-layout') {
|
|
2244
|
+
role = 'split-media-section';
|
|
2245
|
+
flags.add('split-media');
|
|
2246
|
+
}
|
|
2247
|
+
else if (layout === 'card-grid-layout') {
|
|
2248
|
+
role = 'card-grid-section';
|
|
2249
|
+
flags.add('card-grid');
|
|
2250
|
+
}
|
|
2251
|
+
else {
|
|
2252
|
+
role = 'section-shell';
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
if (kind === 'layout') {
|
|
2256
|
+
roleGroup = 'layout';
|
|
2257
|
+
if (layout === 'split-media-layout') {
|
|
2258
|
+
role = 'split-media-layout';
|
|
2259
|
+
flags.add('split-media');
|
|
2260
|
+
}
|
|
2261
|
+
else if (layout === 'card-grid-layout') {
|
|
2262
|
+
role = 'card-grid-layout';
|
|
2263
|
+
flags.add('card-grid');
|
|
2264
|
+
}
|
|
2265
|
+
else if (layout === 'hero-content-layout') {
|
|
2266
|
+
role = 'hero-content-layout';
|
|
2267
|
+
flags.add('hero-content');
|
|
2268
|
+
}
|
|
2269
|
+
else {
|
|
2270
|
+
role = `layout-${layout}`;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
if (kind === 'heading') {
|
|
2274
|
+
roleGroup = tagName === 'h1' ? 'hero-heading' : 'heading';
|
|
2275
|
+
if (tagName === 'h1') {
|
|
2276
|
+
if (isOversizedHeadingScale(classes)) {
|
|
2277
|
+
role = 'hero-heading-oversized';
|
|
2278
|
+
flags.add('oversized-heading');
|
|
2279
|
+
}
|
|
2280
|
+
else if (isHeroHeadingScale(classes)) {
|
|
2281
|
+
role = 'hero-heading-standard';
|
|
2282
|
+
flags.add('hero-heading');
|
|
2283
|
+
}
|
|
2284
|
+
else {
|
|
2285
|
+
role = 'page-heading';
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
else if (isMetricValueScale(classes)) {
|
|
2289
|
+
role = 'metric-heading';
|
|
2290
|
+
flags.add('metric-value');
|
|
2291
|
+
}
|
|
2292
|
+
else {
|
|
2293
|
+
role = 'section-heading';
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
if (kind === 'badge') {
|
|
2297
|
+
roleGroup = 'badge';
|
|
2298
|
+
if (isOverlayPill(classes)) {
|
|
2299
|
+
role = 'overlay-disclaimer-pill';
|
|
2300
|
+
flags.add('overlay-pill');
|
|
2301
|
+
}
|
|
2302
|
+
else if (hasIcon || hasDivider || hasMultipleTextSegments) {
|
|
2303
|
+
role = 'brand-lockup-badge';
|
|
2304
|
+
flags.add('compound-badge');
|
|
2305
|
+
}
|
|
2306
|
+
else if (isCompactHeroEyebrow(classes)) {
|
|
2307
|
+
role = 'hero-eyebrow-compact';
|
|
2308
|
+
flags.add('compact-eyebrow');
|
|
2309
|
+
}
|
|
2310
|
+
else {
|
|
2311
|
+
role = 'badge-pill';
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
if (kind === 'card') {
|
|
2315
|
+
roleGroup = 'surface';
|
|
2316
|
+
if (isCompactStatCard(classes, childRoles)) {
|
|
2317
|
+
role = 'hero-stat-card-compact';
|
|
2318
|
+
flags.add('compact-stat-card');
|
|
2319
|
+
}
|
|
2320
|
+
else if (isInteractiveServiceCard(classes)) {
|
|
2321
|
+
role = 'interactive-service-card';
|
|
2322
|
+
flags.add('interactive-card');
|
|
2323
|
+
}
|
|
2324
|
+
else if (isStaticArticleCard(classes)) {
|
|
2325
|
+
role = 'static-article-card';
|
|
2326
|
+
flags.add('static-article-surface');
|
|
2327
|
+
}
|
|
2328
|
+
else if (isMediaPanelCard(classes, childKinds, childRoles)) {
|
|
2329
|
+
role = 'media-panel-card';
|
|
2330
|
+
flags.add('media-panel');
|
|
2331
|
+
}
|
|
2332
|
+
else {
|
|
2333
|
+
role = 'surface-card';
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
if (kind === 'text') {
|
|
2337
|
+
roleGroup = 'text';
|
|
2338
|
+
if (isCompactStatValue(classes)) {
|
|
2339
|
+
role = 'compact-stat-value';
|
|
2340
|
+
flags.add('compact-stat-value');
|
|
2341
|
+
}
|
|
2342
|
+
else if (isLargeMetricValue(classes)) {
|
|
2343
|
+
role = 'large-metric-value';
|
|
2344
|
+
flags.add('large-metric-value');
|
|
2345
|
+
}
|
|
2346
|
+
else if (isOverlayPill(classes)) {
|
|
2347
|
+
role = 'overlay-disclaimer-text';
|
|
2348
|
+
flags.add('overlay-pill');
|
|
2349
|
+
}
|
|
2350
|
+
else {
|
|
2351
|
+
role = 'body-copy';
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
if (kind === 'button') {
|
|
2355
|
+
roleGroup = 'action';
|
|
2356
|
+
if (isQuietAction(classes)) {
|
|
2357
|
+
role = 'quiet-action';
|
|
2358
|
+
flags.add('low-emphasis-action');
|
|
2359
|
+
}
|
|
2360
|
+
else if (isCompactAction(classes)) {
|
|
2361
|
+
role = 'compact-action-button';
|
|
2362
|
+
flags.add('compact-action');
|
|
2363
|
+
}
|
|
2364
|
+
else {
|
|
2365
|
+
role = 'action-button';
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
if (!classes.length && !childRoles.length && role === `${kind}-${input.layoutRole ?? 'element'}`) {
|
|
2369
|
+
return undefined;
|
|
2370
|
+
}
|
|
2371
|
+
return {
|
|
2372
|
+
role,
|
|
2373
|
+
roleGroup,
|
|
2374
|
+
scale,
|
|
2375
|
+
density,
|
|
2376
|
+
surface,
|
|
2377
|
+
layout,
|
|
2378
|
+
flags: Array.from(flags).sort(),
|
|
2379
|
+
exactClassFacts,
|
|
2380
|
+
...(childRoles.length ? { childRoles } : {}),
|
|
2381
|
+
...(childKinds.length ? { childKinds } : {}),
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
function exactRoleClassFacts(classes) {
|
|
2385
|
+
return (0, utils_1.unique)(classes.filter((className) => {
|
|
2386
|
+
const base = (0, utils_1.classBase)(className);
|
|
2387
|
+
return (isTypographyClass(className) ||
|
|
2388
|
+
isSpacingClass(className) ||
|
|
2389
|
+
isSizingClass(className) ||
|
|
2390
|
+
isLayoutClass(className) ||
|
|
2391
|
+
isStateClass(className) ||
|
|
2392
|
+
/^(?:bg-|text-|border|rounded|shadow|backdrop|opacity|ring|outline|z-|absolute|relative|fixed|sticky|top-|right-|bottom-|left-|overflow-|object-|aspect-|group|transition|duration|ease)/.test(base));
|
|
2393
|
+
})).slice(0, 120);
|
|
2394
|
+
}
|
|
2395
|
+
function createRoleScaleKey(classes, children = []) {
|
|
2396
|
+
const scaleClasses = (0, utils_1.unique)([
|
|
2397
|
+
...classes,
|
|
2398
|
+
...children.flatMap((child) => child.classes.slice(0, 24)),
|
|
2399
|
+
].filter((className) => {
|
|
2400
|
+
const base = (0, utils_1.classBase)(className);
|
|
2401
|
+
return (isTypographyClass(className) ||
|
|
2402
|
+
isSizingClass(className) ||
|
|
2403
|
+
/^(?:max-w-|min-w-|leading-|tracking-|text-|font-|h-|w-|min-h-|max-h-)/.test(base));
|
|
2404
|
+
}))
|
|
2405
|
+
.sort(roleClassSort)
|
|
2406
|
+
.slice(0, 40);
|
|
2407
|
+
return scaleClasses.length ? scaleClasses.join(' ') : 'scale-unspecified';
|
|
2408
|
+
}
|
|
2409
|
+
function createRoleDensityKey(classes, children = []) {
|
|
2410
|
+
const densityClasses = (0, utils_1.unique)([
|
|
2411
|
+
...classes,
|
|
2412
|
+
...children.flatMap((child) => child.classes.slice(0, 16)),
|
|
2413
|
+
].filter((className) => {
|
|
2414
|
+
const base = (0, utils_1.classBase)(className);
|
|
2415
|
+
return /^(?:p[trblxy]?|m[trblxy]?|gap-|space-|h-|min-h-|max-h-|w-|min-w-|max-w-)/.test(base);
|
|
2416
|
+
}))
|
|
2417
|
+
.sort(roleClassSort)
|
|
2418
|
+
.slice(0, 40);
|
|
2419
|
+
if (!densityClasses.length) {
|
|
2420
|
+
return 'density-unspecified';
|
|
2421
|
+
}
|
|
2422
|
+
if (densityClasses.some((className) => /(?:^|:)py-(?:1|1\.5|2)$/.test(className)) ||
|
|
2423
|
+
densityClasses.some((className) => /(?:^|:)h-(?:8|9|10|11|12)$/.test(className))) {
|
|
2424
|
+
return `compact:${densityClasses.join(' ')}`;
|
|
2425
|
+
}
|
|
2426
|
+
if (densityClasses.some((className) => /(?:^|:)p-(?:8|10|12|16|20|24)$/.test(className)) ||
|
|
2427
|
+
densityClasses.some((className) => /(?:^|:)py-(?:8|10|12|16|20|24)$/.test(className))) {
|
|
2428
|
+
return `spacious:${densityClasses.join(' ')}`;
|
|
2429
|
+
}
|
|
2430
|
+
return densityClasses.join(' ');
|
|
2431
|
+
}
|
|
2432
|
+
function createRoleSurfaceKey(classes) {
|
|
2433
|
+
const surfaceClasses = (0, utils_1.unique)(classes.filter((className) => {
|
|
2434
|
+
const base = (0, utils_1.classBase)(className);
|
|
2435
|
+
return /^(?:bg-|border|rounded|shadow|ring|outline|backdrop|opacity|group|transition|hover:border|hover:bg)/.test(base);
|
|
2436
|
+
}))
|
|
2437
|
+
.sort(roleClassSort)
|
|
2438
|
+
.slice(0, 40);
|
|
2439
|
+
return surfaceClasses.length ? surfaceClasses.join(' ') : 'surface-unspecified';
|
|
2440
|
+
}
|
|
2441
|
+
function createRoleLayoutKey(kind, tagName, classes, children = [], parentClasses = []) {
|
|
2442
|
+
const bases = classes.map(utils_1.classBase);
|
|
2443
|
+
const baseSet = new Set(bases);
|
|
2444
|
+
const childKinds = new Set(children.map((child) => child.kind));
|
|
2445
|
+
const childRoles = new Set(children.flatMap((child) => [child.semanticRole, child.roleSignature?.role].filter(Boolean)));
|
|
2446
|
+
const childClassBases = children.flatMap((child) => child.classes.map(utils_1.classBase));
|
|
2447
|
+
const hasMediaChild = childKinds.has('image') ||
|
|
2448
|
+
childRoles.has('image') ||
|
|
2449
|
+
childRoles.has('media-panel-card') ||
|
|
2450
|
+
childClassBases.some((base) => /^(?:aspect-|object-|h-\[|min-h-\[)/.test(base));
|
|
2451
|
+
const hasCardChildren = children.filter((child) => child.kind === 'card').length >= 2;
|
|
2452
|
+
const hasTextChildren = children.some((child) => ['heading', 'text', 'badge'].includes(child.kind) ||
|
|
2453
|
+
(child.kind === 'layout' &&
|
|
2454
|
+
child.classes.some((className) => /^(?:max-w-|space-y-|prose|flex|grid)/.test((0, utils_1.classBase)(className)))));
|
|
2455
|
+
const parentBaseSet = new Set(parentClasses.map(utils_1.classBase));
|
|
2456
|
+
if (baseSet.has('min-h-screen')) {
|
|
2457
|
+
return 'full-screen-hero-layout';
|
|
2458
|
+
}
|
|
2459
|
+
if (baseSet.has('absolute') &&
|
|
2460
|
+
(baseSet.has('bottom-3') || baseSet.has('bottom-4')) &&
|
|
2461
|
+
(baseSet.has('right-3') || baseSet.has('right-4'))) {
|
|
2462
|
+
return 'absolute-bottom-right-overlay';
|
|
2463
|
+
}
|
|
2464
|
+
if ((baseSet.has('grid') || parentBaseSet.has('grid')) &&
|
|
2465
|
+
hasMediaChild &&
|
|
2466
|
+
hasTextChildren &&
|
|
2467
|
+
classes.some((className) => /(?:^|:)grid-cols-|(?:^|:)lg:grid-cols-/.test(className))) {
|
|
2468
|
+
return 'split-media-layout';
|
|
2469
|
+
}
|
|
2470
|
+
if ((baseSet.has('grid') || parentBaseSet.has('grid')) && hasCardChildren) {
|
|
2471
|
+
return 'card-grid-layout';
|
|
2472
|
+
}
|
|
2473
|
+
if (tagName === 'main' || (kind === 'layout' && baseSet.has('mx-auto'))) {
|
|
2474
|
+
return 'page-content-layout';
|
|
2475
|
+
}
|
|
2476
|
+
if (baseSet.has('flex') || baseSet.has('inline-flex')) {
|
|
2477
|
+
return 'flex-layout';
|
|
2478
|
+
}
|
|
2479
|
+
if (baseSet.has('grid')) {
|
|
2480
|
+
return 'grid-layout';
|
|
2481
|
+
}
|
|
2482
|
+
return 'layout-unspecified';
|
|
2483
|
+
}
|
|
2484
|
+
function isCompactHeroEyebrow(classes) {
|
|
2485
|
+
const bases = classes.map(utils_1.classBase);
|
|
2486
|
+
const baseSet = new Set(bases);
|
|
2487
|
+
return (baseSet.has('inline-flex') &&
|
|
2488
|
+
baseSet.has('rounded-full') &&
|
|
2489
|
+
baseSet.has('uppercase') &&
|
|
2490
|
+
classes.some((className) => /(?:^|:)text-xs$/.test(className)) &&
|
|
2491
|
+
classes.some((className) => /(?:^|:)sm:text-sm$/.test(className)) &&
|
|
2492
|
+
bases.some((base) => /^py-(?:1\.5|2)$/.test(base)) &&
|
|
2493
|
+
bases.some((base) => /^px-(?:3|4|5)$/.test(base)));
|
|
2494
|
+
}
|
|
2495
|
+
function isOverlayPill(classes) {
|
|
2496
|
+
const bases = classes.map(utils_1.classBase);
|
|
2497
|
+
const baseSet = new Set(bases);
|
|
2498
|
+
return (baseSet.has('absolute') &&
|
|
2499
|
+
baseSet.has('rounded-full') &&
|
|
2500
|
+
bases.some((base) => /^bottom-/.test(base)) &&
|
|
2501
|
+
bases.some((base) => /^right-/.test(base)) &&
|
|
2502
|
+
(bases.some((base) => /^text-\[(?:10|11)px\]$/.test(base)) ||
|
|
2503
|
+
classes.some((className) => /(?:^|:)sm:text-\[(?:10|11)px\]$/.test(className))));
|
|
2504
|
+
}
|
|
2505
|
+
function isHeroHeadingScale(classes) {
|
|
2506
|
+
const classSet = new Set(classes);
|
|
2507
|
+
const bases = classes.map(utils_1.classBase);
|
|
2508
|
+
return (classSet.has('text-4xl') &&
|
|
2509
|
+
classSet.has('sm:text-6xl') &&
|
|
2510
|
+
classSet.has('lg:text-7xl') &&
|
|
2511
|
+
bases.some((base) => /^leading-/.test(base)));
|
|
2512
|
+
}
|
|
2513
|
+
function isOversizedHeadingScale(classes) {
|
|
2514
|
+
const classSet = new Set(classes);
|
|
2515
|
+
return (classSet.has('text-6xl') ||
|
|
2516
|
+
classSet.has('sm:text-7xl') ||
|
|
2517
|
+
classSet.has('lg:text-8xl') ||
|
|
2518
|
+
classSet.has('xl:text-9xl') ||
|
|
2519
|
+
classes.some((className) => /(?:^|:)text-\[(?:8|9|10)\dpx\]/.test(className)));
|
|
2520
|
+
}
|
|
2521
|
+
function isMetricValueScale(classes) {
|
|
2522
|
+
return classes.some((className) => /(?:^|:)text-(?:lg|xl|2xl|3xl|4xl)$/.test(className));
|
|
2523
|
+
}
|
|
2524
|
+
function isCompactStatValue(classes) {
|
|
2525
|
+
const classSet = new Set(classes);
|
|
2526
|
+
const bases = classes.map(utils_1.classBase);
|
|
2527
|
+
return (classSet.has('text-lg') &&
|
|
2528
|
+
bases.some((base) => /^font-(?:bold|extrabold|black)$/.test(base)) &&
|
|
2529
|
+
bases.some((base) => /^text-\[|^text-(?:brand|accent|yellow|amber|gold)/.test(base)));
|
|
2530
|
+
}
|
|
2531
|
+
function isLargeMetricValue(classes) {
|
|
2532
|
+
const classSet = new Set(classes);
|
|
2533
|
+
return (classSet.has('text-2xl') ||
|
|
2534
|
+
classSet.has('sm:text-3xl') ||
|
|
2535
|
+
classSet.has('text-3xl') ||
|
|
2536
|
+
classSet.has('text-4xl'));
|
|
2537
|
+
}
|
|
2538
|
+
function isCompactStatCard(classes, childRoles) {
|
|
2539
|
+
const bases = classes.map(utils_1.classBase);
|
|
2540
|
+
const baseSet = new Set(bases);
|
|
2541
|
+
return (childRoles.includes('compact-stat-value') ||
|
|
2542
|
+
(baseSet.has('rounded-lg') &&
|
|
2543
|
+
baseSet.has('border') &&
|
|
2544
|
+
bases.some((base) => /^p-(?:4|5)$/.test(base)) &&
|
|
2545
|
+
!baseSet.has('group')));
|
|
2546
|
+
}
|
|
2547
|
+
function isInteractiveServiceCard(classes) {
|
|
2548
|
+
const bases = classes.map(utils_1.classBase);
|
|
2549
|
+
const baseSet = new Set(bases);
|
|
2550
|
+
return (baseSet.has('group') ||
|
|
2551
|
+
classes.some((className) => className.includes('hover:')) ||
|
|
2552
|
+
(baseSet.has('transition') && bases.includes('shadow-2xl')));
|
|
2553
|
+
}
|
|
2554
|
+
function isStaticArticleCard(classes) {
|
|
2555
|
+
const bases = classes.map(utils_1.classBase);
|
|
2556
|
+
const baseSet = new Set(bases);
|
|
2557
|
+
return (baseSet.has('flex') &&
|
|
2558
|
+
baseSet.has('h-full') &&
|
|
2559
|
+
baseSet.has('flex-col') &&
|
|
2560
|
+
baseSet.has('rounded-lg') &&
|
|
2561
|
+
baseSet.has('border') &&
|
|
2562
|
+
bases.includes('p-6') &&
|
|
2563
|
+
!baseSet.has('group') &&
|
|
2564
|
+
!classes.some((className) => className.includes('hover:')));
|
|
2565
|
+
}
|
|
2566
|
+
function isMediaPanelCard(classes, childKinds, childRoles) {
|
|
2567
|
+
const bases = classes.map(utils_1.classBase);
|
|
2568
|
+
return (childKinds.includes('image') ||
|
|
2569
|
+
childRoles.includes('image') ||
|
|
2570
|
+
bases.some((base) => /^(?:aspect-|object-|overflow-hidden)$/.test(base)));
|
|
2571
|
+
}
|
|
2572
|
+
function isQuietAction(classes) {
|
|
2573
|
+
const bases = classes.map(utils_1.classBase);
|
|
2574
|
+
return (bases.includes('bg-transparent') &&
|
|
2575
|
+
(bases.includes('border-transparent') ||
|
|
2576
|
+
bases.some((base) => /^text-(?:muted|secondary|slate|gray)/.test(base))));
|
|
2577
|
+
}
|
|
2578
|
+
function isCompactAction(classes) {
|
|
2579
|
+
const bases = classes.map(utils_1.classBase);
|
|
2580
|
+
return (bases.some((base) => /^h-(?:9|10|11|12)$/.test(base)) &&
|
|
2581
|
+
bases.some((base) => /^px-(?:4|5)$/.test(base)) &&
|
|
2582
|
+
classes.some((className) => /(?:^|:)text-sm$/.test(className)));
|
|
2583
|
+
}
|
|
2584
|
+
function roleClassSort(a, b) {
|
|
2585
|
+
const priority = (value) => {
|
|
2586
|
+
const base = (0, utils_1.classBase)(value);
|
|
2587
|
+
if (/^(?:max-w-|min-w-|w-|h-|min-h-|max-h-)/.test(base))
|
|
2588
|
+
return 0;
|
|
2589
|
+
if (/^(?:text-|font-|leading-|tracking-)/.test(base))
|
|
2590
|
+
return 1;
|
|
2591
|
+
if (/^(?:p|m|gap|space)-/.test(base))
|
|
2592
|
+
return 2;
|
|
2593
|
+
if (/^(?:bg-|border|rounded|shadow|backdrop)/.test(base))
|
|
2594
|
+
return 3;
|
|
2595
|
+
return 4;
|
|
2596
|
+
};
|
|
2597
|
+
return priority(a) - priority(b) || a.localeCompare(b);
|
|
2598
|
+
}
|
|
2599
|
+
function deriveUiChildSemanticRole(tag, kind, classes, label) {
|
|
2600
|
+
const tagName = tag.toLowerCase();
|
|
2601
|
+
const tagBase = tag.split('.')[0].split(':').pop() ?? tag;
|
|
2602
|
+
const lowerBase = tagBase.toLowerCase();
|
|
2603
|
+
const classBases = classes.map(utils_1.classBase);
|
|
2604
|
+
const classHaystack = [tagName, lowerBase, ...classBases].join(' ').toLowerCase();
|
|
2605
|
+
const labelHaystack = (label ?? '').toLowerCase();
|
|
2606
|
+
if (kind === 'icon') {
|
|
2607
|
+
return 'icon';
|
|
2608
|
+
}
|
|
2609
|
+
if (kind === 'image') {
|
|
2610
|
+
return 'image';
|
|
2611
|
+
}
|
|
2612
|
+
if (kind === 'divider') {
|
|
2613
|
+
return 'divider';
|
|
2614
|
+
}
|
|
2615
|
+
if (tagName === 'img' ||
|
|
2616
|
+
tagName === 'picture' ||
|
|
2617
|
+
tagName === 'svg' ||
|
|
2618
|
+
/(?:icon|logo|mark|glyph|avatar|image|img)$/.test(lowerBase) ||
|
|
2619
|
+
/\b(?:icon|logo|mark|glyph|avatar)\b/.test(classHaystack)) {
|
|
2620
|
+
return tagName === 'img' || tagName === 'picture' ? 'image' : 'icon';
|
|
2621
|
+
}
|
|
2622
|
+
if (classes.some((className) => /^(?:w-px|h-px|border-l|border-r|border-t|border-b|divide-x|divide-y)$/.test((0, utils_1.classBase)(className))) ||
|
|
2623
|
+
/\b(?:divider|separator|sep|rule)\b/.test(classHaystack)) {
|
|
2624
|
+
return 'divider';
|
|
2625
|
+
}
|
|
2626
|
+
if (kind === 'text' || kind === 'heading' || kind === 'badge') {
|
|
2627
|
+
if (classes.some((className) => /^(?:text-muted|text-muted-foreground|text-secondary|opacity-|font-normal)/.test((0, utils_1.classBase)(className)))) {
|
|
2628
|
+
return 'secondary-text';
|
|
2629
|
+
}
|
|
2630
|
+
if (classes.some((className) => /^(?:font-bold|font-semibold|font-black|text-primary|text-brand|text-accent|text-yellow|text-amber|text-gold|text-\[)/.test((0, utils_1.classBase)(className))) ||
|
|
2631
|
+
(labelHaystack.length > 0 && labelHaystack.length <= 32)) {
|
|
2632
|
+
return 'primary-text';
|
|
2633
|
+
}
|
|
2634
|
+
return 'text-segment';
|
|
2635
|
+
}
|
|
2636
|
+
if (/^[A-Z]/.test(tagBase) && !classes.length && !label) {
|
|
2637
|
+
return 'component-icon';
|
|
2638
|
+
}
|
|
2639
|
+
return undefined;
|
|
2640
|
+
}
|
|
2641
|
+
function deriveLayoutRole(tag, kind, classes, label) {
|
|
2642
|
+
const tagName = tag.toLowerCase();
|
|
2643
|
+
const tagBase = tag.split('.')[0].split(':').pop() ?? tag;
|
|
2644
|
+
const lowerBase = tagBase.toLowerCase();
|
|
2645
|
+
const classSet = new Set(classes);
|
|
2646
|
+
const classHaystack = classes.join(' ').toLowerCase();
|
|
2647
|
+
const labelHaystack = (label ?? '').toLowerCase();
|
|
2648
|
+
if (lowerBase === 'container') {
|
|
2649
|
+
return 'container';
|
|
2650
|
+
}
|
|
2651
|
+
if (lowerBase === 'cardheader') {
|
|
2652
|
+
return 'card-header-slot';
|
|
2653
|
+
}
|
|
2654
|
+
if (lowerBase === 'cardcontent') {
|
|
2655
|
+
return 'card-content-slot';
|
|
2656
|
+
}
|
|
2657
|
+
if (lowerBase === 'cardfooter') {
|
|
2658
|
+
return 'card-footer-slot';
|
|
2659
|
+
}
|
|
2660
|
+
if (lowerBase === 'cardtitle') {
|
|
2661
|
+
return 'card-title-slot';
|
|
2662
|
+
}
|
|
2663
|
+
if (lowerBase === 'carddescription') {
|
|
2664
|
+
return 'card-description-slot';
|
|
2665
|
+
}
|
|
2666
|
+
if (tagName === 'h1') {
|
|
2667
|
+
return 'page-heading';
|
|
2668
|
+
}
|
|
2669
|
+
if (/^h[2-6]$/.test(tagName)) {
|
|
2670
|
+
return kind === 'heading' && classSet.has('text-xl')
|
|
2671
|
+
? 'card-or-section-heading'
|
|
2672
|
+
: 'section-heading';
|
|
2673
|
+
}
|
|
2674
|
+
if (kind === 'badge' ||
|
|
2675
|
+
classSet.has('uppercase') ||
|
|
2676
|
+
/\beyebrow\b/.test(classHaystack) ||
|
|
2677
|
+
/\b(product context|platform|workflow|plans|contact|faq|final cta)\b/.test(labelHaystack)) {
|
|
2678
|
+
return 'eyebrow-or-badge';
|
|
2679
|
+
}
|
|
2680
|
+
if (classSet.has('section-frame') || classSet.has('sm:section-frame')) {
|
|
2681
|
+
return 'framed-section-shell';
|
|
2682
|
+
}
|
|
2683
|
+
if (classSet.has('context-surface')) {
|
|
2684
|
+
return 'technical-context-surface';
|
|
2685
|
+
}
|
|
2686
|
+
if (tagName === 'section' || lowerBase === 'section') {
|
|
2687
|
+
return 'page-section';
|
|
2688
|
+
}
|
|
2689
|
+
if (classSet.has('grid') ||
|
|
2690
|
+
classes.some((className) => (0, utils_1.classBase)(className).startsWith('grid-cols-'))) {
|
|
2691
|
+
return 'grid-layout';
|
|
2692
|
+
}
|
|
2693
|
+
if (classSet.has('flex') || classSet.has('inline-flex')) {
|
|
2694
|
+
return 'flex-layout';
|
|
2695
|
+
}
|
|
2696
|
+
if (classes.some((className) => (0, utils_1.classBase)(className).startsWith('space-y-'))) {
|
|
2697
|
+
return 'stack-layout';
|
|
2698
|
+
}
|
|
2699
|
+
if (tagName === 'p' || tagName === 'span' || tagName === 'small' || tagName === 'code') {
|
|
2700
|
+
return 'text-copy';
|
|
2701
|
+
}
|
|
2702
|
+
return undefined;
|
|
2703
|
+
}
|
|
2704
|
+
function isStateClass(className) {
|
|
2705
|
+
const prefixes = className.split(':').slice(0, -1);
|
|
2706
|
+
return prefixes.some((prefix) => /^(?:hover|focus|active|disabled|visited|dark|group|peer|aria|data)/.test(prefix));
|
|
2707
|
+
}
|
|
2708
|
+
function extractApiCalls(content) {
|
|
2709
|
+
const calls = new Set();
|
|
2710
|
+
for (const pattern of [
|
|
2711
|
+
/\bfetch\(\s*['"`]([^'"`]+)['"`]/g,
|
|
2712
|
+
/\baxios\.(?:get|post|put|patch|delete)\(\s*['"`]([^'"`]+)['"`]/g,
|
|
2713
|
+
/\b(?:get|post|put|patch|delete)Json\(\s*['"`]([^'"`]+)['"`]/g,
|
|
2714
|
+
/\$\.ajax\(\s*\{[\s\S]*?url\s*:\s*['"`]([^'"`]+)['"`]/g,
|
|
2715
|
+
]) {
|
|
2716
|
+
for (const match of content.matchAll(pattern)) {
|
|
2717
|
+
const value = match[1].trim();
|
|
2718
|
+
if (value.startsWith('/')) {
|
|
2719
|
+
calls.add((0, utils_1.stripQuery)(value));
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
return Array.from(calls).sort();
|
|
2724
|
+
}
|
|
2725
|
+
function extractEnvVars(content) {
|
|
2726
|
+
return (0, utils_1.unique)(Array.from(content.matchAll(/(?:process\.env\.|process\.env\[['"]|os\.environ\[['"]|ENV\[['"]|getenv\(['"]|env\(['"]|import\.meta\.env\.)([A-Z][A-Z0-9_]*)/g)).map((match) => match[1])).slice(0, 80);
|
|
2727
|
+
}
|
|
2728
|
+
function extractTestNames(content, relPath) {
|
|
2729
|
+
if (!isTestFile(relPath)) {
|
|
2730
|
+
return [];
|
|
2731
|
+
}
|
|
2732
|
+
const names = new Set();
|
|
2733
|
+
for (const match of content.matchAll(/\b(?:describe|it|test|specify)\(\s*['"`]([^'"`]+)['"`]/g)) {
|
|
2734
|
+
names.add(match[1].slice(0, 120));
|
|
2735
|
+
}
|
|
2736
|
+
for (const match of content.matchAll(/^\s*def\s+(test_[A-Za-z0-9_]+)/gm)) {
|
|
2737
|
+
names.add(match[1]);
|
|
2738
|
+
}
|
|
2739
|
+
return Array.from(names).sort().slice(0, 80);
|
|
2740
|
+
}
|
|
2741
|
+
function extractDocsHeadings(content, relPath) {
|
|
2742
|
+
if (!/\.(md|mdx)$/i.test(relPath)) {
|
|
2743
|
+
return [];
|
|
2744
|
+
}
|
|
2745
|
+
return Array.from(content.matchAll(/^#{1,4}\s+(.+)$/gm))
|
|
2746
|
+
.map((match) => match[1].trim())
|
|
2747
|
+
.slice(0, 80);
|
|
2748
|
+
}
|
|
2749
|
+
function extractConfigKeys(relPath, content) {
|
|
2750
|
+
const baseName = path.basename(relPath);
|
|
2751
|
+
if (!isConfigFile(relPath)) {
|
|
2752
|
+
return [];
|
|
2753
|
+
}
|
|
2754
|
+
if (/\.(json)$/.test(relPath) || baseName === 'composer.json' || baseName === 'package.json') {
|
|
2755
|
+
try {
|
|
2756
|
+
const parsed = JSON.parse(content);
|
|
2757
|
+
return Object.keys(parsed).slice(0, 80);
|
|
2758
|
+
}
|
|
2759
|
+
catch {
|
|
2760
|
+
return [];
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
if (/\.(yml|yaml|toml)$/.test(relPath) ||
|
|
2764
|
+
['Gemfile', 'go.mod', 'requirements.txt'].includes(baseName)) {
|
|
2765
|
+
return (0, utils_1.unique)(content
|
|
2766
|
+
.split('\n')
|
|
2767
|
+
.map((line) => line.trim().match(/^([A-Za-z0-9_.-]+)\s*[:=]/)?.[1])
|
|
2768
|
+
.filter((item) => Boolean(item))).slice(0, 80);
|
|
2769
|
+
}
|
|
2770
|
+
return Array.from(content.matchAll(/\b([A-Za-z_$][\w$]*)\s*[:=]/g))
|
|
2771
|
+
.map((match) => match[1])
|
|
2772
|
+
.slice(0, 80);
|
|
2773
|
+
}
|
|
2774
|
+
function extractTemplateIncludes(content, relPath) {
|
|
2775
|
+
if (!isTemplateFile(relPath) && !/\.(html|htm|php|phtml)$/.test(relPath)) {
|
|
2776
|
+
return [];
|
|
2777
|
+
}
|
|
2778
|
+
const includes = new Set();
|
|
2779
|
+
for (const pattern of [
|
|
2780
|
+
/@include\(\s*['"]([^'"]+)['"]/g,
|
|
2781
|
+
/\b(?:include|require|include_once|require_once)\s*(?:\(?\s*)['"]([^'"]+)['"]/g,
|
|
2782
|
+
/\{%\s*(?:include|extends|embed)\s+['"]([^'"]+)['"]/g,
|
|
2783
|
+
/<%-?\s*include\(\s*['"]([^'"]+)['"]/g,
|
|
2784
|
+
/{{>\s*([A-Za-z0-9_./-]+)/g,
|
|
2785
|
+
]) {
|
|
2786
|
+
for (const match of content.matchAll(pattern)) {
|
|
2787
|
+
includes.add(match[1]);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
return Array.from(includes).sort();
|
|
2791
|
+
}
|
|
2792
|
+
function extractAuthHints(content) {
|
|
2793
|
+
const hints = new Set();
|
|
2794
|
+
for (const hint of [
|
|
2795
|
+
'auth',
|
|
2796
|
+
'authenticated',
|
|
2797
|
+
'authorize',
|
|
2798
|
+
'permission',
|
|
2799
|
+
'role',
|
|
2800
|
+
'guard',
|
|
2801
|
+
'session',
|
|
2802
|
+
'csrf',
|
|
2803
|
+
'jwt',
|
|
2804
|
+
'token',
|
|
2805
|
+
]) {
|
|
2806
|
+
if (new RegExp(`\\b${hint}\\b`, 'i').test(content)) {
|
|
2807
|
+
hints.add(hint);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
return Array.from(hints).sort();
|
|
2811
|
+
}
|
|
2812
|
+
function extractMiddleware(content, relPath) {
|
|
2813
|
+
const names = new Set();
|
|
2814
|
+
if (/middleware/i.test(relPath)) {
|
|
2815
|
+
names.add(path.basename(relPath).replace(/\.[^.]+$/, ''));
|
|
2816
|
+
}
|
|
2817
|
+
for (const match of content.matchAll(/\b(?:middleware|use|before_action|before_filter)\(\s*['"]?([A-Za-z_][\w:.-]*)/g)) {
|
|
2818
|
+
names.add(match[1]);
|
|
2819
|
+
}
|
|
2820
|
+
for (const match of content.matchAll(/class\s+([A-Z][A-Za-z0-9_]*Middleware)\b/g)) {
|
|
2821
|
+
names.add(match[1]);
|
|
2822
|
+
}
|
|
2823
|
+
return Array.from(names).sort();
|
|
2824
|
+
}
|
|
2825
|
+
function extractJobs(content, relPath) {
|
|
2826
|
+
const jobs = new Set();
|
|
2827
|
+
for (const className of extractClasses(content, relPath)) {
|
|
2828
|
+
if (/(Job|Worker|Task|Command|Cron|Queue)$/.test(className)) {
|
|
2829
|
+
jobs.add(className);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
for (const match of content.matchAll(/\b(?:queue|dispatch|enqueue|schedule|cron)\(['"`]?([A-Za-z0-9_.:-]+)?/g)) {
|
|
2833
|
+
if (match[1]) {
|
|
2834
|
+
jobs.add(match[1]);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
return Array.from(jobs).sort();
|
|
2838
|
+
}
|
|
2839
|
+
function extractEvents(content) {
|
|
2840
|
+
const events = new Set();
|
|
2841
|
+
for (const match of content.matchAll(/\b(?:emit|dispatch|publish|subscribe|on)\(\s*['"`]([^'"`]+)['"`]/g)) {
|
|
2842
|
+
events.add(match[1]);
|
|
2843
|
+
}
|
|
2844
|
+
return Array.from(events).sort().slice(0, 80);
|
|
2845
|
+
}
|
|
2846
|
+
function detectKind(relPath, content) {
|
|
2847
|
+
const baseName = path.basename(relPath);
|
|
2848
|
+
if (baseName === 'package.json') {
|
|
2849
|
+
return 'package';
|
|
2850
|
+
}
|
|
2851
|
+
if (isConfigFile(relPath)) {
|
|
2852
|
+
return 'config';
|
|
2853
|
+
}
|
|
2854
|
+
if (isTestFile(relPath)) {
|
|
2855
|
+
return 'test';
|
|
2856
|
+
}
|
|
2857
|
+
if (/\.(md|mdx)$/i.test(relPath)) {
|
|
2858
|
+
return 'doc';
|
|
2859
|
+
}
|
|
2860
|
+
if (isNextPageFile(relPath)) {
|
|
2861
|
+
return 'next-page';
|
|
2862
|
+
}
|
|
2863
|
+
if (isNextLayoutFile(relPath)) {
|
|
2864
|
+
return 'next-layout';
|
|
2865
|
+
}
|
|
2866
|
+
if (isNextRouteFile(relPath)) {
|
|
2867
|
+
return 'next-route-handler';
|
|
2868
|
+
}
|
|
2869
|
+
if (isPagesRouterPageFile(relPath)) {
|
|
2870
|
+
return 'pages-route';
|
|
2871
|
+
}
|
|
2872
|
+
if (isRawPageFile(relPath)) {
|
|
2873
|
+
return 'web-page';
|
|
2874
|
+
}
|
|
2875
|
+
if (/\.module\.(css|scss)$/.test(relPath)) {
|
|
2876
|
+
return 'css-module';
|
|
2877
|
+
}
|
|
2878
|
+
if (/\.(css|scss)$/.test(relPath) || /tailwind\.config\./.test(relPath)) {
|
|
2879
|
+
return 'stylesheet';
|
|
2880
|
+
}
|
|
2881
|
+
if (/schema\.prisma$/.test(relPath) || /\.(sql|graphql)$/.test(relPath)) {
|
|
2882
|
+
return 'database-schema';
|
|
2883
|
+
}
|
|
2884
|
+
if (/@Controller\(/.test(content)) {
|
|
2885
|
+
return 'backend-controller';
|
|
2886
|
+
}
|
|
2887
|
+
if (/\bclass\s+\w+Controller\b/.test(content) || /Controller\.php$/.test(relPath)) {
|
|
2888
|
+
return 'backend-controller';
|
|
2889
|
+
}
|
|
2890
|
+
if (/(?:export\s+)?class\s+\w+(Service|Repository|Client|Gateway|Provider|Manager)\b/.test(content)) {
|
|
2891
|
+
return 'backend-service';
|
|
2892
|
+
}
|
|
2893
|
+
if (/(?:export\s+)?class\s+\w+(Module|Package|Bundle)\b/.test(content)) {
|
|
2894
|
+
return 'backend-module';
|
|
2895
|
+
}
|
|
2896
|
+
if (/\b(?:router|app|server|Route::)\.(?:get|post|put|patch|delete)|Route::(?:get|post|put|patch|delete)/i.test(content)) {
|
|
2897
|
+
return 'api-route';
|
|
2898
|
+
}
|
|
2899
|
+
if (/\.(tsx|jsx|vue|svelte|astro)$/.test(relPath)) {
|
|
2900
|
+
return 'ui-component';
|
|
2901
|
+
}
|
|
2902
|
+
if (isTemplateFile(relPath)) {
|
|
2903
|
+
return 'template';
|
|
2904
|
+
}
|
|
2905
|
+
return 'source';
|
|
2906
|
+
}
|
|
2907
|
+
function detectLayer(relPath, kind) {
|
|
2908
|
+
if (kind === 'doc') {
|
|
2909
|
+
return 'docs';
|
|
2910
|
+
}
|
|
2911
|
+
if (kind === 'test') {
|
|
2912
|
+
return 'tests';
|
|
2913
|
+
}
|
|
2914
|
+
if (kind === 'config') {
|
|
2915
|
+
return 'config';
|
|
2916
|
+
}
|
|
2917
|
+
if (kind.startsWith('backend') ||
|
|
2918
|
+
relPath.startsWith('prisma/') ||
|
|
2919
|
+
relPath.startsWith('database/') ||
|
|
2920
|
+
relPath.startsWith('db/')) {
|
|
2921
|
+
return 'backend';
|
|
2922
|
+
}
|
|
2923
|
+
if (kind.startsWith('next') ||
|
|
2924
|
+
kind.includes('component') ||
|
|
2925
|
+
relPath.includes('/components/') ||
|
|
2926
|
+
relPath.startsWith('components/')) {
|
|
2927
|
+
return 'frontend';
|
|
2928
|
+
}
|
|
2929
|
+
if (kind.includes('page') || kind === 'template' || isTemplateFile(relPath)) {
|
|
2930
|
+
return 'frontend';
|
|
2931
|
+
}
|
|
2932
|
+
if (kind.includes('style') || kind.includes('css')) {
|
|
2933
|
+
return 'style';
|
|
2934
|
+
}
|
|
2935
|
+
if (relPath.includes('/api/') || relPath.startsWith('api/')) {
|
|
2936
|
+
return 'api';
|
|
2937
|
+
}
|
|
2938
|
+
return 'shared';
|
|
2939
|
+
}
|
|
2940
|
+
function detectLanguage(filePath) {
|
|
2941
|
+
const normalized = (0, utils_1.toPosixPath)(filePath).toLowerCase();
|
|
2942
|
+
if (normalized.endsWith('.blade.php')) {
|
|
2943
|
+
return 'blade.php';
|
|
2944
|
+
}
|
|
2945
|
+
if (normalized.endsWith('.module.scss')) {
|
|
2946
|
+
return 'scss-module';
|
|
2947
|
+
}
|
|
2948
|
+
if (normalized.endsWith('.module.css')) {
|
|
2949
|
+
return 'css-module';
|
|
2950
|
+
}
|
|
2951
|
+
const extension = path.extname(filePath).replace('.', '');
|
|
2952
|
+
return extension || path.basename(filePath);
|
|
2953
|
+
}
|
|
2954
|
+
function summarizeFile(input) {
|
|
2955
|
+
if (input.routes.length) {
|
|
2956
|
+
return `Frontend route ${input.routes.join(', ')}.`;
|
|
2957
|
+
}
|
|
2958
|
+
if (input.apiHandlers.length) {
|
|
2959
|
+
return `API handlers: ${input.apiHandlers.join(', ')}.`;
|
|
2960
|
+
}
|
|
2961
|
+
if (input.components.length) {
|
|
2962
|
+
return `UI components: ${input.components.join(', ')}.`;
|
|
2963
|
+
}
|
|
2964
|
+
if (input.controllers.length) {
|
|
2965
|
+
return `Backend controllers: ${input.controllers.join(', ')}.`;
|
|
2966
|
+
}
|
|
2967
|
+
if (input.services.length) {
|
|
2968
|
+
return `Backend services: ${input.services.join(', ')}.`;
|
|
2969
|
+
}
|
|
2970
|
+
if (input.modules.length) {
|
|
2971
|
+
return `Backend modules: ${input.modules.join(', ')}.`;
|
|
2972
|
+
}
|
|
2973
|
+
if (input.models.length) {
|
|
2974
|
+
return `Data models: ${input.models.join(', ')}.`;
|
|
2975
|
+
}
|
|
2976
|
+
if (input.hooks.length) {
|
|
2977
|
+
return `Hooks: ${input.hooks.join(', ')}.`;
|
|
2978
|
+
}
|
|
2979
|
+
if (input.dependencies.length) {
|
|
2980
|
+
return `Package manifest with ${input.dependencies.length} dependencies.`;
|
|
2981
|
+
}
|
|
2982
|
+
if (input.uiElements.length) {
|
|
2983
|
+
const kinds = (0, utils_1.unique)(input.uiElements.map((element) => element.kind));
|
|
2984
|
+
return `Template UI surface with ${kinds.join(', ')} elements.`;
|
|
2985
|
+
}
|
|
2986
|
+
if (input.tests.length) {
|
|
2987
|
+
return `Test file with ${input.tests.length} discovered test cases.`;
|
|
2988
|
+
}
|
|
2989
|
+
if (input.docsHeadings.length) {
|
|
2990
|
+
return `Documentation file with headings: ${input.docsHeadings.slice(0, 3).join(', ')}.`;
|
|
2991
|
+
}
|
|
2992
|
+
if (input.configKeys.length) {
|
|
2993
|
+
return `Config file with keys: ${input.configKeys.slice(0, 5).join(', ')}.`;
|
|
2994
|
+
}
|
|
2995
|
+
if (input.symbols.length) {
|
|
2996
|
+
return `Source file with symbols: ${input.symbols.slice(0, 5).join(', ')}.`;
|
|
2997
|
+
}
|
|
2998
|
+
return `${input.kind} file: ${input.relPath}.`;
|
|
2999
|
+
}
|
|
3000
|
+
function isNextPageFile(relPath) {
|
|
3001
|
+
const parts = (0, utils_1.toPosixPath)(relPath).split('/');
|
|
3002
|
+
return parts.includes('app') && /^page\.(tsx|jsx|ts|js)$/.test(parts[parts.length - 1]);
|
|
3003
|
+
}
|
|
3004
|
+
function isNextLayoutFile(relPath) {
|
|
3005
|
+
const parts = (0, utils_1.toPosixPath)(relPath).split('/');
|
|
3006
|
+
return parts.includes('app') && /^layout\.(tsx|jsx|ts|js)$/.test(parts[parts.length - 1]);
|
|
3007
|
+
}
|
|
3008
|
+
function isNextRouteFile(relPath) {
|
|
3009
|
+
const parts = (0, utils_1.toPosixPath)(relPath).split('/');
|
|
3010
|
+
return parts.includes('app') && /^route\.(tsx|jsx|ts|js)$/.test(parts[parts.length - 1]);
|
|
3011
|
+
}
|
|
3012
|
+
function isPagesRouterPageFile(relPath) {
|
|
3013
|
+
const parts = (0, utils_1.toPosixPath)(relPath).split('/');
|
|
3014
|
+
const pagesIndex = parts.lastIndexOf('pages');
|
|
3015
|
+
const fileName = parts[parts.length - 1];
|
|
3016
|
+
return pagesIndex >= 0 && !parts.includes('api') && /\.(tsx|jsx|ts|js|vue)$/.test(fileName);
|
|
3017
|
+
}
|
|
3018
|
+
function isSvelteKitPageFile(relPath) {
|
|
3019
|
+
return /(?:^|\/)routes\/.*\/\+page\.svelte$/.test((0, utils_1.toPosixPath)(relPath));
|
|
3020
|
+
}
|
|
3021
|
+
function isAstroPageFile(relPath) {
|
|
3022
|
+
return /(?:^|\/)pages\/.*\.astro$/.test((0, utils_1.toPosixPath)(relPath));
|
|
3023
|
+
}
|
|
3024
|
+
function isNuxtPageFile(relPath) {
|
|
3025
|
+
return /(?:^|\/)pages\/.*\.(vue|ts|js)$/.test((0, utils_1.toPosixPath)(relPath));
|
|
3026
|
+
}
|
|
3027
|
+
function isRawPageFile(relPath) {
|
|
3028
|
+
const normalized = (0, utils_1.toPosixPath)(relPath);
|
|
3029
|
+
if (!/\.(html|htm|php|phtml)$/.test(normalized)) {
|
|
3030
|
+
return false;
|
|
3031
|
+
}
|
|
3032
|
+
return !/(?:^|\/)(partials?|includes?|components?|layouts?|templates\/partials)\//.test(normalized);
|
|
3033
|
+
}
|
|
3034
|
+
function toNextRoutePath(relPath) {
|
|
3035
|
+
const parts = (0, utils_1.toPosixPath)(relPath).split('/');
|
|
3036
|
+
const appIndex = parts.lastIndexOf('app');
|
|
3037
|
+
const routeParts = parts
|
|
3038
|
+
.slice(appIndex + 1, -1)
|
|
3039
|
+
.filter((part) => part && !part.startsWith('(') && !part.startsWith('@'))
|
|
3040
|
+
.map((part) => part.startsWith('[') && part.endsWith(']')
|
|
3041
|
+
? `:${part.slice(1, -1).replace('...', '')}`
|
|
3042
|
+
: part);
|
|
3043
|
+
return `/${routeParts.join('/')}`.replace(/\/+$/, '') || '/';
|
|
3044
|
+
}
|
|
3045
|
+
function toPagesRoutePath(relPath) {
|
|
3046
|
+
const parts = (0, utils_1.toPosixPath)(relPath).split('/');
|
|
3047
|
+
const pagesIndex = parts.lastIndexOf('pages');
|
|
3048
|
+
const routeParts = parts
|
|
3049
|
+
.slice(pagesIndex + 1)
|
|
3050
|
+
.join('/')
|
|
3051
|
+
.replace(/\.(tsx|jsx|ts|js|vue)$/, '')
|
|
3052
|
+
.split('/')
|
|
3053
|
+
.filter((part) => part !== 'index' && !part.startsWith('_'))
|
|
3054
|
+
.map((part) => part.startsWith('[') && part.endsWith(']')
|
|
3055
|
+
? `:${part.slice(1, -1).replace('...', '')}`
|
|
3056
|
+
: part);
|
|
3057
|
+
return `/${routeParts.join('/')}`.replace(/\/+$/, '') || '/';
|
|
3058
|
+
}
|
|
3059
|
+
function toSvelteKitRoutePath(relPath) {
|
|
3060
|
+
const parts = (0, utils_1.toPosixPath)(relPath).split('/');
|
|
3061
|
+
const routesIndex = parts.lastIndexOf('routes');
|
|
3062
|
+
const routeParts = parts
|
|
3063
|
+
.slice(routesIndex + 1, -1)
|
|
3064
|
+
.filter((part) => part && !part.startsWith('('))
|
|
3065
|
+
.map((part) => (part.startsWith('[') && part.endsWith(']') ? `:${part.slice(1, -1)}` : part));
|
|
3066
|
+
return `/${routeParts.join('/')}`.replace(/\/+$/, '') || '/';
|
|
3067
|
+
}
|
|
3068
|
+
function toAstroRoutePath(relPath) {
|
|
3069
|
+
const parts = (0, utils_1.toPosixPath)(relPath).split('/');
|
|
3070
|
+
const pagesIndex = parts.lastIndexOf('pages');
|
|
3071
|
+
const routeParts = parts
|
|
3072
|
+
.slice(pagesIndex + 1)
|
|
3073
|
+
.join('/')
|
|
3074
|
+
.replace(/\.astro$/, '')
|
|
3075
|
+
.split('/')
|
|
3076
|
+
.filter((part) => part !== 'index')
|
|
3077
|
+
.map((part) => (part.startsWith('[') && part.endsWith(']') ? `:${part.slice(1, -1)}` : part));
|
|
3078
|
+
return `/${routeParts.join('/')}`.replace(/\/+$/, '') || '/';
|
|
3079
|
+
}
|
|
3080
|
+
function toNuxtRoutePath(relPath) {
|
|
3081
|
+
return toPagesRoutePath(relPath).replace(/\/_/g, '/:');
|
|
3082
|
+
}
|
|
3083
|
+
function toRawPageRoutePath(relPath) {
|
|
3084
|
+
const normalized = (0, utils_1.toPosixPath)(relPath);
|
|
3085
|
+
const withoutExtension = normalized.replace(/\.(html|htm|php|phtml)$/, '');
|
|
3086
|
+
const route = withoutExtension
|
|
3087
|
+
.replace(/^(public|static|web|src|app)\//, '')
|
|
3088
|
+
.replace(/^resources\/views\//, '')
|
|
3089
|
+
.replace(/^views\//, '')
|
|
3090
|
+
.replace(/^templates\//, '')
|
|
3091
|
+
.replace(/\/index$/, '')
|
|
3092
|
+
.replace(/^index$/, '');
|
|
3093
|
+
return normalizeRoutePath(route);
|
|
3094
|
+
}
|
|
3095
|
+
function extractFrameworkRoutePaths(relPath, content) {
|
|
3096
|
+
const routes = new Set();
|
|
3097
|
+
for (const handler of extractApiHandlers(relPath, content)) {
|
|
3098
|
+
routes.add(handler.replace(/^[A-Z]+\s+/, ''));
|
|
3099
|
+
}
|
|
3100
|
+
return Array.from(routes).filter(Boolean);
|
|
3101
|
+
}
|
|
3102
|
+
function normalizeRoutePath(value) {
|
|
3103
|
+
const route = (0, utils_1.stripQuery)(value)
|
|
3104
|
+
.replace(/^['"`]|['"`]$/g, '')
|
|
3105
|
+
.replace(/^\^/, '')
|
|
3106
|
+
.replace(/\$$/, '')
|
|
3107
|
+
.replace(/\/+$/, '')
|
|
3108
|
+
.replace(/^\s+|\s+$/g, '');
|
|
3109
|
+
return `/${route.replace(/^\/+/, '')}`.replace(/\/+$/, '') || '/';
|
|
3110
|
+
}
|
|
3111
|
+
function shouldIgnoreDirectory(relPath) {
|
|
3112
|
+
if (relPath === '.') {
|
|
3113
|
+
return false;
|
|
3114
|
+
}
|
|
3115
|
+
return relPath.split('/').some((part) => constants_1.ignoredDirNames.has(part));
|
|
3116
|
+
}
|
|
3117
|
+
function isExcluded(relPath, excludePaths, explicitIncludePaths = false) {
|
|
3118
|
+
const normalized = relPath === '.' ? relPath : (0, utils_1.stripLeadingDot)((0, utils_1.toPosixPath)(relPath));
|
|
3119
|
+
if (shouldIgnoreFileName(path.basename(normalized))) {
|
|
3120
|
+
return true;
|
|
3121
|
+
}
|
|
3122
|
+
if (!explicitIncludePaths && shouldIgnoreNonCanonicalPath(normalized)) {
|
|
3123
|
+
return true;
|
|
3124
|
+
}
|
|
3125
|
+
return excludePaths.some((excludePath) => {
|
|
3126
|
+
const normalizedExclude = (0, utils_1.stripLeadingDot)((0, utils_1.toPosixPath)(excludePath));
|
|
3127
|
+
return normalized === normalizedExclude || normalized.startsWith(`${normalizedExclude}/`);
|
|
3128
|
+
});
|
|
3129
|
+
}
|
|
3130
|
+
function shouldIgnoreNonCanonicalPath(relPath) {
|
|
3131
|
+
return (/^(?:src\/)?app\/test(?:\/|$)/i.test(relPath) ||
|
|
3132
|
+
/^(?:src\/)?pages\/test(?:\/|$)/i.test(relPath) ||
|
|
3133
|
+
/^(?:src\/)?app\/(?:playground|playgrounds|demo|demos|example|examples|sandbox)(?:\/|$)/i.test(relPath) ||
|
|
3134
|
+
/(?:^|\/)playwright-mcp\.page[^/]*\.(?:tsx|jsx|ts|js|html|mdx?)$/i.test(relPath));
|
|
3135
|
+
}
|
|
3136
|
+
function isScannableFile(filePath) {
|
|
3137
|
+
const fileName = path.basename(filePath);
|
|
3138
|
+
const normalized = (0, utils_1.toPosixPath)(filePath).toLowerCase();
|
|
3139
|
+
return (constants_1.scannableFileNames.has(fileName) ||
|
|
3140
|
+
constants_1.scannableExtensions.has(path.extname(filePath)) ||
|
|
3141
|
+
normalized.endsWith('.blade.php'));
|
|
3142
|
+
}
|
|
3143
|
+
function shouldIgnoreFileName(fileName) {
|
|
3144
|
+
if (constants_1.ignoredFileNames.has(fileName)) {
|
|
3145
|
+
return true;
|
|
3146
|
+
}
|
|
3147
|
+
if (/^\.env(?:\.|$)/.test(fileName)) {
|
|
3148
|
+
return true;
|
|
3149
|
+
}
|
|
3150
|
+
if (/\.(pem|key|crt|p12|pfx|keystore|jks)$/i.test(fileName)) {
|
|
3151
|
+
return true;
|
|
3152
|
+
}
|
|
3153
|
+
return false;
|
|
3154
|
+
}
|
|
3155
|
+
function isUiFile(relPath) {
|
|
3156
|
+
return /\.(tsx|jsx|vue|svelte|astro)$/.test(relPath) || isTemplateFile(relPath);
|
|
3157
|
+
}
|
|
3158
|
+
function isTemplateFile(relPath) {
|
|
3159
|
+
return (/\.(html|htm|php|phtml|twig|erb|ejs|hbs|handlebars|liquid|njk|pug)$/.test(relPath) ||
|
|
3160
|
+
relPath.endsWith('.blade.php'));
|
|
3161
|
+
}
|
|
3162
|
+
function isTestFile(relPath) {
|
|
3163
|
+
return (/(?:^|\/)(?:app|pages|src\/app|src\/pages)\/test\//i.test(relPath) ||
|
|
3164
|
+
/(?:^|\/)(?:__tests__|tests?|spec)\//i.test(relPath) ||
|
|
3165
|
+
/\.(test|spec)\.[A-Za-z0-9]+$/.test(relPath));
|
|
3166
|
+
}
|
|
3167
|
+
function isConfigFile(relPath) {
|
|
3168
|
+
const baseName = path.basename(relPath);
|
|
3169
|
+
return (constants_1.rootConfigFiles.includes(baseName) ||
|
|
3170
|
+
constants_1.scannableFileNames.has(baseName) ||
|
|
3171
|
+
/(?:^|\/)\.github\/workflows\/.*\.ya?ml$/.test(relPath) ||
|
|
3172
|
+
/(?:^|\/)(?:docker-compose|compose)\.ya?ml$/.test(relPath) ||
|
|
3173
|
+
/(?:^|\/)(?:config|configs?)\/.+\.(json|ya?ml|toml|xml)$/.test(relPath) ||
|
|
3174
|
+
/\.(config|conf)\.(js|mjs|ts|json|ya?ml)$/.test(relPath));
|
|
3175
|
+
}
|
|
3176
|
+
function routeExtractorForFile(file) {
|
|
3177
|
+
if (file.kind.startsWith('next')) {
|
|
3178
|
+
return 'nextjs.app-router';
|
|
3179
|
+
}
|
|
3180
|
+
if (file.kind === 'pages-route') {
|
|
3181
|
+
return 'nextjs.pages-router';
|
|
3182
|
+
}
|
|
3183
|
+
if (file.language === 'svelte') {
|
|
3184
|
+
return 'sveltekit';
|
|
3185
|
+
}
|
|
3186
|
+
if (file.language === 'astro') {
|
|
3187
|
+
return 'astro';
|
|
3188
|
+
}
|
|
3189
|
+
if (file.language === 'vue') {
|
|
3190
|
+
return 'vue-nuxt';
|
|
3191
|
+
}
|
|
3192
|
+
if (file.language === 'php' || file.language === 'blade.php') {
|
|
3193
|
+
return 'php-web';
|
|
3194
|
+
}
|
|
3195
|
+
if (file.language === 'py') {
|
|
3196
|
+
return 'python-web';
|
|
3197
|
+
}
|
|
3198
|
+
if (file.language === 'rb') {
|
|
3199
|
+
return 'ruby-web';
|
|
3200
|
+
}
|
|
3201
|
+
return 'web.routes';
|
|
3202
|
+
}
|
|
3203
|
+
function routeConfidenceForFile(file) {
|
|
3204
|
+
return file.kind.startsWith('next') || file.kind === 'pages-route' || file.kind === 'web-page'
|
|
3205
|
+
? 'high'
|
|
3206
|
+
: 'medium';
|
|
3207
|
+
}
|
|
3208
|
+
function apiExtractorForFile(file) {
|
|
3209
|
+
if (file.kind.includes('next')) {
|
|
3210
|
+
return 'nextjs.route-handler';
|
|
3211
|
+
}
|
|
3212
|
+
if (file.language === 'php' || file.language === 'blade.php') {
|
|
3213
|
+
return 'php.routes';
|
|
3214
|
+
}
|
|
3215
|
+
if (file.language === 'py') {
|
|
3216
|
+
return 'python.routes';
|
|
3217
|
+
}
|
|
3218
|
+
if (file.language === 'rb') {
|
|
3219
|
+
return 'ruby.routes';
|
|
3220
|
+
}
|
|
3221
|
+
if (file.language === 'go') {
|
|
3222
|
+
return 'go.routes';
|
|
3223
|
+
}
|
|
3224
|
+
return 'api.routes';
|
|
3225
|
+
}
|
|
3226
|
+
function controllerExtractorForFile(file) {
|
|
3227
|
+
if (file.language === 'php') {
|
|
3228
|
+
return 'php-controller';
|
|
3229
|
+
}
|
|
3230
|
+
if (file.language === 'rb') {
|
|
3231
|
+
return 'rails-controller';
|
|
3232
|
+
}
|
|
3233
|
+
if (file.language === 'py') {
|
|
3234
|
+
return 'python-controller';
|
|
3235
|
+
}
|
|
3236
|
+
return 'nestjs';
|
|
3237
|
+
}
|
|
3238
|
+
function serviceExtractorForFile(file) {
|
|
3239
|
+
if (file.language === 'php') {
|
|
3240
|
+
return 'php-service';
|
|
3241
|
+
}
|
|
3242
|
+
if (file.language === 'rb') {
|
|
3243
|
+
return 'ruby-service';
|
|
3244
|
+
}
|
|
3245
|
+
if (file.language === 'py') {
|
|
3246
|
+
return 'python-service';
|
|
3247
|
+
}
|
|
3248
|
+
if (file.language === 'go') {
|
|
3249
|
+
return 'go-service';
|
|
3250
|
+
}
|
|
3251
|
+
return 'service-convention';
|
|
3252
|
+
}
|
|
3253
|
+
function moduleExtractorForFile(file) {
|
|
3254
|
+
return file.language === 'ts' ? 'nestjs' : 'module-convention';
|
|
3255
|
+
}
|
|
3256
|
+
function modelExtractorForFile(file) {
|
|
3257
|
+
if (file.language === 'prisma') {
|
|
3258
|
+
return 'prisma';
|
|
3259
|
+
}
|
|
3260
|
+
if (file.language === 'sql') {
|
|
3261
|
+
return 'sql';
|
|
3262
|
+
}
|
|
3263
|
+
if (file.language === 'rb') {
|
|
3264
|
+
return 'rails-model';
|
|
3265
|
+
}
|
|
3266
|
+
if (file.language === 'py') {
|
|
3267
|
+
return 'django-model';
|
|
3268
|
+
}
|
|
3269
|
+
if (file.language === 'php') {
|
|
3270
|
+
return 'php-model';
|
|
3271
|
+
}
|
|
3272
|
+
return 'orm-model';
|
|
3273
|
+
}
|
|
3274
|
+
function styleExtractorForFile(file) {
|
|
3275
|
+
if (file.language === 'css' ||
|
|
3276
|
+
file.language === 'scss' ||
|
|
3277
|
+
file.language === 'css-module' ||
|
|
3278
|
+
file.language === 'scss-module') {
|
|
3279
|
+
return 'styles';
|
|
3280
|
+
}
|
|
3281
|
+
if (file.tailwindUtilities.length) {
|
|
3282
|
+
return 'tailwind-classes';
|
|
3283
|
+
}
|
|
3284
|
+
if (isTemplateFile(file.relPath)) {
|
|
3285
|
+
return 'template-styles';
|
|
3286
|
+
}
|
|
3287
|
+
return 'ui-styles';
|
|
3288
|
+
}
|
|
3289
|
+
function styleConfidenceForFile(file) {
|
|
3290
|
+
return file.cssClasses.length ||
|
|
3291
|
+
file.cssVariables.length ||
|
|
3292
|
+
file.cssVariableValues.length ||
|
|
3293
|
+
file.cssRules.length ||
|
|
3294
|
+
file.componentStyleDefinitions.length ||
|
|
3295
|
+
file.themeTokens.length ||
|
|
3296
|
+
file.inlineStyles.length
|
|
3297
|
+
? 'high'
|
|
3298
|
+
: file.classReferences.length || file.classExpressions.length || file.uiElements.length
|
|
3299
|
+
? 'medium'
|
|
3300
|
+
: 'low';
|
|
3301
|
+
}
|
|
3302
|
+
function entryMatchesConcept(entry, terms) {
|
|
3303
|
+
const uiElements = Array.isArray(entry.value.uiElements) ? entry.value.uiElements : [];
|
|
3304
|
+
const componentStyleDefinitions = Array.isArray(entry.value.componentStyleDefinitions)
|
|
3305
|
+
? entry.value.componentStyleDefinitions
|
|
3306
|
+
: [];
|
|
3307
|
+
const themeTokens = Array.isArray(entry.value.themeTokens) ? entry.value.themeTokens : [];
|
|
3308
|
+
const haystack = [
|
|
3309
|
+
entry.key,
|
|
3310
|
+
entry.summary,
|
|
3311
|
+
...entry.tags,
|
|
3312
|
+
String(entry.path ?? ''),
|
|
3313
|
+
String(entry.value.name ?? ''),
|
|
3314
|
+
String(entry.value.kind ?? ''),
|
|
3315
|
+
String(entry.value.concept ?? ''),
|
|
3316
|
+
Array.isArray(entry.value.classes) ? entry.value.classes.join(' ') : '',
|
|
3317
|
+
Array.isArray(entry.value.classReferences) ? entry.value.classReferences.join(' ') : '',
|
|
3318
|
+
Array.isArray(entry.value.cssClasses) ? entry.value.cssClasses.join(' ') : '',
|
|
3319
|
+
Array.isArray(entry.value.tailwindUtilities) ? entry.value.tailwindUtilities.join(' ') : '',
|
|
3320
|
+
Array.isArray(entry.value.cssVariables) ? entry.value.cssVariables.join(' ') : '',
|
|
3321
|
+
Array.isArray(entry.value.cssVariableReferences)
|
|
3322
|
+
? entry.value.cssVariableReferences.join(' ')
|
|
3323
|
+
: '',
|
|
3324
|
+
...componentStyleDefinitions.map((definition) => typeof definition === 'object' && definition !== null && 'name' in definition
|
|
3325
|
+
? String(definition.name)
|
|
3326
|
+
: ''),
|
|
3327
|
+
...themeTokens.map((token) => typeof token === 'object' && token !== null && 'path' in token ? String(token.path) : ''),
|
|
3328
|
+
...uiElements.flatMap((element) => {
|
|
3329
|
+
if (typeof element !== 'object' || element === null) {
|
|
3330
|
+
return [];
|
|
3331
|
+
}
|
|
3332
|
+
const value = element;
|
|
3333
|
+
return [
|
|
3334
|
+
String(value.kind ?? ''),
|
|
3335
|
+
String(value.tag ?? ''),
|
|
3336
|
+
String(value.originalTag ?? ''),
|
|
3337
|
+
Array.isArray(value.classes) ? value.classes.join(' ') : '',
|
|
3338
|
+
];
|
|
3339
|
+
}),
|
|
3340
|
+
]
|
|
3341
|
+
.join(' ')
|
|
3342
|
+
.toLowerCase();
|
|
3343
|
+
return terms.some((term) => {
|
|
3344
|
+
const normalized = term.toLowerCase();
|
|
3345
|
+
return (new RegExp(`\\b${(0, utils_1.escapeRegExp)(normalized)}s?\\b`).test(haystack) ||
|
|
3346
|
+
haystack.includes(normalized));
|
|
3347
|
+
});
|
|
3348
|
+
}
|
|
3349
|
+
function splitClassList(value) {
|
|
3350
|
+
return (0, utils_1.unique)(value
|
|
3351
|
+
.replace(/\{[^}]*\}/g, ' ')
|
|
3352
|
+
.replace(/\$\([^)]*\)/g, ' ')
|
|
3353
|
+
.split(/\s+/)
|
|
3354
|
+
.map((item) => item
|
|
3355
|
+
.trim()
|
|
3356
|
+
.replace(/^['"`]+|['"`]+$/g, '')
|
|
3357
|
+
.replace(/,$/, ''))
|
|
3358
|
+
.filter((item) => Boolean(item) &&
|
|
3359
|
+
item.length <= 180 &&
|
|
3360
|
+
!/[{};'"]/.test(item) &&
|
|
3361
|
+
!item.includes('=') &&
|
|
3362
|
+
!item.endsWith(':') &&
|
|
3363
|
+
!/(?:^|\/)<\/?[A-Za-z]/.test(item) &&
|
|
3364
|
+
(!/[<>]/.test(item) || item.includes('[')) &&
|
|
3365
|
+
(!/[()]/.test(item) || item.includes('[')) &&
|
|
3366
|
+
!/^(?:const|let|var|return|class|className|defaultVariants|variants|variant|size|true|false|null|undefined|map|item|props|children|function)$/.test(item) &&
|
|
3367
|
+
!constants_1.classTokenStopWords.has(item.toLowerCase()) &&
|
|
3368
|
+
!/^[A-Z][a-z]+$/.test(item) &&
|
|
3369
|
+
/[A-Za-z0-9\]]/.test(item)));
|
|
3370
|
+
}
|
|
3371
|
+
function classifyUiElement(tag, attrs, classes, role) {
|
|
3372
|
+
const originalTag = tag;
|
|
3373
|
+
const tagName = originalTag.toLowerCase();
|
|
3374
|
+
const tagBase = originalTag.split('.')[0].split(':').pop() ?? originalTag;
|
|
3375
|
+
const lowerBase = tagBase.toLowerCase();
|
|
3376
|
+
const classHaystack = [tagName, role ?? '', ...classes].join(' ').toLowerCase();
|
|
3377
|
+
const classHelper = readPrimaryClassHelperUsage(readClassSource(attrs) ?? '');
|
|
3378
|
+
const classHelperName = classHelper?.name.toLowerCase() ?? '';
|
|
3379
|
+
const propHaystack = [
|
|
3380
|
+
tagName,
|
|
3381
|
+
role ?? '',
|
|
3382
|
+
(0, utils_1.readAttr)(attrs, 'variant') ?? '',
|
|
3383
|
+
(0, utils_1.readAttr)(attrs, 'type') ?? '',
|
|
3384
|
+
]
|
|
3385
|
+
.join(' ')
|
|
3386
|
+
.toLowerCase();
|
|
3387
|
+
if (tagName === 'form' || lowerBase === 'form') {
|
|
3388
|
+
return 'form';
|
|
3389
|
+
}
|
|
3390
|
+
if (/^h[1-6]$/.test(tagName) || /^(heading|title|headline)$/i.test(tagBase)) {
|
|
3391
|
+
return 'heading';
|
|
3392
|
+
}
|
|
3393
|
+
if (tagName === 'img' ||
|
|
3394
|
+
tagName === 'picture' ||
|
|
3395
|
+
/^(image|img|avatar)$/i.test(tagBase) ||
|
|
3396
|
+
/(?:image|img|avatar)$/.test(lowerBase)) {
|
|
3397
|
+
return 'image';
|
|
3398
|
+
}
|
|
3399
|
+
if (tagName === 'svg' ||
|
|
3400
|
+
/^(icon|logo|mark|glyph)$/i.test(tagBase) ||
|
|
3401
|
+
/(?:icon|logo|mark|glyph)$/.test(lowerBase) ||
|
|
3402
|
+
/\b(icon|logo|mark|glyph)\b/.test(classHaystack)) {
|
|
3403
|
+
return 'icon';
|
|
3404
|
+
}
|
|
3405
|
+
if (/^(divider|separator|rule)$/i.test(tagBase) ||
|
|
3406
|
+
/\b(divider|separator|rule)\b/.test(classHaystack) ||
|
|
3407
|
+
classes.some((className) => /^(?:w-px|h-px|border-l|border-r|border-t|border-b|divide-x|divide-y)$/.test((0, utils_1.classBase)(className)))) {
|
|
3408
|
+
return 'divider';
|
|
3409
|
+
}
|
|
3410
|
+
if (['input', 'textarea', 'select', 'option', 'label', 'fieldset'].includes(tagName) ||
|
|
3411
|
+
/^(input|textarea|select|field|fieldlabel|label|checkbox|switch|slider|radio|combobox)$/i.test(tagBase) ||
|
|
3412
|
+
/\b(field|input|textarea|select|checkbox|switch|slider|radio)\b/.test(classHaystack)) {
|
|
3413
|
+
return 'input';
|
|
3414
|
+
}
|
|
3415
|
+
if (/(?:button|cta|action)/.test(classHelperName) ||
|
|
3416
|
+
tagName === 'button' ||
|
|
3417
|
+
role === 'button' ||
|
|
3418
|
+
/^(button|iconbutton|submitbutton|cta)$/i.test(tagBase) ||
|
|
3419
|
+
/\b(btn|button|cta|submit)\b/.test(classHaystack) ||
|
|
3420
|
+
/\bsubmit\b/.test(propHaystack)) {
|
|
3421
|
+
return 'button';
|
|
3422
|
+
}
|
|
3423
|
+
if (tagName === 'a' ||
|
|
3424
|
+
role === 'link' ||
|
|
3425
|
+
/^(link|navlink|menulink)$/i.test(tagBase) ||
|
|
3426
|
+
/\blink\b/.test(classHaystack)) {
|
|
3427
|
+
return 'link';
|
|
3428
|
+
}
|
|
3429
|
+
if (tagName === 'nav' ||
|
|
3430
|
+
role === 'navigation' ||
|
|
3431
|
+
/^(nav|navbar|navigation|menu|menubar|breadcrumb|sidebar)$/i.test(tagBase) ||
|
|
3432
|
+
/\b(nav|navbar|navigation|menu|sidebar|breadcrumb)\b/.test(classHaystack)) {
|
|
3433
|
+
return 'nav';
|
|
3434
|
+
}
|
|
3435
|
+
if (tagName === 'table' || ['thead', 'tbody', 'tfoot', 'tr', 'td', 'th'].includes(tagName)) {
|
|
3436
|
+
return 'table';
|
|
3437
|
+
}
|
|
3438
|
+
if (tagName === 'dialog' ||
|
|
3439
|
+
role === 'dialog' ||
|
|
3440
|
+
/^(dialog|modal|popover|drawer|sheet|alertdialog)$/i.test(tagBase) ||
|
|
3441
|
+
/\b(dialog|modal|popover|drawer|sheet)\b/.test(classHaystack)) {
|
|
3442
|
+
return 'dialog';
|
|
3443
|
+
}
|
|
3444
|
+
if (/(?:badge|chip|pill|status)/.test(classHelperName) ||
|
|
3445
|
+
/^(badge|chip|pill|tag|status|eyebrow)$/i.test(tagBase) ||
|
|
3446
|
+
/\b(badge|chip|pill|tag|status|eyebrow)\b/.test(classHaystack) ||
|
|
3447
|
+
looksLikePillBadge(tagName, classes)) {
|
|
3448
|
+
return 'badge';
|
|
3449
|
+
}
|
|
3450
|
+
if (/^(card|panel|tile|surface|sectionframe|frame|callout)$/i.test(tagBase) ||
|
|
3451
|
+
/\b(card|panel|tile|section-frame|context-surface|frame|callout)\b/.test(classHaystack) ||
|
|
3452
|
+
looksLikeSurfaceContainer(tagName, tagBase, classes)) {
|
|
3453
|
+
return 'card';
|
|
3454
|
+
}
|
|
3455
|
+
if (tagName === 'section' || lowerBase === 'section') {
|
|
3456
|
+
return 'section';
|
|
3457
|
+
}
|
|
3458
|
+
if (['p', 'span', 'small', 'strong', 'em', 'code', 'pre', 'li'].includes(tagName) &&
|
|
3459
|
+
classes.some((className) => isTypographyClass(className) || isSpacingClass(className))) {
|
|
3460
|
+
return 'text';
|
|
3461
|
+
}
|
|
3462
|
+
if (classes.some((className) => /^(?:grid|flex|inline-flex|block|hidden|relative|absolute|mx-auto|max-w-|min-w-|w-full|gap-|space-y-|items-|justify-)/.test((0, utils_1.classBase)(className)))) {
|
|
3463
|
+
return 'layout';
|
|
3464
|
+
}
|
|
3465
|
+
return 'unknown';
|
|
3466
|
+
}
|
|
3467
|
+
function looksLikeSurfaceContainer(tagName, tagBase, classes) {
|
|
3468
|
+
const bases = classes.map(utils_1.classBase);
|
|
3469
|
+
const isContainerTag = ['div', 'article', 'section', 'aside', 'figure', 'li', 'main', 'header', 'footer'].includes(tagName) ||
|
|
3470
|
+
/^[A-Z]/.test(tagBase);
|
|
3471
|
+
const isTinyIndicator = tagName === 'span' &&
|
|
3472
|
+
bases.includes('rounded-full') &&
|
|
3473
|
+
bases.some((base) => /^(?:h|size)-(?:px|0\.5|1|1\.5|2|2\.5|3)$/.test(base)) &&
|
|
3474
|
+
bases.some((base) => /^(?:w|size)-(?:px|0\.5|1|1\.5|2|2\.5|3)$/.test(base));
|
|
3475
|
+
if (!isContainerTag || isTinyIndicator) {
|
|
3476
|
+
return false;
|
|
3477
|
+
}
|
|
3478
|
+
const hasSurfaceBackground = bases.some((base) => base === 'bg-card' ||
|
|
3479
|
+
base === 'section-frame' ||
|
|
3480
|
+
base === 'context-surface' ||
|
|
3481
|
+
/^bg-/.test(base) ||
|
|
3482
|
+
/^bg-\[var\(--(?:surface|card)-/.test(base));
|
|
3483
|
+
const hasContainerShape = bases.some((base) => /^(?:border|rounded|shadow|ring|outline|overflow-hidden)/.test(base));
|
|
3484
|
+
const hasContainerStructure = bases.some((base) => /^(?:p[trblxy]?|gap-|space-|min-h-|max-w-|w-full|flex|grid|relative)/.test(base));
|
|
3485
|
+
return hasSurfaceBackground && hasContainerShape && hasContainerStructure;
|
|
3486
|
+
}
|
|
3487
|
+
function looksLikePillBadge(tagName, classes) {
|
|
3488
|
+
const bases = classes.map(utils_1.classBase);
|
|
3489
|
+
const baseSet = new Set(bases);
|
|
3490
|
+
const allowedTag = ['div', 'span', 'small', 'p', 'li'].includes(tagName);
|
|
3491
|
+
const hasPillShape = baseSet.has('rounded-full') || bases.some((base) => /^rounded-\[/.test(base));
|
|
3492
|
+
const hasCompactSpacing = bases.some((base) => /^px-(?:2|2\.5|3|4|5|6)$/.test(base)) &&
|
|
3493
|
+
bases.some((base) => /^py-(?:1|1\.5|2|2\.5|3)$/.test(base));
|
|
3494
|
+
const hasBadgeText = baseSet.has('uppercase') ||
|
|
3495
|
+
classes.some((className) => /(?:^|:)text-(?:xs|sm|\[(?:10|11|12|13|14)px\])$/.test(className));
|
|
3496
|
+
const hasCompoundPillLayout = bases.some((base) => /^gap-/.test(base));
|
|
3497
|
+
const hasBadgeSurface = bases.some((base) => /^bg-/.test(base)) ||
|
|
3498
|
+
bases.some((base) => /^border/.test(base)) ||
|
|
3499
|
+
bases.some((base) => /^shadow/.test(base));
|
|
3500
|
+
const hasInlineLayout = baseSet.has('inline-flex') ||
|
|
3501
|
+
baseSet.has('inline-grid') ||
|
|
3502
|
+
(baseSet.has('absolute') && bases.some((base) => /^bottom-|^top-/.test(base)));
|
|
3503
|
+
return (allowedTag &&
|
|
3504
|
+
hasPillShape &&
|
|
3505
|
+
hasCompactSpacing &&
|
|
3506
|
+
(hasBadgeText || hasCompoundPillLayout) &&
|
|
3507
|
+
hasBadgeSurface &&
|
|
3508
|
+
hasInlineLayout);
|
|
3509
|
+
}
|
|
3510
|
+
function stripTags(value) {
|
|
3511
|
+
return value.replace(/<[^>]+>/g, ' ').replace(/\{[{%][\s\S]*?[}%]\}/g, ' ');
|
|
3512
|
+
}
|
|
3513
|
+
//# sourceMappingURL=extractors.js.map
|