snoop-ui 0.0.1
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/LICENSE +21 -0
- package/README.md +61 -0
- package/package.json +95 -0
- package/packages/adapter-vite/dist/vite.d.ts +29 -0
- package/packages/adapter-vite/dist/vite.js +281 -0
- package/packages/adapter-vite/dist/vite.js.map +1 -0
- package/packages/cli/dist/cli.d.ts +1 -0
- package/packages/cli/dist/cli.js +136 -0
- package/packages/cli/dist/cli.js.map +1 -0
- package/packages/core/dist/index.d.ts +607 -0
- package/packages/core/dist/index.js +2421 -0
- package/packages/core/dist/index.js.map +1 -0
- package/packages/runtime/dist/agent.js +751 -0
- package/packages/runtime/dist/agent.js.map +1 -0
- package/schemas/config.v1.json +99 -0
- package/schemas/diagnostics.v1.json +132 -0
- package/schemas/manifest.v1.json +207 -0
- package/schemas/styles.v1.json +231 -0
|
@@ -0,0 +1,2421 @@
|
|
|
1
|
+
// package.json
|
|
2
|
+
var package_default = {
|
|
3
|
+
name: "@snoop-ui/core",
|
|
4
|
+
version: "0.0.1",
|
|
5
|
+
private: true,
|
|
6
|
+
license: "MIT",
|
|
7
|
+
type: "module",
|
|
8
|
+
description: "Snoop core: scanner, styles, rules, schemas, protocols",
|
|
9
|
+
main: "dist/index.js",
|
|
10
|
+
module: "dist/index.js",
|
|
11
|
+
types: "dist/index.d.ts",
|
|
12
|
+
exports: {
|
|
13
|
+
".": {
|
|
14
|
+
types: "./dist/index.d.ts",
|
|
15
|
+
import: "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./schemas/*": "./schemas/*"
|
|
18
|
+
},
|
|
19
|
+
files: [
|
|
20
|
+
"dist",
|
|
21
|
+
"schemas"
|
|
22
|
+
],
|
|
23
|
+
scripts: {
|
|
24
|
+
build: "tsup",
|
|
25
|
+
dev: "tsup --watch",
|
|
26
|
+
test: "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"test:browser": "vitest run --config vitest.browser.config.ts",
|
|
29
|
+
typecheck: "tsc --noEmit",
|
|
30
|
+
lint: "eslint .",
|
|
31
|
+
format: "prettier --write .",
|
|
32
|
+
"format:check": "prettier --check .",
|
|
33
|
+
schemas: "node scripts/generate-schemas.mjs",
|
|
34
|
+
"bundle:size": "node scripts/check-bundle-size.mjs",
|
|
35
|
+
"docs:links": "node scripts/check-docs-links.mjs",
|
|
36
|
+
"licenses:check": "node scripts/check-licenses.mjs"
|
|
37
|
+
},
|
|
38
|
+
dependencies: {
|
|
39
|
+
"@babel/parser": "^7.28.5",
|
|
40
|
+
"@babel/traverse": "^7.28.5",
|
|
41
|
+
"@babel/types": "^7.28.5",
|
|
42
|
+
cac: "^7.1.0",
|
|
43
|
+
jiti: "^2.7.0",
|
|
44
|
+
postcss: "^8.5.32",
|
|
45
|
+
"postcss-selector-parser": "^7.2.0",
|
|
46
|
+
"postcss-value-parser": "^4.2.0",
|
|
47
|
+
"react-docgen": "^8.0.4",
|
|
48
|
+
"react-docgen-typescript": "^2.4.0",
|
|
49
|
+
tinyglobby: "^0.2.17",
|
|
50
|
+
ws: "^8.22.2",
|
|
51
|
+
zod: "^4.6.7"
|
|
52
|
+
},
|
|
53
|
+
peerDependencies: {
|
|
54
|
+
react: "^18.3.0 || ^19.0.0",
|
|
55
|
+
"react-dom": "^18.3.0 || ^19.0.0",
|
|
56
|
+
tailwindcss: "^3.4.0 || ^4.0.0",
|
|
57
|
+
vite: "^7.0.0 || ^8.0.0"
|
|
58
|
+
},
|
|
59
|
+
peerDependenciesMeta: {
|
|
60
|
+
tailwindcss: {
|
|
61
|
+
optional: true
|
|
62
|
+
},
|
|
63
|
+
vite: {
|
|
64
|
+
optional: true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
devDependencies: {
|
|
68
|
+
"@babel/core": "^7.28.5",
|
|
69
|
+
"@types/babel__traverse": "7.28.0",
|
|
70
|
+
"@types/node": "^22.20.5",
|
|
71
|
+
"@types/react": "^19.3.0",
|
|
72
|
+
"@types/react-dom": "^19.3.0",
|
|
73
|
+
"@types/ws": "^8.18.2",
|
|
74
|
+
eslint: "^10.14.0",
|
|
75
|
+
prettier: "^3.10.0",
|
|
76
|
+
react: "^19.3.1",
|
|
77
|
+
"react-dom": "^19.3.1",
|
|
78
|
+
tsup: "^9.0.0",
|
|
79
|
+
typescript: "^6.0.3",
|
|
80
|
+
"typescript-eslint": "^8.72.2",
|
|
81
|
+
vitest: "^5.1.0"
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/config.ts
|
|
86
|
+
import { access } from "fs/promises";
|
|
87
|
+
import { resolve } from "path";
|
|
88
|
+
import { createJiti } from "jiti";
|
|
89
|
+
|
|
90
|
+
// src/schema/config.ts
|
|
91
|
+
import { z } from "zod";
|
|
92
|
+
var RuleSettingSchema = z.enum(["off", "info", "warning", "error"]);
|
|
93
|
+
var DEFAULT_INCLUDE = ["src/**/*.{jsx,tsx}"];
|
|
94
|
+
var DEFAULT_EXCLUDE = [
|
|
95
|
+
"**/node_modules/**",
|
|
96
|
+
"**/dist/**",
|
|
97
|
+
"**/build/**",
|
|
98
|
+
"**/*.test.{js,jsx,ts,tsx}",
|
|
99
|
+
"**/*.spec.{js,jsx,ts,tsx}",
|
|
100
|
+
"**/*.stories.{js,jsx,ts,tsx}"
|
|
101
|
+
];
|
|
102
|
+
var ConfigSchema = z.object({
|
|
103
|
+
include: z.array(z.string()).default([...DEFAULT_INCLUDE]),
|
|
104
|
+
exclude: z.array(z.string()).default([...DEFAULT_EXCLUDE]),
|
|
105
|
+
tokens: z.array(z.string()).default([]),
|
|
106
|
+
/**
|
|
107
|
+
* Reserved for isolated previews. Not used by the zero-config MVP.
|
|
108
|
+
*/
|
|
109
|
+
providers: z.string().optional(),
|
|
110
|
+
tolerance: z.object({
|
|
111
|
+
px: z.number().nonnegative()
|
|
112
|
+
}).default({ px: 1 }),
|
|
113
|
+
baseline: z.string().default(".snoop/baseline.json"),
|
|
114
|
+
basePath: z.string().startsWith("/").default("/ui"),
|
|
115
|
+
appUrl: z.string().default("/"),
|
|
116
|
+
rules: z.record(z.string(), RuleSettingSchema).default({}),
|
|
117
|
+
editor: z.string().optional()
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// src/config.ts
|
|
121
|
+
var CONFIG_FILES = [
|
|
122
|
+
"snoop.config.ts",
|
|
123
|
+
"snoop.config.mts",
|
|
124
|
+
"snoop.config.js",
|
|
125
|
+
"snoop.config.mjs",
|
|
126
|
+
"snoop.config.cjs"
|
|
127
|
+
];
|
|
128
|
+
function defineConfig(config) {
|
|
129
|
+
return config;
|
|
130
|
+
}
|
|
131
|
+
async function exists(path) {
|
|
132
|
+
try {
|
|
133
|
+
await access(path);
|
|
134
|
+
return true;
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function findConfigFile(options = {}) {
|
|
140
|
+
const cwd = options.cwd ?? process.cwd();
|
|
141
|
+
if (options.configFile) {
|
|
142
|
+
const candidate = resolve(cwd, options.configFile);
|
|
143
|
+
if (!await exists(candidate)) {
|
|
144
|
+
throw new Error(`Snoop config not found: ${candidate}`);
|
|
145
|
+
}
|
|
146
|
+
return candidate;
|
|
147
|
+
}
|
|
148
|
+
for (const name of CONFIG_FILES) {
|
|
149
|
+
const candidate = resolve(cwd, name);
|
|
150
|
+
if (await exists(candidate)) {
|
|
151
|
+
return candidate;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return void 0;
|
|
155
|
+
}
|
|
156
|
+
async function loadConfig(options = {}) {
|
|
157
|
+
const configFile = await findConfigFile(options);
|
|
158
|
+
if (!configFile) {
|
|
159
|
+
return ConfigSchema.parse({});
|
|
160
|
+
}
|
|
161
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true });
|
|
162
|
+
let loaded;
|
|
163
|
+
try {
|
|
164
|
+
loaded = await jiti.import(configFile);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
167
|
+
throw new Error(`Failed to load Snoop config at ${configFile}: ${message}`, {
|
|
168
|
+
cause: error
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const raw = isRecord(loaded) && "default" in loaded ? loaded.default : loaded;
|
|
172
|
+
const result = ConfigSchema.safeParse(raw);
|
|
173
|
+
if (!result.success) {
|
|
174
|
+
const details = result.error.issues.map((issue) => ` - ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n");
|
|
175
|
+
throw new Error(`Invalid Snoop config at ${configFile}:
|
|
176
|
+
${details}`);
|
|
177
|
+
}
|
|
178
|
+
return result.data;
|
|
179
|
+
}
|
|
180
|
+
function isRecord(value) {
|
|
181
|
+
return typeof value === "object" && value !== null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/scanner/analyze.ts
|
|
185
|
+
import { parse as parseSource } from "@babel/parser";
|
|
186
|
+
import traverseModule from "@babel/traverse";
|
|
187
|
+
import { ERROR_CODES, builtinResolvers, parse as parseComponents } from "react-docgen";
|
|
188
|
+
|
|
189
|
+
// src/utils/paths.ts
|
|
190
|
+
import { relative, sep } from "path";
|
|
191
|
+
function toPosixPath(value) {
|
|
192
|
+
return value.split(sep).join("/");
|
|
193
|
+
}
|
|
194
|
+
function relativePosix(from, to) {
|
|
195
|
+
return toPosixPath(relative(from, to));
|
|
196
|
+
}
|
|
197
|
+
function sanitizeMessage(message, cwd) {
|
|
198
|
+
const firstLine = message.split("\n")[0] ?? message;
|
|
199
|
+
return firstLine.split(cwd).join(".").split("\\").join("/");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/scanner/analyze.ts
|
|
203
|
+
var traverse = typeof traverseModule === "function" ? traverseModule : traverseModule.default;
|
|
204
|
+
var COMPONENT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
205
|
+
".ts",
|
|
206
|
+
".tsx",
|
|
207
|
+
".mts",
|
|
208
|
+
".cts",
|
|
209
|
+
".js",
|
|
210
|
+
".jsx",
|
|
211
|
+
".mjs",
|
|
212
|
+
".cjs"
|
|
213
|
+
]);
|
|
214
|
+
function analyzeFile(options) {
|
|
215
|
+
const { absolutePath, cwd, file, hash, source } = options;
|
|
216
|
+
const extension = absolutePath.slice(absolutePath.lastIndexOf("."));
|
|
217
|
+
let ast;
|
|
218
|
+
try {
|
|
219
|
+
ast = parseSource(source, {
|
|
220
|
+
sourceType: "unambiguous",
|
|
221
|
+
plugins: parserPlugins(extension),
|
|
222
|
+
attachComment: false,
|
|
223
|
+
errorRecovery: false
|
|
224
|
+
});
|
|
225
|
+
} catch (error) {
|
|
226
|
+
return {
|
|
227
|
+
error: {
|
|
228
|
+
code: "parse/syntax",
|
|
229
|
+
file,
|
|
230
|
+
message: sanitizeMessage(
|
|
231
|
+
error instanceof Error ? error.message : "Unknown syntax error",
|
|
232
|
+
cwd
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const moduleInfo = collectModuleInfo(ast.program.body);
|
|
238
|
+
const imports = collectImports(ast.program.body);
|
|
239
|
+
const usages = collectJsxUsages(ast);
|
|
240
|
+
const inlineStyles = collectInlineStyles(ast, file);
|
|
241
|
+
let docs = [];
|
|
242
|
+
let docgenError;
|
|
243
|
+
try {
|
|
244
|
+
docs = parseComponents(source, {
|
|
245
|
+
filename: absolutePath,
|
|
246
|
+
babelOptions: {
|
|
247
|
+
filename: absolutePath,
|
|
248
|
+
babelrc: false,
|
|
249
|
+
configFile: false
|
|
250
|
+
},
|
|
251
|
+
resolver: new builtinResolvers.FindExportedDefinitionsResolver()
|
|
252
|
+
});
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (!isMissingDefinition(error)) {
|
|
255
|
+
docgenError = {
|
|
256
|
+
code: "parse/components",
|
|
257
|
+
file,
|
|
258
|
+
message: sanitizeMessage(
|
|
259
|
+
error instanceof Error ? error.message : "Could not extract component metadata",
|
|
260
|
+
cwd
|
|
261
|
+
)
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const components = buildComponents(docs, moduleInfo);
|
|
266
|
+
return {
|
|
267
|
+
analysis: {
|
|
268
|
+
file,
|
|
269
|
+
hash,
|
|
270
|
+
components,
|
|
271
|
+
imports,
|
|
272
|
+
usages,
|
|
273
|
+
inlineStyles
|
|
274
|
+
},
|
|
275
|
+
...docgenError ? { error: docgenError } : {}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function parserPlugins(extension) {
|
|
279
|
+
const plugins = ["jsx", "decorators-legacy", "importAttributes"];
|
|
280
|
+
if (COMPONENT_EXTENSIONS.has(extension) && extension !== ".js" && extension !== ".jsx") {
|
|
281
|
+
plugins.push("typescript");
|
|
282
|
+
}
|
|
283
|
+
return plugins;
|
|
284
|
+
}
|
|
285
|
+
function collectModuleInfo(body) {
|
|
286
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
287
|
+
const exportsByLocal = /* @__PURE__ */ new Map();
|
|
288
|
+
const addExport = (localName, exportName) => {
|
|
289
|
+
const names = exportsByLocal.get(localName) ?? /* @__PURE__ */ new Set();
|
|
290
|
+
names.add(exportName);
|
|
291
|
+
exportsByLocal.set(localName, names);
|
|
292
|
+
};
|
|
293
|
+
const addDeclaration = (name, node) => {
|
|
294
|
+
const loc = sourceRange(node);
|
|
295
|
+
if (!loc) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (!declarations.has(name)) {
|
|
299
|
+
declarations.set(name, {
|
|
300
|
+
name,
|
|
301
|
+
node,
|
|
302
|
+
start: node.start ?? 0,
|
|
303
|
+
end: node.end ?? 0,
|
|
304
|
+
loc
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
const addVariableDeclaration = (node, exported) => {
|
|
309
|
+
for (const declarator of node.declarations) {
|
|
310
|
+
if (declarator.id.type !== "Identifier") {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
addDeclaration(declarator.id.name, declarator);
|
|
314
|
+
if (exported) {
|
|
315
|
+
addExport(declarator.id.name, declarator.id.name);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
let defaultDeclaration;
|
|
320
|
+
for (const statement of body) {
|
|
321
|
+
switch (statement.type) {
|
|
322
|
+
case "ExportNamedDeclaration": {
|
|
323
|
+
if (statement.exportKind === "type") {
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
const declaration = statement.declaration;
|
|
327
|
+
switch (declaration?.type) {
|
|
328
|
+
case "FunctionDeclaration":
|
|
329
|
+
case "ClassDeclaration":
|
|
330
|
+
if (declaration.id) {
|
|
331
|
+
addDeclaration(declaration.id.name, declaration);
|
|
332
|
+
addExport(declaration.id.name, declaration.id.name);
|
|
333
|
+
}
|
|
334
|
+
break;
|
|
335
|
+
case "VariableDeclaration":
|
|
336
|
+
addVariableDeclaration(declaration, true);
|
|
337
|
+
break;
|
|
338
|
+
default:
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
for (const specifier of statement.specifiers) {
|
|
342
|
+
if (specifier.type !== "ExportSpecifier" || specifier.exportKind === "type") {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const localName = identifierName(specifier.local);
|
|
346
|
+
const exportName = specifier.exported.type === "Identifier" ? specifier.exported.name : specifier.exported.value;
|
|
347
|
+
if (localName) {
|
|
348
|
+
addExport(localName, exportName);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
case "ExportDefaultDeclaration": {
|
|
354
|
+
const declaration = statement.declaration;
|
|
355
|
+
const loc = sourceRange(declaration);
|
|
356
|
+
if (!loc) {
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
const name = declarationName(declaration);
|
|
360
|
+
defaultDeclaration = {
|
|
361
|
+
name: name ?? "DefaultExport",
|
|
362
|
+
node: declaration,
|
|
363
|
+
start: declaration.start ?? 0,
|
|
364
|
+
end: declaration.end ?? 0,
|
|
365
|
+
loc
|
|
366
|
+
};
|
|
367
|
+
if (name) {
|
|
368
|
+
addExport(name, "default");
|
|
369
|
+
}
|
|
370
|
+
if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && declaration.id) {
|
|
371
|
+
addDeclaration(declaration.id.name, declaration);
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case "FunctionDeclaration":
|
|
376
|
+
case "ClassDeclaration":
|
|
377
|
+
if (statement.id) {
|
|
378
|
+
addDeclaration(statement.id.name, statement);
|
|
379
|
+
}
|
|
380
|
+
break;
|
|
381
|
+
case "VariableDeclaration":
|
|
382
|
+
addVariableDeclaration(statement, false);
|
|
383
|
+
break;
|
|
384
|
+
default:
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
declarations,
|
|
390
|
+
exportsByLocal,
|
|
391
|
+
...defaultDeclaration ? { defaultDeclaration } : {}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function collectImports(body) {
|
|
395
|
+
const imports = [];
|
|
396
|
+
for (const statement of body) {
|
|
397
|
+
if (statement.type !== "ImportDeclaration" || statement.importKind === "type") {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const source = statement.source.value;
|
|
401
|
+
for (const specifier of statement.specifiers) {
|
|
402
|
+
if (specifier.type === "ImportSpecifier" && specifier.importKind === "type") {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
switch (specifier.type) {
|
|
406
|
+
case "ImportDefaultSpecifier":
|
|
407
|
+
imports.push({ source, imported: "default", local: specifier.local.name });
|
|
408
|
+
break;
|
|
409
|
+
case "ImportNamespaceSpecifier":
|
|
410
|
+
imports.push({ source, imported: "*", local: specifier.local.name });
|
|
411
|
+
break;
|
|
412
|
+
case "ImportSpecifier": {
|
|
413
|
+
const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
|
|
414
|
+
imports.push({ source, imported, local: specifier.local.name });
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
default:
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return imports.sort((a, b) => compare(a.local, b.local));
|
|
423
|
+
}
|
|
424
|
+
function collectInlineStyles(ast, file) {
|
|
425
|
+
const inlineStyles = [];
|
|
426
|
+
traverse(ast, {
|
|
427
|
+
JSXOpeningElement(path) {
|
|
428
|
+
const name = path.node.name;
|
|
429
|
+
if (name.type !== "JSXIdentifier" || !isIntrinsicName(name.name)) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const attribute = path.node.attributes.find(
|
|
433
|
+
(candidate) => candidate.type === "JSXAttribute" && candidate.name.type === "JSXIdentifier" && candidate.name.name === "style"
|
|
434
|
+
);
|
|
435
|
+
if (!attribute) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const range = sourceRange(attribute);
|
|
439
|
+
const line = name.loc?.start.line;
|
|
440
|
+
const col = name.loc ? name.loc.start.column + 1 : 0;
|
|
441
|
+
if (!range || !line || col < 1) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
inlineStyles.push({
|
|
445
|
+
src: `${file}:${line}:${col}`,
|
|
446
|
+
file,
|
|
447
|
+
range,
|
|
448
|
+
confidence: isStaticStyleObject(attribute) ? "EXTRACTED" : "INFERRED"
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
return inlineStyles.sort((a, b) => compare(a.src, b.src));
|
|
453
|
+
}
|
|
454
|
+
function isStaticStyleObject(attribute) {
|
|
455
|
+
const value = attribute.value;
|
|
456
|
+
if (value?.type !== "JSXExpressionContainer") {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
const expression = value.expression;
|
|
460
|
+
return expression.type === "ObjectExpression" && !expression.properties.some((property) => property.type === "SpreadElement");
|
|
461
|
+
}
|
|
462
|
+
function isIntrinsicName(name) {
|
|
463
|
+
const first = name.charAt(0);
|
|
464
|
+
return first === first.toLowerCase() && first !== first.toUpperCase();
|
|
465
|
+
}
|
|
466
|
+
function collectJsxUsages(ast) {
|
|
467
|
+
const usages = [];
|
|
468
|
+
traverse(ast, {
|
|
469
|
+
JSXOpeningElement(path) {
|
|
470
|
+
const name = jsxName(path.node.name);
|
|
471
|
+
if (!name) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const loc = sourceRange(path.node);
|
|
475
|
+
if (!loc) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
usages.push({
|
|
479
|
+
local: name.local,
|
|
480
|
+
...name.member ? { member: name.member } : {},
|
|
481
|
+
start: path.node.start ?? 0,
|
|
482
|
+
loc
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
return usages.sort((a, b) => a.start - b.start || compare(a.local, b.local));
|
|
487
|
+
}
|
|
488
|
+
function buildComponents(docs, moduleInfo) {
|
|
489
|
+
const matched = [];
|
|
490
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
491
|
+
for (const doc of docs) {
|
|
492
|
+
const displayName = typeof doc.displayName === "string" ? doc.displayName : void 0;
|
|
493
|
+
let declaration = displayName ? moduleInfo.declarations.get(displayName) : void 0;
|
|
494
|
+
if (!declaration && moduleInfo.defaultDeclaration) {
|
|
495
|
+
if (!displayName || docs.length === 1 || moduleInfo.defaultDeclaration.name === displayName) {
|
|
496
|
+
declaration = moduleInfo.defaultDeclaration;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (!declaration) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const exportNames = new Set(moduleInfo.exportsByLocal.get(declaration.name) ?? []);
|
|
503
|
+
if (moduleInfo.defaultDeclaration?.node === declaration.node) {
|
|
504
|
+
exportNames.add("default");
|
|
505
|
+
}
|
|
506
|
+
if (exportNames.size === 0) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const name = uniqueName(declaration.name, usedNames);
|
|
510
|
+
const sortedExports = [...exportNames].sort(compare);
|
|
511
|
+
const exportName = sortedExports.includes("default") ? "default" : sortedExports[0];
|
|
512
|
+
if (!exportName) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
matched.push({
|
|
516
|
+
start: declaration.start,
|
|
517
|
+
component: {
|
|
518
|
+
name,
|
|
519
|
+
exportNames: sortedExports,
|
|
520
|
+
loc: declaration.loc,
|
|
521
|
+
start: declaration.start,
|
|
522
|
+
end: declaration.end,
|
|
523
|
+
props: propsFromDoc(doc)
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
matched.sort((a, b) => a.start - b.start);
|
|
528
|
+
return matched.map((entry) => entry.component);
|
|
529
|
+
}
|
|
530
|
+
function propsFromDoc(doc) {
|
|
531
|
+
const props = [];
|
|
532
|
+
for (const [name, descriptor] of Object.entries(doc.props ?? {})) {
|
|
533
|
+
const type = typeText(descriptor);
|
|
534
|
+
const defaultValue = descriptor.defaultValue?.value;
|
|
535
|
+
props.push({
|
|
536
|
+
name,
|
|
537
|
+
required: Boolean(descriptor.required),
|
|
538
|
+
...type ? { type } : {},
|
|
539
|
+
...defaultValue !== void 0 && defaultValue !== null ? { defaultValue: String(defaultValue) } : {}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return props.sort((a, b) => compare(a.name, b.name));
|
|
543
|
+
}
|
|
544
|
+
function typeText(descriptor) {
|
|
545
|
+
return typeDescriptorText(descriptor.tsType) ?? typeDescriptorText(descriptor.flowType) ?? propTypeText(descriptor);
|
|
546
|
+
}
|
|
547
|
+
function typeDescriptorText(descriptor) {
|
|
548
|
+
if (!descriptor) {
|
|
549
|
+
return void 0;
|
|
550
|
+
}
|
|
551
|
+
const candidate = descriptor;
|
|
552
|
+
if (typeof candidate.raw === "string" && candidate.raw.length > 0) {
|
|
553
|
+
return candidate.raw;
|
|
554
|
+
}
|
|
555
|
+
if (candidate.name === "literal" && typeof candidate.value === "string") {
|
|
556
|
+
return candidate.value;
|
|
557
|
+
}
|
|
558
|
+
if (typeof candidate.name === "string" && candidate.name.length > 0) {
|
|
559
|
+
return candidate.name;
|
|
560
|
+
}
|
|
561
|
+
return void 0;
|
|
562
|
+
}
|
|
563
|
+
function propTypeText(descriptor) {
|
|
564
|
+
if (!descriptor.type) {
|
|
565
|
+
return void 0;
|
|
566
|
+
}
|
|
567
|
+
if (typeof descriptor.type.raw === "string" && descriptor.type.raw.length > 0) {
|
|
568
|
+
return descriptor.type.raw;
|
|
569
|
+
}
|
|
570
|
+
if (typeof descriptor.type.name === "string") {
|
|
571
|
+
return descriptor.type.name;
|
|
572
|
+
}
|
|
573
|
+
return void 0;
|
|
574
|
+
}
|
|
575
|
+
function jsxName(name) {
|
|
576
|
+
if (name.type === "JSXIdentifier") {
|
|
577
|
+
return isComponentName(name.name) ? { local: name.name } : void 0;
|
|
578
|
+
}
|
|
579
|
+
if (name.type === "JSXMemberExpression" && name.object.type === "JSXIdentifier" && name.property.type === "JSXIdentifier" && isComponentName(name.object.name)) {
|
|
580
|
+
return { local: name.object.name, member: name.property.name };
|
|
581
|
+
}
|
|
582
|
+
return void 0;
|
|
583
|
+
}
|
|
584
|
+
function isComponentName(name) {
|
|
585
|
+
return /^[A-Z]/.test(name);
|
|
586
|
+
}
|
|
587
|
+
function declarationName(node) {
|
|
588
|
+
switch (node.type) {
|
|
589
|
+
case "Identifier":
|
|
590
|
+
return node.name;
|
|
591
|
+
case "FunctionDeclaration":
|
|
592
|
+
case "FunctionExpression":
|
|
593
|
+
case "ClassDeclaration":
|
|
594
|
+
case "ClassExpression":
|
|
595
|
+
return node.id?.name;
|
|
596
|
+
case "CallExpression": {
|
|
597
|
+
const argument = node.arguments[0];
|
|
598
|
+
return argument ? declarationName(argument) : void 0;
|
|
599
|
+
}
|
|
600
|
+
case "ParenthesizedExpression":
|
|
601
|
+
case "TSAsExpression":
|
|
602
|
+
case "TSSatisfiesExpression":
|
|
603
|
+
case "TSNonNullExpression":
|
|
604
|
+
case "TSTypeAssertion":
|
|
605
|
+
return declarationName(node.expression);
|
|
606
|
+
default:
|
|
607
|
+
return void 0;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function identifierName(node) {
|
|
611
|
+
return node.type === "Identifier" ? node.name : void 0;
|
|
612
|
+
}
|
|
613
|
+
function sourceRange(node) {
|
|
614
|
+
if (!node.loc) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
return {
|
|
618
|
+
start: {
|
|
619
|
+
line: node.loc.start.line,
|
|
620
|
+
col: node.loc.start.column + 1
|
|
621
|
+
},
|
|
622
|
+
end: {
|
|
623
|
+
line: node.loc.end.line,
|
|
624
|
+
col: node.loc.end.column + 1
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function uniqueName(name, used) {
|
|
629
|
+
if (!used.has(name)) {
|
|
630
|
+
used.add(name);
|
|
631
|
+
return name;
|
|
632
|
+
}
|
|
633
|
+
let counter = 2;
|
|
634
|
+
while (used.has(`${name}~${counter}`)) {
|
|
635
|
+
counter += 1;
|
|
636
|
+
}
|
|
637
|
+
const unique = `${name}~${counter}`;
|
|
638
|
+
used.add(unique);
|
|
639
|
+
return unique;
|
|
640
|
+
}
|
|
641
|
+
function compare(a, b) {
|
|
642
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
643
|
+
}
|
|
644
|
+
function isMissingDefinition(error) {
|
|
645
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === ERROR_CODES.MISSING_DEFINITION;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/scanner/resolve.ts
|
|
649
|
+
import { existsSync, statSync } from "fs";
|
|
650
|
+
import { dirname, extname, resolve as resolve2 } from "path";
|
|
651
|
+
var RESOLVABLE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mts", ".mjs", ".cts", ".cjs"];
|
|
652
|
+
function createImportResolver(cwd, files) {
|
|
653
|
+
return (fromFile, rawSource) => {
|
|
654
|
+
const source = rawSource.split(/[?#]/)[0] ?? rawSource;
|
|
655
|
+
if (!source) {
|
|
656
|
+
return void 0;
|
|
657
|
+
}
|
|
658
|
+
let base;
|
|
659
|
+
if (source.startsWith(".")) {
|
|
660
|
+
base = resolve2(cwd, dirname(fromFile), source);
|
|
661
|
+
} else if (source.startsWith("/")) {
|
|
662
|
+
base = resolve2(cwd, source.slice(1));
|
|
663
|
+
} else {
|
|
664
|
+
return void 0;
|
|
665
|
+
}
|
|
666
|
+
const sourceExtension = extname(base);
|
|
667
|
+
if (sourceExtension && !RESOLVABLE_EXTENSIONS.includes(sourceExtension)) {
|
|
668
|
+
return void 0;
|
|
669
|
+
}
|
|
670
|
+
const candidates = [];
|
|
671
|
+
if (sourceExtension) {
|
|
672
|
+
candidates.push(base);
|
|
673
|
+
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
674
|
+
candidates.push(`${base.slice(0, -sourceExtension.length)}${extension}`);
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
candidates.push(base);
|
|
678
|
+
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
679
|
+
candidates.push(`${base}${extension}`);
|
|
680
|
+
candidates.push(resolve2(base, `index${extension}`));
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
for (const candidate of candidates) {
|
|
684
|
+
if (!isFile(candidate)) {
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
const relative3 = relativePosix(cwd, candidate);
|
|
688
|
+
if (files.has(relative3)) {
|
|
689
|
+
return relative3;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return void 0;
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function isFile(path) {
|
|
696
|
+
if (!existsSync(path)) {
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
try {
|
|
700
|
+
return statSync(path).isFile();
|
|
701
|
+
} catch {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/scanner/scan.ts
|
|
707
|
+
import { createHash } from "crypto";
|
|
708
|
+
import { mkdir, readFile as readFile2, rename, writeFile } from "fs/promises";
|
|
709
|
+
import { dirname as dirname2, resolve as resolve5 } from "path";
|
|
710
|
+
import { glob as glob2 } from "tinyglobby";
|
|
711
|
+
|
|
712
|
+
// src/schema/manifest.ts
|
|
713
|
+
import { z as z3 } from "zod";
|
|
714
|
+
|
|
715
|
+
// src/schema/common.ts
|
|
716
|
+
import { z as z2 } from "zod";
|
|
717
|
+
var ConfidenceSchema = z2.enum(["EXTRACTED", "INFERRED"]);
|
|
718
|
+
var PositionSchema = z2.object({
|
|
719
|
+
line: z2.number().int().positive(),
|
|
720
|
+
col: z2.number().int().positive()
|
|
721
|
+
});
|
|
722
|
+
var RangeSchema = z2.object({
|
|
723
|
+
start: PositionSchema,
|
|
724
|
+
end: PositionSchema
|
|
725
|
+
});
|
|
726
|
+
var PosixPathSchema = z2.string().min(1).refine((value) => !value.includes("\\"), "Use POSIX path separators");
|
|
727
|
+
function compareStrings(a, b) {
|
|
728
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// src/schema/version.ts
|
|
732
|
+
var MANIFEST_VERSION = 1;
|
|
733
|
+
var DIAGNOSTICS_VERSION = 1;
|
|
734
|
+
var STYLE_INDEX_VERSION = 1;
|
|
735
|
+
|
|
736
|
+
// src/schema/manifest.ts
|
|
737
|
+
var PropSchema = z3.object({
|
|
738
|
+
name: z3.string().min(1),
|
|
739
|
+
type: z3.string().optional(),
|
|
740
|
+
required: z3.boolean().default(false),
|
|
741
|
+
defaultValue: z3.string().optional()
|
|
742
|
+
});
|
|
743
|
+
var ComponentNodeSchema = z3.object({
|
|
744
|
+
id: z3.string().min(1),
|
|
745
|
+
kind: z3.literal("component"),
|
|
746
|
+
name: z3.string().min(1),
|
|
747
|
+
file: PosixPathSchema,
|
|
748
|
+
exportName: z3.string().optional(),
|
|
749
|
+
loc: RangeSchema.optional(),
|
|
750
|
+
props: z3.array(PropSchema).default([])
|
|
751
|
+
});
|
|
752
|
+
var EdgeRelationSchema = z3.enum(["renders", "imports"]);
|
|
753
|
+
var EdgeSchema = z3.object({
|
|
754
|
+
from: z3.string().min(1),
|
|
755
|
+
to: z3.string().min(1),
|
|
756
|
+
relation: EdgeRelationSchema,
|
|
757
|
+
confidence: ConfidenceSchema
|
|
758
|
+
});
|
|
759
|
+
var ScanErrorSchema = z3.object({
|
|
760
|
+
code: z3.string().min(1),
|
|
761
|
+
file: PosixPathSchema,
|
|
762
|
+
message: z3.string().min(1)
|
|
763
|
+
});
|
|
764
|
+
var TokenTableSchema = z3.record(z3.string(), z3.string());
|
|
765
|
+
var ManifestSchema = z3.object({
|
|
766
|
+
version: z3.literal(MANIFEST_VERSION),
|
|
767
|
+
tokens: TokenTableSchema.default({}),
|
|
768
|
+
nodes: z3.array(ComponentNodeSchema).default([]),
|
|
769
|
+
edges: z3.array(EdgeSchema).default([]),
|
|
770
|
+
errors: z3.array(ScanErrorSchema).default([])
|
|
771
|
+
});
|
|
772
|
+
function sortManifest(manifest) {
|
|
773
|
+
return {
|
|
774
|
+
...manifest,
|
|
775
|
+
tokens: Object.fromEntries(
|
|
776
|
+
Object.entries(manifest.tokens).sort(([a], [b]) => compareStrings(a, b))
|
|
777
|
+
),
|
|
778
|
+
nodes: [...manifest.nodes].sort((a, b) => compareStrings(a.id, b.id)),
|
|
779
|
+
edges: [...manifest.edges].sort((a, b) => {
|
|
780
|
+
const left = `${a.from}\0${a.to}\0${a.relation}`;
|
|
781
|
+
const right = `${b.from}\0${b.to}\0${b.relation}`;
|
|
782
|
+
return compareStrings(left, right);
|
|
783
|
+
}),
|
|
784
|
+
errors: [...manifest.errors].sort((a, b) => {
|
|
785
|
+
const left = `${a.file}\0${a.code}\0${a.message}`;
|
|
786
|
+
const right = `${b.file}\0${b.code}\0${b.message}`;
|
|
787
|
+
return compareStrings(left, right);
|
|
788
|
+
})
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/styles/scan.ts
|
|
793
|
+
import { readFile } from "fs/promises";
|
|
794
|
+
import { relative as relative2, resolve as resolve4 } from "path";
|
|
795
|
+
import { glob } from "tinyglobby";
|
|
796
|
+
|
|
797
|
+
// src/schema/styles.ts
|
|
798
|
+
import { z as z4 } from "zod";
|
|
799
|
+
var StyleDeclarationSchema = z4.object({
|
|
800
|
+
property: z4.string().min(1),
|
|
801
|
+
value: z4.string(),
|
|
802
|
+
important: z4.boolean().default(false),
|
|
803
|
+
line: z4.number().int().positive(),
|
|
804
|
+
col: z4.number().int().positive()
|
|
805
|
+
});
|
|
806
|
+
var StyleRuleSchema = z4.object({
|
|
807
|
+
file: PosixPathSchema,
|
|
808
|
+
/** The selector exactly as written in the source file, normalized for lookup. */
|
|
809
|
+
selector: z4.string().min(1),
|
|
810
|
+
/**
|
|
811
|
+
* The selector the browser matches after CSS Modules rename classes. Absent
|
|
812
|
+
* when it is identical to `selector`.
|
|
813
|
+
*/
|
|
814
|
+
runtimeSelector: z4.string().min(1).optional(),
|
|
815
|
+
declarations: z4.array(StyleDeclarationSchema).default([]),
|
|
816
|
+
/** Enclosing conditional at-rules, outermost first, for example `@media (...)` */
|
|
817
|
+
atRules: z4.array(z4.string()).default([]),
|
|
818
|
+
/** True when the rule comes from a `*.module.css` file. */
|
|
819
|
+
module: z4.boolean().default(false),
|
|
820
|
+
line: z4.number().int().positive(),
|
|
821
|
+
col: z4.number().int().positive()
|
|
822
|
+
});
|
|
823
|
+
var InlineStyleSchema = z4.object({
|
|
824
|
+
/** `file:line:col` of the JSX element, matching `data-snoop-src`. */
|
|
825
|
+
src: z4.string().min(1),
|
|
826
|
+
file: PosixPathSchema,
|
|
827
|
+
range: RangeSchema,
|
|
828
|
+
/** EXTRACTED for an object literal, INFERRED for a spread or expression. */
|
|
829
|
+
confidence: ConfidenceSchema
|
|
830
|
+
});
|
|
831
|
+
var StyleErrorSchema = z4.object({
|
|
832
|
+
code: z4.string().min(1),
|
|
833
|
+
file: PosixPathSchema,
|
|
834
|
+
message: z4.string().min(1)
|
|
835
|
+
});
|
|
836
|
+
var StyleIndexSchema = z4.object({
|
|
837
|
+
version: z4.literal(STYLE_INDEX_VERSION),
|
|
838
|
+
tokens: z4.record(z4.string(), z4.string()).default({}),
|
|
839
|
+
rules: z4.array(StyleRuleSchema).default([]),
|
|
840
|
+
inlineStyles: z4.array(InlineStyleSchema).default([]),
|
|
841
|
+
/** True when the project uses Tailwind classes or a Tailwind config. */
|
|
842
|
+
tailwind: z4.boolean().default(false),
|
|
843
|
+
errors: z4.array(StyleErrorSchema).default([])
|
|
844
|
+
});
|
|
845
|
+
function sortStyleIndex(index) {
|
|
846
|
+
return {
|
|
847
|
+
...index,
|
|
848
|
+
tokens: Object.fromEntries(
|
|
849
|
+
Object.entries(index.tokens).sort(([a], [b]) => compareStrings(a, b))
|
|
850
|
+
),
|
|
851
|
+
rules: [...index.rules].sort((a, b) => {
|
|
852
|
+
const left = `${a.file}\0${String(a.line).padStart(8, "0")}\0${String(a.col).padStart(4, "0")}\0${a.selector}\0${a.runtimeSelector ?? ""}`;
|
|
853
|
+
const right = `${b.file}\0${String(b.line).padStart(8, "0")}\0${String(b.col).padStart(4, "0")}\0${b.selector}\0${b.runtimeSelector ?? ""}`;
|
|
854
|
+
return compareStrings(left, right);
|
|
855
|
+
}),
|
|
856
|
+
inlineStyles: [...index.inlineStyles].sort((a, b) => compareStrings(a.src, b.src)),
|
|
857
|
+
errors: [...index.errors].sort((a, b) => {
|
|
858
|
+
const left = `${a.file}\0${a.code}\0${a.message}`;
|
|
859
|
+
const right = `${b.file}\0${b.code}\0${b.message}`;
|
|
860
|
+
return compareStrings(left, right);
|
|
861
|
+
})
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/utils/async.ts
|
|
866
|
+
async function mapLimit(items, limit, task) {
|
|
867
|
+
const results = new Array(items.length);
|
|
868
|
+
let nextIndex = 0;
|
|
869
|
+
const workerCount = Math.max(1, Math.min(limit, items.length));
|
|
870
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
871
|
+
while (true) {
|
|
872
|
+
const index = nextIndex;
|
|
873
|
+
nextIndex += 1;
|
|
874
|
+
if (index >= items.length) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const item = items[index];
|
|
878
|
+
if (item === void 0) {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
results[index] = await task(item, index);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
await Promise.all(workers);
|
|
885
|
+
return results;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/utils/globs.ts
|
|
889
|
+
var ALWAYS_IGNORED = [
|
|
890
|
+
".snoop/**",
|
|
891
|
+
"**/node_modules/**",
|
|
892
|
+
"**/dist/**",
|
|
893
|
+
"**/build/**",
|
|
894
|
+
"**/coverage/**"
|
|
895
|
+
];
|
|
896
|
+
|
|
897
|
+
// src/styles/css.ts
|
|
898
|
+
import postcss from "postcss";
|
|
899
|
+
import selectorParser from "postcss-selector-parser";
|
|
900
|
+
|
|
901
|
+
// src/styles/selector.ts
|
|
902
|
+
function normalizeSelector(selector) {
|
|
903
|
+
return splitSelectorList(selector).map(normalizeComplexSelector).filter((part) => part.length > 0).join(", ");
|
|
904
|
+
}
|
|
905
|
+
function splitSelectorList(selector) {
|
|
906
|
+
const parts = [];
|
|
907
|
+
let current = "";
|
|
908
|
+
let quote;
|
|
909
|
+
let brackets = 0;
|
|
910
|
+
let parentheses = 0;
|
|
911
|
+
for (let index = 0; index < selector.length; index += 1) {
|
|
912
|
+
const character = selector[index];
|
|
913
|
+
if (quote) {
|
|
914
|
+
current += character;
|
|
915
|
+
if (character === "\\") {
|
|
916
|
+
const next = selector[index + 1];
|
|
917
|
+
if (next !== void 0) {
|
|
918
|
+
current += next;
|
|
919
|
+
index += 1;
|
|
920
|
+
}
|
|
921
|
+
} else if (character === quote) {
|
|
922
|
+
quote = void 0;
|
|
923
|
+
}
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
if (character === '"' || character === "'") {
|
|
927
|
+
quote = character;
|
|
928
|
+
current += character;
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
if (character === "[") {
|
|
932
|
+
brackets += 1;
|
|
933
|
+
} else if (character === "]") {
|
|
934
|
+
brackets = Math.max(0, brackets - 1);
|
|
935
|
+
} else if (character === "(") {
|
|
936
|
+
parentheses += 1;
|
|
937
|
+
} else if (character === ")") {
|
|
938
|
+
parentheses = Math.max(0, parentheses - 1);
|
|
939
|
+
}
|
|
940
|
+
if (character === "," && brackets === 0 && parentheses === 0) {
|
|
941
|
+
parts.push(current);
|
|
942
|
+
current = "";
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
current += character;
|
|
946
|
+
}
|
|
947
|
+
parts.push(current);
|
|
948
|
+
return parts;
|
|
949
|
+
}
|
|
950
|
+
function normalizeComplexSelector(selector) {
|
|
951
|
+
let output = "";
|
|
952
|
+
let quote;
|
|
953
|
+
let brackets = 0;
|
|
954
|
+
let parentheses = 0;
|
|
955
|
+
let pendingSpace = false;
|
|
956
|
+
const flushSpace = () => {
|
|
957
|
+
if (pendingSpace && output.length > 0 && !/[\s>+~|]$/.test(output)) {
|
|
958
|
+
output += " ";
|
|
959
|
+
}
|
|
960
|
+
pendingSpace = false;
|
|
961
|
+
};
|
|
962
|
+
for (let index = 0; index < selector.length; index += 1) {
|
|
963
|
+
const character = selector[index];
|
|
964
|
+
if (quote) {
|
|
965
|
+
output += character;
|
|
966
|
+
if (character === "\\") {
|
|
967
|
+
const next = selector[index + 1];
|
|
968
|
+
if (next !== void 0) {
|
|
969
|
+
output += next;
|
|
970
|
+
index += 1;
|
|
971
|
+
}
|
|
972
|
+
} else if (character === quote) {
|
|
973
|
+
quote = void 0;
|
|
974
|
+
}
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
if (character === '"' || character === "'") {
|
|
978
|
+
flushSpace();
|
|
979
|
+
quote = character;
|
|
980
|
+
output += character;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (character === "[") {
|
|
984
|
+
flushSpace();
|
|
985
|
+
brackets += 1;
|
|
986
|
+
output += character;
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
if (character === "]") {
|
|
990
|
+
brackets = Math.max(0, brackets - 1);
|
|
991
|
+
output += character;
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
if (character === "(") {
|
|
995
|
+
flushSpace();
|
|
996
|
+
parentheses += 1;
|
|
997
|
+
output += character;
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (character === ")") {
|
|
1001
|
+
parentheses = Math.max(0, parentheses - 1);
|
|
1002
|
+
output += character;
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
if (brackets > 0 || parentheses > 0) {
|
|
1006
|
+
output += character;
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
if (/\s/.test(character)) {
|
|
1010
|
+
pendingSpace = true;
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
if (character === ">" || character === "+" || character === "~") {
|
|
1014
|
+
output = output.replace(/\s+$/, "");
|
|
1015
|
+
output += character;
|
|
1016
|
+
pendingSpace = false;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
if (character === "|" && selector[index + 1] === "|") {
|
|
1020
|
+
output = output.replace(/\s+$/, "");
|
|
1021
|
+
output += "||";
|
|
1022
|
+
pendingSpace = false;
|
|
1023
|
+
index += 1;
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
flushSpace();
|
|
1027
|
+
output += character;
|
|
1028
|
+
}
|
|
1029
|
+
return output.trim();
|
|
1030
|
+
}
|
|
1031
|
+
var LEGACY_PSEUDO_ELEMENTS = /* @__PURE__ */ new Set(["before", "after", "first-line", "first-letter"]);
|
|
1032
|
+
function specificityOf(selector) {
|
|
1033
|
+
let best = { id: 0, class: 0, type: 0 };
|
|
1034
|
+
for (const part of splitSelectorList(selector)) {
|
|
1035
|
+
const value = specificityOfComplexSelector(part);
|
|
1036
|
+
if (compareSpecificity(value, best) > 0) {
|
|
1037
|
+
best = value;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return best;
|
|
1041
|
+
}
|
|
1042
|
+
function compareSpecificity(a, b) {
|
|
1043
|
+
return a.id - b.id || a.class - b.class || a.type - b.type;
|
|
1044
|
+
}
|
|
1045
|
+
function specificityOfComplexSelector(selector) {
|
|
1046
|
+
const result = { id: 0, class: 0, type: 0 };
|
|
1047
|
+
let index = 0;
|
|
1048
|
+
while (index < selector.length) {
|
|
1049
|
+
const character = selector[index];
|
|
1050
|
+
if (/\s/.test(character) || character === ">" || character === "+" || character === "~") {
|
|
1051
|
+
index += 1;
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
if (character === "," || character === "*") {
|
|
1055
|
+
index += 1;
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
if (character === '"' || character === "'") {
|
|
1059
|
+
index = skipString(selector, index);
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
if (character === "[") {
|
|
1063
|
+
result.class += 1;
|
|
1064
|
+
index = skipBalanced(selector, index, "[", "]");
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
if (character === "#") {
|
|
1068
|
+
result.id += 1;
|
|
1069
|
+
index = skipIdentifier(selector, index + 1);
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
if (character === ".") {
|
|
1073
|
+
result.class += 1;
|
|
1074
|
+
index = skipIdentifier(selector, index + 1);
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
if (character === ":") {
|
|
1078
|
+
const doubleColon = selector[index + 1] === ":";
|
|
1079
|
+
const nameStart = index + (doubleColon ? 2 : 1);
|
|
1080
|
+
const nameEnd = skipIdentifier(selector, nameStart);
|
|
1081
|
+
const name = selector.slice(nameStart, nameEnd).toLowerCase();
|
|
1082
|
+
if (doubleColon || LEGACY_PSEUDO_ELEMENTS.has(name)) {
|
|
1083
|
+
result.type += 1;
|
|
1084
|
+
index = nameEnd;
|
|
1085
|
+
if (selector[index] === "(") {
|
|
1086
|
+
index = skipBalanced(selector, index, "(", ")");
|
|
1087
|
+
}
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
if (selector[nameEnd] === "(") {
|
|
1091
|
+
const argumentEnd = skipBalanced(selector, nameEnd, "(", ")");
|
|
1092
|
+
const argument = selector.slice(nameEnd + 1, argumentEnd - 1);
|
|
1093
|
+
if (name === "where") {
|
|
1094
|
+
} else if (name === "is" || name === "not" || name === "has") {
|
|
1095
|
+
addInPlace(result, maxSpecificity(argument));
|
|
1096
|
+
} else if (name === "nth-child" || name === "nth-last-child") {
|
|
1097
|
+
result.class += 1;
|
|
1098
|
+
const ofIndex = findNthOf(argument);
|
|
1099
|
+
if (ofIndex >= 0) {
|
|
1100
|
+
addInPlace(result, maxSpecificity(argument.slice(ofIndex)));
|
|
1101
|
+
}
|
|
1102
|
+
} else {
|
|
1103
|
+
result.class += 1;
|
|
1104
|
+
}
|
|
1105
|
+
index = argumentEnd;
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
result.class += 1;
|
|
1109
|
+
index = nameEnd;
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
if (character === "\\") {
|
|
1113
|
+
result.type += 1;
|
|
1114
|
+
index = skipIdentifier(selector, index);
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (character === "&") {
|
|
1118
|
+
index += 1;
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
if (isIdentifierStart(character)) {
|
|
1122
|
+
const end = skipIdentifier(selector, index);
|
|
1123
|
+
if (selector[end] === "|" && selector[end + 1] !== "|") {
|
|
1124
|
+
index = end + 1;
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
result.type += 1;
|
|
1128
|
+
index = end;
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
index += 1;
|
|
1132
|
+
}
|
|
1133
|
+
return result;
|
|
1134
|
+
}
|
|
1135
|
+
function maxSpecificity(selectorList) {
|
|
1136
|
+
let best = { id: 0, class: 0, type: 0 };
|
|
1137
|
+
for (const part of splitSelectorList(selectorList)) {
|
|
1138
|
+
const value = specificityOfComplexSelector(part);
|
|
1139
|
+
if (compareSpecificity(value, best) > 0) {
|
|
1140
|
+
best = value;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return best;
|
|
1144
|
+
}
|
|
1145
|
+
function addInPlace(target, value) {
|
|
1146
|
+
target.id += value.id;
|
|
1147
|
+
target.class += value.class;
|
|
1148
|
+
target.type += value.type;
|
|
1149
|
+
}
|
|
1150
|
+
function findNthOf(argument) {
|
|
1151
|
+
let quote;
|
|
1152
|
+
let brackets = 0;
|
|
1153
|
+
let parentheses = 0;
|
|
1154
|
+
for (let index = 0; index < argument.length; index += 1) {
|
|
1155
|
+
const character = argument[index];
|
|
1156
|
+
if (quote) {
|
|
1157
|
+
if (character === "\\") {
|
|
1158
|
+
index += 1;
|
|
1159
|
+
} else if (character === quote) {
|
|
1160
|
+
quote = void 0;
|
|
1161
|
+
}
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
if (character === '"' || character === "'") {
|
|
1165
|
+
quote = character;
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
if (character === "[") {
|
|
1169
|
+
brackets += 1;
|
|
1170
|
+
} else if (character === "]") {
|
|
1171
|
+
brackets = Math.max(0, brackets - 1);
|
|
1172
|
+
} else if (character === "(") {
|
|
1173
|
+
parentheses += 1;
|
|
1174
|
+
} else if (character === ")") {
|
|
1175
|
+
parentheses = Math.max(0, parentheses - 1);
|
|
1176
|
+
} else if (brackets === 0 && parentheses === 0 && character.toLowerCase() === "o" && argument[index + 1]?.toLowerCase() === "f" && /\s/.test(argument[index - 1] ?? " ") && /[\s.:#[]/.test(argument[index + 2] ?? " ")) {
|
|
1177
|
+
return index + 2;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return -1;
|
|
1181
|
+
}
|
|
1182
|
+
function skipString(value, start) {
|
|
1183
|
+
const quote = value[start];
|
|
1184
|
+
let index = start + 1;
|
|
1185
|
+
while (index < value.length) {
|
|
1186
|
+
if (value[index] === "\\") {
|
|
1187
|
+
index += 2;
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
if (value[index] === quote) {
|
|
1191
|
+
return index + 1;
|
|
1192
|
+
}
|
|
1193
|
+
index += 1;
|
|
1194
|
+
}
|
|
1195
|
+
return value.length;
|
|
1196
|
+
}
|
|
1197
|
+
function skipBalanced(value, start, open, close) {
|
|
1198
|
+
let depth = 0;
|
|
1199
|
+
let quote;
|
|
1200
|
+
for (let index = start; index < value.length; index += 1) {
|
|
1201
|
+
const character = value[index];
|
|
1202
|
+
if (quote) {
|
|
1203
|
+
if (character === "\\") {
|
|
1204
|
+
index += 1;
|
|
1205
|
+
} else if (character === quote) {
|
|
1206
|
+
quote = void 0;
|
|
1207
|
+
}
|
|
1208
|
+
continue;
|
|
1209
|
+
}
|
|
1210
|
+
if (character === '"' || character === "'") {
|
|
1211
|
+
quote = character;
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (character === open) {
|
|
1215
|
+
depth += 1;
|
|
1216
|
+
} else if (character === close) {
|
|
1217
|
+
depth -= 1;
|
|
1218
|
+
if (depth === 0) {
|
|
1219
|
+
return index + 1;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return value.length;
|
|
1224
|
+
}
|
|
1225
|
+
function skipIdentifier(value, start) {
|
|
1226
|
+
let index = start;
|
|
1227
|
+
while (index < value.length) {
|
|
1228
|
+
const character = value[index];
|
|
1229
|
+
if (character === "\\") {
|
|
1230
|
+
index += 2;
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
if (!isIdentifierCharacter(character)) {
|
|
1234
|
+
return index;
|
|
1235
|
+
}
|
|
1236
|
+
index += 1;
|
|
1237
|
+
}
|
|
1238
|
+
return index;
|
|
1239
|
+
}
|
|
1240
|
+
function isIdentifierStart(character) {
|
|
1241
|
+
return /[a-zA-Z_\u0080-\uffff-]/.test(character);
|
|
1242
|
+
}
|
|
1243
|
+
function isIdentifierCharacter(character) {
|
|
1244
|
+
return /[a-zA-Z0-9_\u0080-\uffff-]/.test(character);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// src/styles/css.ts
|
|
1248
|
+
function parseStyleSheet(file, source, options = {}) {
|
|
1249
|
+
const root = postcss.parse(source, { from: file });
|
|
1250
|
+
const rules = [];
|
|
1251
|
+
const tokens = {};
|
|
1252
|
+
let tailwind = false;
|
|
1253
|
+
let theme = false;
|
|
1254
|
+
root.walkAtRules((atRule) => {
|
|
1255
|
+
if (atRule.name === "tailwind") {
|
|
1256
|
+
tailwind = true;
|
|
1257
|
+
}
|
|
1258
|
+
if (atRule.name === "theme") {
|
|
1259
|
+
theme = true;
|
|
1260
|
+
}
|
|
1261
|
+
if (atRule.name === "import" && /(^|["'\s])tailwindcss(\/|["'\s]|$)/.test(atRule.params)) {
|
|
1262
|
+
tailwind = true;
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
root.walkDecls((declaration) => {
|
|
1266
|
+
if (declaration.prop.startsWith("--")) {
|
|
1267
|
+
tokens[declaration.prop] = declaration.value.trim();
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
root.walkRules((rule) => {
|
|
1271
|
+
if (isKeyframeRule(rule)) {
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const selector = normalizeSelector(rule.selector);
|
|
1275
|
+
if (!selector) {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
const declarations = directDeclarations(rule);
|
|
1279
|
+
if (declarations.length === 0) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
const runtimeSelector = options.classMap ? rewriteClasses(selector, options.classMap) : void 0;
|
|
1283
|
+
const normalizedRuntime = runtimeSelector ? normalizeSelector(runtimeSelector) : void 0;
|
|
1284
|
+
rules.push({
|
|
1285
|
+
file,
|
|
1286
|
+
selector,
|
|
1287
|
+
...normalizedRuntime && normalizedRuntime !== selector ? { runtimeSelector: normalizedRuntime } : {},
|
|
1288
|
+
declarations,
|
|
1289
|
+
atRules: enclosingAtRules(rule),
|
|
1290
|
+
module: file.endsWith(".module.css"),
|
|
1291
|
+
line: rule.source?.start?.line ?? 1,
|
|
1292
|
+
col: rule.source?.start?.column ?? 1
|
|
1293
|
+
});
|
|
1294
|
+
});
|
|
1295
|
+
return { rules, tokens, tailwind, theme };
|
|
1296
|
+
}
|
|
1297
|
+
function enclosingAtRules(node) {
|
|
1298
|
+
const atRules = [];
|
|
1299
|
+
let parent = node.parent;
|
|
1300
|
+
while (parent) {
|
|
1301
|
+
if (parent.type === "atrule") {
|
|
1302
|
+
const atRule = parent;
|
|
1303
|
+
if (atRule.name === "media" || atRule.name === "supports" || atRule.name === "container") {
|
|
1304
|
+
atRules.unshift(`@${atRule.name} ${atRule.params}`);
|
|
1305
|
+
} else if (atRule.name === "layer") {
|
|
1306
|
+
atRules.unshift(`@layer ${atRule.params}`);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
parent = parent.parent;
|
|
1310
|
+
}
|
|
1311
|
+
return atRules;
|
|
1312
|
+
}
|
|
1313
|
+
function directDeclarations(rule) {
|
|
1314
|
+
const declarations = [];
|
|
1315
|
+
for (const node of rule.nodes ?? []) {
|
|
1316
|
+
if (node.type !== "decl") {
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
const declaration = node;
|
|
1320
|
+
if (declaration.prop === "composes") {
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
declarations.push({
|
|
1324
|
+
property: declaration.prop,
|
|
1325
|
+
value: declaration.value,
|
|
1326
|
+
important: declaration.important ?? false,
|
|
1327
|
+
line: declaration.source?.start?.line ?? rule.source?.start?.line ?? 1,
|
|
1328
|
+
col: declaration.source?.start?.column ?? rule.source?.start?.column ?? 1
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
return declarations;
|
|
1332
|
+
}
|
|
1333
|
+
function isKeyframeRule(rule) {
|
|
1334
|
+
let parent = rule.parent;
|
|
1335
|
+
while (parent) {
|
|
1336
|
+
if (parent.type === "atrule") {
|
|
1337
|
+
const name = parent.name.toLowerCase();
|
|
1338
|
+
if (name === "keyframes" || name.endsWith("-keyframes")) {
|
|
1339
|
+
return true;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
parent = parent.parent;
|
|
1343
|
+
}
|
|
1344
|
+
return false;
|
|
1345
|
+
}
|
|
1346
|
+
function rewriteClasses(selector, classMap) {
|
|
1347
|
+
try {
|
|
1348
|
+
return selectorParser((root) => {
|
|
1349
|
+
root.walkClasses((node) => {
|
|
1350
|
+
const replacement = classMap[node.value];
|
|
1351
|
+
if (replacement) {
|
|
1352
|
+
node.value = replacement;
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
}).processSync(selector);
|
|
1356
|
+
} catch {
|
|
1357
|
+
return selector;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// src/styles/tailwind.ts
|
|
1362
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1363
|
+
import { createRequire } from "module";
|
|
1364
|
+
import { resolve as resolve3 } from "path";
|
|
1365
|
+
import { createJiti as createJiti2 } from "jiti";
|
|
1366
|
+
var CONFIG_FILES2 = [
|
|
1367
|
+
"tailwind.config.js",
|
|
1368
|
+
"tailwind.config.cjs",
|
|
1369
|
+
"tailwind.config.mjs",
|
|
1370
|
+
"tailwind.config.ts",
|
|
1371
|
+
"tailwind.config.cts",
|
|
1372
|
+
"tailwind.config.mts"
|
|
1373
|
+
];
|
|
1374
|
+
var CATEGORY_PREFIXES = {
|
|
1375
|
+
colors: "--color",
|
|
1376
|
+
spacing: "--spacing",
|
|
1377
|
+
borderRadius: "--radius",
|
|
1378
|
+
fontSize: "--text",
|
|
1379
|
+
fontFamily: "--font",
|
|
1380
|
+
fontWeight: "--font-weight",
|
|
1381
|
+
boxShadow: "--shadow",
|
|
1382
|
+
screens: "--breakpoint",
|
|
1383
|
+
transitionDuration: "--duration",
|
|
1384
|
+
transitionTimingFunction: "--ease",
|
|
1385
|
+
letterSpacing: "--tracking",
|
|
1386
|
+
lineHeight: "--leading",
|
|
1387
|
+
maxWidth: "--container"
|
|
1388
|
+
};
|
|
1389
|
+
async function extractTailwindTheme(cwd) {
|
|
1390
|
+
const configFile = CONFIG_FILES2.map((name) => resolve3(cwd, name)).find(
|
|
1391
|
+
(path) => existsSync2(path)
|
|
1392
|
+
);
|
|
1393
|
+
if (!configFile) {
|
|
1394
|
+
return { tokens: {}, found: false, errors: [] };
|
|
1395
|
+
}
|
|
1396
|
+
try {
|
|
1397
|
+
const require2 = createRequire(resolve3(cwd, "package.json"));
|
|
1398
|
+
const resolveConfig = require2("tailwindcss/resolveConfig");
|
|
1399
|
+
const defaultConfig = require2("tailwindcss/defaultConfig");
|
|
1400
|
+
const jiti = createJiti2(import.meta.url, { interopDefault: true });
|
|
1401
|
+
const loaded = await jiti.import(configFile);
|
|
1402
|
+
const userConfig = isRecord2(loaded) && "default" in loaded ? loaded.default : loaded;
|
|
1403
|
+
const resolved = resolveConfig(userConfig);
|
|
1404
|
+
const defaults = resolveConfig(defaultConfig);
|
|
1405
|
+
const tokens = {};
|
|
1406
|
+
for (const [category, prefix] of Object.entries(CATEGORY_PREFIXES)) {
|
|
1407
|
+
const projectValues = flattenCategory(resolved.theme[category]);
|
|
1408
|
+
const defaultValues = flattenCategory(defaults.theme[category]);
|
|
1409
|
+
for (const [path, value] of projectValues) {
|
|
1410
|
+
if (defaultValues.get(path) === value) {
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
tokens[`${prefix}-${tokenName(path)}`] = value;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return { tokens, found: true, errors: [] };
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
return {
|
|
1419
|
+
tokens: {},
|
|
1420
|
+
found: true,
|
|
1421
|
+
errors: [
|
|
1422
|
+
{
|
|
1423
|
+
code: "style/tailwind",
|
|
1424
|
+
file: toRelative(cwd, configFile),
|
|
1425
|
+
message: sanitizeMessage(
|
|
1426
|
+
error instanceof Error ? error.message : "Could not resolve the Tailwind theme",
|
|
1427
|
+
cwd
|
|
1428
|
+
)
|
|
1429
|
+
}
|
|
1430
|
+
]
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
function flattenCategory(value) {
|
|
1435
|
+
const result = /* @__PURE__ */ new Map();
|
|
1436
|
+
flatten(value, [], result);
|
|
1437
|
+
return result;
|
|
1438
|
+
}
|
|
1439
|
+
function flatten(value, path, result) {
|
|
1440
|
+
if (value === null || value === void 0) {
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
if (Array.isArray(value)) {
|
|
1444
|
+
const strings = value.filter((entry) => typeof entry === "string");
|
|
1445
|
+
if (strings.length > 0) {
|
|
1446
|
+
result.set(path.join("."), strings.join(", "));
|
|
1447
|
+
}
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
if (typeof value === "object") {
|
|
1451
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1452
|
+
flatten(child, [...path, key], result);
|
|
1453
|
+
}
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
1457
|
+
result.set(path.join("."), String(value));
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
function tokenName(path) {
|
|
1461
|
+
return path.split(".").filter((part) => part !== "DEFAULT").map(
|
|
1462
|
+
(part) => part.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9-]+/g, "-").toLowerCase()
|
|
1463
|
+
).filter((part) => part.length > 0).join("-");
|
|
1464
|
+
}
|
|
1465
|
+
function toRelative(cwd, file) {
|
|
1466
|
+
return file.slice(cwd.length + 1).split("\\").join("/");
|
|
1467
|
+
}
|
|
1468
|
+
function isRecord2(value) {
|
|
1469
|
+
return typeof value === "object" && value !== null;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// src/styles/scan.ts
|
|
1473
|
+
var READ_CONCURRENCY = 8;
|
|
1474
|
+
async function scanStyles(options = {}) {
|
|
1475
|
+
const cwd = resolve4(options.cwd ?? process.cwd());
|
|
1476
|
+
const defaults = ConfigSchema.parse({});
|
|
1477
|
+
const exclude = options.exclude ?? defaults.exclude;
|
|
1478
|
+
const discovered = await glob("**/*.css", {
|
|
1479
|
+
cwd,
|
|
1480
|
+
ignore: [...exclude, ...ALWAYS_IGNORED],
|
|
1481
|
+
absolute: false,
|
|
1482
|
+
onlyFiles: true,
|
|
1483
|
+
dot: false
|
|
1484
|
+
});
|
|
1485
|
+
const files = /* @__PURE__ */ new Set([...discovered.map(toPosixPath), ...tokenFiles(cwd, options.tokenFiles)]);
|
|
1486
|
+
const sortedFiles = [...files].sort(compare2);
|
|
1487
|
+
const moduleFiles = sortedFiles.filter((file) => file.endsWith(".module.css"));
|
|
1488
|
+
const errors = [];
|
|
1489
|
+
let classMaps = {};
|
|
1490
|
+
if (moduleFiles.length > 0 && options.resolveCssModules) {
|
|
1491
|
+
try {
|
|
1492
|
+
classMaps = await options.resolveCssModules(moduleFiles);
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
errors.push({
|
|
1495
|
+
code: "style/modules",
|
|
1496
|
+
file: moduleFiles[0] ?? "src",
|
|
1497
|
+
message: sanitizeMessage(
|
|
1498
|
+
error instanceof Error ? error.message : "Could not resolve CSS Modules",
|
|
1499
|
+
cwd
|
|
1500
|
+
)
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
const parsed = await mapLimit(sortedFiles, READ_CONCURRENCY, async (file) => {
|
|
1505
|
+
const absolutePath = resolve4(cwd, file);
|
|
1506
|
+
try {
|
|
1507
|
+
const source = await readFile(absolutePath, "utf8");
|
|
1508
|
+
return {
|
|
1509
|
+
file,
|
|
1510
|
+
result: parseStyleSheet(file, source, {
|
|
1511
|
+
...classMaps[file] ? { classMap: classMaps[file] } : {}
|
|
1512
|
+
})
|
|
1513
|
+
};
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
errors.push({
|
|
1516
|
+
code: "style/parse",
|
|
1517
|
+
file,
|
|
1518
|
+
message: sanitizeMessage(
|
|
1519
|
+
error instanceof Error ? error.message : "Could not parse CSS",
|
|
1520
|
+
cwd
|
|
1521
|
+
)
|
|
1522
|
+
});
|
|
1523
|
+
return void 0;
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
const tailwind = await extractTailwindTheme(cwd);
|
|
1527
|
+
errors.push(...tailwind.errors);
|
|
1528
|
+
const tokens = { ...tailwind.tokens };
|
|
1529
|
+
const rules = [];
|
|
1530
|
+
let usesTailwind = tailwind.found;
|
|
1531
|
+
for (const entry of parsed) {
|
|
1532
|
+
if (!entry) {
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
rules.push(...entry.result.rules);
|
|
1536
|
+
Object.assign(tokens, entry.result.tokens);
|
|
1537
|
+
usesTailwind = usesTailwind || entry.result.tailwind || entry.result.theme;
|
|
1538
|
+
}
|
|
1539
|
+
return StyleIndexSchema.parse(
|
|
1540
|
+
sortStyleIndex({
|
|
1541
|
+
version: STYLE_INDEX_VERSION,
|
|
1542
|
+
tokens,
|
|
1543
|
+
rules,
|
|
1544
|
+
inlineStyles: [...options.inlineStyles ?? []],
|
|
1545
|
+
tailwind: usesTailwind,
|
|
1546
|
+
errors
|
|
1547
|
+
})
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
function tokenFiles(cwd, entries) {
|
|
1551
|
+
if (!entries) {
|
|
1552
|
+
return [];
|
|
1553
|
+
}
|
|
1554
|
+
return entries.map((entry) => toPosixPath(relative2(cwd, resolve4(cwd, entry)))).filter((entry) => entry.length > 0);
|
|
1555
|
+
}
|
|
1556
|
+
function compare2(a, b) {
|
|
1557
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// src/rules/index.ts
|
|
1561
|
+
var RULE_NAMES = [
|
|
1562
|
+
"token/hardcoded-match",
|
|
1563
|
+
"token/near-match",
|
|
1564
|
+
"token/undefined-var"
|
|
1565
|
+
];
|
|
1566
|
+
function createRulesEngine(context, enabled = {}) {
|
|
1567
|
+
const allDiagnostics = [];
|
|
1568
|
+
if (enabled["token/hardcoded-match"] !== false) {
|
|
1569
|
+
allDiagnostics.push(...runHardcodedTokenMatch(context).diagnostics);
|
|
1570
|
+
}
|
|
1571
|
+
if (enabled["token/near-match"] !== false) {
|
|
1572
|
+
allDiagnostics.push(...runNearTokenMatch(context).diagnostics);
|
|
1573
|
+
}
|
|
1574
|
+
if (enabled["token/undefined-var"] !== false) {
|
|
1575
|
+
allDiagnostics.push(...runUndefinedCssVar(context).diagnostics);
|
|
1576
|
+
}
|
|
1577
|
+
return { diagnostics: allDiagnostics };
|
|
1578
|
+
}
|
|
1579
|
+
function runHardcodedTokenMatch(context) {
|
|
1580
|
+
const { manifest, styleIndex } = context;
|
|
1581
|
+
const diagnostics = [];
|
|
1582
|
+
const tokenValues = /* @__PURE__ */ new Map();
|
|
1583
|
+
for (const [token, value] of Object.entries(manifest.tokens)) {
|
|
1584
|
+
tokenValues.set(normalizeTokenValue(value), token);
|
|
1585
|
+
}
|
|
1586
|
+
for (const rule of styleIndex.rules) {
|
|
1587
|
+
for (const declaration of rule.declarations) {
|
|
1588
|
+
if (declaration.property.startsWith("--")) continue;
|
|
1589
|
+
if (isLengthOrColor(declaration.value)) {
|
|
1590
|
+
const normalized = normalizeTokenValue(declaration.value);
|
|
1591
|
+
const token = tokenValues.get(normalized);
|
|
1592
|
+
if (token) {
|
|
1593
|
+
if (!declaration.value.includes("var(")) {
|
|
1594
|
+
diagnostics.push({
|
|
1595
|
+
code: "token/hardcoded-match",
|
|
1596
|
+
severity: "warning",
|
|
1597
|
+
confidence: "EXTRACTED",
|
|
1598
|
+
file: rule.file,
|
|
1599
|
+
range: {
|
|
1600
|
+
start: { line: declaration.line, col: declaration.col },
|
|
1601
|
+
end: { line: declaration.line, col: declaration.col + declaration.value.length }
|
|
1602
|
+
},
|
|
1603
|
+
message: `${declaration.property}: ${declaration.value} matches token ${token} (${normalized}).`,
|
|
1604
|
+
fix: { kind: "replace", text: `var(${token})` },
|
|
1605
|
+
fingerprint: `token/hardcoded-match:${rule.file}:${declaration.property}:${normalized}`
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
return { diagnostics };
|
|
1613
|
+
}
|
|
1614
|
+
function runNearTokenMatch(context) {
|
|
1615
|
+
const { manifest, styleIndex, config } = context;
|
|
1616
|
+
const diagnostics = [];
|
|
1617
|
+
const tolerance = config.tolerance.px;
|
|
1618
|
+
const tokenLengths = [];
|
|
1619
|
+
for (const [token, value] of Object.entries(manifest.tokens)) {
|
|
1620
|
+
const parsed = parseLength(value);
|
|
1621
|
+
if (parsed) {
|
|
1622
|
+
tokenLengths.push({ token, value, ...parsed });
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
for (const rule of styleIndex.rules) {
|
|
1626
|
+
for (const declaration of rule.declarations) {
|
|
1627
|
+
if (declaration.property.startsWith("--")) continue;
|
|
1628
|
+
const parsed = parseLength(declaration.value);
|
|
1629
|
+
if (!parsed) continue;
|
|
1630
|
+
if (declaration.value.includes("var(")) continue;
|
|
1631
|
+
for (const token of tokenLengths) {
|
|
1632
|
+
if (parsed.unit !== token.unit) continue;
|
|
1633
|
+
const pixelsPerUnit = PIXELS_PER_UNIT[parsed.unit];
|
|
1634
|
+
if (pixelsPerUnit === void 0) continue;
|
|
1635
|
+
const diff = Math.abs(parsed.numeric - token.numeric) * pixelsPerUnit;
|
|
1636
|
+
if (diff > 0 && diff <= tolerance) {
|
|
1637
|
+
diagnostics.push({
|
|
1638
|
+
code: "token/near-match",
|
|
1639
|
+
severity: "info",
|
|
1640
|
+
confidence: "INFERRED",
|
|
1641
|
+
file: rule.file,
|
|
1642
|
+
range: {
|
|
1643
|
+
start: { line: declaration.line, col: declaration.col },
|
|
1644
|
+
end: { line: declaration.line, col: declaration.col + declaration.value.length }
|
|
1645
|
+
},
|
|
1646
|
+
message: `${declaration.property}: ${declaration.value} is close to ${token.token} (${token.value}).`,
|
|
1647
|
+
fix: { kind: "replace", text: `var(${token.token})` },
|
|
1648
|
+
fingerprint: `token/near-match:${rule.file}:${declaration.property}:${parsed.numeric}${parsed.unit}:${token.token}`
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return { diagnostics };
|
|
1655
|
+
}
|
|
1656
|
+
function runUndefinedCssVar(context) {
|
|
1657
|
+
const { manifest, styleIndex } = context;
|
|
1658
|
+
const diagnostics = [];
|
|
1659
|
+
const definedTokens = new Set(Object.keys(manifest.tokens));
|
|
1660
|
+
for (const rule of styleIndex.rules) {
|
|
1661
|
+
for (const declaration of rule.declarations) {
|
|
1662
|
+
if (declaration.property.startsWith("--")) {
|
|
1663
|
+
definedTokens.add(declaration.property);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
for (const rule of styleIndex.rules) {
|
|
1668
|
+
for (const declaration of rule.declarations) {
|
|
1669
|
+
const varMatches = declaration.value.match(/var\((--[^)]+)\)/g);
|
|
1670
|
+
if (!varMatches) continue;
|
|
1671
|
+
for (const match of varMatches) {
|
|
1672
|
+
const varName = match.slice(4, -1).trim().split(",")[0]?.trim() ?? "";
|
|
1673
|
+
if (!definedTokens.has(varName)) {
|
|
1674
|
+
const suggestion = findClosestToken(varName, definedTokens);
|
|
1675
|
+
diagnostics.push({
|
|
1676
|
+
code: "token/undefined-var",
|
|
1677
|
+
severity: "error",
|
|
1678
|
+
confidence: "INFERRED",
|
|
1679
|
+
file: rule.file,
|
|
1680
|
+
range: {
|
|
1681
|
+
start: { line: declaration.line, col: declaration.col },
|
|
1682
|
+
end: { line: declaration.line, col: declaration.col + declaration.value.length }
|
|
1683
|
+
},
|
|
1684
|
+
message: `Undefined CSS variable ${varName}.${suggestion ? ` Did you mean ${suggestion}?` : ""}`,
|
|
1685
|
+
fix: suggestion ? { kind: "replace", text: declaration.value.replace(varName, suggestion) } : void 0,
|
|
1686
|
+
fingerprint: `token/undefined-var:${rule.file}:${varName}`
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
return { diagnostics };
|
|
1693
|
+
}
|
|
1694
|
+
var PIXELS_PER_UNIT = {
|
|
1695
|
+
px: 1,
|
|
1696
|
+
rem: 16
|
|
1697
|
+
};
|
|
1698
|
+
function normalizeTokenValue(value) {
|
|
1699
|
+
return value.trim().toLowerCase();
|
|
1700
|
+
}
|
|
1701
|
+
function isLengthOrColor(value) {
|
|
1702
|
+
const trimmed = value.trim();
|
|
1703
|
+
if (/^[\d.]+\s*(px|rem|em|ch|ex|vw|vh|vmin|vmax|cm|mm|in|pt|pc)$/i.test(trimmed)) {
|
|
1704
|
+
return true;
|
|
1705
|
+
}
|
|
1706
|
+
if (/^(#[0-9a-f]{3,8}|rgb\(|rgba\(|hsl\(|hsla\()/i.test(trimmed)) {
|
|
1707
|
+
return true;
|
|
1708
|
+
}
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
function parseLength(value) {
|
|
1712
|
+
const match = /^([\d.]+)\s*(px|rem|em|ch|ex|vw|vh|vmin|vmax|cm|mm|in|pt|pc)$/i.exec(value.trim());
|
|
1713
|
+
if (!match || !match[1] || !match[2]) return null;
|
|
1714
|
+
return { numeric: parseFloat(match[1]), unit: match[2].toLowerCase() };
|
|
1715
|
+
}
|
|
1716
|
+
function findClosestToken(target, candidates) {
|
|
1717
|
+
let best = null;
|
|
1718
|
+
let bestDistance = Infinity;
|
|
1719
|
+
for (const candidate of candidates) {
|
|
1720
|
+
const distance = levenshteinDistance(target, candidate);
|
|
1721
|
+
if (distance < bestDistance && distance <= 3) {
|
|
1722
|
+
bestDistance = distance;
|
|
1723
|
+
best = candidate;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return best;
|
|
1727
|
+
}
|
|
1728
|
+
function levenshteinDistance(a, b) {
|
|
1729
|
+
const matrix = Array.from(
|
|
1730
|
+
{ length: b.length + 1 },
|
|
1731
|
+
() => Array.from({ length: a.length + 1 }, () => 0)
|
|
1732
|
+
);
|
|
1733
|
+
for (let i = 0; i <= b.length; i++) {
|
|
1734
|
+
matrix[i][0] = i;
|
|
1735
|
+
}
|
|
1736
|
+
for (let j = 0; j <= a.length; j++) {
|
|
1737
|
+
matrix[0][j] = j;
|
|
1738
|
+
}
|
|
1739
|
+
for (let i = 1; i <= b.length; i++) {
|
|
1740
|
+
for (let j = 1; j <= a.length; j++) {
|
|
1741
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
1742
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
1743
|
+
} else {
|
|
1744
|
+
matrix[i][j] = Math.min(
|
|
1745
|
+
matrix[i - 1][j - 1] + 1,
|
|
1746
|
+
// substitution
|
|
1747
|
+
matrix[i][j - 1] + 1,
|
|
1748
|
+
// insertion
|
|
1749
|
+
matrix[i - 1][j] + 1
|
|
1750
|
+
// deletion
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
return matrix[b.length][a.length];
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// src/schema/diagnostics.ts
|
|
1759
|
+
import { z as z5 } from "zod";
|
|
1760
|
+
var SeveritySchema = z5.enum(["info", "warning", "error"]);
|
|
1761
|
+
var DiagnosticFixSchema = z5.object({
|
|
1762
|
+
kind: z5.literal("replace"),
|
|
1763
|
+
text: z5.string()
|
|
1764
|
+
});
|
|
1765
|
+
var DiagnosticSchema = z5.object({
|
|
1766
|
+
code: z5.string().min(1),
|
|
1767
|
+
severity: SeveritySchema,
|
|
1768
|
+
confidence: ConfidenceSchema,
|
|
1769
|
+
file: PosixPathSchema,
|
|
1770
|
+
range: RangeSchema,
|
|
1771
|
+
message: z5.string().min(1),
|
|
1772
|
+
fix: DiagnosticFixSchema.optional(),
|
|
1773
|
+
/**
|
|
1774
|
+
* Stable identifier for baselines. It must not include line numbers, so an
|
|
1775
|
+
* edit elsewhere in the file does not resurface a known issue.
|
|
1776
|
+
*/
|
|
1777
|
+
fingerprint: z5.string().min(1)
|
|
1778
|
+
});
|
|
1779
|
+
var DiagnosticsFileSchema = z5.object({
|
|
1780
|
+
version: z5.literal(DIAGNOSTICS_VERSION),
|
|
1781
|
+
diagnostics: z5.array(DiagnosticSchema).default([])
|
|
1782
|
+
});
|
|
1783
|
+
function sortDiagnostics(file) {
|
|
1784
|
+
return {
|
|
1785
|
+
...file,
|
|
1786
|
+
diagnostics: [...file.diagnostics].sort((a, b) => {
|
|
1787
|
+
const left = `${a.file}\0${String(a.range.start.line).padStart(8, "0")}\0${a.code}\0${a.fingerprint}`;
|
|
1788
|
+
const right = `${b.file}\0${String(b.range.start.line).padStart(8, "0")}\0${b.code}\0${b.fingerprint}`;
|
|
1789
|
+
return compareStrings(left, right);
|
|
1790
|
+
})
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// src/scanner/types.ts
|
|
1795
|
+
import { z as z6 } from "zod";
|
|
1796
|
+
var ComponentAnalysisSchema = z6.object({
|
|
1797
|
+
name: z6.string().min(1),
|
|
1798
|
+
/** Every name this component is exported under, including `default`. */
|
|
1799
|
+
exportNames: z6.array(z6.string().min(1)).min(1),
|
|
1800
|
+
loc: RangeSchema,
|
|
1801
|
+
/** Character offsets, used to attribute JSX to the smallest containing component. */
|
|
1802
|
+
start: z6.number().int().nonnegative(),
|
|
1803
|
+
end: z6.number().int().nonnegative(),
|
|
1804
|
+
props: z6.array(PropSchema)
|
|
1805
|
+
});
|
|
1806
|
+
var ImportBindingSchema = z6.object({
|
|
1807
|
+
source: z6.string().min(1),
|
|
1808
|
+
/** `default`, `*`, or the imported export name. */
|
|
1809
|
+
imported: z6.string().min(1),
|
|
1810
|
+
local: z6.string().min(1)
|
|
1811
|
+
});
|
|
1812
|
+
var JsxUsageSchema = z6.object({
|
|
1813
|
+
local: z6.string().min(1),
|
|
1814
|
+
member: z6.string().min(1).optional(),
|
|
1815
|
+
start: z6.number().int().nonnegative(),
|
|
1816
|
+
loc: RangeSchema
|
|
1817
|
+
});
|
|
1818
|
+
var InlineStyleAnalysisSchema = z6.object({
|
|
1819
|
+
/** `file:line:col` of the tagged JSX element. */
|
|
1820
|
+
src: z6.string().min(1),
|
|
1821
|
+
file: PosixPathSchema,
|
|
1822
|
+
range: RangeSchema,
|
|
1823
|
+
/** EXTRACTED for an object literal, INFERRED for a spread or expression. */
|
|
1824
|
+
confidence: ConfidenceSchema
|
|
1825
|
+
});
|
|
1826
|
+
var FileAnalysisSchema = z6.object({
|
|
1827
|
+
file: PosixPathSchema,
|
|
1828
|
+
hash: z6.string().regex(/^[a-f0-9]{64}$/),
|
|
1829
|
+
components: z6.array(ComponentAnalysisSchema),
|
|
1830
|
+
imports: z6.array(ImportBindingSchema),
|
|
1831
|
+
usages: z6.array(JsxUsageSchema),
|
|
1832
|
+
inlineStyles: z6.array(InlineStyleAnalysisSchema).default([])
|
|
1833
|
+
});
|
|
1834
|
+
var SCANNER_CACHE_VERSION = 2;
|
|
1835
|
+
var ScannerCacheSchema = z6.object({
|
|
1836
|
+
version: z6.literal(SCANNER_CACHE_VERSION),
|
|
1837
|
+
files: z6.record(PosixPathSchema, FileAnalysisSchema)
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
// src/scanner/scan.ts
|
|
1841
|
+
var CACHE_PATH = ".snoop/cache/scanner.v2.json";
|
|
1842
|
+
var READ_CONCURRENCY2 = 8;
|
|
1843
|
+
async function scanProject(options = {}) {
|
|
1844
|
+
const cwd = resolve5(options.cwd ?? process.cwd());
|
|
1845
|
+
const defaults = ConfigSchema.parse({});
|
|
1846
|
+
const include = options.include ?? defaults.include;
|
|
1847
|
+
const exclude = options.exclude ?? defaults.exclude;
|
|
1848
|
+
const useCache = options.cache !== false;
|
|
1849
|
+
const cachePath = resolve5(cwd, CACHE_PATH);
|
|
1850
|
+
const discovered = await glob2(include, {
|
|
1851
|
+
cwd,
|
|
1852
|
+
ignore: [...exclude, ...ALWAYS_IGNORED],
|
|
1853
|
+
absolute: false,
|
|
1854
|
+
onlyFiles: true,
|
|
1855
|
+
dot: false
|
|
1856
|
+
});
|
|
1857
|
+
const files = [...new Set(discovered.map(toPosixPath))].sort(compare3);
|
|
1858
|
+
const fileSet = new Set(files);
|
|
1859
|
+
const cache = useCache ? await readCache(cachePath) : void 0;
|
|
1860
|
+
const results = await mapLimit(files, READ_CONCURRENCY2, async (file) => {
|
|
1861
|
+
const absolutePath = resolve5(cwd, file);
|
|
1862
|
+
let source;
|
|
1863
|
+
try {
|
|
1864
|
+
source = await readFile2(absolutePath, "utf8");
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
return {
|
|
1867
|
+
error: {
|
|
1868
|
+
code: "scan/read",
|
|
1869
|
+
file,
|
|
1870
|
+
message: sanitizeMessage(
|
|
1871
|
+
error instanceof Error ? error.message : "Could not read file",
|
|
1872
|
+
cwd
|
|
1873
|
+
)
|
|
1874
|
+
},
|
|
1875
|
+
cached: false,
|
|
1876
|
+
parsed: false
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
const hash = hashSource(source);
|
|
1880
|
+
const cached = cache?.files[file];
|
|
1881
|
+
if (cached && cached.hash === hash) {
|
|
1882
|
+
return { analysis: cached, cached: true, parsed: false };
|
|
1883
|
+
}
|
|
1884
|
+
const analyzed = analyzeFile({
|
|
1885
|
+
absolutePath,
|
|
1886
|
+
cwd,
|
|
1887
|
+
file,
|
|
1888
|
+
hash,
|
|
1889
|
+
source
|
|
1890
|
+
});
|
|
1891
|
+
return {
|
|
1892
|
+
...analyzed.analysis ? { analysis: analyzed.analysis } : {},
|
|
1893
|
+
...analyzed.error ? { error: analyzed.error } : {},
|
|
1894
|
+
cached: false,
|
|
1895
|
+
parsed: true
|
|
1896
|
+
};
|
|
1897
|
+
});
|
|
1898
|
+
const analyses = results.flatMap((result) => result.analysis ? [result.analysis] : []);
|
|
1899
|
+
const errors = results.flatMap((result) => result.error ? [result.error] : []);
|
|
1900
|
+
if (useCache) {
|
|
1901
|
+
await writeCache(cachePath, analyses);
|
|
1902
|
+
}
|
|
1903
|
+
const componentIds = /* @__PURE__ */ new Map();
|
|
1904
|
+
const componentsByFile = /* @__PURE__ */ new Map();
|
|
1905
|
+
const nodes = [];
|
|
1906
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
1907
|
+
for (const analysis of [...analyses].sort((a, b) => compare3(a.file, b.file))) {
|
|
1908
|
+
componentsByFile.set(analysis.file, analysis.components);
|
|
1909
|
+
for (const component of analysis.components) {
|
|
1910
|
+
const id = uniqueId(`cmp:${analysis.file}#${component.name}`, usedIds);
|
|
1911
|
+
const exportName = component.exportNames.includes("default") ? "default" : component.exportNames[0];
|
|
1912
|
+
componentIds.set(component, id);
|
|
1913
|
+
nodes.push({
|
|
1914
|
+
id,
|
|
1915
|
+
kind: "component",
|
|
1916
|
+
name: component.name,
|
|
1917
|
+
file: analysis.file,
|
|
1918
|
+
...exportName ? { exportName } : {},
|
|
1919
|
+
loc: component.loc,
|
|
1920
|
+
props: component.props
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
const edges = /* @__PURE__ */ new Map();
|
|
1925
|
+
const addEdge = (from, to, relation, confidence) => {
|
|
1926
|
+
if (!from || !to) {
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
const key = `${from}\0${to}\0${relation}`;
|
|
1930
|
+
if (!edges.has(key)) {
|
|
1931
|
+
edges.set(key, { from, to, relation, confidence });
|
|
1932
|
+
}
|
|
1933
|
+
};
|
|
1934
|
+
const resolveImport = createImportResolver(cwd, fileSet);
|
|
1935
|
+
for (const analysis of analyses) {
|
|
1936
|
+
const components = analysis.components;
|
|
1937
|
+
const importTargets = /* @__PURE__ */ new Map();
|
|
1938
|
+
const namespaceTargets = /* @__PURE__ */ new Map();
|
|
1939
|
+
for (const binding of analysis.imports) {
|
|
1940
|
+
const targetFile = resolveImport(analysis.file, binding.source);
|
|
1941
|
+
if (!targetFile) {
|
|
1942
|
+
continue;
|
|
1943
|
+
}
|
|
1944
|
+
const targetComponents = componentsByFile.get(targetFile) ?? [];
|
|
1945
|
+
if (binding.imported === "*") {
|
|
1946
|
+
namespaceTargets.set(binding.local, targetComponents);
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
const target = selectComponent(targetComponents, binding.imported);
|
|
1950
|
+
if (target) {
|
|
1951
|
+
importTargets.set(binding.local, target);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
for (const usage of analysis.usages) {
|
|
1955
|
+
const source = containingComponent(components, usage.start);
|
|
1956
|
+
if (!source) {
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
let target;
|
|
1960
|
+
if (usage.member) {
|
|
1961
|
+
const namespace = namespaceTargets.get(usage.local);
|
|
1962
|
+
if (namespace) {
|
|
1963
|
+
target = selectComponent(namespace, usage.member);
|
|
1964
|
+
}
|
|
1965
|
+
} else {
|
|
1966
|
+
target = components.find((component) => component.name === usage.local) ?? importTargets.get(usage.local);
|
|
1967
|
+
}
|
|
1968
|
+
addEdge(
|
|
1969
|
+
componentIds.get(source),
|
|
1970
|
+
target ? componentIds.get(target) : void 0,
|
|
1971
|
+
"renders",
|
|
1972
|
+
"EXTRACTED"
|
|
1973
|
+
);
|
|
1974
|
+
}
|
|
1975
|
+
if (components.length === 1) {
|
|
1976
|
+
const source = components[0];
|
|
1977
|
+
if (!source) {
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
for (const target of new Set(importTargets.values())) {
|
|
1981
|
+
if (target === source) {
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
const fromId = componentIds.get(source);
|
|
1985
|
+
const toId = componentIds.get(target);
|
|
1986
|
+
if (!fromId || !toId || hasEdgeBetween(edges, fromId, toId)) {
|
|
1987
|
+
continue;
|
|
1988
|
+
}
|
|
1989
|
+
addEdge(fromId, toId, "imports", "EXTRACTED");
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
const styles = await scanStyles({
|
|
1994
|
+
cwd,
|
|
1995
|
+
...options.tokens ? { tokenFiles: options.tokens } : {},
|
|
1996
|
+
exclude,
|
|
1997
|
+
inlineStyles: analyses.flatMap((analysis) => analysis.inlineStyles),
|
|
1998
|
+
...options.resolveCssModules ? { resolveCssModules: options.resolveCssModules } : {}
|
|
1999
|
+
});
|
|
2000
|
+
const manifest = ManifestSchema.parse(
|
|
2001
|
+
sortManifest({
|
|
2002
|
+
version: MANIFEST_VERSION,
|
|
2003
|
+
tokens: styles.tokens,
|
|
2004
|
+
nodes,
|
|
2005
|
+
edges: [...edges.values()],
|
|
2006
|
+
errors
|
|
2007
|
+
})
|
|
2008
|
+
);
|
|
2009
|
+
const config = ConfigSchema.parse({
|
|
2010
|
+
include,
|
|
2011
|
+
exclude,
|
|
2012
|
+
...options.tokens ? { tokens: options.tokens } : {}
|
|
2013
|
+
});
|
|
2014
|
+
const rulesResult = createRulesEngine({ manifest, styleIndex: styles, config });
|
|
2015
|
+
const diagnostics = DiagnosticsFileSchema.parse(
|
|
2016
|
+
sortDiagnostics({
|
|
2017
|
+
version: 1,
|
|
2018
|
+
diagnostics: rulesResult.diagnostics
|
|
2019
|
+
})
|
|
2020
|
+
);
|
|
2021
|
+
return {
|
|
2022
|
+
manifest,
|
|
2023
|
+
styles,
|
|
2024
|
+
diagnostics,
|
|
2025
|
+
stats: {
|
|
2026
|
+
files: files.length,
|
|
2027
|
+
parsedFiles: results.filter((result) => result.parsed).length,
|
|
2028
|
+
cachedFiles: results.filter((result) => result.cached).length,
|
|
2029
|
+
components: nodes.length,
|
|
2030
|
+
edges: edges.size,
|
|
2031
|
+
errors: errors.length
|
|
2032
|
+
}
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
async function writeManifest(outPath, manifest) {
|
|
2036
|
+
await mkdir(dirname2(outPath), { recursive: true });
|
|
2037
|
+
await writeFile(outPath, `${JSON.stringify(manifest, null, 2)}
|
|
2038
|
+
`, "utf8");
|
|
2039
|
+
}
|
|
2040
|
+
function selectComponent(components, imported) {
|
|
2041
|
+
return components.find((component) => component.exportNames.includes(imported)) ?? components.find((component) => component.name === imported);
|
|
2042
|
+
}
|
|
2043
|
+
function containingComponent(components, start) {
|
|
2044
|
+
let best;
|
|
2045
|
+
for (const component of components) {
|
|
2046
|
+
if (start < component.start || start >= component.end) {
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
if (!best || component.end - component.start < best.end - best.start) {
|
|
2050
|
+
best = component;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
return best;
|
|
2054
|
+
}
|
|
2055
|
+
function hasEdgeBetween(edges, from, to) {
|
|
2056
|
+
const prefix = `${from}\0${to}\0`;
|
|
2057
|
+
for (const key of edges.keys()) {
|
|
2058
|
+
if (key.startsWith(prefix)) {
|
|
2059
|
+
return true;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
return false;
|
|
2063
|
+
}
|
|
2064
|
+
function uniqueId(candidate, used) {
|
|
2065
|
+
if (!used.has(candidate)) {
|
|
2066
|
+
used.add(candidate);
|
|
2067
|
+
return candidate;
|
|
2068
|
+
}
|
|
2069
|
+
let counter = 2;
|
|
2070
|
+
while (used.has(`${candidate}~${counter}`)) {
|
|
2071
|
+
counter += 1;
|
|
2072
|
+
}
|
|
2073
|
+
const unique = `${candidate}~${counter}`;
|
|
2074
|
+
used.add(unique);
|
|
2075
|
+
return unique;
|
|
2076
|
+
}
|
|
2077
|
+
async function readCache(path) {
|
|
2078
|
+
try {
|
|
2079
|
+
const raw = await readFile2(path, "utf8");
|
|
2080
|
+
const parsed = ScannerCacheSchema.safeParse(JSON.parse(raw));
|
|
2081
|
+
return parsed.success ? parsed.data : void 0;
|
|
2082
|
+
} catch {
|
|
2083
|
+
return void 0;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
async function writeCache(path, analyses) {
|
|
2087
|
+
try {
|
|
2088
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
2089
|
+
const files = {};
|
|
2090
|
+
for (const analysis of [...analyses].sort((a, b) => compare3(a.file, b.file))) {
|
|
2091
|
+
files[analysis.file] = analysis;
|
|
2092
|
+
}
|
|
2093
|
+
const payload = {
|
|
2094
|
+
version: SCANNER_CACHE_VERSION,
|
|
2095
|
+
files
|
|
2096
|
+
};
|
|
2097
|
+
const temporaryPath = `${path}.tmp`;
|
|
2098
|
+
await writeFile(temporaryPath, `${JSON.stringify(payload, null, 2)}
|
|
2099
|
+
`, "utf8");
|
|
2100
|
+
await rename(temporaryPath, path);
|
|
2101
|
+
} catch {
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
function hashSource(source) {
|
|
2105
|
+
return createHash("sha256").update(source).digest("hex");
|
|
2106
|
+
}
|
|
2107
|
+
function compare3(a, b) {
|
|
2108
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// src/scanner/containment.ts
|
|
2112
|
+
function parseSourceLocation(src) {
|
|
2113
|
+
const match = /^(.*):(\d+):(\d+)$/.exec(src);
|
|
2114
|
+
if (!match) {
|
|
2115
|
+
return void 0;
|
|
2116
|
+
}
|
|
2117
|
+
const [, file, line, col] = match;
|
|
2118
|
+
if (!file) {
|
|
2119
|
+
return void 0;
|
|
2120
|
+
}
|
|
2121
|
+
return { file, line: Number(line), col: Number(col) };
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// src/styles/provenance.ts
|
|
2125
|
+
function createStyleIndexer(index) {
|
|
2126
|
+
const staticRules = /* @__PURE__ */ new Map();
|
|
2127
|
+
for (const rule of index.rules) {
|
|
2128
|
+
addRule(staticRules, rule.selector, rule);
|
|
2129
|
+
if (rule.runtimeSelector) {
|
|
2130
|
+
addRule(staticRules, rule.runtimeSelector, rule);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
const inlineBySrc = /* @__PURE__ */ new Map();
|
|
2134
|
+
for (const inline of index.inlineStyles) {
|
|
2135
|
+
inlineBySrc.set(inline.src, inline);
|
|
2136
|
+
}
|
|
2137
|
+
return {
|
|
2138
|
+
resolve(detail) {
|
|
2139
|
+
const provenance = {};
|
|
2140
|
+
const matchedRules = detail.matchedRules ?? [];
|
|
2141
|
+
const inlineStyle = detail.inlineStyle ?? {};
|
|
2142
|
+
const classes = detail.classes ?? [];
|
|
2143
|
+
for (const [property, value] of Object.entries(detail.style)) {
|
|
2144
|
+
const candidates = [];
|
|
2145
|
+
if (inlineStyle[property] !== void 0) {
|
|
2146
|
+
candidates.push({
|
|
2147
|
+
declaration: {
|
|
2148
|
+
property,
|
|
2149
|
+
value: inlineStyle[property] ?? value,
|
|
2150
|
+
important: false
|
|
2151
|
+
},
|
|
2152
|
+
specificity: { id: 1e6, class: 0, type: 0 },
|
|
2153
|
+
important: false,
|
|
2154
|
+
order: Number.MAX_SAFE_INTEGER,
|
|
2155
|
+
inline: true
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
for (const rule of matchedRules) {
|
|
2159
|
+
for (const declaration of rule.declarations) {
|
|
2160
|
+
if (declaration.property !== property) {
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
candidates.push({
|
|
2164
|
+
rule,
|
|
2165
|
+
declaration,
|
|
2166
|
+
specificity: specificityOf(rule.selector),
|
|
2167
|
+
important: declaration.important,
|
|
2168
|
+
order: rule.order,
|
|
2169
|
+
inline: false
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
if (candidates.length === 0) {
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
const winner = candidates.reduce(
|
|
2177
|
+
(best, candidate) => isBetter(candidate, best) ? candidate : best
|
|
2178
|
+
);
|
|
2179
|
+
const source = winner.inline ? inlineSource(detail.src, inlineBySrc) : stylesheetSource(winner, staticRules, index, detail, classes);
|
|
2180
|
+
provenance[property] = source;
|
|
2181
|
+
}
|
|
2182
|
+
return provenance;
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
function addRule(target, selector, rule) {
|
|
2187
|
+
const key = normalizeSelector(selector);
|
|
2188
|
+
const list = target.get(key) ?? [];
|
|
2189
|
+
target.set(key, list);
|
|
2190
|
+
for (const declaration of rule.declarations) {
|
|
2191
|
+
list.push({ rule, declaration });
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
function isBetter(candidate, current) {
|
|
2195
|
+
if (candidate.important !== current.important) {
|
|
2196
|
+
return candidate.important;
|
|
2197
|
+
}
|
|
2198
|
+
const specificity = compareSpecificity(candidate.specificity, current.specificity);
|
|
2199
|
+
if (specificity !== 0) {
|
|
2200
|
+
return specificity > 0;
|
|
2201
|
+
}
|
|
2202
|
+
return candidate.order >= current.order;
|
|
2203
|
+
}
|
|
2204
|
+
function inlineSource(src, inlineBySrc) {
|
|
2205
|
+
const inline = inlineBySrc.get(src);
|
|
2206
|
+
if (inline) {
|
|
2207
|
+
return {
|
|
2208
|
+
kind: "inline",
|
|
2209
|
+
file: inline.file,
|
|
2210
|
+
line: inline.range.start.line,
|
|
2211
|
+
col: inline.range.start.col,
|
|
2212
|
+
selector: "style",
|
|
2213
|
+
confidence: inline.confidence
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
const location = parseSourceLocation(src);
|
|
2217
|
+
return {
|
|
2218
|
+
kind: "inline",
|
|
2219
|
+
...location ? { file: location.file, line: location.line, col: location.col } : {},
|
|
2220
|
+
selector: "style",
|
|
2221
|
+
confidence: "INFERRED"
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
function stylesheetSource(winner, staticRules, index, detail, classes) {
|
|
2225
|
+
const rule = winner.rule;
|
|
2226
|
+
const selector = rule?.selector ?? "style";
|
|
2227
|
+
const atRules = rule?.atRules ?? [];
|
|
2228
|
+
const matches = staticRules.get(normalizeSelector(selector)) ?? [];
|
|
2229
|
+
const properties = [winner.declaration.property, ...shorthandsFor(winner.declaration.property)];
|
|
2230
|
+
const forProperty = matches.filter((match) => properties.includes(match.declaration.property)).sort(
|
|
2231
|
+
(a, b) => properties.indexOf(a.declaration.property) - properties.indexOf(b.declaration.property)
|
|
2232
|
+
);
|
|
2233
|
+
if (forProperty.length > 0) {
|
|
2234
|
+
const exact = forProperty.find(
|
|
2235
|
+
(match) => normalizeValue(match.declaration.value) === normalizeValue(winner.declaration.value)
|
|
2236
|
+
);
|
|
2237
|
+
const chosen = exact ?? forProperty[0];
|
|
2238
|
+
const locations = new Set(
|
|
2239
|
+
forProperty.map((match) => `${match.rule.file}:${match.declaration.line}`)
|
|
2240
|
+
);
|
|
2241
|
+
const confidence = locations.size === 1 ? "EXTRACTED" : "INFERRED";
|
|
2242
|
+
return {
|
|
2243
|
+
kind: chosen.rule.module ? "css-module" : "stylesheet",
|
|
2244
|
+
file: chosen.rule.file,
|
|
2245
|
+
line: chosen.declaration.line,
|
|
2246
|
+
col: chosen.declaration.col,
|
|
2247
|
+
selector,
|
|
2248
|
+
...atRules.length > 0 ? { atRules } : {},
|
|
2249
|
+
...locations.size > 1 ? { candidates: locations.size } : {},
|
|
2250
|
+
confidence
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
if (index.tailwind && isTailwindUtility(selector, classes)) {
|
|
2254
|
+
const location = parseSourceLocation(detail.src);
|
|
2255
|
+
return {
|
|
2256
|
+
kind: "tailwind",
|
|
2257
|
+
...location ? { file: location.file, line: location.line, col: location.col } : {},
|
|
2258
|
+
selector,
|
|
2259
|
+
...atRules.length > 0 ? { atRules } : {},
|
|
2260
|
+
confidence: "INFERRED"
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
return {
|
|
2264
|
+
kind: "unknown",
|
|
2265
|
+
selector,
|
|
2266
|
+
...atRules.length > 0 ? { atRules } : {},
|
|
2267
|
+
confidence: "INFERRED"
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
function shorthandsFor(property) {
|
|
2271
|
+
const shorthands = [];
|
|
2272
|
+
const box = /^(margin|padding)-(?:top|right|bottom|left)$/.exec(property);
|
|
2273
|
+
if (box?.[1]) {
|
|
2274
|
+
shorthands.push(box[1]);
|
|
2275
|
+
}
|
|
2276
|
+
const borderWidth = /^border-(top|right|bottom|left)-width$/.exec(property);
|
|
2277
|
+
if (borderWidth?.[1]) {
|
|
2278
|
+
shorthands.push(`border-${borderWidth[1]}`, "border-width", "border");
|
|
2279
|
+
}
|
|
2280
|
+
if (/^border-(?:top|bottom)-(?:left|right)-radius$/.test(property)) {
|
|
2281
|
+
shorthands.push("border-radius");
|
|
2282
|
+
}
|
|
2283
|
+
if (property === "background-color") {
|
|
2284
|
+
shorthands.push("background");
|
|
2285
|
+
}
|
|
2286
|
+
if (property === "flex-direction" || property === "flex-wrap") {
|
|
2287
|
+
shorthands.push("flex-flow");
|
|
2288
|
+
}
|
|
2289
|
+
if (/^(?:row|column)-gap$/.test(property)) {
|
|
2290
|
+
shorthands.push("gap");
|
|
2291
|
+
}
|
|
2292
|
+
if (/^font-(?:size|weight|family)$|^line-height$/.test(property)) {
|
|
2293
|
+
shorthands.push("font");
|
|
2294
|
+
}
|
|
2295
|
+
if (property.startsWith("grid-template-")) {
|
|
2296
|
+
shorthands.push("grid-template", "grid");
|
|
2297
|
+
}
|
|
2298
|
+
if (property.startsWith("overflow-")) {
|
|
2299
|
+
shorthands.push("overflow");
|
|
2300
|
+
}
|
|
2301
|
+
return shorthands;
|
|
2302
|
+
}
|
|
2303
|
+
function normalizeValue(value) {
|
|
2304
|
+
return value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
2305
|
+
}
|
|
2306
|
+
function isTailwindUtility(selector, classes) {
|
|
2307
|
+
const match = /^\.((?:\\.|[^.#:[\]\s>+~])+)/.exec(selector);
|
|
2308
|
+
if (!match?.[1]) {
|
|
2309
|
+
return false;
|
|
2310
|
+
}
|
|
2311
|
+
const className = match[1].replace(/\\(.)/g, "$1");
|
|
2312
|
+
if (!classes.includes(className)) {
|
|
2313
|
+
return false;
|
|
2314
|
+
}
|
|
2315
|
+
const remainder = selector.slice(match[0].length);
|
|
2316
|
+
return /^(?::[a-zA-Z-]+(?:\([^)]*\))?)*$/.test(remainder);
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
// src/runtime/protocol.ts
|
|
2320
|
+
var AGENT_ROLE = "agent";
|
|
2321
|
+
var DASHBOARD_ROLE = "dashboard";
|
|
2322
|
+
function isAgentMessage(value) {
|
|
2323
|
+
if (!isRecord3(value) || typeof value.kind !== "string") {
|
|
2324
|
+
return false;
|
|
2325
|
+
}
|
|
2326
|
+
switch (value.kind) {
|
|
2327
|
+
case "ready":
|
|
2328
|
+
case "presence":
|
|
2329
|
+
case "elements":
|
|
2330
|
+
case "detail":
|
|
2331
|
+
case "click":
|
|
2332
|
+
case "hover":
|
|
2333
|
+
return true;
|
|
2334
|
+
default:
|
|
2335
|
+
return false;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
function isDashboardMessage(value) {
|
|
2339
|
+
if (!isRecord3(value) || typeof value.kind !== "string") {
|
|
2340
|
+
return false;
|
|
2341
|
+
}
|
|
2342
|
+
switch (value.kind) {
|
|
2343
|
+
case "measure":
|
|
2344
|
+
case "inspect":
|
|
2345
|
+
case "isolate":
|
|
2346
|
+
case "select":
|
|
2347
|
+
case "sync":
|
|
2348
|
+
return true;
|
|
2349
|
+
default:
|
|
2350
|
+
return false;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
function isRelayEnvelope(value) {
|
|
2354
|
+
return isRecord3(value) && value.type === "relay" && isRecord3(value.payload) && typeof value.payload.kind === "string";
|
|
2355
|
+
}
|
|
2356
|
+
function parseRole(value) {
|
|
2357
|
+
return value === AGENT_ROLE ? AGENT_ROLE : DASHBOARD_ROLE;
|
|
2358
|
+
}
|
|
2359
|
+
function isRecord3(value) {
|
|
2360
|
+
return typeof value === "object" && value !== null;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
// src/index.ts
|
|
2364
|
+
var VERSION = package_default.version;
|
|
2365
|
+
export {
|
|
2366
|
+
AGENT_ROLE,
|
|
2367
|
+
ComponentNodeSchema,
|
|
2368
|
+
ConfidenceSchema,
|
|
2369
|
+
ConfigSchema,
|
|
2370
|
+
DASHBOARD_ROLE,
|
|
2371
|
+
DEFAULT_EXCLUDE,
|
|
2372
|
+
DEFAULT_INCLUDE,
|
|
2373
|
+
DIAGNOSTICS_VERSION,
|
|
2374
|
+
DiagnosticFixSchema,
|
|
2375
|
+
DiagnosticSchema,
|
|
2376
|
+
DiagnosticsFileSchema,
|
|
2377
|
+
EdgeRelationSchema,
|
|
2378
|
+
EdgeSchema,
|
|
2379
|
+
InlineStyleSchema,
|
|
2380
|
+
MANIFEST_VERSION,
|
|
2381
|
+
ManifestSchema,
|
|
2382
|
+
PositionSchema,
|
|
2383
|
+
PosixPathSchema,
|
|
2384
|
+
PropSchema,
|
|
2385
|
+
RULE_NAMES,
|
|
2386
|
+
RangeSchema,
|
|
2387
|
+
RuleSettingSchema,
|
|
2388
|
+
STYLE_INDEX_VERSION,
|
|
2389
|
+
ScanErrorSchema,
|
|
2390
|
+
SeveritySchema,
|
|
2391
|
+
StyleDeclarationSchema,
|
|
2392
|
+
StyleErrorSchema,
|
|
2393
|
+
StyleIndexSchema,
|
|
2394
|
+
StyleRuleSchema,
|
|
2395
|
+
TokenTableSchema,
|
|
2396
|
+
VERSION,
|
|
2397
|
+
compareSpecificity,
|
|
2398
|
+
compareStrings,
|
|
2399
|
+
createRulesEngine,
|
|
2400
|
+
createStyleIndexer,
|
|
2401
|
+
defineConfig,
|
|
2402
|
+
extractTailwindTheme,
|
|
2403
|
+
findConfigFile,
|
|
2404
|
+
isAgentMessage,
|
|
2405
|
+
isDashboardMessage,
|
|
2406
|
+
isRecord3 as isRecord,
|
|
2407
|
+
isRelayEnvelope,
|
|
2408
|
+
loadConfig,
|
|
2409
|
+
normalizeSelector,
|
|
2410
|
+
parseRole,
|
|
2411
|
+
parseStyleSheet,
|
|
2412
|
+
rewriteClasses,
|
|
2413
|
+
scanProject,
|
|
2414
|
+
scanStyles,
|
|
2415
|
+
sortDiagnostics,
|
|
2416
|
+
sortManifest,
|
|
2417
|
+
sortStyleIndex,
|
|
2418
|
+
specificityOf,
|
|
2419
|
+
writeManifest
|
|
2420
|
+
};
|
|
2421
|
+
//# sourceMappingURL=index.js.map
|