tw-migrate 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -48
- package/dist/bin.js +7 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2176 -532
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { promisify } from "node:util";
|
|
|
3
3
|
import { chmod, lstat, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { execFile } from "node:child_process";
|
|
6
|
-
import { parse } from "parse5";
|
|
7
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { parse } from "parse5";
|
|
8
8
|
//#region src/util/diff.ts
|
|
9
9
|
function unifiedDiff(path, before, after) {
|
|
10
10
|
const oldLines = before.split("\n");
|
|
@@ -18,186 +18,10 @@ function unifiedDiff(path, before, after) {
|
|
|
18
18
|
].join("");
|
|
19
19
|
}
|
|
20
20
|
//#endregion
|
|
21
|
-
//#region src/parser/html.ts
|
|
22
|
-
const TEMPLATE_MARKERS = /\{\{|\}\}|\$\{|<%|%>|<#|#>|\[%|%\]/;
|
|
23
|
-
function parseHtmlSource(path, source) {
|
|
24
|
-
const errors = [];
|
|
25
|
-
const document = parse(source, {
|
|
26
|
-
sourceCodeLocationInfo: true,
|
|
27
|
-
onParseError(error) {
|
|
28
|
-
if (error.code !== "missing-doctype") errors.push(error);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
if (errors.length > 0) throw new Error(`Failed to parse ${path}: ${errors.map((error) => error.code).join(", ")}`);
|
|
32
|
-
const links = [];
|
|
33
|
-
const bases = [];
|
|
34
|
-
const elements = [];
|
|
35
|
-
const dynamicAttributes = [];
|
|
36
|
-
const scriptTexts = [];
|
|
37
|
-
const unquoted = (attribute) => source[attribute.start - 1] !== "\"" && source[attribute.start - 1] !== "'";
|
|
38
|
-
function visit(node) {
|
|
39
|
-
if (node.tagName === "script") {
|
|
40
|
-
for (const child of node.childNodes ?? []) if (child.nodeName === "#text" && child.value) scriptTexts.push(child.value);
|
|
41
|
-
}
|
|
42
|
-
if (node.tagName) {
|
|
43
|
-
const attributes = new Map((node.attrs ?? []).map((attribute) => [attribute.name, attribute.value]));
|
|
44
|
-
const locations = node.sourceCodeLocation?.attrs;
|
|
45
|
-
if (locations) {
|
|
46
|
-
if (node.tagName === "link" && stylesheetRel(attributes.get("rel")) && !attributes.has("disabled")) {
|
|
47
|
-
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
48
|
-
const media = locatedAttribute(source, locations.media, attributes.get("media"));
|
|
49
|
-
const tag = node.sourceCodeLocation?.startTag ?? node.sourceCodeLocation;
|
|
50
|
-
if (href && tag) links.push({
|
|
51
|
-
href: href.value,
|
|
52
|
-
media: media?.value ?? "",
|
|
53
|
-
start: href.start,
|
|
54
|
-
end: href.end,
|
|
55
|
-
tagStart: tag.startOffset,
|
|
56
|
-
tagEnd: tag.endOffset
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
if (node.tagName === "base" && bases.length === 0) {
|
|
60
|
-
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
61
|
-
if (href) bases.push({
|
|
62
|
-
href: href.value,
|
|
63
|
-
writable: href.writable,
|
|
64
|
-
start: href.start,
|
|
65
|
-
end: href.end
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
let classAttribute = locatedAttribute(source, locations.class, attributes.get("class"));
|
|
69
|
-
const idAttribute = locatedAttribute(source, locations.id, attributes.get("id"));
|
|
70
|
-
const dynamic = (!classAttribute && idAttribute && locations.class ? {
|
|
71
|
-
start: locations.class.startOffset,
|
|
72
|
-
end: locations.class.endOffset
|
|
73
|
-
} : void 0) ?? [classAttribute, idAttribute].find((attribute) => attribute && (!attribute.writable || TEMPLATE_MARKERS.test(attribute.value))) ?? (classAttribute && unquoted(classAttribute) ? classAttribute : void 0);
|
|
74
|
-
if (dynamic) dynamicAttributes.push({
|
|
75
|
-
start: dynamic.start,
|
|
76
|
-
end: dynamic.end
|
|
77
|
-
});
|
|
78
|
-
else if (classAttribute || idAttribute) {
|
|
79
|
-
if (!classAttribute && idAttribute) {
|
|
80
|
-
const startTag = node.sourceCodeLocation?.startTag;
|
|
81
|
-
if (startTag && source[startTag.endOffset - 1] === ">") {
|
|
82
|
-
const insertion = classInsertionOffset(source, startTag.startOffset, startTag.endOffset - 1);
|
|
83
|
-
classAttribute = {
|
|
84
|
-
value: "",
|
|
85
|
-
start: insertion,
|
|
86
|
-
end: insertion,
|
|
87
|
-
synthetic: true
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
elements.push({
|
|
92
|
-
classAttribute,
|
|
93
|
-
idAttribute
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
for (const child of node.childNodes ?? []) visit(child);
|
|
99
|
-
if (node.content) visit(node.content);
|
|
100
|
-
}
|
|
101
|
-
visit(document);
|
|
102
|
-
return {
|
|
103
|
-
...toByteOffsets(source, {
|
|
104
|
-
links,
|
|
105
|
-
bases,
|
|
106
|
-
elements,
|
|
107
|
-
dynamicAttributes
|
|
108
|
-
}),
|
|
109
|
-
scriptText: scriptTexts.join("\n")
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
function utf8OffsetMap(source, indices) {
|
|
113
|
-
const sorted = [...new Set(indices)].sort((left, right) => left - right);
|
|
114
|
-
const map = /* @__PURE__ */ new Map();
|
|
115
|
-
let lastIndex = 0;
|
|
116
|
-
let bytes = 0;
|
|
117
|
-
for (const index of sorted) {
|
|
118
|
-
bytes += Buffer.byteLength(source.slice(lastIndex, index));
|
|
119
|
-
lastIndex = index;
|
|
120
|
-
map.set(index, bytes);
|
|
121
|
-
}
|
|
122
|
-
return map;
|
|
123
|
-
}
|
|
124
|
-
function offsetLookup(offsets) {
|
|
125
|
-
return (index) => {
|
|
126
|
-
const byte = offsets.get(index);
|
|
127
|
-
if (byte === void 0) throw new Error(`No byte offset was mapped for index ${index}`);
|
|
128
|
-
return byte;
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
function toByteOffsets(source, parsed) {
|
|
132
|
-
const offset = offsetLookup(utf8OffsetMap(source, [
|
|
133
|
-
...parsed.links.flatMap((link) => [
|
|
134
|
-
link.start,
|
|
135
|
-
link.end,
|
|
136
|
-
link.tagStart,
|
|
137
|
-
link.tagEnd
|
|
138
|
-
]),
|
|
139
|
-
...parsed.bases.flatMap((base) => [base.start, base.end]),
|
|
140
|
-
...parsed.elements.flatMap((element) => [element.classAttribute, element.idAttribute].flatMap((value) => value ? [value.start, value.end] : [])),
|
|
141
|
-
...parsed.dynamicAttributes.flatMap((value) => [value.start, value.end])
|
|
142
|
-
]));
|
|
143
|
-
const attribute = (value) => ({
|
|
144
|
-
...value,
|
|
145
|
-
start: offset(value.start),
|
|
146
|
-
end: offset(value.end)
|
|
147
|
-
});
|
|
148
|
-
return {
|
|
149
|
-
links: parsed.links.map((link) => ({
|
|
150
|
-
...link,
|
|
151
|
-
start: offset(link.start),
|
|
152
|
-
end: offset(link.end),
|
|
153
|
-
tagStart: offset(link.tagStart),
|
|
154
|
-
tagEnd: offset(link.tagEnd)
|
|
155
|
-
})),
|
|
156
|
-
bases: parsed.bases.map((base) => attribute(base)),
|
|
157
|
-
elements: parsed.elements.map((element) => ({
|
|
158
|
-
classAttribute: element.classAttribute && attribute(element.classAttribute),
|
|
159
|
-
idAttribute: element.idAttribute && attribute(element.idAttribute)
|
|
160
|
-
})),
|
|
161
|
-
dynamicAttributes: parsed.dynamicAttributes.map((value) => attribute(value))
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
function classInsertionOffset(source, floor, tagEnd) {
|
|
165
|
-
let offset = tagEnd;
|
|
166
|
-
if (source[offset - 1] === "/") offset -= 1;
|
|
167
|
-
while (offset > floor && /\s/.test(source[offset - 1] ?? "")) offset -= 1;
|
|
168
|
-
return offset;
|
|
169
|
-
}
|
|
170
|
-
function stylesheetRel(value = "") {
|
|
171
|
-
const tokens = value.toLowerCase().split(/\s+/);
|
|
172
|
-
return tokens.includes("stylesheet") && !tokens.includes("alternate");
|
|
173
|
-
}
|
|
174
|
-
function locatedAttribute(source, location, parsedValue) {
|
|
175
|
-
if (!location || parsedValue === void 0) return void 0;
|
|
176
|
-
const raw = source.slice(location.startOffset, location.endOffset);
|
|
177
|
-
const equals = raw.indexOf("=");
|
|
178
|
-
if (equals < 0) return void 0;
|
|
179
|
-
let start = equals + 1;
|
|
180
|
-
while (/\s/.test(raw[start] ?? "")) start += 1;
|
|
181
|
-
const quote = raw[start];
|
|
182
|
-
let end;
|
|
183
|
-
if (quote === "\"" || quote === "'") {
|
|
184
|
-
start += 1;
|
|
185
|
-
end = raw.lastIndexOf(quote);
|
|
186
|
-
if (end < start) return void 0;
|
|
187
|
-
} else {
|
|
188
|
-
end = start;
|
|
189
|
-
while (end < raw.length && !/\s/.test(raw[end] ?? "")) end += 1;
|
|
190
|
-
}
|
|
191
|
-
const value = raw.slice(start, end);
|
|
192
|
-
return {
|
|
193
|
-
value: value.includes("&") ? parsedValue : value,
|
|
194
|
-
writable: !value.includes("&"),
|
|
195
|
-
start: location.startOffset + start,
|
|
196
|
-
end: location.startOffset + end
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
//#endregion
|
|
200
21
|
//#region src/native.ts
|
|
22
|
+
function errorCode(error) {
|
|
23
|
+
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
24
|
+
}
|
|
201
25
|
const require = createRequire(import.meta.url);
|
|
202
26
|
const target = {
|
|
203
27
|
"darwin-arm64": "darwin-arm64",
|
|
@@ -219,7 +43,85 @@ try {
|
|
|
219
43
|
throw new Error(`No tw-migrate native addon was found for ${target}. Reinstall the package or build it locally.`);
|
|
220
44
|
}
|
|
221
45
|
}
|
|
222
|
-
const {
|
|
46
|
+
const { collectMediaConditions, mediaProbeKey, decodeSourceMap, planBatchMigration, validateCss } = binding;
|
|
47
|
+
function object(value) {
|
|
48
|
+
return value !== null && typeof value === "object";
|
|
49
|
+
}
|
|
50
|
+
function decode(label, json, valid) {
|
|
51
|
+
const value = JSON.parse(json);
|
|
52
|
+
if (!valid(value)) throw new Error(`Invalid native ${label} result`);
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function cssDirectives$1(source) {
|
|
56
|
+
try {
|
|
57
|
+
const value = JSON.parse(binding.collectCssDirectives(source));
|
|
58
|
+
return Array.isArray(value) ? value : null;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function sourceImport(value) {
|
|
64
|
+
return object(value) && typeof value.specifier === "string" && typeof value.typeOnly === "boolean" && typeof value.dynamic === "boolean";
|
|
65
|
+
}
|
|
66
|
+
function sourceAnalysisResult(value) {
|
|
67
|
+
return object(value) && Array.isArray(value.imports) && value.imports.every(sourceImport) && Array.isArray(value.staticImports) && value.staticImports.every((item) => typeof item === "string") && Array.isArray(value.defaultImports) && value.defaultImports.every((item) => object(item) && typeof item.source === "string" && typeof item.local === "string") && Array.isArray(value.vueGlobPatterns) && value.vueGlobPatterns.every((item) => typeof item === "string") && Array.isArray(value.templatePrefixes) && value.templatePrefixes.every((item) => typeof item === "string") && Array.isArray(value.useCssModuleLocals) && value.useCssModuleLocals.every((item) => typeof item === "string") && Array.isArray(value.unboundReferences) && value.unboundReferences.every((item) => typeof item === "string") && Array.isArray(value.stylesheetLinks) && value.stylesheetLinks.every((item) => typeof item === "string") && Array.isArray(value.customProperties) && value.customProperties.every((item) => typeof item === "string") && [
|
|
68
|
+
value.vueGlobUnverifiable,
|
|
69
|
+
value.hasDynamicImport,
|
|
70
|
+
value.hasVueFallthroughMacro,
|
|
71
|
+
value.usesCssModule,
|
|
72
|
+
value.hasUnboundUseCssModule,
|
|
73
|
+
value.definesRootUseCssModule,
|
|
74
|
+
value.stylesheetLinksUnverifiable,
|
|
75
|
+
value.rendersStyleElement,
|
|
76
|
+
value.injectsMarkup,
|
|
77
|
+
value.customPropertiesUnbounded
|
|
78
|
+
].every((item) => typeof item === "boolean");
|
|
79
|
+
}
|
|
80
|
+
const ANALYSIS_CACHE_LIMIT = 4096;
|
|
81
|
+
const sourceAnalysisCache = /* @__PURE__ */ new Map();
|
|
82
|
+
const stylesheetAnalysisCache = /* @__PURE__ */ new Map();
|
|
83
|
+
function bound(cache) {
|
|
84
|
+
while (cache.size > ANALYSIS_CACHE_LIMIT) {
|
|
85
|
+
const oldest = cache.keys().next().value;
|
|
86
|
+
if (oldest === void 0) break;
|
|
87
|
+
cache.delete(oldest);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function expressionAnalysis(path, source) {
|
|
91
|
+
return decode("expression analysis", binding.expressionAnalysis(path, source), (value) => object(value) && (typeof value.staticString === "string" || value.staticString === null) && (typeof value.vueModuleMember === "string" || value.vueModuleMember === null) && typeof value.usesCssModule === "boolean" && typeof value.referencesUseCssModule === "boolean" && Array.isArray(value.references) && value.references.every((item) => typeof item === "string") && Array.isArray(value.templatePrefixes) && value.templatePrefixes.every((item) => typeof item === "string"));
|
|
92
|
+
}
|
|
93
|
+
function sourceAnalysis(path, source) {
|
|
94
|
+
const cached = sourceAnalysisCache.get(path);
|
|
95
|
+
if (cached?.source === source) return cached.analysis;
|
|
96
|
+
const analysis = decode("source analysis", binding.sourceAnalysis(path, source), sourceAnalysisResult);
|
|
97
|
+
sourceAnalysisCache.set(path, {
|
|
98
|
+
source,
|
|
99
|
+
analysis
|
|
100
|
+
});
|
|
101
|
+
bound(sourceAnalysisCache);
|
|
102
|
+
return analysis;
|
|
103
|
+
}
|
|
104
|
+
function fontFamilyStack(value) {
|
|
105
|
+
return decode("font family stack", binding.fontFamilyStack(value), (parsed) => parsed === null || object(parsed) && typeof parsed.value === "string" && object(parsed.firstFamily) && typeof parsed.firstFamily.name === "string" && (parsed.firstFamily.kind === "name" || parsed.firstFamily.kind === "generic" || parsed.firstFamily.kind === "css-wide"));
|
|
106
|
+
}
|
|
107
|
+
function compiledShape(css) {
|
|
108
|
+
return decode("compiled shape", binding.compiledShape(css), (value) => object(value) && Array.isArray(value.declarations) && value.declarations.every((item) => object(item) && typeof item.property === "string" && typeof item.important === "boolean" && typeof item.value === "string") && Array.isArray(value.referencedProperties) && value.referencedProperties.every((item) => typeof item === "string"));
|
|
109
|
+
}
|
|
110
|
+
function stylesheetAnalysis(path, source) {
|
|
111
|
+
const cached = stylesheetAnalysisCache.get(path);
|
|
112
|
+
if (cached?.source === source) return cached.analysis;
|
|
113
|
+
const analysis = decode("stylesheet analysis", binding.stylesheetAnalysis(path, source), (value) => object(value) && Array.isArray(value.references) && value.references.every((item) => typeof item === "string") && Array.isArray(value.imports) && value.imports.every((item) => object(item) && typeof item.href === "string" && typeof item.media === "string" && typeof item.start === "number" && typeof item.end === "number") && [
|
|
114
|
+
value.unverifiable,
|
|
115
|
+
value.scopeEscapesUnverifiable,
|
|
116
|
+
value.selectorsUnverifiable
|
|
117
|
+
].every((item) => typeof item === "boolean") && Array.isArray(value.scopeEscapes) && value.scopeEscapes.every((item) => typeof item === "string") && Array.isArray(value.scopeShadowCss) && value.scopeShadowCss.every((item) => typeof item === "string") && object(value.themeTokens) && Object.values(value.themeTokens).every((item) => typeof item === "string") && Array.isArray(value.globalAtRuleIdentities) && value.globalAtRuleIdentities.every((item) => typeof item === "string") && typeof value.globalAtRulesUnverifiable === "boolean" && Array.isArray(value.classNames) && value.classNames.every((item) => typeof item === "string") && typeof value.classReservationsUnbounded === "boolean" && Array.isArray(value.customProperties) && value.customProperties.every((item) => typeof item === "string") && typeof value.customPropertiesUnbounded === "boolean");
|
|
118
|
+
stylesheetAnalysisCache.set(path, {
|
|
119
|
+
source,
|
|
120
|
+
analysis
|
|
121
|
+
});
|
|
122
|
+
bound(stylesheetAnalysisCache);
|
|
123
|
+
return analysis;
|
|
124
|
+
}
|
|
223
125
|
//#endregion
|
|
224
126
|
//#region src/parser/style-compiler.ts
|
|
225
127
|
const SASS_EXTENSIONS = /* @__PURE__ */ new Set([".scss", ".sass"]);
|
|
@@ -355,9 +257,6 @@ const IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
355
257
|
"node_modules"
|
|
356
258
|
]);
|
|
357
259
|
const RECOVERABLE_INPUT_ERROR = "TW_MIGRATE_RECOVERABLE_INPUT:";
|
|
358
|
-
function errorCode(error) {
|
|
359
|
-
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
360
|
-
}
|
|
361
260
|
function errorMessage(error) {
|
|
362
261
|
return error instanceof Error ? error.message : String(error);
|
|
363
262
|
}
|
|
@@ -374,15 +273,6 @@ function isStylesheetModule(path) {
|
|
|
374
273
|
const syntax = stylesheetSyntax(path);
|
|
375
274
|
return syntax !== void 0 && path.endsWith(`.module.${syntax}`);
|
|
376
275
|
}
|
|
377
|
-
function sourceReferencesStyle(file, stylePath) {
|
|
378
|
-
let importPath = normalizedRelativePath(dirname(file.path), stylePath);
|
|
379
|
-
if (!importPath.startsWith(".")) importPath = `./${importPath}`;
|
|
380
|
-
return [
|
|
381
|
-
`'${importPath}'`,
|
|
382
|
-
`"${importPath}"`,
|
|
383
|
-
`\`${importPath}\``
|
|
384
|
-
].some((literal) => file.source.includes(literal));
|
|
385
|
-
}
|
|
386
276
|
function isProjectInput(workspaceRoot, path) {
|
|
387
277
|
return isWithin(workspaceRoot, path) && !relative(workspaceRoot, path).split(/[\\/]/).includes("node_modules");
|
|
388
278
|
}
|
|
@@ -427,24 +317,26 @@ function isIntegrityError(error) {
|
|
|
427
317
|
function normalizedRelativePath(root, path) {
|
|
428
318
|
return relative(root, path).split(sep).join("/");
|
|
429
319
|
}
|
|
430
|
-
function maskCssComments(source) {
|
|
431
|
-
return source.replace(/\/\*[\s\S]*?\*\//g, (comment) => comment.replace(/[^\r\n]/g, " "));
|
|
432
|
-
}
|
|
433
320
|
function indexStylesheetDependents(styleSources) {
|
|
434
321
|
const dependents = /* @__PURE__ */ new Map();
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
322
|
+
const allPossibleTargets = [...styleSources.keys()].filter((path) => isStylesheetModule(path) || isPreprocessorPath(path));
|
|
323
|
+
for (const [path, source] of styleSources) {
|
|
324
|
+
let references;
|
|
325
|
+
try {
|
|
326
|
+
const analysis = stylesheetAnalysis(path, source);
|
|
327
|
+
references = analysis.references;
|
|
328
|
+
if (analysis.unverifiable) references = [...references, ...allPossibleTargets];
|
|
329
|
+
} catch {
|
|
330
|
+
references = allPossibleTargets;
|
|
331
|
+
}
|
|
332
|
+
for (const reference of new Set(references)) {
|
|
333
|
+
const targets = styleSources.has(reference) ? [reference] : stylesheetReferenceTargets(path, reference, styleSources);
|
|
334
|
+
for (const target of targets) {
|
|
335
|
+
if (target === path || !isStylesheetModule(target) && !isPreprocessorPath(target)) continue;
|
|
336
|
+
const paths = dependents.get(target) ?? [];
|
|
337
|
+
paths.push(path);
|
|
338
|
+
dependents.set(target, paths);
|
|
339
|
+
}
|
|
448
340
|
}
|
|
449
341
|
}
|
|
450
342
|
for (const [target, paths] of dependents) dependents.set(target, [...new Set(paths)].sort());
|
|
@@ -466,63 +358,13 @@ function stylesheetReferenceTargets(importer, reference, styleSources) {
|
|
|
466
358
|
join(target, `index${syntax}`)
|
|
467
359
|
])).filter((path) => styleSources.has(path));
|
|
468
360
|
}
|
|
469
|
-
function
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const character = masked[index];
|
|
477
|
-
if (quote) {
|
|
478
|
-
if (character === "\\") index += 1;
|
|
479
|
-
else if (character === quote) quote = void 0;
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
if (character === "\"" || character === "'") {
|
|
483
|
-
quote = character;
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
if (character === "{") {
|
|
487
|
-
depth += 1;
|
|
488
|
-
importsAllowed = false;
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
if (character === "}") {
|
|
492
|
-
depth = Math.max(0, depth - 1);
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
if (!importsAllowed || depth !== 0 || masked.slice(index, index + 7).toLowerCase() !== "@import" || /[-\w]/.test(masked[index + 7] ?? "")) continue;
|
|
496
|
-
let end = index + 7;
|
|
497
|
-
let importQuote;
|
|
498
|
-
let parentheses = 0;
|
|
499
|
-
for (; end < masked.length; end += 1) {
|
|
500
|
-
const next = masked[end];
|
|
501
|
-
if (importQuote) {
|
|
502
|
-
if (next === "\\") end += 1;
|
|
503
|
-
else if (next === importQuote) importQuote = void 0;
|
|
504
|
-
} else if (next === "\"" || next === "'") importQuote = next;
|
|
505
|
-
else if (next === "(") parentheses += 1;
|
|
506
|
-
else if (next === ")") parentheses = Math.max(0, parentheses - 1);
|
|
507
|
-
else if (next === ";" && parentheses === 0) break;
|
|
508
|
-
}
|
|
509
|
-
if (end >= masked.length) continue;
|
|
510
|
-
const statement = masked.slice(index, end + 1);
|
|
511
|
-
const match = /^@import\s+(?:url\(\s*)?(?:["']([^"']+)["']|([^"'()\s;]+))\s*\)?\s*([^;]*);$/i.exec(statement);
|
|
512
|
-
if (match) imports.push({
|
|
513
|
-
href: match[1] ?? match[2],
|
|
514
|
-
media: match[3].trim(),
|
|
515
|
-
start: index,
|
|
516
|
-
end: end + 1
|
|
517
|
-
});
|
|
518
|
-
index = end;
|
|
519
|
-
}
|
|
520
|
-
const offsets = utf8OffsetMap(source, imports.flatMap((imported) => [imported.start, imported.end]));
|
|
521
|
-
return imports.map((imported) => ({
|
|
522
|
-
...imported,
|
|
523
|
-
start: offsets.get(imported.start) ?? imported.start,
|
|
524
|
-
end: offsets.get(imported.end) ?? imported.end
|
|
525
|
-
}));
|
|
361
|
+
function localHrefTarget(href) {
|
|
362
|
+
const target = href.split(/[?#]/, 1)[0];
|
|
363
|
+
if (!target || /^[a-z][a-z0-9+.-]*:|^\/\//i.test(target)) return void 0;
|
|
364
|
+
return target;
|
|
365
|
+
}
|
|
366
|
+
function cssImports(path, source) {
|
|
367
|
+
return stylesheetAnalysis(path, source).imports;
|
|
526
368
|
}
|
|
527
369
|
//#endregion
|
|
528
370
|
//#region src/discovery.ts
|
|
@@ -593,6 +435,28 @@ async function findGitRoot(cwd) {
|
|
|
593
435
|
return;
|
|
594
436
|
}
|
|
595
437
|
}
|
|
438
|
+
async function scannerIgnoredPaths(workspaceRoot, paths) {
|
|
439
|
+
if (paths.length === 0 || await findGitRoot(workspaceRoot) !== workspaceRoot) return /* @__PURE__ */ new Set();
|
|
440
|
+
return new Promise((resolvePromise) => {
|
|
441
|
+
execFile("git", [
|
|
442
|
+
"check-ignore",
|
|
443
|
+
"--no-index",
|
|
444
|
+
"-z",
|
|
445
|
+
"--stdin"
|
|
446
|
+
], {
|
|
447
|
+
cwd: workspaceRoot,
|
|
448
|
+
maxBuffer: 67108864
|
|
449
|
+
}, (error, stdout) => {
|
|
450
|
+
if (error !== null && error.code !== 1) {
|
|
451
|
+
resolvePromise(new Set(paths));
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
455
|
+
for (const entry of stdout.split("\0")) if (entry) ignored.add(resolve(workspaceRoot, entry));
|
|
456
|
+
resolvePromise(ignored);
|
|
457
|
+
}).stdin?.end(paths.map((path) => `${relative(workspaceRoot, path)}\0`).join(""));
|
|
458
|
+
});
|
|
459
|
+
}
|
|
596
460
|
async function isIgnoredByGit(gitRoot, path) {
|
|
597
461
|
if (gitRoot === path) return false;
|
|
598
462
|
try {
|
|
@@ -617,7 +481,7 @@ async function discoverGitFiles(root) {
|
|
|
617
481
|
"."
|
|
618
482
|
], {
|
|
619
483
|
cwd: root,
|
|
620
|
-
maxBuffer:
|
|
484
|
+
maxBuffer: 67108864
|
|
621
485
|
});
|
|
622
486
|
const paths = stdout.split("\0").filter(Boolean).map((path) => resolve(root, path)).filter((path) => !hasIgnoredDirectory(root, path) && isRelevantDiscoveredFile(path));
|
|
623
487
|
return (await Promise.all(paths.map(async (path) => {
|
|
@@ -659,41 +523,532 @@ async function collectFiles(root, include) {
|
|
|
659
523
|
return files;
|
|
660
524
|
}
|
|
661
525
|
//#endregion
|
|
662
|
-
//#region src/
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
if (foreign && !owner) continue;
|
|
675
|
-
const referenceRoot = foreign ? owner : packageRoot;
|
|
676
|
-
const parsed = parseHtmlSource(file.path, file.source);
|
|
677
|
-
const contexts = [];
|
|
678
|
-
let linkBase = dirname(file.path);
|
|
679
|
-
const base = parsed.bases[0];
|
|
680
|
-
if (base) {
|
|
681
|
-
const baseReference = base.href.split(/[?#]/, 1)[0];
|
|
682
|
-
const basePath = base.writable && (baseReference === "" ? file.path : localHtmlReference(referenceRoot, dirname(file.path), base.href));
|
|
683
|
-
if (!basePath || !isWithin(referenceRoot, basePath)) {
|
|
684
|
-
if (!foreign) warnings.push(htmlWarning("unsupported-html-base", file.path, base.start, base.end, "A remote or unrepresentable base URL prevents safe stylesheet link resolution."));
|
|
685
|
-
linkBase = void 0;
|
|
686
|
-
} else linkBase = base.href.split(/[?#]/, 1)[0].endsWith("/") ? basePath : dirname(basePath);
|
|
526
|
+
//#region src/parser/html.ts
|
|
527
|
+
const TEMPLATE_MARKERS = /\{\{|\}\}|\$\{|<%|%>|<#|#>|\[%|%\]/;
|
|
528
|
+
/** Balanced template expressions, for masking a templated attribute value
|
|
529
|
+
* before extracting its static class-prefix tokens. */
|
|
530
|
+
const TEMPLATE_EXPRESSIONS = /\{\{[\s\S]*?\}\}|\$\{[\s\S]*?\}|<%[\s\S]*?%>|<#[\s\S]*?#>|\[%[\s\S]*?%\]/g;
|
|
531
|
+
function parseHtmlSource(path, source) {
|
|
532
|
+
const errors = [];
|
|
533
|
+
const document = parse(source, {
|
|
534
|
+
sourceCodeLocationInfo: true,
|
|
535
|
+
scriptingEnabled: false,
|
|
536
|
+
onParseError(error) {
|
|
537
|
+
if (error.code !== "missing-doctype") errors.push(error);
|
|
687
538
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
539
|
+
});
|
|
540
|
+
if (errors.length > 0) throw new Error(`Failed to parse ${path}: ${errors.map((error) => error.code).join(", ")}`);
|
|
541
|
+
const links = [];
|
|
542
|
+
const bases = [];
|
|
543
|
+
const elements = [];
|
|
544
|
+
const dynamicAttributes = [];
|
|
545
|
+
const dynamicClassValues = [];
|
|
546
|
+
const styleAttributeValues = [];
|
|
547
|
+
const handlerTexts = [];
|
|
548
|
+
const scriptTexts = [];
|
|
549
|
+
let hasStyle = false;
|
|
550
|
+
const unquoted = (attribute) => source[attribute.start - 1] !== "\"" && source[attribute.start - 1] !== "'";
|
|
551
|
+
function visit(node) {
|
|
552
|
+
if (node.tagName === "style") hasStyle = true;
|
|
553
|
+
if (node.tagName === "script") {
|
|
554
|
+
for (const child of node.childNodes ?? []) if (child.nodeName === "#text" && child.value) scriptTexts.push(child.value);
|
|
555
|
+
}
|
|
556
|
+
if (node.tagName) {
|
|
557
|
+
const attributes = new Map((node.attrs ?? []).map((attribute) => [attribute.name, attribute.value]));
|
|
558
|
+
const style = attributes.get("style");
|
|
559
|
+
if (style !== void 0 && style !== "") styleAttributeValues.push(style);
|
|
560
|
+
for (const [name, value] of attributes) if (name.startsWith("on") && value !== "") handlerTexts.push(value);
|
|
561
|
+
const locations = node.sourceCodeLocation?.attrs;
|
|
562
|
+
if (locations) {
|
|
563
|
+
if (node.tagName === "link" && stylesheetRel(attributes.get("rel")) && !attributes.has("disabled")) {
|
|
564
|
+
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
565
|
+
const media = locatedAttribute(source, locations.media, attributes.get("media"));
|
|
566
|
+
const tag = node.sourceCodeLocation?.startTag ?? node.sourceCodeLocation;
|
|
567
|
+
if (href && tag) links.push({
|
|
568
|
+
href: href.value,
|
|
569
|
+
media: media?.value ?? "",
|
|
570
|
+
start: href.start,
|
|
571
|
+
end: href.end,
|
|
572
|
+
tagStart: tag.startOffset,
|
|
573
|
+
tagEnd: tag.endOffset
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
if (node.tagName === "base" && bases.length === 0) {
|
|
577
|
+
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
578
|
+
if (href) bases.push({
|
|
579
|
+
href: href.value,
|
|
580
|
+
writable: href.writable,
|
|
581
|
+
start: href.start,
|
|
582
|
+
end: href.end
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
let classAttribute = locatedAttribute(source, locations.class, attributes.get("class"));
|
|
586
|
+
const idAttribute = locatedAttribute(source, locations.id, attributes.get("id"));
|
|
587
|
+
const unparsedClass = !classAttribute && idAttribute && locations.class ? {
|
|
588
|
+
start: locations.class.startOffset,
|
|
589
|
+
end: locations.class.endOffset
|
|
590
|
+
} : void 0;
|
|
591
|
+
const dynamic = unparsedClass ?? [classAttribute, idAttribute].find((attribute) => attribute && (!attribute.writable || TEMPLATE_MARKERS.test(attribute.value))) ?? (classAttribute && unquoted(classAttribute) ? classAttribute : void 0);
|
|
592
|
+
if (dynamic) {
|
|
593
|
+
dynamicAttributes.push({
|
|
594
|
+
start: dynamic.start,
|
|
595
|
+
end: dynamic.end
|
|
596
|
+
});
|
|
597
|
+
const decoded = attributes.get("class");
|
|
598
|
+
const entityBearing = classAttribute !== void 0 && dynamic === classAttribute && classAttribute.writable === false;
|
|
599
|
+
if (dynamic !== idAttribute && decoded !== void 0 && (TEMPLATE_MARKERS.test(decoded) || entityBearing || dynamic === unparsedClass)) dynamicClassValues.push(decoded);
|
|
600
|
+
} else if (classAttribute || idAttribute) {
|
|
601
|
+
if (!classAttribute && idAttribute) {
|
|
602
|
+
const startTag = node.sourceCodeLocation?.startTag;
|
|
603
|
+
if (startTag && source[startTag.endOffset - 1] === ">") {
|
|
604
|
+
const insertion = classInsertionOffset(source, startTag.startOffset, startTag.endOffset - 1);
|
|
605
|
+
classAttribute = {
|
|
606
|
+
value: "",
|
|
607
|
+
start: insertion,
|
|
608
|
+
end: insertion,
|
|
609
|
+
synthetic: true
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
elements.push({
|
|
614
|
+
classAttribute,
|
|
615
|
+
idAttribute
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
for (const child of node.childNodes ?? []) visit(child);
|
|
621
|
+
if (node.content) visit(node.content);
|
|
622
|
+
}
|
|
623
|
+
visit(document);
|
|
624
|
+
return {
|
|
625
|
+
...toByteOffsets(source, {
|
|
626
|
+
links,
|
|
627
|
+
bases,
|
|
628
|
+
elements,
|
|
629
|
+
dynamicAttributes
|
|
630
|
+
}),
|
|
631
|
+
dynamicClassValues,
|
|
632
|
+
styleAttributeValues,
|
|
633
|
+
handlerText: handlerTexts.join(";\n"),
|
|
634
|
+
scriptText: scriptTexts.join("\n"),
|
|
635
|
+
hasStyle
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function utf8OffsetMap(source, indices) {
|
|
639
|
+
const sorted = [...new Set(indices)].sort((left, right) => left - right);
|
|
640
|
+
const map = /* @__PURE__ */ new Map();
|
|
641
|
+
let lastIndex = 0;
|
|
642
|
+
let bytes = 0;
|
|
643
|
+
for (const index of sorted) {
|
|
644
|
+
bytes += Buffer.byteLength(source.slice(lastIndex, index));
|
|
645
|
+
lastIndex = index;
|
|
646
|
+
map.set(index, bytes);
|
|
647
|
+
}
|
|
648
|
+
return map;
|
|
649
|
+
}
|
|
650
|
+
function offsetLookup(offsets) {
|
|
651
|
+
return (index) => {
|
|
652
|
+
const byte = offsets.get(index);
|
|
653
|
+
if (byte === void 0) throw new Error(`No byte offset was mapped for index ${index}`);
|
|
654
|
+
return byte;
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function toByteOffsets(source, parsed) {
|
|
658
|
+
const offset = offsetLookup(utf8OffsetMap(source, [
|
|
659
|
+
...parsed.links.flatMap((link) => [
|
|
660
|
+
link.start,
|
|
661
|
+
link.end,
|
|
662
|
+
link.tagStart,
|
|
663
|
+
link.tagEnd
|
|
664
|
+
]),
|
|
665
|
+
...parsed.bases.flatMap((base) => [base.start, base.end]),
|
|
666
|
+
...parsed.elements.flatMap((element) => [element.classAttribute, element.idAttribute].flatMap((value) => value ? [value.start, value.end] : [])),
|
|
667
|
+
...parsed.dynamicAttributes.flatMap((value) => [value.start, value.end])
|
|
668
|
+
]));
|
|
669
|
+
const attribute = (value) => ({
|
|
670
|
+
...value,
|
|
671
|
+
start: offset(value.start),
|
|
672
|
+
end: offset(value.end)
|
|
673
|
+
});
|
|
674
|
+
return {
|
|
675
|
+
links: parsed.links.map((link) => ({
|
|
676
|
+
...link,
|
|
677
|
+
start: offset(link.start),
|
|
678
|
+
end: offset(link.end),
|
|
679
|
+
tagStart: offset(link.tagStart),
|
|
680
|
+
tagEnd: offset(link.tagEnd)
|
|
681
|
+
})),
|
|
682
|
+
bases: parsed.bases.map((base) => attribute(base)),
|
|
683
|
+
elements: parsed.elements.map((element) => ({
|
|
684
|
+
classAttribute: element.classAttribute && attribute(element.classAttribute),
|
|
685
|
+
idAttribute: element.idAttribute && attribute(element.idAttribute)
|
|
686
|
+
})),
|
|
687
|
+
dynamicAttributes: parsed.dynamicAttributes.map((value) => attribute(value))
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function classInsertionOffset(source, floor, tagEnd) {
|
|
691
|
+
let offset = tagEnd;
|
|
692
|
+
if (source[offset - 1] === "/") offset -= 1;
|
|
693
|
+
while (offset > floor && /\s/.test(source[offset - 1] ?? "")) offset -= 1;
|
|
694
|
+
return offset;
|
|
695
|
+
}
|
|
696
|
+
function stylesheetRel(value = "") {
|
|
697
|
+
const tokens = value.toLowerCase().split(/\s+/);
|
|
698
|
+
return tokens.includes("stylesheet") && !tokens.includes("alternate");
|
|
699
|
+
}
|
|
700
|
+
function locatedAttribute(source, location, parsedValue) {
|
|
701
|
+
if (!location || parsedValue === void 0) return void 0;
|
|
702
|
+
const raw = source.slice(location.startOffset, location.endOffset);
|
|
703
|
+
const equals = raw.indexOf("=");
|
|
704
|
+
if (equals < 0) return void 0;
|
|
705
|
+
let start = equals + 1;
|
|
706
|
+
while (/\s/.test(raw[start] ?? "")) start += 1;
|
|
707
|
+
const quote = raw[start];
|
|
708
|
+
let end;
|
|
709
|
+
if (quote === "\"" || quote === "'") {
|
|
710
|
+
start += 1;
|
|
711
|
+
end = raw.lastIndexOf(quote);
|
|
712
|
+
if (end < start) return void 0;
|
|
713
|
+
} else {
|
|
714
|
+
end = start;
|
|
715
|
+
while (end < raw.length && !/\s/.test(raw[end] ?? "")) end += 1;
|
|
716
|
+
}
|
|
717
|
+
const value = raw.slice(start, end);
|
|
718
|
+
return {
|
|
719
|
+
value: value.includes("&") ? parsedValue : value,
|
|
720
|
+
writable: !value.includes("&"),
|
|
721
|
+
start: location.startOffset + start,
|
|
722
|
+
end: location.startOffset + end
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
//#endregion
|
|
726
|
+
//#region src/plan/entry.ts
|
|
727
|
+
function emitsUtilities(specifier) {
|
|
728
|
+
return specifier === "tailwindcss" || specifier === "tailwindcss/utilities" || specifier === "tailwindcss/utilities.css";
|
|
729
|
+
}
|
|
730
|
+
function hasTailwindImport(source) {
|
|
731
|
+
return cssDirectives(source)?.some((directive) => directive.kind === "import" && directive.specifier !== null && emitsUtilities(directive.specifier)) ?? false;
|
|
732
|
+
}
|
|
733
|
+
function tailwindEntryCatalog(styleSources, pathOwners) {
|
|
734
|
+
const catalog = /* @__PURE__ */ new Map();
|
|
735
|
+
for (const [path, source] of styleSources) {
|
|
736
|
+
if (extname(path) !== ".css") continue;
|
|
737
|
+
if (!hasTailwindImport(source)) continue;
|
|
738
|
+
const owner = pathOwners.get(path);
|
|
739
|
+
if (owner === void 0) continue;
|
|
740
|
+
const entries = catalog.get(owner) ?? [];
|
|
741
|
+
entries.push(path);
|
|
742
|
+
catalog.set(owner, entries);
|
|
743
|
+
}
|
|
744
|
+
for (const entries of catalog.values()) entries.sort();
|
|
745
|
+
return catalog;
|
|
746
|
+
}
|
|
747
|
+
function cssDirectives(source) {
|
|
748
|
+
return cssDirectives$1(source)?.filter((directive) => directive !== null && typeof directive === "object" && "kind" in directive && typeof directive.kind === "string") ?? null;
|
|
749
|
+
}
|
|
750
|
+
function entryGraphSheets(entry, styleSources) {
|
|
751
|
+
const sheets = [];
|
|
752
|
+
const seen = /* @__PURE__ */ new Set();
|
|
753
|
+
const pending = [entry];
|
|
754
|
+
while (pending.length > 0) {
|
|
755
|
+
const path = pending.pop();
|
|
756
|
+
if (path === void 0 || seen.has(path)) continue;
|
|
757
|
+
seen.add(path);
|
|
758
|
+
const raw = styleSources.get(path);
|
|
759
|
+
if (raw === void 0) return null;
|
|
760
|
+
const directives = cssDirectives(raw);
|
|
761
|
+
if (directives === null) return null;
|
|
762
|
+
sheets.push({
|
|
763
|
+
path,
|
|
764
|
+
directives
|
|
765
|
+
});
|
|
766
|
+
for (const directive of directives) {
|
|
767
|
+
if (directive.kind !== "import") continue;
|
|
768
|
+
if (directive.specifier === null) return null;
|
|
769
|
+
const spec = directive.specifier;
|
|
770
|
+
if (directive.tailwind) continue;
|
|
771
|
+
if (!spec.startsWith(".") && !spec.startsWith("/")) {
|
|
772
|
+
let resolved;
|
|
773
|
+
try {
|
|
774
|
+
resolved = createRequire(join(dirname(path), "package.json")).resolve(spec);
|
|
775
|
+
} catch {
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
if (/[\\/]node_modules[\\/]/.test(resolved)) continue;
|
|
779
|
+
pending.push(resolved);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
const resolved = resolve(dirname(path), spec);
|
|
783
|
+
pending.push(styleSources.has(resolved) ? resolved : `${resolved}.css`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return sheets;
|
|
787
|
+
}
|
|
788
|
+
function scanProof(options) {
|
|
789
|
+
const sheets = entryGraphSheets(options.entry, new Map([...options.styleSources ?? [], [options.entry, options.entrySource]]));
|
|
790
|
+
if (sheets === null) return null;
|
|
791
|
+
let literal = false;
|
|
792
|
+
let automaticDisabled = false;
|
|
793
|
+
for (const { path, directives } of sheets) {
|
|
794
|
+
const base = dirname(path);
|
|
795
|
+
const coversChild = (scope) => {
|
|
796
|
+
if (scope.includes("*")) return false;
|
|
797
|
+
return isWithin(resolve(base, scope), options.packageRoot);
|
|
798
|
+
};
|
|
799
|
+
const excludesChild = (scope) => {
|
|
800
|
+
const stripped = resolve(base, scope.replace(/\*.*$/, ""));
|
|
801
|
+
if (scope.includes("*")) return options.packageRoot.startsWith(stripped) || stripped.startsWith(options.packageRoot);
|
|
802
|
+
return isWithin(stripped, options.packageRoot) || isWithin(options.packageRoot, stripped);
|
|
803
|
+
};
|
|
804
|
+
for (const directive of directives) if (directive.kind === "source") {
|
|
805
|
+
if (directive.inline) continue;
|
|
806
|
+
if (directive.unreadable || directive.scope === null) return null;
|
|
807
|
+
if (directive.not) {
|
|
808
|
+
if (excludesChild(directive.scope)) return null;
|
|
809
|
+
} else if (coversChild(directive.scope)) literal = true;
|
|
810
|
+
} else if (directive.kind === "import" && directive.tailwind) {
|
|
811
|
+
if (directive.sourceUnreadable) return null;
|
|
812
|
+
if (directive.source === "none") automaticDisabled = true;
|
|
813
|
+
else if (directive.source !== null) {
|
|
814
|
+
if (coversChild(directive.source)) literal = true;
|
|
815
|
+
else automaticDisabled = true;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (literal) return "literal";
|
|
820
|
+
if (automaticDisabled) return null;
|
|
821
|
+
return isWithin(dirname(options.entry), options.packageRoot) ? "automatic" : null;
|
|
822
|
+
}
|
|
823
|
+
const OPAQUE_SOURCE_IMPORT = {
|
|
824
|
+
specifier: "#unverifiable-source",
|
|
825
|
+
typeOnly: false,
|
|
826
|
+
dynamic: true
|
|
827
|
+
};
|
|
828
|
+
function sourceImports(file) {
|
|
829
|
+
const extension = extname(file.path);
|
|
830
|
+
if (extension === ".html") return [];
|
|
831
|
+
if (extension === ".vue") return file.sourceImports && !file.sourceImportsUnverifiable ? file.sourceImports : [OPAQUE_SOURCE_IMPORT];
|
|
832
|
+
try {
|
|
833
|
+
return sourceAnalysis(file.path, file.source).imports;
|
|
834
|
+
} catch {
|
|
835
|
+
return [OPAQUE_SOURCE_IMPORT];
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
const RESOLVABLE_EXTENSIONS = [
|
|
839
|
+
".ts",
|
|
840
|
+
".tsx",
|
|
841
|
+
".mts",
|
|
842
|
+
".cts",
|
|
843
|
+
".js",
|
|
844
|
+
".jsx",
|
|
845
|
+
".mjs",
|
|
846
|
+
".cjs",
|
|
847
|
+
".vue"
|
|
848
|
+
];
|
|
849
|
+
function resolveImport(fromDir, spec, known) {
|
|
850
|
+
if (!spec.startsWith(".")) return void 0;
|
|
851
|
+
const base = resolve(fromDir, spec);
|
|
852
|
+
const emitted = base.match(/^(.*)\.(?:mjs|cjs|js|jsx)$/);
|
|
853
|
+
return [
|
|
854
|
+
base,
|
|
855
|
+
...emitted ? [
|
|
856
|
+
".ts",
|
|
857
|
+
".tsx",
|
|
858
|
+
".mts",
|
|
859
|
+
".cts"
|
|
860
|
+
].map((extension) => `${emitted[1]}${extension}`) : [],
|
|
861
|
+
...RESOLVABLE_EXTENSIONS.map((extension) => `${base}${extension}`),
|
|
862
|
+
...RESOLVABLE_EXTENSIONS.map((extension) => join(base, `index${extension}`))
|
|
863
|
+
].find((candidate) => known.has(candidate));
|
|
864
|
+
}
|
|
865
|
+
function scriptLikeSpecifier(spec) {
|
|
866
|
+
if (!spec.startsWith(".")) return false;
|
|
867
|
+
const trimmed = spec.split(/[?#]/, 1)[0];
|
|
868
|
+
return /\.(?:m?[jt]sx?|c[jt]s|vue)$/.test(trimmed) || !/\.[^/.]+$/.test(trimmed);
|
|
869
|
+
}
|
|
870
|
+
function externalPackageSpecifier(spec) {
|
|
871
|
+
return /^(?:node:[\w/.-]+|(?:@[a-z0-9~][\w.~-]*\/)?[a-z0-9~][\w.~-]*(?:\/.*)?)$/.test(spec);
|
|
872
|
+
}
|
|
873
|
+
function importEdges(files) {
|
|
874
|
+
const known = new Set(files.map((file) => file.path));
|
|
875
|
+
const edges = /* @__PURE__ */ new Map();
|
|
876
|
+
for (const file of files) {
|
|
877
|
+
if (extname(file.path) === ".html") continue;
|
|
878
|
+
const targets = [];
|
|
879
|
+
for (const record of sourceImports(file)) {
|
|
880
|
+
if (record.typeOnly) continue;
|
|
881
|
+
const resolved = resolveImport(dirname(file.path), record.specifier, known);
|
|
882
|
+
if (resolved !== void 0) targets.push(resolved);
|
|
883
|
+
}
|
|
884
|
+
edges.set(file.path, targets);
|
|
885
|
+
}
|
|
886
|
+
return edges;
|
|
887
|
+
}
|
|
888
|
+
function reachableFrom(starts, edges) {
|
|
889
|
+
const reached = new Set(starts);
|
|
890
|
+
const pending = [...reached];
|
|
891
|
+
while (pending.length > 0) {
|
|
892
|
+
const current = pending.pop();
|
|
893
|
+
if (current === void 0) break;
|
|
894
|
+
for (const target of edges.get(current) ?? []) if (!reached.has(target)) {
|
|
895
|
+
reached.add(target);
|
|
896
|
+
pending.push(target);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return reached;
|
|
900
|
+
}
|
|
901
|
+
function exposedFiles(packageRoot, packageJson, files, inboundSeeds) {
|
|
902
|
+
const known = new Set(files.map((file) => file.path));
|
|
903
|
+
const specs = [];
|
|
904
|
+
const collect = (value) => {
|
|
905
|
+
if (typeof value === "string") {
|
|
906
|
+
if (value.includes("*")) return "all";
|
|
907
|
+
specs.push(value);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if (value !== null && typeof value === "object") for (const [key, child] of Object.entries(value)) {
|
|
911
|
+
if (key.includes("*")) return "all";
|
|
912
|
+
if (collect(child) === "all") return "all";
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
for (const field of [
|
|
916
|
+
"main",
|
|
917
|
+
"module",
|
|
918
|
+
"exports",
|
|
919
|
+
"browser"
|
|
920
|
+
]) if (collect(packageJson[field]) === "all") return "all";
|
|
921
|
+
if (packageJson.exports === void 0 && packageJson.private !== true) return "all";
|
|
922
|
+
const declared = specs.length > 0;
|
|
923
|
+
const entryFileSeeds = [...inboundSeeds];
|
|
924
|
+
if (!declared) specs.push("./index");
|
|
925
|
+
const entryFiles = [...entryFileSeeds];
|
|
926
|
+
for (const spec of specs) {
|
|
927
|
+
const resolved = resolveImport(packageRoot, spec.startsWith(".") ? spec : `./${spec}`, known);
|
|
928
|
+
if (resolved === void 0) {
|
|
929
|
+
if (declared) return "all";
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
entryFiles.push(resolved);
|
|
933
|
+
}
|
|
934
|
+
const byPath = new Map(files.map((file) => [file.path, file]));
|
|
935
|
+
const exposed = new Set(entryFiles);
|
|
936
|
+
const pending = [...entryFiles];
|
|
937
|
+
while (pending.length > 0) {
|
|
938
|
+
const current = pending.pop();
|
|
939
|
+
if (current === void 0) break;
|
|
940
|
+
const file = byPath.get(current);
|
|
941
|
+
if (file === void 0 || extname(file.path) === ".html") continue;
|
|
942
|
+
for (const record of sourceImports(file)) {
|
|
943
|
+
if (record.typeOnly) continue;
|
|
944
|
+
const resolved = resolveImport(dirname(file.path), record.specifier, known);
|
|
945
|
+
if (resolved === void 0) {
|
|
946
|
+
if (scriptLikeSpecifier(record.specifier) || !record.specifier.startsWith(".") && !externalPackageSpecifier(record.specifier)) return "all";
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
if (!exposed.has(resolved)) {
|
|
950
|
+
exposed.add(resolved);
|
|
951
|
+
pending.push(resolved);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return exposed;
|
|
956
|
+
}
|
|
957
|
+
function proveSharedEntry(options) {
|
|
958
|
+
const scan = scanProof(options);
|
|
959
|
+
if (scan === null) return null;
|
|
960
|
+
const ownedSources = options.packageSources.filter((file) => options.owned(file.path));
|
|
961
|
+
const loaders = ownedSources.filter((file) => options.writable(file.path)).filter((file) => (file.htmlStylesheets ?? []).some((context) => context.cssPath === options.entry) || htmlLinksEntry(file, options.entry) || extname(file.path) !== ".html" && importsEntry(file, options.entry)).map((file) => file.path);
|
|
962
|
+
if (loaders.length === 0) return null;
|
|
963
|
+
const reachable = reachableFrom(loaders, importEdges(ownedSources));
|
|
964
|
+
const ownedPaths = new Set(ownedSources.map((file) => file.path));
|
|
965
|
+
const packageName = typeof options.packageJson.name === "string" ? options.packageJson.name : void 0;
|
|
966
|
+
const resolveInbound = (fromDir, spec) => {
|
|
967
|
+
const relative = resolveImport(fromDir, spec, ownedPaths);
|
|
968
|
+
if (relative !== void 0) return relative;
|
|
969
|
+
if (packageName !== void 0 && spec.startsWith(`${packageName}/`)) return resolveImport(options.packageRoot, `./${spec.slice(packageName.length + 1)}`, ownedPaths);
|
|
970
|
+
};
|
|
971
|
+
const inboundSeeds = [];
|
|
972
|
+
let foreignUnverifiable = false;
|
|
973
|
+
for (const file of options.packageSources) {
|
|
974
|
+
if (options.owned(file.path) || extname(file.path) === ".html") continue;
|
|
975
|
+
if (importsEntry(file, options.entry)) continue;
|
|
976
|
+
const records = sourceImports(file);
|
|
977
|
+
if (records.includes(OPAQUE_SOURCE_IMPORT)) {
|
|
978
|
+
foreignUnverifiable = true;
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
for (const record of records) {
|
|
982
|
+
if (record.typeOnly) continue;
|
|
983
|
+
const resolved = resolveInbound(dirname(file.path), record.specifier);
|
|
984
|
+
if (resolved !== void 0) inboundSeeds.push(resolved);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
const exposed = foreignUnverifiable ? "all" : exposedFiles(options.packageRoot, options.packageJson, ownedSources, inboundSeeds);
|
|
988
|
+
const provenConsumer = (consumer) => options.owned(consumer.path) && !sourceImports(consumer).includes(OPAQUE_SOURCE_IMPORT) && reachable.has(consumer.path) && (exposed === "all" ? false : !exposed.has(consumer.path)) && (scan === "literal" || !options.ignoredPaths.has(consumer.path));
|
|
989
|
+
return {
|
|
990
|
+
provenConsumer,
|
|
991
|
+
provenStyle: (consumers) => consumers.length > 0 && consumers.every(provenConsumer)
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
function htmlLinksEntry(file, entry) {
|
|
995
|
+
if (extname(file.path) !== ".html") return false;
|
|
996
|
+
try {
|
|
997
|
+
const parsed = parseHtmlSource(file.path, file.source);
|
|
998
|
+
if (parsed.bases.length > 0) return false;
|
|
999
|
+
return parsed.links.some((link) => {
|
|
1000
|
+
const media = link.media.trim().toLowerCase();
|
|
1001
|
+
if (media !== "" && media !== "all") return false;
|
|
1002
|
+
const href = localHrefTarget(link.href);
|
|
1003
|
+
if (href === void 0) return false;
|
|
1004
|
+
return resolve(dirname(file.path), href) === entry;
|
|
1005
|
+
});
|
|
1006
|
+
} catch {
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
function importsStylesheet(file, stylePath) {
|
|
1011
|
+
return sourceImports(file).some((record) => record === OPAQUE_SOURCE_IMPORT && file.source.includes(basename(stylePath)) || !record.typeOnly && record.specifier.startsWith(".") && resolve(dirname(file.path), record.specifier) === stylePath);
|
|
1012
|
+
}
|
|
1013
|
+
function importsEntry(file, entry) {
|
|
1014
|
+
return sourceImports(file).some((record) => !record.typeOnly && !record.dynamic && record.specifier.startsWith(".") && resolve(dirname(file.path), record.specifier) === entry);
|
|
1015
|
+
}
|
|
1016
|
+
//#endregion
|
|
1017
|
+
//#region src/plan/html.ts
|
|
1018
|
+
async function preparePackageHtml({ packageRoot, sourceFiles, styleSources, snapshots, pathOwners, styleDependents }) {
|
|
1019
|
+
const files = [];
|
|
1020
|
+
const stylePaths = /* @__PURE__ */ new Set();
|
|
1021
|
+
const generatedPaths = /* @__PURE__ */ new Set();
|
|
1022
|
+
const removableLinks = [];
|
|
1023
|
+
const warnings = [];
|
|
1024
|
+
const htmlFiles = sourceFiles.filter((file) => extname(file.path) === ".html");
|
|
1025
|
+
const orderedFiles = [...htmlFiles.filter((file) => pathOwners.get(file.path) === packageRoot), ...htmlFiles.filter((file) => pathOwners.get(file.path) !== packageRoot)];
|
|
1026
|
+
for (const file of orderedFiles) {
|
|
1027
|
+
const owner = pathOwners.get(file.path);
|
|
1028
|
+
const foreign = owner !== packageRoot;
|
|
1029
|
+
if (foreign && !owner) continue;
|
|
1030
|
+
const referenceRoot = foreign ? owner : packageRoot;
|
|
1031
|
+
const parsed = parseHtmlSource(file.path, file.source);
|
|
1032
|
+
const contexts = [];
|
|
1033
|
+
let linkBase = dirname(file.path);
|
|
1034
|
+
const base = parsed.bases[0];
|
|
1035
|
+
if (base) {
|
|
1036
|
+
const baseReference = base.href.split(/[?#]/, 1)[0];
|
|
1037
|
+
const basePath = base.writable && (baseReference === "" ? file.path : localHtmlReference(referenceRoot, dirname(file.path), base.href));
|
|
1038
|
+
if (!basePath || !isWithin(referenceRoot, basePath)) {
|
|
1039
|
+
if (!foreign) warnings.push(htmlWarning("unsupported-html-base", file.path, base.start, base.end, "A remote or unrepresentable base URL prevents safe stylesheet link resolution."));
|
|
1040
|
+
linkBase = void 0;
|
|
1041
|
+
} else linkBase = base.href.split(/[?#]/, 1)[0].endsWith("/") ? basePath : dirname(basePath);
|
|
1042
|
+
}
|
|
1043
|
+
for (const link of parsed.links) {
|
|
1044
|
+
const linkedPath = linkBase && localHtmlReference(referenceRoot, linkBase, link.href);
|
|
1045
|
+
if (!linkedPath || !isWithin(packageRoot, linkedPath) || foreign && pathOwners.get(linkedPath) !== packageRoot) {
|
|
1046
|
+
if (!foreign) warnings.push(htmlWarning("unsupported-html-stylesheet-link", file.path, link.start, link.end, "Only local package stylesheet links are analyzed."));
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
const variants = mediaVariants(link.media);
|
|
1050
|
+
if (variants === void 0) {
|
|
1051
|
+
const cssPath = (foreign ? void 0 : inferredPreprocessorPath({
|
|
697
1052
|
path: linkedPath,
|
|
698
1053
|
packageRoot,
|
|
699
1054
|
styleSources,
|
|
@@ -783,7 +1138,7 @@ async function collectHtmlStyleContexts(state) {
|
|
|
783
1138
|
analyzable: true
|
|
784
1139
|
});
|
|
785
1140
|
if (extname(state.path) !== ".css") return;
|
|
786
|
-
for (const imported of cssImports(source)) {
|
|
1141
|
+
for (const imported of cssImports(state.path, source)) {
|
|
787
1142
|
const variants = mediaVariants(imported.media);
|
|
788
1143
|
if (variants === void 0) {
|
|
789
1144
|
state.warnings.push(htmlWarning("unsupported-link-media", state.path, imported.start, imported.end, `The stylesheet import media condition ${JSON.stringify(imported.media)} cannot be represented safely.`));
|
|
@@ -806,7 +1161,7 @@ function inferredPreprocessorPath(state) {
|
|
|
806
1161
|
return matches.length === 1 ? matches[0] : void 0;
|
|
807
1162
|
}
|
|
808
1163
|
function addInferredPreprocessorContext(state) {
|
|
809
|
-
if (state.styleSources.has(state.path) && state.sourceFiles.some((file) => extname(file.path) !== ".html" &&
|
|
1164
|
+
if (state.styleSources.has(state.path) && state.sourceFiles.some((file) => extname(file.path) !== ".html" && importsStylesheet(file, state.path))) return false;
|
|
810
1165
|
const path = inferredPreprocessorPath(state);
|
|
811
1166
|
if (!path) return false;
|
|
812
1167
|
state.generatedPaths.add(state.path);
|
|
@@ -821,8 +1176,8 @@ function addInferredPreprocessorContext(state) {
|
|
|
821
1176
|
return true;
|
|
822
1177
|
}
|
|
823
1178
|
function localHtmlReference(packageRoot, base, reference) {
|
|
824
|
-
const path = reference
|
|
825
|
-
if (
|
|
1179
|
+
const path = localHrefTarget(reference);
|
|
1180
|
+
if (path === void 0) return void 0;
|
|
826
1181
|
let decoded;
|
|
827
1182
|
try {
|
|
828
1183
|
decoded = decodeURIComponent(path);
|
|
@@ -857,22 +1212,14 @@ function htmlWarning(code, file, start, end, message) {
|
|
|
857
1212
|
}
|
|
858
1213
|
//#endregion
|
|
859
1214
|
//#region src/tailwind.ts
|
|
860
|
-
function
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
});
|
|
866
|
-
if (configuredPath) return {
|
|
867
|
-
path: configuredPath,
|
|
868
|
-
entries
|
|
869
|
-
};
|
|
1215
|
+
function findTailwindEntries(stylePaths, styleSources) {
|
|
1216
|
+
return stylePaths.filter((path) => extname(path) === ".css" && importEntries(styleSources.get(path) ?? "").some(({ specifier }) => specifier === "tailwindcss" || specifier.startsWith("tailwindcss/")));
|
|
1217
|
+
}
|
|
1218
|
+
function selectTailwindEntry(entries, configuredPath) {
|
|
1219
|
+
if (configuredPath) return configuredPath;
|
|
870
1220
|
if (entries.length === 0) throw new Error("No Tailwind v4 CSS entry was found. Pass --tailwind-css.");
|
|
871
1221
|
if (entries.length > 1) throw new Error("Multiple Tailwind CSS entries were found. Pass --tailwind-css.");
|
|
872
|
-
return
|
|
873
|
-
path: entries[0],
|
|
874
|
-
entries
|
|
875
|
-
};
|
|
1222
|
+
return entries[0];
|
|
876
1223
|
}
|
|
877
1224
|
async function loadTailwind(packageRoot, tailwindCss, snapshots, workspaceRoot) {
|
|
878
1225
|
const projectRequire = createRequire(join(packageRoot, "package.json"));
|
|
@@ -907,49 +1254,560 @@ async function loadTailwind(packageRoot, tailwindCss, snapshots, workspaceRoot)
|
|
|
907
1254
|
} else if (id.startsWith(".") || isAbsolute(id)) path = resolve(sheetBase, id);
|
|
908
1255
|
else path = projectRequire.resolve(id);
|
|
909
1256
|
return {
|
|
910
|
-
content: isProjectInput(workspaceRoot, path) ? await snapshotFile(snapshots, path) : await readFile(path, "utf8"),
|
|
911
|
-
base: dirname(path)
|
|
1257
|
+
content: isProjectInput(workspaceRoot, path) ? await snapshotFile(snapshots, path) : await readFile(path, "utf8"),
|
|
1258
|
+
base: dirname(path)
|
|
1259
|
+
};
|
|
1260
|
+
};
|
|
1261
|
+
const defaultTheme = await readFile(join(dirname(packagePath), "theme.css"), "utf8");
|
|
1262
|
+
const graphSources = [];
|
|
1263
|
+
const referenceTokens = /* @__PURE__ */ new Set();
|
|
1264
|
+
const themeTokens = {
|
|
1265
|
+
...extractThemeTokens("tailwind-default.theme.css", defaultTheme),
|
|
1266
|
+
...await extractThemeTokensFromGraph(tailwindCss, css, base, loadStylesheet, graphSources, referenceTokens)
|
|
1267
|
+
};
|
|
1268
|
+
graphSources.push({
|
|
1269
|
+
path: tailwindCss,
|
|
1270
|
+
source: css
|
|
1271
|
+
});
|
|
1272
|
+
const designSystem = await loadDesignSystem(css, {
|
|
1273
|
+
base,
|
|
1274
|
+
loadModule,
|
|
1275
|
+
loadStylesheet
|
|
1276
|
+
});
|
|
1277
|
+
const loadWith = (replacement) => loadDesignSystem(replacement, {
|
|
1278
|
+
base,
|
|
1279
|
+
loadModule,
|
|
1280
|
+
loadStylesheet
|
|
1281
|
+
});
|
|
1282
|
+
return {
|
|
1283
|
+
designSystem,
|
|
1284
|
+
css,
|
|
1285
|
+
path: tailwindCss,
|
|
1286
|
+
themeTokens,
|
|
1287
|
+
referenceTokens,
|
|
1288
|
+
graphSources,
|
|
1289
|
+
loadWith
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function extractThemeTokens(key, css) {
|
|
1293
|
+
return stylesheetAnalysis(key, css).themeTokens;
|
|
1294
|
+
}
|
|
1295
|
+
function importEntries(css) {
|
|
1296
|
+
return (cssDirectives$1(css) ?? []).flatMap((directive) => directive !== null && typeof directive === "object" && "kind" in directive && directive.kind === "import" && "specifier" in directive && typeof directive.specifier === "string" ? [{
|
|
1297
|
+
specifier: directive.specifier,
|
|
1298
|
+
reference: "reference" in directive && directive.reference === true
|
|
1299
|
+
}] : []);
|
|
1300
|
+
}
|
|
1301
|
+
async function extractThemeTokensFromGraph(identity, css, base, loadStylesheet, graphSources, referenceTokens, inReference = false, seen = /* @__PURE__ */ new Set()) {
|
|
1302
|
+
const tokens = {};
|
|
1303
|
+
for (const { specifier: spec, reference } of importEntries(css)) {
|
|
1304
|
+
const key = `${base}\0${spec}`;
|
|
1305
|
+
if (seen.has(key)) continue;
|
|
1306
|
+
seen.add(key);
|
|
1307
|
+
const loaded = await loadStylesheet(spec, base);
|
|
1308
|
+
if (spec !== "tailwindcss" && !spec.startsWith("tailwindcss/")) graphSources.push({
|
|
1309
|
+
path: key,
|
|
1310
|
+
source: loaded.content
|
|
1311
|
+
});
|
|
1312
|
+
Object.assign(tokens, await extractThemeTokensFromGraph(key, loaded.content, loaded.base, loadStylesheet, graphSources, referenceTokens, inReference || reference, seen));
|
|
1313
|
+
}
|
|
1314
|
+
const own = extractThemeTokens(`${identity}.theme.css`, css);
|
|
1315
|
+
if (inReference) for (const token of Object.keys(own)) referenceTokens.add(token);
|
|
1316
|
+
return Object.assign(tokens, own);
|
|
1317
|
+
}
|
|
1318
|
+
function invalidCandidates(system, candidates) {
|
|
1319
|
+
const generated = system.candidatesToCss(candidates);
|
|
1320
|
+
return candidates.filter((_, index) => generated[index] === null);
|
|
1321
|
+
}
|
|
1322
|
+
//#endregion
|
|
1323
|
+
//#region src/plan/canonicalize.ts
|
|
1324
|
+
const caches = /* @__PURE__ */ new WeakMap();
|
|
1325
|
+
function canonicalCandidate(system, candidate) {
|
|
1326
|
+
if (typeof system.canonicalizeCandidates !== "function") return null;
|
|
1327
|
+
let cache = caches.get(system);
|
|
1328
|
+
if (cache === void 0) {
|
|
1329
|
+
cache = /* @__PURE__ */ new Map();
|
|
1330
|
+
caches.set(system, cache);
|
|
1331
|
+
}
|
|
1332
|
+
const cached = cache.get(candidate);
|
|
1333
|
+
if (cached !== void 0) return cached;
|
|
1334
|
+
let canonical;
|
|
1335
|
+
try {
|
|
1336
|
+
const results = system.canonicalizeCandidates([candidate]);
|
|
1337
|
+
canonical = results.length === 1 && typeof results[0] === "string" && results[0] !== candidate ? results[0] : null;
|
|
1338
|
+
} catch {
|
|
1339
|
+
canonical = null;
|
|
1340
|
+
}
|
|
1341
|
+
cache.set(candidate, canonical);
|
|
1342
|
+
return canonical;
|
|
1343
|
+
}
|
|
1344
|
+
function spellingReservations(styleSources, resolvedExtras, resolvedImports = /* @__PURE__ */ new Set()) {
|
|
1345
|
+
const reservations = {
|
|
1346
|
+
names: /* @__PURE__ */ new Set(),
|
|
1347
|
+
prefixes: /* @__PURE__ */ new Set(),
|
|
1348
|
+
unbounded: false,
|
|
1349
|
+
properties: /* @__PURE__ */ new Set(),
|
|
1350
|
+
propertiesUnbounded: false
|
|
1351
|
+
};
|
|
1352
|
+
for (const [path, source] of styleSources) scanStylesheetReservations(path, source, styleSources, reservations, resolvedImports);
|
|
1353
|
+
for (const [path, source] of resolvedExtras) try {
|
|
1354
|
+
const analysis = stylesheetAnalysis(path, source);
|
|
1355
|
+
for (const name of analysis.classNames) reservations.names.add(name);
|
|
1356
|
+
if (analysis.classReservationsUnbounded) reservations.unbounded = true;
|
|
1357
|
+
for (const property of analysis.customProperties) reservations.properties.add(property);
|
|
1358
|
+
if (analysis.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
1359
|
+
if (entryDirectiveHazard(source)) reservations.unbounded = true;
|
|
1360
|
+
} catch {
|
|
1361
|
+
reservations.unbounded = true;
|
|
1362
|
+
}
|
|
1363
|
+
return reservations;
|
|
1364
|
+
}
|
|
1365
|
+
function scanStylesheetReservations(path, source, styleSources, reservations, resolvedImports = /* @__PURE__ */ new Set()) {
|
|
1366
|
+
try {
|
|
1367
|
+
const analysis = stylesheetAnalysis(path, source);
|
|
1368
|
+
for (const name of analysis.classNames) reservations.names.add(name);
|
|
1369
|
+
if (analysis.classReservationsUnbounded) reservations.unbounded = true;
|
|
1370
|
+
for (const property of analysis.customProperties) reservations.properties.add(property);
|
|
1371
|
+
if (analysis.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
1372
|
+
if (analysis.unverifiable) reservations.unbounded = true;
|
|
1373
|
+
for (const reference of analysis.references) {
|
|
1374
|
+
if (reference === "tailwindcss" || reference.startsWith("tailwindcss/")) continue;
|
|
1375
|
+
if (reference.startsWith("sass:")) continue;
|
|
1376
|
+
if (resolvedImports.has(`${dirname(path)}\0${reference}`) || styleSources.has(reference)) continue;
|
|
1377
|
+
if (stylesheetReferenceTargets(path, reference, styleSources).length === 0) reservations.unbounded = true;
|
|
1378
|
+
}
|
|
1379
|
+
} catch {
|
|
1380
|
+
reservations.unbounded = true;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
function entryDirectiveHazard(source) {
|
|
1384
|
+
const directives = cssDirectives$1(source);
|
|
1385
|
+
if (directives === null) return true;
|
|
1386
|
+
return directives.some((directive) => {
|
|
1387
|
+
if (directive === null || typeof directive !== "object" || !("kind" in directive)) return false;
|
|
1388
|
+
if (directive.kind === "import") return "source" in directive && directive.source !== null || "sourceUnreadable" in directive && directive.sourceUnreadable === true;
|
|
1389
|
+
if (directive.kind === "source") return "not" in directive && directive.not === true || "unreadable" in directive && directive.unreadable === true;
|
|
1390
|
+
return directive.kind === "other" && "name" in directive && (directive.name === "plugin" || directive.name === "config");
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
function scanInlineStyleReservations(key, value, reservations) {
|
|
1394
|
+
try {
|
|
1395
|
+
const analysis = stylesheetAnalysis(key, `.x{${value}}`);
|
|
1396
|
+
for (const property of analysis.customProperties) reservations.properties.add(property);
|
|
1397
|
+
if (analysis.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
1398
|
+
} catch {
|
|
1399
|
+
reservations.propertiesUnbounded = true;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
function reservedSpelling(reservations, spelling) {
|
|
1403
|
+
if (reservations.names.has(spelling)) return true;
|
|
1404
|
+
for (const prefix of reservations.prefixes) if (spelling.startsWith(prefix)) return true;
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
function utilitySegment(candidate) {
|
|
1408
|
+
let depth = 0;
|
|
1409
|
+
let start = 0;
|
|
1410
|
+
for (let index = 0; index < candidate.length; index += 1) {
|
|
1411
|
+
const character = candidate[index];
|
|
1412
|
+
if (character === "[" || character === "(") depth += 1;
|
|
1413
|
+
else if (character === "]" || character === ")") depth -= 1;
|
|
1414
|
+
else if (character === ":" && depth === 0) start = index + 1;
|
|
1415
|
+
}
|
|
1416
|
+
return candidate.slice(start);
|
|
1417
|
+
}
|
|
1418
|
+
function sameShape(left, right) {
|
|
1419
|
+
return left.length === right.length && left.every((declaration, index) => declaration.property === right[index].property && declaration.important === right[index].important);
|
|
1420
|
+
}
|
|
1421
|
+
function acceptedCandidateAliases(system, probes, reservations) {
|
|
1422
|
+
const aliases = {};
|
|
1423
|
+
if (reservations.unbounded) return aliases;
|
|
1424
|
+
const prefix = system.theme.prefix;
|
|
1425
|
+
for (const probe of new Set(probes)) {
|
|
1426
|
+
const canonical = canonicalCandidate(system, probe);
|
|
1427
|
+
if (canonical === null) continue;
|
|
1428
|
+
const unprefixed = prefix !== null && canonical.startsWith(`${prefix}:`) ? canonical.slice(prefix.length + 1) : null;
|
|
1429
|
+
const segment = utilitySegment(canonical).replace(/!$/, "");
|
|
1430
|
+
if (segment.includes("[") || reservedSpelling(reservations, canonical) || unprefixed !== null && reservedSpelling(reservations, unprefixed) || reservedSpelling(reservations, segment)) continue;
|
|
1431
|
+
const [compiledProbe, compiledCanonical] = system.candidatesToCss([probe, canonical]);
|
|
1432
|
+
if (compiledProbe == null || compiledCanonical == null) continue;
|
|
1433
|
+
let probeShape;
|
|
1434
|
+
let canonicalShape;
|
|
1435
|
+
try {
|
|
1436
|
+
probeShape = compiledShape(compiledProbe);
|
|
1437
|
+
canonicalShape = compiledShape(compiledCanonical);
|
|
1438
|
+
} catch {
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
if (canonicalShape.referencedProperties.length > 0 || probeShape.referencedProperties.length > 0 || !sameShape(probeShape.declarations, canonicalShape.declarations)) continue;
|
|
1442
|
+
aliases[probe] = canonical;
|
|
1443
|
+
}
|
|
1444
|
+
return aliases;
|
|
1445
|
+
}
|
|
1446
|
+
//#endregion
|
|
1447
|
+
//#region src/plan/fonts.ts
|
|
1448
|
+
function fontTokenName(family) {
|
|
1449
|
+
const collapsed = family.normalize("NFC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "");
|
|
1450
|
+
if (collapsed === "") return "family";
|
|
1451
|
+
return /^\p{N}/u.test(collapsed) ? `family-${collapsed}` : collapsed;
|
|
1452
|
+
}
|
|
1453
|
+
function aliasedCandidate(candidate, utility) {
|
|
1454
|
+
const segment = utilitySegment(candidate);
|
|
1455
|
+
const important = segment.endsWith("!") ? "!" : "";
|
|
1456
|
+
return candidate.slice(0, candidate.length - segment.length) + utility + important;
|
|
1457
|
+
}
|
|
1458
|
+
function appendFontTheme(css, tokens) {
|
|
1459
|
+
const names = Object.keys(tokens).sort();
|
|
1460
|
+
if (names.length === 0) return css;
|
|
1461
|
+
let output = css;
|
|
1462
|
+
if (!output.endsWith("\n")) output += "\n";
|
|
1463
|
+
if (!output.endsWith("\n\n")) output += "\n";
|
|
1464
|
+
output += `@theme {\n${names.map((name) => ` --font-${name}: ${tokens[name]};\n`).join("")}}\n`;
|
|
1465
|
+
return output;
|
|
1466
|
+
}
|
|
1467
|
+
function fontShapesAccept(system, probeCandidate, aliased, reservations, backingToken) {
|
|
1468
|
+
const [compiledProbe, compiledAliased] = system.candidatesToCss([probeCandidate, aliased]);
|
|
1469
|
+
if (compiledProbe == null || compiledAliased == null) return false;
|
|
1470
|
+
let probeShape;
|
|
1471
|
+
let aliasedShape;
|
|
1472
|
+
try {
|
|
1473
|
+
probeShape = compiledShape(compiledProbe);
|
|
1474
|
+
aliasedShape = compiledShape(compiledAliased);
|
|
1475
|
+
} catch {
|
|
1476
|
+
return false;
|
|
1477
|
+
}
|
|
1478
|
+
if (probeShape.referencedProperties.length > 0) return false;
|
|
1479
|
+
if (aliasedShape.referencedProperties.some((property) => reservations.properties.has(property))) return false;
|
|
1480
|
+
if (backingToken !== void 0) {
|
|
1481
|
+
if (!aliasedShape.declarations.some((declaration) => declaration.property === "font-family" && /^var\(--[\w-]+\)$/.test(declaration.value) && (declaration.value === `var(--${backingToken})` || declaration.value.endsWith(`-${backingToken})`)))) return false;
|
|
1482
|
+
}
|
|
1483
|
+
return sameShape(probeShape.declarations, aliasedShape.declarations);
|
|
1484
|
+
}
|
|
1485
|
+
async function registerFontTokens(options) {
|
|
1486
|
+
const { system, reservations } = options;
|
|
1487
|
+
if (reservations.unbounded || reservations.propertiesUnbounded) return {
|
|
1488
|
+
aliases: {},
|
|
1489
|
+
tokens: {},
|
|
1490
|
+
failures: /* @__PURE__ */ new Set()
|
|
1491
|
+
};
|
|
1492
|
+
const prefix = system.theme.prefix;
|
|
1493
|
+
const aliases = {};
|
|
1494
|
+
const failures = /* @__PURE__ */ new Set();
|
|
1495
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1496
|
+
const allocatedByStack = /* @__PURE__ */ new Map();
|
|
1497
|
+
const proposals = [];
|
|
1498
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
1499
|
+
const tokenStacks = fontTokenStacks(options.themeTokens, options.referenceTokens);
|
|
1500
|
+
for (const probe of options.probes) {
|
|
1501
|
+
if (seen.has(probe.candidate)) continue;
|
|
1502
|
+
seen.add(probe.candidate);
|
|
1503
|
+
if (probe.firstFamily.kind !== "name") continue;
|
|
1504
|
+
if (probe.candidate in options.existingAliases) continue;
|
|
1505
|
+
let reused = false;
|
|
1506
|
+
for (const token of matchingFontTokens(tokenStacks, probe.value)) {
|
|
1507
|
+
const aliased = aliasedCandidate(probe.candidate, token);
|
|
1508
|
+
if (reservedSpelling(reservations, token) || reservedSpelling(reservations, aliased)) continue;
|
|
1509
|
+
const reuseName = token.replace(/^font-/, "");
|
|
1510
|
+
const owner = options.globalAllocations.get(reuseName);
|
|
1511
|
+
if (owner !== void 0 && (owner === null || owner !== probe.value)) continue;
|
|
1512
|
+
if (fontShapesAccept(system, probe.candidate, aliased, reservations, token)) {
|
|
1513
|
+
aliases[probe.candidate] = aliased;
|
|
1514
|
+
options.globalAllocations.set(reuseName, probe.value);
|
|
1515
|
+
reused = true;
|
|
1516
|
+
break;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (reused) continue;
|
|
1520
|
+
if (!options.generate) continue;
|
|
1521
|
+
const stackKey = probe.value;
|
|
1522
|
+
const shared = allocatedByStack.get(stackKey);
|
|
1523
|
+
if (shared !== void 0) {
|
|
1524
|
+
proposals.push({
|
|
1525
|
+
probe,
|
|
1526
|
+
name: shared
|
|
1527
|
+
});
|
|
1528
|
+
continue;
|
|
1529
|
+
}
|
|
1530
|
+
const base = fontTokenName(probe.firstFamily.name);
|
|
1531
|
+
let allocated = false;
|
|
1532
|
+
for (let attempt = 1; attempt <= 100; attempt += 1) {
|
|
1533
|
+
const name = attempt === 1 ? base : `${base}-${attempt}`;
|
|
1534
|
+
if (claimed.has(name)) continue;
|
|
1535
|
+
if (reservations.properties.has(`--font-${name}`)) continue;
|
|
1536
|
+
if (prefix !== null && reservations.properties.has(`--${prefix}-font-${name}`)) continue;
|
|
1537
|
+
if (reservedSpelling(reservations, `font-${name}`)) continue;
|
|
1538
|
+
if (options.mentionedSegments.has(`font-${name}`)) continue;
|
|
1539
|
+
const global = options.globalAllocations.get(name);
|
|
1540
|
+
if (global !== void 0 && (global === null || global !== probe.value)) continue;
|
|
1541
|
+
const compiled = prefix !== null ? `${prefix}:font-${name}` : `font-${name}`;
|
|
1542
|
+
if (system.candidatesToCss([compiled])[0] !== null) continue;
|
|
1543
|
+
claimed.add(name);
|
|
1544
|
+
allocatedByStack.set(stackKey, name);
|
|
1545
|
+
proposals.push({
|
|
1546
|
+
probe,
|
|
1547
|
+
name
|
|
1548
|
+
});
|
|
1549
|
+
allocated = true;
|
|
1550
|
+
break;
|
|
1551
|
+
}
|
|
1552
|
+
if (!allocated) failures.add(probe.candidate);
|
|
1553
|
+
}
|
|
1554
|
+
if (proposals.length === 0) return {
|
|
1555
|
+
aliases,
|
|
1556
|
+
tokens: {},
|
|
1557
|
+
failures
|
|
1558
|
+
};
|
|
1559
|
+
const tokens = {};
|
|
1560
|
+
for (const { probe, name } of proposals) tokens[name] = probe.value;
|
|
1561
|
+
let fontSystem;
|
|
1562
|
+
try {
|
|
1563
|
+
fontSystem = await options.loadWith(appendFontTheme(options.entryCss, tokens));
|
|
1564
|
+
} catch (error) {
|
|
1565
|
+
if (isIntegrityError(error)) throw error;
|
|
1566
|
+
for (const { probe } of proposals) failures.add(probe.candidate);
|
|
1567
|
+
return {
|
|
1568
|
+
aliases,
|
|
1569
|
+
tokens: {},
|
|
1570
|
+
failures
|
|
912
1571
|
};
|
|
1572
|
+
}
|
|
1573
|
+
const before = system.candidatesToCss(options.corpus);
|
|
1574
|
+
const after = fontSystem.candidatesToCss(options.corpus);
|
|
1575
|
+
const corpusChanged = options.corpus.some((candidate, index) => before[index] !== after[index]);
|
|
1576
|
+
const accepted = {};
|
|
1577
|
+
for (const { probe, name } of proposals) {
|
|
1578
|
+
const canonical = corpusChanged ? null : canonicalCandidate(fontSystem, probe.candidate);
|
|
1579
|
+
const expected = aliasedCandidate(probe.candidate, `font-${name}`);
|
|
1580
|
+
if (canonical !== expected || !fontShapesAccept(fontSystem, probe.candidate, expected, reservations)) {
|
|
1581
|
+
failures.add(probe.candidate);
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
aliases[probe.candidate] = expected;
|
|
1585
|
+
accepted[name] = probe.value;
|
|
1586
|
+
}
|
|
1587
|
+
return {
|
|
1588
|
+
aliases,
|
|
1589
|
+
tokens: accepted,
|
|
1590
|
+
failures
|
|
913
1591
|
};
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1592
|
+
}
|
|
1593
|
+
function fontTokenStacks(themeTokens, referenceTokens = /* @__PURE__ */ new Set()) {
|
|
1594
|
+
const stacks = /* @__PURE__ */ new Map();
|
|
1595
|
+
for (const [token, value] of Object.entries(themeTokens)) {
|
|
1596
|
+
if (!token.startsWith("font-")) continue;
|
|
1597
|
+
if (referenceTokens.has(token)) continue;
|
|
1598
|
+
const parsed = fontFamilyStack(value);
|
|
1599
|
+
if (parsed !== null) stacks.set(token, parsed.value);
|
|
1600
|
+
}
|
|
1601
|
+
return stacks;
|
|
1602
|
+
}
|
|
1603
|
+
function matchingFontTokens(stacks, normalizedStack) {
|
|
1604
|
+
return [...stacks].filter(([, stack]) => stack === normalizedStack).map(([token]) => token).sort();
|
|
1605
|
+
}
|
|
1606
|
+
//#endregion
|
|
1607
|
+
//#region src/plan/media.ts
|
|
1608
|
+
const GENERATED_NAMESPACE = "twm-media";
|
|
1609
|
+
/**
|
|
1610
|
+
* Resolve one fixed name per key across a complete entry group.
|
|
1611
|
+
*
|
|
1612
|
+
* Resolution follows the RFC order: a built-in candidate with a verified
|
|
1613
|
+
* effective expansion, an existing breakpoint matched by exact value and
|
|
1614
|
+
* verified the same way, an adopted definition whose name, normalized
|
|
1615
|
+
* meaning, and effective expansion equal what the migration would emit,
|
|
1616
|
+
* the readable generated name, the digest name, and finally the
|
|
1617
|
+
* arbitrary-variant fallback. Reservation is ownership-blind:
|
|
1618
|
+
* authored names with any different meaning, active theme names, explicit
|
|
1619
|
+
* reserved names such as inert scanned candidates, and any name the design
|
|
1620
|
+
* system resolves all block a generated name.
|
|
1621
|
+
*/
|
|
1622
|
+
function resolveMediaNames(collections, themeTokens, probes, reservedNames = []) {
|
|
1623
|
+
const components = mergeComponents(collections);
|
|
1624
|
+
const authoredKeysByName = mergeAuthoredVariants(collections);
|
|
1625
|
+
const reserved = new Set(reservedNames);
|
|
1626
|
+
for (const token of Object.keys(themeTokens)) if (token.startsWith("breakpoint-")) reserved.add(token.slice(11));
|
|
1627
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
1628
|
+
const adoptableKey = (name) => {
|
|
1629
|
+
const keys = authoredKeysByName.get(name);
|
|
1630
|
+
if (keys === void 0 || keys.size !== 1) return null;
|
|
1631
|
+
const [key] = keys;
|
|
1632
|
+
return key ?? null;
|
|
917
1633
|
};
|
|
1634
|
+
const taken = (name) => claimed.has(name) || reserved.has(name) || authoredKeysByName.has(name) || probes.resolves(name);
|
|
1635
|
+
const names = /* @__PURE__ */ new Map();
|
|
1636
|
+
const fallbacks = /* @__PURE__ */ new Set();
|
|
1637
|
+
for (const component of components) {
|
|
1638
|
+
const resolved = (name, kind) => {
|
|
1639
|
+
claimed.add(name);
|
|
1640
|
+
names.set(component.key, {
|
|
1641
|
+
name,
|
|
1642
|
+
kind
|
|
1643
|
+
});
|
|
1644
|
+
};
|
|
1645
|
+
if (component.builtin !== null && probes.expansionMatches(component.builtin, component.key)) {
|
|
1646
|
+
resolved(component.builtin, "builtin");
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
if (component.breakpoint !== null && probes.expansionMatches(component.breakpoint, component.key)) {
|
|
1650
|
+
resolved(component.breakpoint, "breakpoint");
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
const digestName = `${GENERATED_NAMESPACE}-${component.digest}`;
|
|
1654
|
+
const candidates = component.readableName === null ? [digestName] : [component.readableName, digestName];
|
|
1655
|
+
let done = false;
|
|
1656
|
+
for (const candidate of candidates) {
|
|
1657
|
+
if (adoptableKey(candidate) === component.key && !claimed.has(candidate) && probes.expansionMatches(candidate, component.key)) {
|
|
1658
|
+
resolved(candidate, "adopted");
|
|
1659
|
+
done = true;
|
|
1660
|
+
break;
|
|
1661
|
+
}
|
|
1662
|
+
if (!taken(candidate)) {
|
|
1663
|
+
resolved(candidate, "generated");
|
|
1664
|
+
done = true;
|
|
1665
|
+
break;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
if (!done) fallbacks.add(component.key);
|
|
1669
|
+
}
|
|
918
1670
|
return {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
loadModule,
|
|
922
|
-
loadStylesheet
|
|
923
|
-
}),
|
|
924
|
-
css,
|
|
925
|
-
path: tailwindCss,
|
|
926
|
-
themeTokens
|
|
1671
|
+
names,
|
|
1672
|
+
fallbacks
|
|
927
1673
|
};
|
|
928
1674
|
}
|
|
929
|
-
function
|
|
930
|
-
const
|
|
931
|
-
for (const
|
|
932
|
-
return
|
|
1675
|
+
function mergeComponents(collections) {
|
|
1676
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1677
|
+
for (const collection of collections) for (const component of collection.components) if (!byKey.has(component.key)) byKey.set(component.key, component);
|
|
1678
|
+
return [...byKey.values()].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
|
|
1679
|
+
}
|
|
1680
|
+
function mergeAuthoredVariants(collections) {
|
|
1681
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1682
|
+
for (const collection of collections) for (const variant of collection.authoredVariants) {
|
|
1683
|
+
const keys = byName.get(variant.name) ?? /* @__PURE__ */ new Set();
|
|
1684
|
+
keys.add(variant.mediaQueryKey);
|
|
1685
|
+
byName.set(variant.name, keys);
|
|
1686
|
+
}
|
|
1687
|
+
return byName;
|
|
1688
|
+
}
|
|
1689
|
+
const PROBE_UTILITY = "[--tw-probe:1]";
|
|
1690
|
+
function probeCandidate(prefix, name) {
|
|
1691
|
+
return `${prefix === null || prefix === "" ? "" : `${prefix}:`}${name}:${PROBE_UTILITY}`;
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* Build the effective-expansion probes against the unaugmented loaded
|
|
1695
|
+
* design system. `resolves` proves only existence; `expansionMatches`
|
|
1696
|
+
* proves that compiling the name yields exactly one `@media` wrapper whose
|
|
1697
|
+
* normalized query equals the key.
|
|
1698
|
+
*/
|
|
1699
|
+
function buildMediaProbes(tailwind) {
|
|
1700
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
1701
|
+
const prefix = tailwind.designSystem.theme.prefix;
|
|
1702
|
+
const compile = (name) => {
|
|
1703
|
+
let css = compiled.get(name);
|
|
1704
|
+
if (css === void 0) {
|
|
1705
|
+
[css = null] = tailwind.designSystem.candidatesToCss([probeCandidate(prefix, name)]);
|
|
1706
|
+
compiled.set(name, css);
|
|
1707
|
+
}
|
|
1708
|
+
return css;
|
|
1709
|
+
};
|
|
1710
|
+
return {
|
|
1711
|
+
resolves: (name) => compile(name) !== null,
|
|
1712
|
+
expansionMatches: (name, key) => {
|
|
1713
|
+
const css = compile(name);
|
|
1714
|
+
if (css === null) return false;
|
|
1715
|
+
return JSON.parse(mediaProbeKey(css)) === key;
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
933
1718
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1719
|
+
/**
|
|
1720
|
+
* Conservative scanned-corpus reservation: every colon-chained token in the
|
|
1721
|
+
* package's scanned sources reserves its variant segments, so an inert
|
|
1722
|
+
* candidate such as `width-lte-768px:hidden` is never activated with a new
|
|
1723
|
+
* meaning. Over-reservation only pushes a generated name to its digest
|
|
1724
|
+
* form, and content-identity adoption bypasses these reservations, so
|
|
1725
|
+
* reruns stay idempotent.
|
|
1726
|
+
*/
|
|
1727
|
+
function scannedVariantReservations(sources) {
|
|
1728
|
+
const reserved = /* @__PURE__ */ new Set();
|
|
1729
|
+
for (const source of sources) for (const match of source.matchAll(/[A-Za-z][\w-]*(?::[^\s"'`{}]+)+/g)) for (const segment of match[0].split(":").slice(0, -1)) reserved.add(segment);
|
|
1730
|
+
return reserved;
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Run native condition collection for one member package of an entry
|
|
1734
|
+
* group. Kept separate from resolution so a recoverable collection
|
|
1735
|
+
* failure is attributable to its member.
|
|
1736
|
+
*/
|
|
1737
|
+
function collectMemberMediaConditions(stylesheets, tailwind) {
|
|
1738
|
+
return JSON.parse(collectMediaConditions(JSON.stringify({
|
|
1739
|
+
stylesheets,
|
|
1740
|
+
themeTokens: tailwind.themeTokens,
|
|
1741
|
+
tailwindSources: tailwind.graphSources
|
|
1742
|
+
})));
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Resolve the fixed map the planner consumes plus the definitions
|
|
1746
|
+
* extraction may emit from the group's member collections. Name
|
|
1747
|
+
* allocation runs once for the complete group, so different keys from
|
|
1748
|
+
* different packages can never independently claim one name.
|
|
1749
|
+
*/
|
|
1750
|
+
function planMediaExtraction(options) {
|
|
1751
|
+
const resolution = resolveMediaNames(options.collections, options.tailwind.themeTokens, buildMediaProbes(options.tailwind), scannedVariantReservations([...options.scannedSources, ...options.tailwind.graphSources.map((graphSource) => graphSource.source)]));
|
|
1752
|
+
const names = {};
|
|
1753
|
+
const generated = [];
|
|
1754
|
+
for (const [key, resolved] of resolution.names) {
|
|
1755
|
+
names[key] = resolved.name;
|
|
1756
|
+
if (resolved.kind === "generated") generated.push({
|
|
1757
|
+
key,
|
|
1758
|
+
name: resolved.name
|
|
1759
|
+
});
|
|
942
1760
|
}
|
|
943
|
-
return
|
|
1761
|
+
return {
|
|
1762
|
+
names,
|
|
1763
|
+
generated
|
|
1764
|
+
};
|
|
944
1765
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1766
|
+
/**
|
|
1767
|
+
* The generated definitions the final plan actually uses, ordered by the
|
|
1768
|
+
* first applied rule that uses each name. A rule counts as applied only
|
|
1769
|
+
* when every one of its candidates survived into `plan.candidates`: a
|
|
1770
|
+
* retained global rule still applies its utilities to proven consumers,
|
|
1771
|
+
* while a blocked rule always contains a dropped candidate, so its
|
|
1772
|
+
* occurrences must not fix registration order for a candidate another rule
|
|
1773
|
+
* still applies. Registration order controls conflicting utility
|
|
1774
|
+
* precedence, so the order follows the applied rules rather than every
|
|
1775
|
+
* encountered condition.
|
|
1776
|
+
*/
|
|
1777
|
+
function usedGeneratedDefinitions(extraction, plan) {
|
|
1778
|
+
const applied = new Set(plan.candidates);
|
|
1779
|
+
const byName = new Map(extraction.generated.map((definition) => [definition.name, definition]));
|
|
1780
|
+
const ordered = [];
|
|
1781
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1782
|
+
for (const rule of plan.rules) {
|
|
1783
|
+
if (!rule.candidates.every((candidate) => applied.has(candidate))) continue;
|
|
1784
|
+
for (const candidate of rule.candidates) for (const segment of candidate.split(":").slice(0, -1)) {
|
|
1785
|
+
const definition = byName.get(segment);
|
|
1786
|
+
if (definition !== void 0 && !seen.has(segment)) {
|
|
1787
|
+
seen.add(segment);
|
|
1788
|
+
ordered.push(definition);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return ordered;
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Append the used definitions to the entry source, each as its own block,
|
|
1796
|
+
* matching the planner's moved-block formatting. Definitions already
|
|
1797
|
+
* present are recognized through adoption before this runs, so appending
|
|
1798
|
+
* never duplicates one.
|
|
1799
|
+
*/
|
|
1800
|
+
function appendMediaDefinitions(source, definitions) {
|
|
1801
|
+
let output = source;
|
|
1802
|
+
for (const { key, name } of definitions) {
|
|
1803
|
+
if (!output.endsWith("\n")) output += "\n";
|
|
1804
|
+
if (!output.endsWith("\n\n")) output += "\n";
|
|
1805
|
+
output += `@custom-variant ${name} {\n @media ${key} {\n @slot;\n }\n}\n`;
|
|
1806
|
+
}
|
|
1807
|
+
return output;
|
|
948
1808
|
}
|
|
949
1809
|
//#endregion
|
|
950
1810
|
//#region src/parser/vue.ts
|
|
951
|
-
const ESCAPE_SELECTOR = /(?:::v-|:)(?:deep|global|slotted)\(([^)]*)\)/g;
|
|
952
|
-
const ESCAPE_RESIDUE = /(?:>>>|\/deep\/|::v-deep|:deep|::v-slotted|:slotted|::v-global|:global)/;
|
|
953
1811
|
const SUPPORTED_STYLE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
954
1812
|
"lang",
|
|
955
1813
|
"module",
|
|
@@ -1002,7 +1860,9 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1002
1860
|
const { descriptor, errors } = compiler.parse(source, { filename: path });
|
|
1003
1861
|
const styleRanges = descriptor.styles.map((style) => ({
|
|
1004
1862
|
start: style.loc.start.offset,
|
|
1005
|
-
end: style.loc.end.offset
|
|
1863
|
+
end: style.loc.end.offset,
|
|
1864
|
+
lang: style.lang ?? "css",
|
|
1865
|
+
...style.src !== void 0 ? { src: style.src } : {}
|
|
1006
1866
|
}));
|
|
1007
1867
|
const byteRanges = (offset) => ({
|
|
1008
1868
|
warnings: warnings.map((warning) => ({
|
|
@@ -1011,6 +1871,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1011
1871
|
end: offset(warning.end)
|
|
1012
1872
|
})),
|
|
1013
1873
|
styleRanges: styleRanges.map((range) => ({
|
|
1874
|
+
...range,
|
|
1014
1875
|
start: offset(range.start),
|
|
1015
1876
|
end: offset(range.end)
|
|
1016
1877
|
}))
|
|
@@ -1053,6 +1914,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1053
1914
|
const shadowPreprocessorTexts = [];
|
|
1054
1915
|
const unscopedShadowPreprocessorTexts = [];
|
|
1055
1916
|
let escapeUnverifiable = false;
|
|
1917
|
+
let opaqueStyleBlocks = false;
|
|
1056
1918
|
let moduleSiblingUnsupported = false;
|
|
1057
1919
|
for (const style of descriptor.styles) {
|
|
1058
1920
|
const start = style.loc.start.offset;
|
|
@@ -1063,6 +1925,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1063
1925
|
warn("unsupported-sfc-block", start, end, `Unsupported <style> attributes: ${unsupportedAttributes.join(", ")}.`);
|
|
1064
1926
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1065
1927
|
escapeUnverifiable = true;
|
|
1928
|
+
opaqueStyleBlocks = true;
|
|
1066
1929
|
continue;
|
|
1067
1930
|
}
|
|
1068
1931
|
if (style.src !== void 0) {
|
|
@@ -1070,6 +1933,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1070
1933
|
warn("unsupported-sfc-block", start, end, "A <style module src> block is not supported.");
|
|
1071
1934
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1072
1935
|
escapeUnverifiable = true;
|
|
1936
|
+
opaqueStyleBlocks = true;
|
|
1073
1937
|
} else styleBlockImports.push({
|
|
1074
1938
|
reference: style.src,
|
|
1075
1939
|
start,
|
|
@@ -1081,12 +1945,19 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1081
1945
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1082
1946
|
warn("unsupported-sfc-block", start, end, "Named <style module> blocks are not supported.");
|
|
1083
1947
|
shadowModuleCssTexts.push(style.content);
|
|
1948
|
+
opaqueStyleBlocks = true;
|
|
1084
1949
|
continue;
|
|
1085
1950
|
}
|
|
1086
1951
|
if (!SUPPORTED_STYLE_LANGUAGES.has(style.lang)) {
|
|
1087
1952
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1088
1953
|
warn("preprocessor-style-block", start, end, `The <style lang="${style.lang}"> language is not supported.`);
|
|
1089
1954
|
shadowPreprocessorTexts.push(style.content);
|
|
1955
|
+
opaqueStyleBlocks = true;
|
|
1956
|
+
try {
|
|
1957
|
+
if (stylesheetAnalysis(`${path}.css`, style.content).selectorsUnverifiable) escapeUnverifiable = true;
|
|
1958
|
+
} catch {
|
|
1959
|
+
escapeUnverifiable = true;
|
|
1960
|
+
}
|
|
1090
1961
|
continue;
|
|
1091
1962
|
}
|
|
1092
1963
|
const outerStart = source.lastIndexOf("<style", start);
|
|
@@ -1094,6 +1965,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1094
1965
|
if (outerStart < 0 || !closing) {
|
|
1095
1966
|
warn("unsupported-sfc-block", start, end, "The style block tags could not be located.");
|
|
1096
1967
|
escapeUnverifiable = true;
|
|
1968
|
+
opaqueStyleBlocks = true;
|
|
1097
1969
|
continue;
|
|
1098
1970
|
}
|
|
1099
1971
|
const block = {
|
|
@@ -1110,24 +1982,45 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1110
1982
|
}
|
|
1111
1983
|
if (!style.scoped) {
|
|
1112
1984
|
unscopedBlocks.push(block);
|
|
1113
|
-
if (style.lang && style.lang !== "css")
|
|
1114
|
-
|
|
1985
|
+
if (style.lang && style.lang !== "css") {
|
|
1986
|
+
unscopedShadowPreprocessorTexts.push(style.content);
|
|
1987
|
+
try {
|
|
1988
|
+
if (stylesheetAnalysis(`${path}.${style.lang}`, style.content).selectorsUnverifiable) escapeUnverifiable = true;
|
|
1989
|
+
} catch {
|
|
1990
|
+
escapeUnverifiable = true;
|
|
1991
|
+
}
|
|
1992
|
+
} else unscopedShadowCssTexts.push(style.content);
|
|
1115
1993
|
continue;
|
|
1116
1994
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1995
|
+
try {
|
|
1996
|
+
const escapes = stylesheetAnalysis(`${path}.${style.lang ?? "css"}`, style.content);
|
|
1997
|
+
shadowCssTexts.push(...escapes.scopeEscapes);
|
|
1998
|
+
if (escapes.scopeEscapes.length > 0) block.shadowSource = escapes.scopeShadowCss.join("\n") || "/* scope escapes extracted */";
|
|
1999
|
+
if (escapes.scopeEscapesUnverifiable || escapes.selectorsUnverifiable) escapeUnverifiable = true;
|
|
2000
|
+
} catch {
|
|
2001
|
+
escapeUnverifiable = true;
|
|
1120
2002
|
}
|
|
1121
|
-
if (ESCAPE_RESIDUE.test(style.content.replace(ESCAPE_SELECTOR, " "))) escapeUnverifiable = true;
|
|
1122
2003
|
blocks.push(block);
|
|
1123
2004
|
}
|
|
2005
|
+
const scriptLang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
|
|
1124
2006
|
const state = {
|
|
1125
2007
|
elements: [],
|
|
1126
2008
|
components: [],
|
|
1127
2009
|
dynamic: false,
|
|
1128
2010
|
vHtml: false,
|
|
1129
2011
|
hasSlot: false,
|
|
1130
|
-
|
|
2012
|
+
moduleClosureBroken: false,
|
|
2013
|
+
referencesUseCssModule: false,
|
|
2014
|
+
expressionReferences: /* @__PURE__ */ new Set(),
|
|
2015
|
+
templatePrefixes: /* @__PURE__ */ new Set(),
|
|
2016
|
+
entityClassTokens: [],
|
|
2017
|
+
styleAttributeValues: [],
|
|
2018
|
+
styleAttributesUnverifiable: false,
|
|
2019
|
+
customProperties: /* @__PURE__ */ new Set(),
|
|
2020
|
+
customPropertiesUnbounded: false,
|
|
2021
|
+
stylesheetLinks: [],
|
|
2022
|
+
stylesheetLinksUnverifiable: false,
|
|
2023
|
+
expressionPath: scriptLang === "ts" || scriptLang === "tsx" ? "Component.ts" : "Component.js"
|
|
1131
2024
|
};
|
|
1132
2025
|
visitTemplateNode(source, template.ast, state);
|
|
1133
2026
|
const alwaysRenderedRoots = template.ast.children.filter((node) => node.type === NODE_ELEMENT && (node.tagType === TAG_ELEMENT || node.tagType === TAG_COMPONENT) && !node.props?.some((prop) => prop.type === PROP_DIRECTIVE && [
|
|
@@ -1137,8 +2030,17 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1137
2030
|
"for"
|
|
1138
2031
|
].includes(prop.name))).length;
|
|
1139
2032
|
const scriptText = [descriptor.script?.content, descriptor.scriptSetup?.content].filter(Boolean).join("\n");
|
|
1140
|
-
const fallthroughUnverifiable = Boolean(descriptor.script) || descriptor.scriptSetup?.src !== void 0 || /\b(?:defineOptions|defineProps|inheritAttrs)\b/.test(descriptor.scriptSetup?.content ?? "");
|
|
1141
2033
|
let scriptImportsUnverifiable = false;
|
|
2034
|
+
const scriptImports = [];
|
|
2035
|
+
let setupAnalysis;
|
|
2036
|
+
let scriptUsesCssModule = false;
|
|
2037
|
+
let scriptHasDynamicImport = false;
|
|
2038
|
+
const scriptCustomProperties = [];
|
|
2039
|
+
let scriptCustomPropertiesUnbounded = false;
|
|
2040
|
+
const scriptVueReferences = [];
|
|
2041
|
+
const scriptVueGlobPatterns = [];
|
|
2042
|
+
const scriptTemplatePrefixes = [];
|
|
2043
|
+
let scriptVueGlobUnverifiable = false;
|
|
1142
2044
|
const styleImports = [descriptor.script, descriptor.scriptSetup].flatMap((script) => {
|
|
1143
2045
|
if (!script) return [];
|
|
1144
2046
|
if (script.src || !SUPPORTED_SCRIPT_LANGUAGES.has(script.lang)) {
|
|
@@ -1146,18 +2048,25 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1146
2048
|
return [];
|
|
1147
2049
|
}
|
|
1148
2050
|
try {
|
|
1149
|
-
|
|
2051
|
+
const analysis = sourceAnalysis(`${path}.${script.lang ?? "js"}`, script.content);
|
|
2052
|
+
if (script === descriptor.scriptSetup) setupAnalysis = analysis;
|
|
2053
|
+
if (analysis.usesCssModule || analysis.hasUnboundUseCssModule) scriptUsesCssModule = true;
|
|
2054
|
+
if (analysis.hasDynamicImport) scriptHasDynamicImport = true;
|
|
2055
|
+
scriptImports.push(...analysis.imports);
|
|
2056
|
+
scriptVueReferences.push(...analysis.imports.filter((record) => !record.typeOnly).map((record) => record.specifier));
|
|
2057
|
+
scriptVueGlobPatterns.push(...analysis.vueGlobPatterns);
|
|
2058
|
+
scriptTemplatePrefixes.push(...analysis.templatePrefixes);
|
|
2059
|
+
scriptCustomProperties.push(...analysis.customProperties);
|
|
2060
|
+
if (analysis.customPropertiesUnbounded) scriptCustomPropertiesUnbounded = true;
|
|
2061
|
+
if (analysis.vueGlobUnverifiable) scriptVueGlobUnverifiable = true;
|
|
2062
|
+
return analysis.staticImports;
|
|
1150
2063
|
} catch {
|
|
1151
2064
|
scriptImportsUnverifiable = true;
|
|
1152
2065
|
return [];
|
|
1153
2066
|
}
|
|
1154
2067
|
});
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
componentImports = staticImportBindings(`${path}.${descriptor.scriptSetup.lang ?? "js"}`, descriptor.scriptSetup.content);
|
|
1158
|
-
} catch {
|
|
1159
|
-
componentImports = [];
|
|
1160
|
-
}
|
|
2068
|
+
const fallthroughUnverifiable = Boolean(descriptor.script) || descriptor.scriptSetup?.src !== void 0 || Boolean(descriptor.scriptSetup) && !setupAnalysis || setupAnalysis?.hasVueFallthroughMacro === true;
|
|
2069
|
+
const componentImports = setupAnalysis?.defaultImports ?? [];
|
|
1161
2070
|
const offsets = utf8OffsetMap(source, [
|
|
1162
2071
|
...warnings.flatMap((warning) => [warning.start, warning.end]),
|
|
1163
2072
|
...styleRanges.flatMap((range) => [range.start, range.end]),
|
|
@@ -1209,7 +2118,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1209
2118
|
blocks: blocks.map(toByteBlock),
|
|
1210
2119
|
unscopedBlocks: unscopedBlocks.map(toByteBlock),
|
|
1211
2120
|
moduleBlocks: moduleBlocks.map(toByteBlock),
|
|
1212
|
-
moduleClosureBroken: moduleSiblingUnsupported || scriptImportsUnverifiable ||
|
|
2121
|
+
moduleClosureBroken: moduleSiblingUnsupported || scriptImportsUnverifiable || scriptUsesCssModule || state.moduleClosureBroken || state.referencesUseCssModule && setupAnalysis?.definesRootUseCssModule !== true || (setupAnalysis?.useCssModuleLocals ?? []).some((name) => state.expressionReferences.has(name)),
|
|
1213
2122
|
htmlElements: state.elements.map(toByteSite),
|
|
1214
2123
|
componentSites: state.components.map(toByteSite),
|
|
1215
2124
|
rootStarts: template.ast.children.filter((node) => node.type === NODE_ELEMENT).map((node) => offsets.get(node.loc.start.offset)),
|
|
@@ -1217,7 +2126,21 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1217
2126
|
rootFragment: template.ast.children.filter((node) => node.type === NODE_ELEMENT || node.type === NODE_INTERPOLATION || node.type === NODE_TEXT && node.content?.trim?.() !== "").length !== 1,
|
|
1218
2127
|
scriptText,
|
|
1219
2128
|
scriptStyleImports: [...new Set(styleImports)],
|
|
2129
|
+
scriptImports,
|
|
1220
2130
|
scriptImportsUnverifiable,
|
|
2131
|
+
scriptHasDynamicImport,
|
|
2132
|
+
scriptVueReferences,
|
|
2133
|
+
scriptVueGlobPatterns,
|
|
2134
|
+
scriptVueGlobUnverifiable,
|
|
2135
|
+
templatePrefixes: [.../* @__PURE__ */ new Set([...state.templatePrefixes, ...scriptTemplatePrefixes])],
|
|
2136
|
+
templateClassTokens: [...new Set(state.entityClassTokens)],
|
|
2137
|
+
templateStyleValues: state.styleAttributeValues,
|
|
2138
|
+
templateStylesUnverifiable: state.styleAttributesUnverifiable,
|
|
2139
|
+
scriptCustomProperties: [...scriptCustomProperties, ...state.customProperties],
|
|
2140
|
+
scriptCustomPropertiesUnbounded: scriptCustomPropertiesUnbounded || state.customPropertiesUnbounded,
|
|
2141
|
+
templateStylesheetLinks: state.stylesheetLinks,
|
|
2142
|
+
templateStylesheetLinksUnverifiable: state.stylesheetLinksUnverifiable,
|
|
2143
|
+
hasOpaqueStyleBlocks: opaqueStyleBlocks,
|
|
1221
2144
|
styleBlockImports: styleBlockImports.map((entry) => ({
|
|
1222
2145
|
...entry,
|
|
1223
2146
|
start: offset(entry.start),
|
|
@@ -1249,32 +2172,68 @@ function verifyVueSource(compiler, path, source, includeUnscoped = false) {
|
|
|
1249
2172
|
function visitTemplateNode(source, node, state) {
|
|
1250
2173
|
if (node.type === NODE_ELEMENT) {
|
|
1251
2174
|
if (node.tagType === TAG_SLOT) state.hasSlot = true;
|
|
2175
|
+
for (const prop of node.props ?? []) {
|
|
2176
|
+
if (prop.type === PROP_ATTRIBUTE && prop.name === "style" && prop.value?.content) state.styleAttributeValues.push(prop.value.content);
|
|
2177
|
+
if (prop.type === PROP_DIRECTIVE && prop.name === "bind") {
|
|
2178
|
+
const argument = prop.arg?.isStatic === true ? prop.arg.content : void 0;
|
|
2179
|
+
if (prop.arg === void 0 || argument === void 0 || argument === "style") state.styleAttributesUnverifiable = true;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
if (node.tag === "link" && node.tagType === TAG_ELEMENT) {
|
|
2183
|
+
let rel;
|
|
2184
|
+
let href;
|
|
2185
|
+
let dynamic = false;
|
|
2186
|
+
for (const prop of node.props ?? []) if (prop.type === PROP_ATTRIBUTE) {
|
|
2187
|
+
if (prop.name === "rel") rel = prop.value?.content ?? "";
|
|
2188
|
+
if (prop.name === "href") href = prop.value?.content ?? "";
|
|
2189
|
+
} else if (prop.type === PROP_DIRECTIVE && prop.name === "bind") {
|
|
2190
|
+
const argument = prop.arg?.isStatic ? prop.arg.content : void 0;
|
|
2191
|
+
if (argument === void 0 || argument === "rel" || argument === "href") dynamic = true;
|
|
2192
|
+
}
|
|
2193
|
+
if ((rel?.toLowerCase().split(/\s+/) ?? []).includes("stylesheet") || rel === void 0 && dynamic) {
|
|
2194
|
+
if (href !== void 0 && !dynamic) state.stylesheetLinks.push(href);
|
|
2195
|
+
else state.stylesheetLinksUnverifiable = true;
|
|
2196
|
+
} else if (dynamic) state.stylesheetLinksUnverifiable = true;
|
|
2197
|
+
}
|
|
1252
2198
|
const bindingClasses = [];
|
|
1253
2199
|
let classOpaque = false;
|
|
1254
2200
|
let moduleBinding;
|
|
1255
2201
|
for (const prop of node.props ?? []) {
|
|
1256
2202
|
if (prop.type !== PROP_DIRECTIVE) continue;
|
|
2203
|
+
const expression = prop.exp?.content ? prop.name === "on" ? templateHandler(prop.exp.content) : templateExpression(state.expressionPath, prop.exp.content) : void 0;
|
|
2204
|
+
if (prop.exp?.content && !expression) state.moduleClosureBroken = true;
|
|
1257
2205
|
let provenModuleExpression = false;
|
|
1258
2206
|
if (prop.name === "bind") {
|
|
1259
2207
|
if (!prop.arg || !prop.arg.isStatic) classOpaque = true;
|
|
1260
2208
|
else if (prop.arg.content === "class") {
|
|
1261
|
-
const member = node.tagType === TAG_ELEMENT
|
|
2209
|
+
const member = node.tagType === TAG_ELEMENT ? expression?.vueModuleMember : void 0;
|
|
1262
2210
|
if (member && !moduleBinding) {
|
|
1263
2211
|
moduleBinding = {
|
|
1264
|
-
name: member
|
|
2212
|
+
name: member,
|
|
1265
2213
|
start: attributeRemovalStart(source, node, prop),
|
|
1266
2214
|
end: prop.loc.end.offset
|
|
1267
2215
|
};
|
|
1268
2216
|
provenModuleExpression = true;
|
|
1269
2217
|
} else {
|
|
1270
|
-
const value =
|
|
2218
|
+
const value = expression?.staticString ?? void 0;
|
|
1271
2219
|
if (value === void 0) classOpaque = true;
|
|
1272
2220
|
else bindingClasses.push(...value.split(/[\t\n\f\r ]+/).filter(Boolean));
|
|
1273
2221
|
}
|
|
1274
2222
|
} else if (prop.arg.content === "id") classOpaque = true;
|
|
1275
2223
|
}
|
|
1276
|
-
if (!provenModuleExpression &&
|
|
1277
|
-
if (
|
|
2224
|
+
if (!provenModuleExpression && expression?.usesCssModule) state.moduleClosureBroken = true;
|
|
2225
|
+
if (expression?.referencesUseCssModule) state.referencesUseCssModule = true;
|
|
2226
|
+
for (const name of expression?.references ?? []) state.expressionReferences.add(name);
|
|
2227
|
+
for (const prefix of expression?.templatePrefixes ?? []) state.templatePrefixes.add(prefix);
|
|
2228
|
+
for (const name of expression?.customProperties ?? []) state.customProperties.add(name);
|
|
2229
|
+
if (expression?.customPropertiesUnbounded) state.customPropertiesUnbounded = true;
|
|
2230
|
+
if (prop.arg && !prop.arg.isStatic && prop.arg.content) {
|
|
2231
|
+
const argExpression = templateExpression(state.expressionPath, prop.arg.content);
|
|
2232
|
+
state.moduleClosureBroken ||= argExpression?.usesCssModule ?? true;
|
|
2233
|
+
state.referencesUseCssModule ||= argExpression?.referencesUseCssModule ?? false;
|
|
2234
|
+
for (const name of argExpression?.references ?? []) state.expressionReferences.add(name);
|
|
2235
|
+
for (const prefix of argExpression?.templatePrefixes ?? []) state.templatePrefixes.add(prefix);
|
|
2236
|
+
}
|
|
1278
2237
|
if (prop.name === "html") state.vHtml = true;
|
|
1279
2238
|
}
|
|
1280
2239
|
if (classOpaque) state.dynamic = true;
|
|
@@ -1288,7 +2247,13 @@ function visitTemplateNode(source, node, state) {
|
|
|
1288
2247
|
if (node.tagType === TAG_COMPONENT) state.components.push(element);
|
|
1289
2248
|
else if (node.tagType === TAG_ELEMENT) state.elements.push(element);
|
|
1290
2249
|
}
|
|
1291
|
-
if (node.type === NODE_INTERPOLATION && node.content?.content)
|
|
2250
|
+
if (node.type === NODE_INTERPOLATION && node.content?.content) {
|
|
2251
|
+
const interpolation = templateExpression(state.expressionPath, node.content.content);
|
|
2252
|
+
state.moduleClosureBroken ||= interpolation?.usesCssModule ?? true;
|
|
2253
|
+
state.referencesUseCssModule ||= interpolation?.referencesUseCssModule ?? false;
|
|
2254
|
+
for (const name of interpolation?.references ?? []) state.expressionReferences.add(name);
|
|
2255
|
+
for (const prefix of interpolation?.templatePrefixes ?? []) state.templatePrefixes.add(prefix);
|
|
2256
|
+
}
|
|
1292
2257
|
for (const child of node.children ?? []) visitTemplateNode(source, child, state);
|
|
1293
2258
|
}
|
|
1294
2259
|
function attributeRemovalStart(source, node, prop) {
|
|
@@ -1297,20 +2262,39 @@ function attributeRemovalStart(source, node, prop) {
|
|
|
1297
2262
|
while (start > node.loc.start.offset + 1 && /\s/.test(source[start - 1])) start -= 1;
|
|
1298
2263
|
return /[\r\n]/.test(source.slice(start, attributeStart)) ? attributeStart : start;
|
|
1299
2264
|
}
|
|
1300
|
-
function
|
|
1301
|
-
|
|
2265
|
+
function templateExpression(path, source) {
|
|
2266
|
+
try {
|
|
2267
|
+
return expressionAnalysis(path, source);
|
|
2268
|
+
} catch {
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
function templateHandler(source) {
|
|
1302
2273
|
try {
|
|
1303
|
-
|
|
2274
|
+
const analysis = sourceAnalysis("Component.handler.ts", `async function handler($event) { ${source} }`);
|
|
2275
|
+
return {
|
|
2276
|
+
staticString: null,
|
|
2277
|
+
vueModuleMember: null,
|
|
2278
|
+
usesCssModule: analysis.usesCssModule,
|
|
2279
|
+
referencesUseCssModule: analysis.hasUnboundUseCssModule,
|
|
2280
|
+
references: analysis.unboundReferences,
|
|
2281
|
+
templatePrefixes: analysis.templatePrefixes,
|
|
2282
|
+
customProperties: analysis.customProperties,
|
|
2283
|
+
customPropertiesUnbounded: analysis.customPropertiesUnbounded
|
|
2284
|
+
};
|
|
1304
2285
|
} catch {
|
|
1305
2286
|
return;
|
|
1306
2287
|
}
|
|
1307
2288
|
}
|
|
1308
2289
|
function templateSite(source, node, bindingClasses, classOpaque, state) {
|
|
1309
|
-
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
const
|
|
1313
|
-
if (
|
|
2290
|
+
const classProp = staticAttribute(node, "class");
|
|
2291
|
+
const idProp = staticAttribute(node, "id");
|
|
2292
|
+
let classAttribute = literalAttribute(source, classProp);
|
|
2293
|
+
const idAttribute = literalAttribute(source, idProp);
|
|
2294
|
+
if (classProp && !classAttribute || idProp && !idAttribute) {
|
|
2295
|
+
if (classProp && !classAttribute) {
|
|
2296
|
+
for (const token of classProp.value?.content.split(/[\t\n\f\r ]+/) ?? []) if (token !== "") state.entityClassTokens.push(token);
|
|
2297
|
+
}
|
|
1314
2298
|
state.dynamic = true;
|
|
1315
2299
|
return { idAttribute };
|
|
1316
2300
|
}
|
|
@@ -1331,8 +2315,10 @@ function templateSite(source, node, bindingClasses, classOpaque, state) {
|
|
|
1331
2315
|
matchClasses
|
|
1332
2316
|
};
|
|
1333
2317
|
}
|
|
1334
|
-
function
|
|
1335
|
-
|
|
2318
|
+
function staticAttribute(node, name) {
|
|
2319
|
+
return node.props?.find((prop) => prop.type === PROP_ATTRIBUTE && prop.name === name);
|
|
2320
|
+
}
|
|
2321
|
+
function literalAttribute(source, prop) {
|
|
1336
2322
|
if (!prop?.value) return void 0;
|
|
1337
2323
|
const start = prop.value.loc.start.offset;
|
|
1338
2324
|
const end = prop.value.loc.end.offset;
|
|
@@ -1371,12 +2357,12 @@ function vueReferenceTarget(importer, reference, vuePaths) {
|
|
|
1371
2357
|
join(target, "index.vue")
|
|
1372
2358
|
].find((path) => vuePaths.has(path));
|
|
1373
2359
|
}
|
|
1374
|
-
function
|
|
1375
|
-
return new Set(
|
|
1376
|
-
if (/[?*[\]{}]/.test(
|
|
1377
|
-
const target = vueReferenceTarget(
|
|
2360
|
+
function vueGlobTargets(importer, patterns, vuePaths) {
|
|
2361
|
+
return new Set(patterns.flatMap((pattern) => {
|
|
2362
|
+
if (/[?*[\]{}]/.test(pattern)) return pattern.includes("vue") || isLocalVueReference(pattern) ? [...vuePaths] : [];
|
|
2363
|
+
const target = vueReferenceTarget(importer, pattern, vuePaths);
|
|
1378
2364
|
if (target) return [target];
|
|
1379
|
-
return
|
|
2365
|
+
return pattern.includes(".vue") ? [...vuePaths] : [];
|
|
1380
2366
|
}));
|
|
1381
2367
|
}
|
|
1382
2368
|
function normalizedVueTag(value) {
|
|
@@ -1423,28 +2409,48 @@ function buildVueComponentGraph(ownedVue, sourceFiles, analyses, { styleSources,
|
|
|
1423
2409
|
if (analysis.componentsOpen) for (const target of vuePaths) callerOpen.add(target);
|
|
1424
2410
|
}
|
|
1425
2411
|
for (const file of sourceFiles) {
|
|
2412
|
+
if (extname(file.path) === ".html") continue;
|
|
1426
2413
|
let references;
|
|
2414
|
+
let vueReferences;
|
|
1427
2415
|
let blockReferences = [];
|
|
2416
|
+
let hasDynamicImport = false;
|
|
2417
|
+
let vueGlobPatterns = [];
|
|
2418
|
+
let vueGlobUnverifiable = false;
|
|
1428
2419
|
if (extname(file.path) === ".vue") {
|
|
1429
2420
|
const fileAnalysis = analyses.get(file.path);
|
|
1430
2421
|
if (fileAnalysis && !fileAnalysis.retained) {
|
|
1431
2422
|
references = fileAnalysis.scriptStyleImports;
|
|
2423
|
+
vueReferences = fileAnalysis.scriptVueReferences;
|
|
1432
2424
|
blockReferences = fileAnalysis.styleBlockImports.map((entry) => entry.reference);
|
|
1433
|
-
|
|
2425
|
+
hasDynamicImport = fileAnalysis.scriptHasDynamicImport || fileAnalysis.scriptImportsUnverifiable;
|
|
2426
|
+
vueGlobPatterns = fileAnalysis.scriptVueGlobPatterns;
|
|
2427
|
+
vueGlobUnverifiable = fileAnalysis.scriptVueGlobUnverifiable;
|
|
2428
|
+
} else {
|
|
2429
|
+
references = [];
|
|
2430
|
+
vueReferences = [];
|
|
2431
|
+
hasDynamicImport = true;
|
|
2432
|
+
}
|
|
1434
2433
|
} else try {
|
|
1435
|
-
|
|
2434
|
+
const analysis = sourceAnalysis(file.path, file.source);
|
|
2435
|
+
references = analysis.staticImports;
|
|
2436
|
+
vueReferences = analysis.imports.filter((record) => !record.typeOnly).map((record) => record.specifier);
|
|
2437
|
+
hasDynamicImport = analysis.hasDynamicImport;
|
|
2438
|
+
vueGlobPatterns = analysis.vueGlobPatterns;
|
|
2439
|
+
vueGlobUnverifiable = analysis.vueGlobUnverifiable;
|
|
1436
2440
|
} catch {
|
|
1437
2441
|
references = [];
|
|
2442
|
+
vueReferences = [];
|
|
2443
|
+
hasDynamicImport = true;
|
|
1438
2444
|
}
|
|
1439
|
-
if (
|
|
2445
|
+
if (hasDynamicImport || vueGlobUnverifiable || vueReferences.some((reference) => !stylesheetReference.test(reference) && isLocalVueReference(reference) && couldResolveToVue(reference) && !vueReferenceTarget(file.path, reference, vuePaths))) for (const target of vuePaths) callerOpen.add(target);
|
|
1440
2446
|
const resolvesOwned = (reference) => stylesheetReferenceTargets(file.path, reference, styleSources).some((path) => pathOwners.get(path) === packageRoot);
|
|
1441
2447
|
if (pathOwners.get(file.path) === packageRoot && (references.some((reference) => stylesheetReference.test(reference) && (!reference.startsWith(".") || !resolvesOwned(reference))) || blockReferences.some((reference) => !resolvesOwned(reference)))) unresolvedStyleImport = true;
|
|
1442
|
-
const imported = new Set(
|
|
2448
|
+
const imported = new Set(vueReferences.flatMap((reference) => {
|
|
1443
2449
|
const target = vueReferenceTarget(file.path, reference, vuePaths);
|
|
1444
2450
|
return target ? [target] : [];
|
|
1445
2451
|
}));
|
|
1446
2452
|
for (const target of imported) if (extname(file.path) !== ".vue" || !analyses.get(file.path)?.setupImports?.has(target)) callerOpen.add(target);
|
|
1447
|
-
for (const target of
|
|
2453
|
+
for (const target of vueGlobTargets(file.path, vueGlobPatterns, vuePaths)) if (!imported.has(target)) callerOpen.add(target);
|
|
1448
2454
|
}
|
|
1449
2455
|
return {
|
|
1450
2456
|
callers,
|
|
@@ -1452,15 +2458,16 @@ function buildVueComponentGraph(ownedVue, sourceFiles, analyses, { styleSources,
|
|
|
1452
2458
|
unresolvedStyleImport
|
|
1453
2459
|
};
|
|
1454
2460
|
}
|
|
1455
|
-
function vueWarningsOnlyResult(preparedVue) {
|
|
1456
|
-
|
|
2461
|
+
function vueWarningsOnlyResult(preparedVue, extraWarnings = []) {
|
|
2462
|
+
const warnings = [...preparedVue.warnings, ...extraWarnings];
|
|
2463
|
+
if (warnings.length === 0) return {};
|
|
1457
2464
|
return { plan: {
|
|
1458
2465
|
files: [],
|
|
1459
2466
|
deletedFiles: [],
|
|
1460
2467
|
unlinkedFiles: [],
|
|
1461
2468
|
candidates: [],
|
|
1462
2469
|
rules: [],
|
|
1463
|
-
warnings
|
|
2470
|
+
warnings,
|
|
1464
2471
|
convertedRules: 0,
|
|
1465
2472
|
retainedRules: 0
|
|
1466
2473
|
} };
|
|
@@ -1542,13 +2549,17 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1542
2549
|
pathOwners,
|
|
1543
2550
|
packageRoot
|
|
1544
2551
|
});
|
|
1545
|
-
const generatesSelectors = (text) => /#\{|@\{|&[\w-]/.test(text);
|
|
1546
2552
|
const vueShadowCss = [];
|
|
1547
2553
|
const vueShadowModuleCss = [];
|
|
1548
2554
|
let vueShadowUnverifiable = vueGraph.unresolvedStyleImport;
|
|
1549
2555
|
for (const [path, source] of styleSources) {
|
|
1550
2556
|
if (pathOwners.get(path) !== packageRoot) continue;
|
|
1551
|
-
|
|
2557
|
+
try {
|
|
2558
|
+
if (stylesheetAnalysis(path, source).selectorsUnverifiable) {
|
|
2559
|
+
vueShadowUnverifiable = true;
|
|
2560
|
+
continue;
|
|
2561
|
+
}
|
|
2562
|
+
} catch {
|
|
1552
2563
|
vueShadowUnverifiable = true;
|
|
1553
2564
|
continue;
|
|
1554
2565
|
}
|
|
@@ -1558,21 +2569,23 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1558
2569
|
source
|
|
1559
2570
|
});
|
|
1560
2571
|
}
|
|
1561
|
-
for (const file of sourceFiles) if (extname(file.path) === ".html" && pathOwners.get(file.path) === packageRoot
|
|
2572
|
+
for (const file of sourceFiles) if (extname(file.path) === ".html" && pathOwners.get(file.path) === packageRoot) try {
|
|
2573
|
+
if (parseHtmlSource(file.path, file.source).hasStyle) vueShadowUnverifiable = true;
|
|
2574
|
+
} catch {
|
|
2575
|
+
vueShadowUnverifiable = true;
|
|
2576
|
+
}
|
|
1562
2577
|
for (const file of ownedVue) {
|
|
1563
2578
|
const analysis = analysisOf(file.path);
|
|
1564
2579
|
if (analysis.retained) {
|
|
1565
|
-
if (
|
|
2580
|
+
if (analysis.styleRanges.length > 0) vueShadowUnverifiable = true;
|
|
1566
2581
|
continue;
|
|
1567
2582
|
}
|
|
1568
2583
|
if (analysis.escapeUnverifiable || analysis.scriptImportsUnverifiable) vueShadowUnverifiable = true;
|
|
1569
|
-
for (const text of analysis.shadowPreprocessorTexts)
|
|
1570
|
-
else vueShadowCss.push({
|
|
2584
|
+
for (const text of analysis.shadowPreprocessorTexts) vueShadowCss.push({
|
|
1571
2585
|
path: file.path,
|
|
1572
2586
|
source: text
|
|
1573
2587
|
});
|
|
1574
|
-
for (const text of analysis.unscopedShadowPreprocessorTexts)
|
|
1575
|
-
else vueShadowCss.push({
|
|
2588
|
+
for (const text of analysis.unscopedShadowPreprocessorTexts) vueShadowCss.push({
|
|
1576
2589
|
path: file.path,
|
|
1577
2590
|
source: text,
|
|
1578
2591
|
migratingUnscoped: true
|
|
@@ -1586,7 +2599,7 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1586
2599
|
migratingUnscoped: true
|
|
1587
2600
|
})), ...analysis.blocks.map((block) => ({
|
|
1588
2601
|
path: file.path,
|
|
1589
|
-
source: block.analysisSource ?? block.content,
|
|
2602
|
+
source: block.shadowSource ?? block.analysisSource ?? block.content,
|
|
1590
2603
|
scoped: true
|
|
1591
2604
|
})));
|
|
1592
2605
|
vueShadowModuleCss.push(...analysis.shadowModuleCssTexts, ...analysis.moduleBlocks.map((block) => block.analysisSource ?? block.content));
|
|
@@ -1627,7 +2640,7 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1627
2640
|
for (const path of importedStyles) {
|
|
1628
2641
|
stylePaths.add(path);
|
|
1629
2642
|
if (extname(path) !== ".css") continue;
|
|
1630
|
-
for (const imported of cssImports(styleSources.get(path) ?? "")) {
|
|
2643
|
+
for (const imported of cssImports(path, styleSources.get(path) ?? "")) {
|
|
1631
2644
|
if (imported.media) continue;
|
|
1632
2645
|
for (const target of stylesheetReferenceTargets(path, imported.href, styleSources)) if (pathOwners.get(target) === packageRoot) importedStyles.add(target);
|
|
1633
2646
|
}
|
|
@@ -1712,6 +2725,19 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1712
2725
|
message
|
|
1713
2726
|
});
|
|
1714
2727
|
}
|
|
2728
|
+
const allStyleBlocks = [
|
|
2729
|
+
...analysis.blocks,
|
|
2730
|
+
...analysis.moduleBlocks,
|
|
2731
|
+
...analysis.unscopedBlocks
|
|
2732
|
+
];
|
|
2733
|
+
let vueAtRuleIdentities;
|
|
2734
|
+
if (analysis.hasOpaqueStyleBlocks) vueAtRuleIdentities = null;
|
|
2735
|
+
else try {
|
|
2736
|
+
const registrations = allStyleBlocks.map((block) => stylesheetAnalysis(`${file.path}.${block.analysisSource ? "css" : block.syntax}`, block.analysisSource ?? block.content));
|
|
2737
|
+
vueAtRuleIdentities = registrations.some((entry) => entry.globalAtRulesUnverifiable) ? null : registrations.flatMap((entry) => entry.globalAtRuleIdentities);
|
|
2738
|
+
} catch {
|
|
2739
|
+
vueAtRuleIdentities = null;
|
|
2740
|
+
}
|
|
1715
2741
|
const vueBlocks = migrateUnscoped ? analysis.unscopedBlocks : analysis.blocks;
|
|
1716
2742
|
if (vueBlocks.length === 0 && !migrateModule) continue;
|
|
1717
2743
|
const vueRetention = migrateUnscoped ? void 0 : retention;
|
|
@@ -1719,19 +2745,20 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1719
2745
|
if (migrateModule) stylesheets.push({
|
|
1720
2746
|
cssPath: file.path,
|
|
1721
2747
|
cssSource: file.source,
|
|
1722
|
-
cssModuleId: normalizedRelativePath(
|
|
2748
|
+
cssModuleId: normalizedRelativePath(workspaceRoot, file.path),
|
|
1723
2749
|
syntax: "css",
|
|
1724
2750
|
isModule: true,
|
|
1725
2751
|
vueBlocks: analysis.moduleBlocks,
|
|
1726
2752
|
vueModule: true,
|
|
1727
2753
|
vueShadowCss: shadowCss,
|
|
1728
2754
|
vueShadowModuleCss,
|
|
1729
|
-
vueShadowUnverifiable
|
|
2755
|
+
vueShadowUnverifiable,
|
|
2756
|
+
vueAtRuleIdentities
|
|
1730
2757
|
});
|
|
1731
2758
|
if (vueBlocks.length > 0) stylesheets.push({
|
|
1732
2759
|
cssPath: file.path,
|
|
1733
2760
|
cssSource: file.source,
|
|
1734
|
-
cssModuleId: normalizedRelativePath(
|
|
2761
|
+
cssModuleId: normalizedRelativePath(workspaceRoot, file.path),
|
|
1735
2762
|
syntax: "css",
|
|
1736
2763
|
isModule: !vueRetention,
|
|
1737
2764
|
vueBlocks,
|
|
@@ -1739,7 +2766,8 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1739
2766
|
vueUnscoped: migrateUnscoped,
|
|
1740
2767
|
vueShadowCss: vueRetention ? void 0 : shadowCss,
|
|
1741
2768
|
vueShadowModuleCss: vueRetention ? void 0 : vueShadowModuleCss,
|
|
1742
|
-
vueShadowUnverifiable: vueRetention ? void 0 : vueShadowUnverifiable
|
|
2769
|
+
vueShadowUnverifiable: vueRetention ? void 0 : vueShadowUnverifiable,
|
|
2770
|
+
vueAtRuleIdentities
|
|
1743
2771
|
});
|
|
1744
2772
|
}
|
|
1745
2773
|
for (const file of ownedVue) {
|
|
@@ -1747,19 +2775,31 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1747
2775
|
if (analysis.retained) continue;
|
|
1748
2776
|
const elements = elementsByFile.get(file.path) ?? [];
|
|
1749
2777
|
const contextPaths = [...new Set(elements.flatMap((element) => element.cssPaths))];
|
|
1750
|
-
if (contextPaths.length
|
|
1751
|
-
await rejectSymlinkTarget(file.path, packageRoot);
|
|
2778
|
+
if (contextPaths.length > 0) await rejectSymlinkTarget(file.path, packageRoot);
|
|
1752
2779
|
files.set(file.path, {
|
|
1753
2780
|
...file,
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
cssPath
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
2781
|
+
...contextPaths.length > 0 ? {
|
|
2782
|
+
htmlElements: elements,
|
|
2783
|
+
htmlStylesheets: contextPaths.map((cssPath) => ({
|
|
2784
|
+
cssPath,
|
|
2785
|
+
variants: [],
|
|
2786
|
+
direct: cssPath === file.path,
|
|
2787
|
+
analyzable: true
|
|
2788
|
+
})),
|
|
2789
|
+
htmlReferencesSafe: !analysis.dynamic,
|
|
2790
|
+
htmlScriptText: analysis.scriptText
|
|
2791
|
+
} : {},
|
|
2792
|
+
sourceImports: analysis.scriptImports,
|
|
2793
|
+
sourceImportsUnverifiable: analysis.scriptImportsUnverifiable,
|
|
2794
|
+
templatePrefixes: analysis.templatePrefixes,
|
|
2795
|
+
templateInjectsMarkup: analysis.vHtml,
|
|
2796
|
+
templateClassTokens: analysis.templateClassTokens,
|
|
2797
|
+
templateStyleValues: analysis.templateStyleValues,
|
|
2798
|
+
templateStylesUnverifiable: analysis.templateStylesUnverifiable,
|
|
2799
|
+
sourceCustomProperties: analysis.scriptCustomProperties,
|
|
2800
|
+
sourceCustomPropertiesUnbounded: analysis.scriptCustomPropertiesUnbounded,
|
|
2801
|
+
templateStylesheetLinks: analysis.templateStylesheetLinks,
|
|
2802
|
+
templateStylesheetLinksUnverifiable: analysis.templateStylesheetLinksUnverifiable
|
|
1763
2803
|
});
|
|
1764
2804
|
}
|
|
1765
2805
|
return {
|
|
@@ -1865,7 +2905,13 @@ async function migrate(options = {}) {
|
|
|
1865
2905
|
Promise.all(stylePaths.map(async (path) => [path, await snapshotFile(snapshots, path)])).then((entries) => new Map(entries)),
|
|
1866
2906
|
Promise.all(sourcePaths.map(async (path) => {
|
|
1867
2907
|
const source = await readFile(path, "utf8");
|
|
1868
|
-
const mayReferenceModule = extname(path) !== ".html" ||
|
|
2908
|
+
const mayReferenceModule = extname(path) !== ".html" || (() => {
|
|
2909
|
+
try {
|
|
2910
|
+
return parseHtmlSource(path, source).links.length > 0;
|
|
2911
|
+
} catch {
|
|
2912
|
+
return /<link\b/i.test(source);
|
|
2913
|
+
}
|
|
2914
|
+
})();
|
|
1869
2915
|
if (!scope.targetable.has(path) && !mayReferenceModule) return void 0;
|
|
1870
2916
|
return {
|
|
1871
2917
|
path,
|
|
@@ -1885,15 +2931,36 @@ async function migrate(options = {}) {
|
|
|
1885
2931
|
styleSources,
|
|
1886
2932
|
sourceFiles,
|
|
1887
2933
|
styleDependents: indexStylesheetDependents(styleSources),
|
|
1888
|
-
vueStyleRanges
|
|
2934
|
+
vueStyleRanges,
|
|
2935
|
+
entryCatalog: tailwindEntryCatalog(styleSources, scope.pathOwners),
|
|
2936
|
+
ignoredPaths: await scannerIgnoredPaths(workspaceRoot, sourceFiles.map((file) => file.path)),
|
|
2937
|
+
fontAllocations: /* @__PURE__ */ new Map()
|
|
1889
2938
|
};
|
|
1890
2939
|
const failures = [];
|
|
1891
2940
|
const plans = [];
|
|
2941
|
+
const prepared = [];
|
|
1892
2942
|
for (const packageRoot of selectedPackages) {
|
|
1893
|
-
const
|
|
1894
|
-
if (
|
|
1895
|
-
else if (
|
|
1896
|
-
|
|
2943
|
+
const preparation = await preparePackage(context, packageRoot);
|
|
2944
|
+
if (preparation.failure) failures.push(preparation.failure);
|
|
2945
|
+
else if (preparation.plan) plans.push(preparation.plan);
|
|
2946
|
+
if (preparation.prepared) prepared.push(preparation.prepared);
|
|
2947
|
+
}
|
|
2948
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2949
|
+
for (const preparation of prepared) {
|
|
2950
|
+
const members = groups.get(preparation.tailwind.path) ?? [];
|
|
2951
|
+
members.push(preparation);
|
|
2952
|
+
groups.set(preparation.tailwind.path, members);
|
|
2953
|
+
}
|
|
2954
|
+
for (const [, members] of groups) for (const [token, value] of Object.entries(members[0].tailwind.themeTokens)) {
|
|
2955
|
+
if (!token.startsWith("font-")) continue;
|
|
2956
|
+
const parsed = fontFamilyStack(value);
|
|
2957
|
+
const name = token.replace(/^font-/, "");
|
|
2958
|
+
const stack = parsed && parsed.value;
|
|
2959
|
+
const existing = context.fontAllocations.get(name);
|
|
2960
|
+
context.fontAllocations.set(name, existing === void 0 || existing === stack ? stack : null);
|
|
2961
|
+
}
|
|
2962
|
+
for (const [, members] of [...groups].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) for (const result of await planPreparedGroup(context, members)) if (result.failure) failures.push(result.failure);
|
|
2963
|
+
else if (result.plan) plans.push(result.plan);
|
|
1897
2964
|
const originals = new Map([...styleSources, ...sourceFiles.map((file) => [file.path, file.source])]);
|
|
1898
2965
|
const { filesByPath, deletedPaths, candidates, rules, warnings, convertedRules, retainedRules } = mergePlans(plans, originals);
|
|
1899
2966
|
const changed = [...filesByPath.values()].map((file) => ({
|
|
@@ -1980,7 +3047,7 @@ async function migrate(options = {}) {
|
|
|
1980
3047
|
failures
|
|
1981
3048
|
};
|
|
1982
3049
|
}
|
|
1983
|
-
async function
|
|
3050
|
+
async function preparePackage(context, packageRoot) {
|
|
1984
3051
|
const { options, snapshots, workspaceRoot, explicitStyle, configuredEntry, styleSources, sourceFiles, styleDependents, pathOwners, targetable, selectedPackages, scannedPaths, vueStyleRanges } = context;
|
|
1985
3052
|
const recover = (error, fatal = false) => {
|
|
1986
3053
|
if (!options.force || fatal) throw error;
|
|
@@ -2030,19 +3097,84 @@ async function planPackage(context, packageRoot) {
|
|
|
2030
3097
|
if (ownedStyles.length === 0 && preparedVue.stylesheets.length === 0) return vueWarningsOnlyResult(preparedVue);
|
|
2031
3098
|
let tailwindPath;
|
|
2032
3099
|
let tailwindEntries;
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
3100
|
+
let entryOwner = packageRoot;
|
|
3101
|
+
let sharedProofs;
|
|
3102
|
+
const ownEntries = findTailwindEntries(ownedStyles, styleSources);
|
|
3103
|
+
if (ownEntries.length === 0 && configuredEntry === void 0 && options.workspaces && options.extractMediaQueries !== false) {
|
|
3104
|
+
let packageJson = {};
|
|
3105
|
+
try {
|
|
3106
|
+
const rawPackageJson = snapshots.get(join(packageRoot, "package.json"));
|
|
3107
|
+
if (rawPackageJson !== void 0) {
|
|
3108
|
+
const parsed = JSON.parse(rawPackageJson);
|
|
3109
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) packageJson = { ...parsed };
|
|
3110
|
+
}
|
|
3111
|
+
} catch (error) {
|
|
3112
|
+
return recover(error);
|
|
3113
|
+
}
|
|
3114
|
+
const ancestors = selectedPackages.filter((root) => root !== packageRoot && isWithin(root, packageRoot)).sort((left, right) => right.length - left.length);
|
|
3115
|
+
for (const ancestor of ancestors) {
|
|
3116
|
+
const proven = [];
|
|
3117
|
+
for (const candidate of context.entryCatalog.get(ancestor) ?? []) {
|
|
3118
|
+
const proofs = proveSharedEntry({
|
|
3119
|
+
packageRoot,
|
|
3120
|
+
entry: candidate,
|
|
3121
|
+
entrySource: styleSources.get(candidate) ?? "",
|
|
3122
|
+
styleSources,
|
|
3123
|
+
packageSources,
|
|
3124
|
+
owned: (path) => pathOwners.get(path) === packageRoot,
|
|
3125
|
+
writable: (path) => targetable.has(path) && pathOwners.get(path) === packageRoot,
|
|
3126
|
+
packageJson,
|
|
3127
|
+
ignoredPaths: context.ignoredPaths
|
|
3128
|
+
});
|
|
3129
|
+
if (proofs) proven.push({
|
|
3130
|
+
entry: candidate,
|
|
3131
|
+
proofs
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3134
|
+
if (proven.length > 1) return recover(/* @__PURE__ */ new Error("Multiple proven ancestor Tailwind entries were found. Pass --tailwind-css."));
|
|
3135
|
+
if (proven.length === 1) {
|
|
3136
|
+
[{entry: tailwindPath, proofs: sharedProofs}] = proven;
|
|
3137
|
+
entryOwner = ancestor;
|
|
3138
|
+
break;
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
if (sharedProofs && tailwindPath !== void 0) tailwindEntries = [];
|
|
3143
|
+
else {
|
|
3144
|
+
tailwindEntries = ownEntries;
|
|
3145
|
+
try {
|
|
3146
|
+
tailwindPath = selectTailwindEntry(ownEntries, configuredEntry);
|
|
3147
|
+
} catch (error) {
|
|
3148
|
+
return recover(error);
|
|
3149
|
+
}
|
|
2037
3150
|
}
|
|
2038
3151
|
const excludedEntries = /* @__PURE__ */ new Set([...tailwindEntries, tailwindPath]);
|
|
2039
3152
|
const explicitCss = explicitStyle && extname(explicitStyle) !== ".vue" ? explicitStyle : void 0;
|
|
2040
|
-
const targets = explicitCss ? [explicitCss] : explicitStyle ? [...preparedVue.stylePaths].filter((path) => !isStylesheetModule(path) && !excludedEntries.has(path)) : ownedStyles.filter((path) => !excludedEntries.has(path) && !preparedHtml.generatedPaths.has(path) && (!isPreprocessorPath(path) || preparedHtml.stylePaths.has(path) || packageSources.some((file) =>
|
|
3153
|
+
const targets = explicitCss ? [explicitCss] : explicitStyle ? [...preparedVue.stylePaths].filter((path) => !isStylesheetModule(path) && !excludedEntries.has(path)) : ownedStyles.filter((path) => !excludedEntries.has(path) && !preparedHtml.generatedPaths.has(path) && (!isPreprocessorPath(path) || preparedHtml.stylePaths.has(path) || preparedVue.stylePaths.has(path) || packageSources.some((file) => importsStylesheet(file, path))));
|
|
2041
3154
|
if (targets.length === 0 && preparedVue.stylesheets.length === 0) return vueWarningsOnlyResult(preparedVue);
|
|
2042
3155
|
if (targets.some((path) => excludedEntries.has(path))) throw new Error("The Tailwind CSS entry cannot be migrated.");
|
|
3156
|
+
const proofWarnings = [];
|
|
3157
|
+
const unprovenFlow = (path) => ({
|
|
3158
|
+
code: "unproven-shared-entry-flow",
|
|
3159
|
+
file: path,
|
|
3160
|
+
start: 0,
|
|
3161
|
+
end: 0,
|
|
3162
|
+
message: "A consumer flow could not be proven to load the shared Tailwind entry, so the stylesheet is retained."
|
|
3163
|
+
});
|
|
3164
|
+
const provenTargets = sharedProofs ? targets.filter((path) => {
|
|
3165
|
+
const proven = sharedProofs.provenStyle(packageSources.filter((file) => importsStylesheet(file, path) || (file.htmlStylesheets ?? []).some((linked) => linked.cssPath === path)));
|
|
3166
|
+
if (!proven) proofWarnings.push(unprovenFlow(path));
|
|
3167
|
+
return proven;
|
|
3168
|
+
}) : targets;
|
|
3169
|
+
const provenVueStylesheets = sharedProofs ? preparedVue.stylesheets.filter((stylesheet) => {
|
|
3170
|
+
const proven = sharedProofs.provenStyle(packageSources.filter((file) => file.path === stylesheet.cssPath));
|
|
3171
|
+
if (!proven && !proofWarnings.some((warning) => warning.file === stylesheet.cssPath)) proofWarnings.push(unprovenFlow(stylesheet.cssPath));
|
|
3172
|
+
return proven;
|
|
3173
|
+
}) : preparedVue.stylesheets;
|
|
3174
|
+
if (provenTargets.length === 0 && provenVueStylesheets.length === 0) return vueWarningsOnlyResult(preparedVue, proofWarnings);
|
|
2043
3175
|
let tailwind;
|
|
2044
3176
|
try {
|
|
2045
|
-
tailwind = await loadTailwind(
|
|
3177
|
+
tailwind = await loadTailwind(entryOwner, tailwindPath, snapshots, workspaceRoot);
|
|
2046
3178
|
} catch (error) {
|
|
2047
3179
|
return recover(error, isIntegrityError(error));
|
|
2048
3180
|
}
|
|
@@ -2057,13 +3189,13 @@ async function planPackage(context, packageRoot) {
|
|
|
2057
3189
|
try {
|
|
2058
3190
|
stylesheets = [];
|
|
2059
3191
|
const compilerDependents = /* @__PURE__ */ new Map();
|
|
2060
|
-
for (const stylePath of
|
|
3192
|
+
for (const stylePath of provenTargets.sort()) {
|
|
2061
3193
|
await rejectSymlinkTarget(stylePath, packageRoot);
|
|
2062
3194
|
const isPartial = isSassPath(stylePath) && basename(stylePath).startsWith("_");
|
|
2063
3195
|
const stylesheet = {
|
|
2064
3196
|
cssPath: stylePath,
|
|
2065
3197
|
cssSource: styleSources.get(stylePath) ?? "",
|
|
2066
|
-
cssModuleId: normalizedRelativePath(
|
|
3198
|
+
cssModuleId: normalizedRelativePath(workspaceRoot, stylePath),
|
|
2067
3199
|
cssDependents: styleDependents.get(stylePath) ?? [],
|
|
2068
3200
|
syntax: stylesheetSyntax(stylePath),
|
|
2069
3201
|
isModule: isStylesheetModule(stylePath),
|
|
@@ -2097,125 +3229,637 @@ async function planPackage(context, packageRoot) {
|
|
|
2097
3229
|
} catch (error) {
|
|
2098
3230
|
return recover(error, isIntegrityError(error));
|
|
2099
3231
|
}
|
|
2100
|
-
stylesheets.push(...
|
|
2101
|
-
|
|
3232
|
+
stylesheets.push(...provenVueStylesheets);
|
|
3233
|
+
return { prepared: {
|
|
3234
|
+
packageRoot,
|
|
3235
|
+
preparedHtml,
|
|
3236
|
+
preparedVue,
|
|
3237
|
+
styleCompilers,
|
|
3238
|
+
files,
|
|
2102
3239
|
stylesheets,
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
3240
|
+
tailwind,
|
|
3241
|
+
sharedProofs,
|
|
3242
|
+
warnings: proofWarnings
|
|
3243
|
+
} };
|
|
3244
|
+
}
|
|
3245
|
+
async function planPreparedGroup(context, members) {
|
|
3246
|
+
const { options, workspaceRoot, targetable } = context;
|
|
3247
|
+
const results = [];
|
|
3248
|
+
let active = [...members].sort((left, right) => {
|
|
3249
|
+
const leftPath = normalizedRelativePath(workspaceRoot, left.packageRoot);
|
|
3250
|
+
const rightPath = normalizedRelativePath(workspaceRoot, right.packageRoot);
|
|
3251
|
+
return leftPath < rightPath ? -1 : leftPath > rightPath ? 1 : 0;
|
|
3252
|
+
});
|
|
3253
|
+
const entry = active[0].tailwind;
|
|
3254
|
+
const reservations = spellingReservations(context.styleSources, entry.graphSources.map((graphSource) => [`${graphSource.path}.graph.css`, graphSource.source]), new Set(entry.graphSources.map((graphSource) => graphSource.path).filter((path) => path.includes("\0"))));
|
|
3255
|
+
const groupFiles = new Map(active.flatMap((member) => member.files.map((file) => [file.path, file])));
|
|
3256
|
+
const mentionedSegments = /* @__PURE__ */ new Set();
|
|
3257
|
+
const mentionedSources = [...[...groupFiles.values()].map((file) => file.source), ...entry.graphSources.map((graphSource) => graphSource.source)];
|
|
3258
|
+
const addSegment = (token) => {
|
|
3259
|
+
if (token === "") return;
|
|
3260
|
+
const segment = utilitySegment(token).replace(/!$/, "");
|
|
3261
|
+
if (segment) mentionedSegments.add(segment);
|
|
2108
3262
|
};
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
3263
|
+
for (const source of mentionedSources) {
|
|
3264
|
+
for (const token of source.split(/[\s"'`<>=,;{}()\\]+/)) addSegment(token);
|
|
3265
|
+
for (const inline of source.matchAll(/inline\(([^)]*)\)/g)) for (const candidate of inline[1].matchAll(/([^\s"',{}]*)\{([^{}]*)\}([^\s"',{}]*)/g)) for (const option of candidate[2].split(",")) {
|
|
3266
|
+
const range = /^(\d+)\.\.(\d+)(?:\.\.(\d+))?$/.exec(option);
|
|
3267
|
+
const stride = range === null ? 0 : Number(range[3] ?? "1");
|
|
3268
|
+
if (range !== null && stride > 0) {
|
|
3269
|
+
const from = Number(range[1]);
|
|
3270
|
+
const to = Number(range[2]);
|
|
3271
|
+
let steps = 0;
|
|
3272
|
+
for (let value = Math.min(from, to); value <= Math.max(from, to); value += stride) {
|
|
3273
|
+
addSegment(`${candidate[1]}${value}${candidate[3]}`);
|
|
3274
|
+
if ((steps += 1) >= 1e3) break;
|
|
3275
|
+
}
|
|
3276
|
+
continue;
|
|
3277
|
+
}
|
|
3278
|
+
addSegment(`${candidate[1]}${option}${candidate[3]}`);
|
|
3279
|
+
}
|
|
2119
3280
|
}
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
const
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
3281
|
+
const fileRoots = new Map(active.flatMap((member) => member.files.map((file) => [file.path, member.packageRoot])));
|
|
3282
|
+
const workspaceEntries = new Set([...context.entryCatalog.values()].flat());
|
|
3283
|
+
for (const file of groupFiles.values()) {
|
|
3284
|
+
if (reservations.unbounded) break;
|
|
3285
|
+
if (context.ignoredPaths.has(file.path)) {
|
|
3286
|
+
reservations.unbounded = true;
|
|
3287
|
+
break;
|
|
3288
|
+
}
|
|
3289
|
+
const opaqueStylesheetLink = (link) => {
|
|
3290
|
+
const resolved = localHtmlReference(fileRoots.get(file.path) ?? context.workspaceRoot, dirname(file.path), link);
|
|
3291
|
+
return resolved === void 0 || !context.styleSources.has(resolved) || workspaceEntries.has(resolved) && resolved !== entry.path;
|
|
3292
|
+
};
|
|
3293
|
+
const opaqueStylesheetImports = (records) => {
|
|
3294
|
+
for (const record of records) {
|
|
3295
|
+
if (record.typeOnly) continue;
|
|
3296
|
+
const specifier = record.specifier.split(/[?#]/, 1)[0];
|
|
3297
|
+
if (!isStylesheetPath(specifier)) {
|
|
3298
|
+
if (extname(specifier) !== "" || specifier.startsWith(".")) continue;
|
|
3299
|
+
try {
|
|
3300
|
+
const resolved = createRequire(file.path).resolve(specifier);
|
|
3301
|
+
if (isStylesheetPath(resolved) && !context.styleSources.has(resolved)) reservations.unbounded = true;
|
|
3302
|
+
} catch {}
|
|
3303
|
+
continue;
|
|
3304
|
+
}
|
|
3305
|
+
const resolved = specifier.startsWith(".") ? resolve(dirname(file.path), specifier) : void 0;
|
|
3306
|
+
if (resolved === void 0 || !context.styleSources.has(resolved) || workspaceEntries.has(resolved) && resolved !== entry.path) reservations.unbounded = true;
|
|
3307
|
+
}
|
|
3308
|
+
};
|
|
3309
|
+
if (extname(file.path) === ".html") {
|
|
3310
|
+
try {
|
|
3311
|
+
const parsed = parseHtmlSource(file.path, file.source);
|
|
3312
|
+
if (parsed.hasStyle) reservations.unbounded = true;
|
|
3313
|
+
if (parsed.bases.length > 0) reservations.unbounded = true;
|
|
3314
|
+
for (const link of parsed.links) if (opaqueStylesheetLink(link.href)) reservations.unbounded = true;
|
|
3315
|
+
for (const [index, value] of parsed.styleAttributeValues.entries()) {
|
|
3316
|
+
if (TEMPLATE_MARKERS.test(value)) {
|
|
3317
|
+
reservations.propertiesUnbounded = true;
|
|
3318
|
+
continue;
|
|
3319
|
+
}
|
|
3320
|
+
scanInlineStyleReservations(`${file.path}.style.${index}.css`, value, reservations);
|
|
3321
|
+
}
|
|
3322
|
+
for (const value of parsed.dynamicClassValues) {
|
|
3323
|
+
const masked = value.replace(TEMPLATE_EXPRESSIONS, "\0");
|
|
3324
|
+
if (TEMPLATE_MARKERS.test(masked)) {
|
|
3325
|
+
reservations.unbounded = true;
|
|
3326
|
+
continue;
|
|
3327
|
+
}
|
|
3328
|
+
for (const token of masked.split(/\s+/)) {
|
|
3329
|
+
if (token === "") continue;
|
|
3330
|
+
const index = token.indexOf("\0");
|
|
3331
|
+
if (index < 0) reservations.names.add(token);
|
|
3332
|
+
else if (index === 0) reservations.unbounded = true;
|
|
3333
|
+
else reservations.prefixes.add(token.slice(0, index));
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
if (parsed.handlerText.trim() !== "") try {
|
|
3337
|
+
const handlers = sourceAnalysis(`${file.path}.handlers.js`, parsed.handlerText);
|
|
3338
|
+
for (const prefix of handlers.templatePrefixes) reservations.prefixes.add(prefix);
|
|
3339
|
+
for (const property of handlers.customProperties) reservations.properties.add(property);
|
|
3340
|
+
if (handlers.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
3341
|
+
} catch {
|
|
3342
|
+
reservations.unbounded = true;
|
|
3343
|
+
}
|
|
3344
|
+
if (parsed.scriptText.trim() !== "") try {
|
|
3345
|
+
const script = sourceAnalysis(`${file.path}.inline.js`, parsed.scriptText);
|
|
3346
|
+
for (const prefix of script.templatePrefixes) reservations.prefixes.add(prefix);
|
|
3347
|
+
for (const property of script.customProperties) reservations.properties.add(property);
|
|
3348
|
+
if (script.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
3349
|
+
opaqueStylesheetImports(script.imports);
|
|
3350
|
+
} catch {
|
|
3351
|
+
reservations.unbounded = true;
|
|
3352
|
+
}
|
|
3353
|
+
} catch {
|
|
3354
|
+
reservations.unbounded = true;
|
|
3355
|
+
}
|
|
3356
|
+
continue;
|
|
3357
|
+
}
|
|
3358
|
+
if (extname(file.path) === ".vue") {
|
|
3359
|
+
if (file.templatePrefixes === void 0 || file.sourceImportsUnverifiable === true || file.templateInjectsMarkup === true) {
|
|
3360
|
+
reservations.unbounded = true;
|
|
3361
|
+
continue;
|
|
3362
|
+
}
|
|
3363
|
+
const styleRanges = context.vueStyleRanges.get(file.path);
|
|
3364
|
+
if (styleRanges === void 0) {
|
|
3365
|
+
reservations.unbounded = true;
|
|
3366
|
+
continue;
|
|
2134
3367
|
}
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
3368
|
+
const fileBytes = Buffer.from(file.source);
|
|
3369
|
+
for (const [index, range] of styleRanges.entries()) {
|
|
3370
|
+
if (range.src !== void 0) {
|
|
3371
|
+
const resolved = range.src.startsWith(".") ? resolve(dirname(file.path), range.src) : void 0;
|
|
3372
|
+
if (resolved === void 0 || !context.styleSources.has(resolved)) reservations.unbounded = true;
|
|
2140
3373
|
continue;
|
|
2141
3374
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
3375
|
+
const block = fileBytes.subarray(range.start, range.end).toString();
|
|
3376
|
+
if (![
|
|
3377
|
+
"css",
|
|
3378
|
+
"scss",
|
|
3379
|
+
"sass",
|
|
3380
|
+
"less"
|
|
3381
|
+
].includes(range.lang)) {
|
|
3382
|
+
reservations.unbounded = true;
|
|
3383
|
+
continue;
|
|
3384
|
+
}
|
|
3385
|
+
scanStylesheetReservations(`${file.path}.block.${index}.${range.lang}`, block, context.styleSources, reservations);
|
|
2144
3386
|
}
|
|
3387
|
+
for (const prefix of file.templatePrefixes) reservations.prefixes.add(prefix);
|
|
3388
|
+
for (const token of file.templateClassTokens ?? []) reservations.names.add(token);
|
|
3389
|
+
for (const property of file.sourceCustomProperties ?? []) reservations.properties.add(property);
|
|
3390
|
+
if (file.sourceCustomPropertiesUnbounded === true) reservations.propertiesUnbounded = true;
|
|
3391
|
+
if (file.templateStylesUnverifiable === true) reservations.propertiesUnbounded = true;
|
|
3392
|
+
for (const [index, value] of (file.templateStyleValues ?? []).entries()) scanInlineStyleReservations(`${file.path}.style.${index}.css`, value, reservations);
|
|
3393
|
+
if (file.templateStylesheetLinksUnverifiable === true) reservations.unbounded = true;
|
|
3394
|
+
for (const link of file.templateStylesheetLinks ?? []) if (opaqueStylesheetLink(link)) reservations.unbounded = true;
|
|
3395
|
+
opaqueStylesheetImports(file.sourceImports ?? []);
|
|
3396
|
+
continue;
|
|
2145
3397
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
3398
|
+
if (![
|
|
3399
|
+
".js",
|
|
3400
|
+
".jsx",
|
|
3401
|
+
".ts",
|
|
3402
|
+
".tsx",
|
|
3403
|
+
".mjs",
|
|
3404
|
+
".cjs",
|
|
3405
|
+
".mts",
|
|
3406
|
+
".cts"
|
|
3407
|
+
].includes(extname(file.path))) continue;
|
|
3408
|
+
try {
|
|
3409
|
+
const analysis = sourceAnalysis(file.path, file.source);
|
|
3410
|
+
for (const prefix of analysis.templatePrefixes) reservations.prefixes.add(prefix);
|
|
3411
|
+
for (const property of analysis.customProperties) reservations.properties.add(property);
|
|
3412
|
+
if (analysis.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
3413
|
+
opaqueStylesheetImports(analysis.imports);
|
|
3414
|
+
if (analysis.rendersStyleElement) reservations.unbounded = true;
|
|
3415
|
+
if (analysis.injectsMarkup) reservations.unbounded = true;
|
|
3416
|
+
if (analysis.stylesheetLinksUnverifiable) reservations.unbounded = true;
|
|
3417
|
+
for (const link of analysis.stylesheetLinks) if (opaqueStylesheetLink(link)) reservations.unbounded = true;
|
|
3418
|
+
} catch {
|
|
3419
|
+
reservations.unbounded = true;
|
|
2151
3420
|
}
|
|
2152
|
-
} catch (error) {
|
|
2153
|
-
return recover(error, !isMissingStyleCompilerError(error));
|
|
2154
3421
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
if (
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
3422
|
+
const recoverGroup = (error, fatal = false) => {
|
|
3423
|
+
if (!options.force || fatal) throw error;
|
|
3424
|
+
for (const member of active) results.push({ failure: packageFailure(workspaceRoot, member.packageRoot, error) });
|
|
3425
|
+
return results;
|
|
3426
|
+
};
|
|
3427
|
+
const membersByRoot = new Map(active.map((member) => [member.packageRoot, member]));
|
|
3428
|
+
for (const member of options.extractMediaQueries !== false ? active : []) member.stylesheets = member.stylesheets.filter((stylesheet) => {
|
|
3429
|
+
const proven = member.files.filter((file) => context.pathOwners.get(file.path) !== member.packageRoot && (importsStylesheet(file, stylesheet.cssPath) || (file.htmlStylesheets ?? []).some((linked) => linked.cssPath === stylesheet.cssPath))).every((consumer) => {
|
|
3430
|
+
const consumerRoot = context.pathOwners.get(consumer.path);
|
|
3431
|
+
const consumerMember = consumerRoot === void 0 ? void 0 : membersByRoot.get(consumerRoot);
|
|
3432
|
+
if (consumerMember === void 0) return false;
|
|
3433
|
+
if (consumerMember.sharedProofs === void 0) return true;
|
|
3434
|
+
return consumerMember.sharedProofs.provenConsumer(consumer);
|
|
3435
|
+
});
|
|
3436
|
+
if (!proven) member.warnings.push({
|
|
3437
|
+
code: "unproven-shared-entry-flow",
|
|
3438
|
+
file: stylesheet.cssPath,
|
|
3439
|
+
start: 0,
|
|
3440
|
+
end: 0,
|
|
3441
|
+
message: "A consumer flow could not be proven to load the shared Tailwind entry, so the stylesheet is retained."
|
|
3442
|
+
});
|
|
3443
|
+
return proven;
|
|
3444
|
+
});
|
|
3445
|
+
for (const member of active.filter((remaining) => remaining.stylesheets.length === 0)) if (member.warnings.length > 0) results.push({ plan: {
|
|
3446
|
+
files: [],
|
|
3447
|
+
deletedFiles: [],
|
|
3448
|
+
unlinkedFiles: [],
|
|
3449
|
+
candidates: [],
|
|
3450
|
+
rules: [],
|
|
3451
|
+
warnings: [
|
|
3452
|
+
...member.warnings,
|
|
3453
|
+
...member.preparedHtml.warnings,
|
|
3454
|
+
...member.preparedVue.warnings
|
|
3455
|
+
],
|
|
3456
|
+
convertedRules: 0,
|
|
3457
|
+
retainedRules: 0
|
|
3458
|
+
} });
|
|
3459
|
+
active = active.filter((remaining) => remaining.stylesheets.length > 0);
|
|
3460
|
+
if (active.length === 0) return results;
|
|
3461
|
+
const groupWritable = !await entryUnsafe(entry.path, workspaceRoot, targetable);
|
|
3462
|
+
const mediaWarnings = [];
|
|
3463
|
+
let extraction;
|
|
3464
|
+
const collections = [];
|
|
3465
|
+
for (const member of active) try {
|
|
3466
|
+
collections.push(collectMemberMediaConditions(member.stylesheets, entry));
|
|
3467
|
+
} catch (error) {
|
|
3468
|
+
if (!options.force || !isRecoverablePlanningError(error)) throw error;
|
|
3469
|
+
results.push({ failure: packageFailure(workspaceRoot, member.packageRoot, error) });
|
|
3470
|
+
active = active.filter((remaining) => remaining !== member);
|
|
3471
|
+
}
|
|
3472
|
+
if (active.length === 0) return results;
|
|
3473
|
+
let names = extraction?.names;
|
|
3474
|
+
let generated = extraction?.generated ?? [];
|
|
3475
|
+
if (options.extractMediaQueries !== false) {
|
|
3476
|
+
if (!groupWritable) {
|
|
3477
|
+
names = {};
|
|
3478
|
+
generated = [];
|
|
3479
|
+
if (collections.some((collection) => collection.components.length > 0)) mediaWarnings.push({
|
|
3480
|
+
code: "media-query-definition-fallback",
|
|
3481
|
+
file: entry.path,
|
|
3482
|
+
start: 0,
|
|
3483
|
+
end: 0,
|
|
3484
|
+
message: "The Tailwind entry could not be edited safely, so media behavior was preserved with arbitrary variants."
|
|
3485
|
+
});
|
|
3486
|
+
} else {
|
|
3487
|
+
try {
|
|
3488
|
+
extraction = planMediaExtraction({
|
|
3489
|
+
collections,
|
|
3490
|
+
tailwind: entry,
|
|
3491
|
+
scannedSources: context.sourceFiles.map((file) => file.source)
|
|
2176
3492
|
});
|
|
2177
|
-
|
|
3493
|
+
} catch (error) {
|
|
3494
|
+
return recoverGroup(error, !isRecoverablePlanningError(error));
|
|
2178
3495
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
plan = JSON.parse(planBatchMigration(JSON.stringify({
|
|
2183
|
-
...request,
|
|
2184
|
-
stylesheets: request.stylesheets.map((stylesheet, index) => ({
|
|
2185
|
-
...stylesheet,
|
|
2186
|
-
blockedRules: [...blockedByStylesheet.get(index)?.values() ?? []].map((entry) => entry.ruleId)
|
|
2187
|
-
}))
|
|
2188
|
-
})));
|
|
3496
|
+
names = extraction.names;
|
|
3497
|
+
generated = extraction.generated;
|
|
3498
|
+
}
|
|
2189
3499
|
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
3500
|
+
let plan = {
|
|
3501
|
+
files: [],
|
|
3502
|
+
deletedFiles: [],
|
|
3503
|
+
unlinkedFiles: [],
|
|
3504
|
+
candidates: [],
|
|
3505
|
+
rules: [],
|
|
3506
|
+
warnings: [],
|
|
3507
|
+
convertedRules: 0,
|
|
3508
|
+
retainedRules: 0
|
|
3509
|
+
};
|
|
3510
|
+
let augmented = entry.css;
|
|
3511
|
+
const blocked = /* @__PURE__ */ new Map();
|
|
3512
|
+
let candidateAliases;
|
|
3513
|
+
let canonicalized = false;
|
|
3514
|
+
let fontTokens = {};
|
|
3515
|
+
let fontFailures = /* @__PURE__ */ new Set();
|
|
3516
|
+
let composeEntry = () => entry.css;
|
|
3517
|
+
finalization: for (;;) {
|
|
3518
|
+
const baseIdentities = new Set(atRuleIdentities(`${entry.path}.group-base.css`, [entry.css, ...entry.graphSources.map((graphSource) => graphSource.source)].join("\n")));
|
|
3519
|
+
const sheets = active.flatMap((member) => member.stylesheets.map((stylesheet) => ({
|
|
3520
|
+
member,
|
|
3521
|
+
stylesheet
|
|
3522
|
+
})));
|
|
3523
|
+
const movable = ({ member, stylesheet }) => member.sharedProofs === void 0 && stylesheet.isModule === true && (stylesheet.syntax === void 0 || stylesheet.syntax === "css") && (stylesheet.vueBlocks === void 0 || stylesheet.vueBlocks.length === 0);
|
|
3524
|
+
for (const sheet of sheets) {
|
|
3525
|
+
if (movable(sheet)) continue;
|
|
3526
|
+
for (const identity of stylesheetAtRuleIdentities(sheet.stylesheet)) baseIdentities.add(identity);
|
|
3527
|
+
}
|
|
3528
|
+
const sheetIdentities = sheets.map((sheet) => movable(sheet) ? new Set(stylesheetAtRuleIdentities(sheet.stylesheet)) : /* @__PURE__ */ new Set());
|
|
3529
|
+
const opaqueVueRegistrations = sheets.some(({ stylesheet }) => stylesheet.vueAtRuleIdentities === null);
|
|
3530
|
+
const movesDisabled = new Set(opaqueVueRegistrations ? sheets.flatMap((sheet, index) => movable(sheet) ? [index] : []) : []);
|
|
3531
|
+
for (const [index, identities] of sheetIdentities.entries()) for (const identity of identities) {
|
|
3532
|
+
if (baseIdentities.has(identity)) movesDisabled.add(index);
|
|
3533
|
+
for (const [other, otherIdentities] of sheetIdentities.entries()) if (other !== index && otherIdentities.has(identity)) {
|
|
3534
|
+
movesDisabled.add(index);
|
|
3535
|
+
movesDisabled.add(other);
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
const filesByPath = /* @__PURE__ */ new Map();
|
|
3539
|
+
const contextsByPath = /* @__PURE__ */ new Map();
|
|
3540
|
+
for (const member of active) for (const file of member.files) {
|
|
3541
|
+
if (file.htmlStylesheets !== void 0 && file.htmlStylesheets.length > 0) {
|
|
3542
|
+
const contexts = contextsByPath.get(file.path) ?? [];
|
|
3543
|
+
contexts.push(...file.htmlStylesheets);
|
|
3544
|
+
contextsByPath.set(file.path, contexts);
|
|
3545
|
+
}
|
|
3546
|
+
if (!filesByPath.has(file.path) || context.pathOwners.get(file.path) === member.packageRoot) filesByPath.set(file.path, file);
|
|
3547
|
+
}
|
|
3548
|
+
const files = [...filesByPath.values()].map((file) => {
|
|
3549
|
+
const contexts = contextsByPath.get(file.path);
|
|
3550
|
+
if (contexts === void 0) return file;
|
|
3551
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3552
|
+
const merged = contexts.filter((linked) => {
|
|
3553
|
+
const key = `${linked.cssPath}\0${linked.variants.join(",")}\0${linked.direct}\0${linked.analyzable}`;
|
|
3554
|
+
if (seen.has(key)) return false;
|
|
3555
|
+
seen.add(key);
|
|
3556
|
+
return true;
|
|
3557
|
+
});
|
|
3558
|
+
return {
|
|
3559
|
+
...file,
|
|
3560
|
+
htmlStylesheets: merged
|
|
3561
|
+
};
|
|
3562
|
+
}).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
3563
|
+
planning: for (;;) {
|
|
3564
|
+
const request = {
|
|
3565
|
+
stylesheets: sheets.map(({ member, stylesheet }, index) => {
|
|
3566
|
+
const rules = blocked.get(index);
|
|
3567
|
+
const movesOff = member.sharedProofs !== void 0 || movesDisabled.has(index);
|
|
3568
|
+
return {
|
|
3569
|
+
...stylesheet,
|
|
3570
|
+
...rules ? { blockedRules: [...rules.values()].map((blockedRule) => blockedRule.ruleId) } : {},
|
|
3571
|
+
...movesOff ? { globalAtRuleMoves: false } : {}
|
|
3572
|
+
};
|
|
3573
|
+
}),
|
|
3574
|
+
tailwindPath: entry.path,
|
|
3575
|
+
tailwindSource: entry.css,
|
|
3576
|
+
utilityPrefix: entry.designSystem.theme.prefix,
|
|
3577
|
+
themeTokens: entry.themeTokens,
|
|
3578
|
+
...names ? { mediaNames: names } : {},
|
|
3579
|
+
...candidateAliases ? { candidateAliases } : {},
|
|
3580
|
+
...groupWritable ? {} : { entryWritable: false },
|
|
3581
|
+
files
|
|
3582
|
+
};
|
|
3583
|
+
try {
|
|
3584
|
+
plan = JSON.parse(planBatchMigration(JSON.stringify(request)));
|
|
3585
|
+
} catch (error) {
|
|
3586
|
+
return recoverGroup(error, !isRecoverablePlanningError(error));
|
|
3587
|
+
}
|
|
3588
|
+
let composed = entry.css;
|
|
3589
|
+
const plannedEntry = plan.files.findIndex((file) => file.path === entry.path);
|
|
3590
|
+
if (plannedEntry >= 0) {
|
|
3591
|
+
if (!groupWritable) throw new Error(`The planner claimed an unwritable Tailwind entry: ${entry.path}`);
|
|
3592
|
+
composed = plan.files[plannedEntry].source;
|
|
3593
|
+
plan.files.splice(plannedEntry, 1);
|
|
3594
|
+
if (!composed.startsWith(entry.css)) throw new Error(`The planner rewrote the Tailwind entry instead of appending: ${entry.path}`);
|
|
3595
|
+
}
|
|
3596
|
+
const currentExtraction = names !== void 0 ? {
|
|
3597
|
+
names,
|
|
3598
|
+
generated
|
|
3599
|
+
} : void 0;
|
|
3600
|
+
const used = currentExtraction ? usedGeneratedDefinitions(currentExtraction, plan) : [];
|
|
3601
|
+
composeEntry = (tokens) => {
|
|
3602
|
+
const withFonts = appendFontTheme(composed, tokens);
|
|
3603
|
+
return currentExtraction ? appendMediaDefinitions(withFonts, used) : withFonts;
|
|
3604
|
+
};
|
|
3605
|
+
augmented = composeEntry(fontTokens);
|
|
3606
|
+
let system;
|
|
3607
|
+
try {
|
|
3608
|
+
system = augmented === entry.css ? entry.designSystem : await entry.loadWith(augmented);
|
|
3609
|
+
} catch (error) {
|
|
3610
|
+
if (isIntegrityError(error)) throw error;
|
|
3611
|
+
if (names !== void 0 && generated.length > 0) {
|
|
3612
|
+
const generatedKeys = new Set(generated.map((definition) => definition.key));
|
|
3613
|
+
names = Object.fromEntries(Object.entries(names).filter(([key]) => !generatedKeys.has(key)));
|
|
3614
|
+
generated = [];
|
|
3615
|
+
continue planning;
|
|
3616
|
+
}
|
|
3617
|
+
return recoverGroup(error);
|
|
3618
|
+
}
|
|
3619
|
+
if (currentExtraction && names !== void 0) {
|
|
3620
|
+
const prefix = entry.designSystem.theme.prefix;
|
|
3621
|
+
const rejected = new Set(used.filter(({ name }) => system.candidatesToCss([probeCandidate(prefix, name)])[0] === null).map(({ key }) => key));
|
|
3622
|
+
if (rejected.size > 0) {
|
|
3623
|
+
names = Object.fromEntries(Object.entries(names).filter(([key]) => !rejected.has(key)));
|
|
3624
|
+
generated = generated.filter(({ key }) => !rejected.has(key));
|
|
3625
|
+
continue planning;
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
const failing = invalidCandidates(system, plan.candidates);
|
|
3629
|
+
if (failing.length > 0) {
|
|
3630
|
+
if (!accumulateBlockedRules(blocked, plan, failing)) throw new Error(`Tailwind did not generate CSS for candidate: ${failing[0]}`);
|
|
3631
|
+
continue planning;
|
|
3632
|
+
}
|
|
3633
|
+
if (!canonicalized) {
|
|
3634
|
+
canonicalized = true;
|
|
3635
|
+
const aliases = acceptedCandidateAliases(system, plan.candidateProbes ?? [], reservations);
|
|
3636
|
+
const fonts = await registerFontTokens({
|
|
3637
|
+
system,
|
|
3638
|
+
entryCss: augmented,
|
|
3639
|
+
loadWith: entry.loadWith,
|
|
3640
|
+
themeTokens: entry.themeTokens,
|
|
3641
|
+
probes: plan.fontFamilyProbes ?? [],
|
|
3642
|
+
corpus: [.../* @__PURE__ */ new Set([...plan.candidates, ...plan.candidateProbes ?? []])],
|
|
3643
|
+
reservations,
|
|
3644
|
+
existingAliases: aliases,
|
|
3645
|
+
generate: groupWritable,
|
|
3646
|
+
mentionedSegments,
|
|
3647
|
+
referenceTokens: entry.referenceTokens,
|
|
3648
|
+
globalAllocations: context.fontAllocations
|
|
3649
|
+
});
|
|
3650
|
+
Object.assign(aliases, fonts.aliases);
|
|
3651
|
+
fontTokens = fonts.tokens;
|
|
3652
|
+
fontFailures = fonts.failures;
|
|
3653
|
+
for (const [name, value] of Object.entries(fonts.tokens)) context.fontAllocations.set(name, value);
|
|
3654
|
+
if (Object.keys(aliases).length > 0) {
|
|
3655
|
+
candidateAliases = aliases;
|
|
3656
|
+
continue planning;
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
break;
|
|
3660
|
+
}
|
|
3661
|
+
const ruleStatus = new Map(plan.rules.map((rule) => [`${rule.stylesheet}:${rule.ruleId.start}-${rule.ruleId.end}`, rule.status]));
|
|
3662
|
+
const warned = /* @__PURE__ */ new Set();
|
|
3663
|
+
for (const probe of plan.fontFamilyProbes ?? []) {
|
|
3664
|
+
if (probe.firstFamily.kind !== "name") continue;
|
|
3665
|
+
if (candidateAliases?.[probe.candidate] !== void 0) continue;
|
|
3666
|
+
const ruleKey = `${probe.stylesheet}:${probe.ruleId.start}-${probe.ruleId.end}`;
|
|
3667
|
+
if (ruleStatus.get(ruleKey) !== "retained" || warned.has(ruleKey)) continue;
|
|
3668
|
+
const failed = fontFailures.has(probe.candidate);
|
|
3669
|
+
if (groupWritable && !failed) continue;
|
|
3670
|
+
warned.add(ruleKey);
|
|
2194
3671
|
plan.warnings.push({
|
|
2195
|
-
code: "
|
|
2196
|
-
file: cssPath,
|
|
2197
|
-
start: authoredSpan.start,
|
|
2198
|
-
end: authoredSpan.end,
|
|
2199
|
-
message:
|
|
3672
|
+
code: groupWritable ? "font-theme-registration-failed" : "font-theme-registration-required",
|
|
3673
|
+
file: sheets[probe.stylesheet].stylesheet.cssPath,
|
|
3674
|
+
start: probe.authoredSpan.start,
|
|
3675
|
+
end: probe.authoredSpan.end,
|
|
3676
|
+
message: groupWritable ? "A safe Tailwind font theme token could not be registered, so the rule is retained." : "The font family requires a Tailwind theme token, but the Tailwind entry cannot be edited, so the rule is retained."
|
|
3677
|
+
});
|
|
3678
|
+
}
|
|
3679
|
+
if (Object.keys(fontTokens).length > 0) {
|
|
3680
|
+
const applied = new Set(plan.candidates.map((candidate) => utilitySegment(candidate).replace(/!$/, "")));
|
|
3681
|
+
const pruned = Object.fromEntries(Object.entries(fontTokens).filter(([name]) => applied.has(`font-${name}`)));
|
|
3682
|
+
if (Object.keys(pruned).length !== Object.keys(fontTokens).length) {
|
|
3683
|
+
fontTokens = pruned;
|
|
3684
|
+
augmented = composeEntry(pruned);
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
for (const [index, rules] of blocked) {
|
|
3688
|
+
const cssPath = sheets[index].stylesheet.cssPath;
|
|
3689
|
+
for (const { authoredSpan, candidates } of rules.values()) {
|
|
3690
|
+
const failed = [...candidates].sort().map((candidate) => `\`${candidate}\``).join(", ");
|
|
3691
|
+
plan.warnings.push({
|
|
3692
|
+
code: "candidate-compilation-failure",
|
|
3693
|
+
file: cssPath,
|
|
3694
|
+
start: authoredSpan.start,
|
|
3695
|
+
end: authoredSpan.end,
|
|
3696
|
+
message: `Tailwind did not generate CSS for ${failed}, so the rule is retained.`
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
plan.warnings.push(...mediaWarnings);
|
|
3701
|
+
for (const member of active) plan.warnings.push(...member.warnings);
|
|
3702
|
+
removeGroupHtmlLinks(plan, active);
|
|
3703
|
+
const failure = await finishGroupPlan(context, active, plan);
|
|
3704
|
+
if (failure === void 0) break;
|
|
3705
|
+
if (!options.force || failure.fatal) throw failure.error;
|
|
3706
|
+
const failed = failure.member ?? active[0];
|
|
3707
|
+
results.push({ failure: packageFailure(workspaceRoot, failed.packageRoot, failure.error) });
|
|
3708
|
+
active = active.filter((member) => member !== failed);
|
|
3709
|
+
if (active.length === 0) return results;
|
|
3710
|
+
blocked.clear();
|
|
3711
|
+
plan.warnings = [];
|
|
3712
|
+
continue finalization;
|
|
3713
|
+
}
|
|
3714
|
+
const seenWarnings = /* @__PURE__ */ new Set();
|
|
3715
|
+
plan.warnings = plan.warnings.filter((warning) => {
|
|
3716
|
+
const key = `${warning.code}\0${warning.file}\0${warning.start}\0${warning.end}\0${warning.message}`;
|
|
3717
|
+
if (seenWarnings.has(key)) return false;
|
|
3718
|
+
seenWarnings.add(key);
|
|
3719
|
+
return true;
|
|
3720
|
+
});
|
|
3721
|
+
results.push({ plan });
|
|
3722
|
+
if (augmented !== entry.css) results.push({ plan: {
|
|
3723
|
+
files: [{
|
|
3724
|
+
path: entry.path,
|
|
3725
|
+
source: augmented
|
|
3726
|
+
}],
|
|
3727
|
+
deletedFiles: [],
|
|
3728
|
+
unlinkedFiles: [],
|
|
3729
|
+
candidates: [],
|
|
3730
|
+
rules: [],
|
|
3731
|
+
warnings: [],
|
|
3732
|
+
convertedRules: 0,
|
|
3733
|
+
retainedRules: 0
|
|
3734
|
+
} });
|
|
3735
|
+
return results;
|
|
3736
|
+
}
|
|
3737
|
+
function accumulateBlockedRules(blocked, plan, failing) {
|
|
3738
|
+
let progressed = false;
|
|
3739
|
+
for (const rule of plan.rules) {
|
|
3740
|
+
const failed = rule.candidates.filter((candidate) => failing.includes(candidate));
|
|
3741
|
+
if (failed.length === 0) continue;
|
|
3742
|
+
let rules = blocked.get(rule.stylesheet);
|
|
3743
|
+
if (!rules) blocked.set(rule.stylesheet, rules = /* @__PURE__ */ new Map());
|
|
3744
|
+
const key = `${rule.ruleId.start}-${rule.ruleId.end}`;
|
|
3745
|
+
let blockedRule = rules.get(key);
|
|
3746
|
+
if (!blockedRule) {
|
|
3747
|
+
rules.set(key, blockedRule = {
|
|
3748
|
+
ruleId: rule.ruleId,
|
|
3749
|
+
authoredSpan: rule.authoredSpan,
|
|
3750
|
+
candidates: /* @__PURE__ */ new Set()
|
|
2200
3751
|
});
|
|
3752
|
+
progressed = true;
|
|
3753
|
+
}
|
|
3754
|
+
for (const candidate of failed) blockedRule.candidates.add(candidate);
|
|
3755
|
+
}
|
|
3756
|
+
return progressed;
|
|
3757
|
+
}
|
|
3758
|
+
async function entryUnsafe(entryPath, workspaceRoot, targetable) {
|
|
3759
|
+
if (!targetable.has(entryPath) || !isProjectInput(workspaceRoot, entryPath)) return true;
|
|
3760
|
+
return (await lstat(entryPath)).isSymbolicLink();
|
|
3761
|
+
}
|
|
3762
|
+
function atRuleIdentities(key, css) {
|
|
3763
|
+
return css.trim() === "" ? [] : stylesheetAnalysis(key, css).globalAtRuleIdentities;
|
|
3764
|
+
}
|
|
3765
|
+
function stylesheetAtRuleIdentities(stylesheet) {
|
|
3766
|
+
if (stylesheet.vueAtRuleIdentities) return stylesheet.vueAtRuleIdentities;
|
|
3767
|
+
if (stylesheet.vueBlocks) return stylesheet.vueBlocks.flatMap((block, index) => atRuleIdentities(`${stylesheet.cssPath}.${index}.at-rules.${block.analysisSource ? "css" : block.syntax}`, block.analysisSource ?? block.content));
|
|
3768
|
+
return stylesheet.analysisSource === void 0 ? atRuleIdentities(`${stylesheet.cssPath}.at-rules.${stylesheet.syntax ?? "css"}`, stylesheet.cssSource) : atRuleIdentities(`${stylesheet.cssPath}.at-rules.css`, stylesheet.analysisSource);
|
|
3769
|
+
}
|
|
3770
|
+
async function finishGroupPlan(context, members, plan) {
|
|
3771
|
+
const { snapshots } = context;
|
|
3772
|
+
const membersByRoot = new Map(members.map((member) => [member.packageRoot, member]));
|
|
3773
|
+
const ownerOf = (path) => {
|
|
3774
|
+
const root = context.pathOwners.get(path);
|
|
3775
|
+
return root === void 0 ? void 0 : membersByRoot.get(root);
|
|
3776
|
+
};
|
|
3777
|
+
for (const member of members) {
|
|
3778
|
+
plan.warnings.push(...member.preparedHtml.warnings);
|
|
3779
|
+
plan.warnings.push(...member.preparedVue.warnings);
|
|
3780
|
+
}
|
|
3781
|
+
try {
|
|
3782
|
+
for (const file of plan.files.filter((file) => extname(file.path) === ".html")) try {
|
|
3783
|
+
parseHtmlSource(file.path, file.source);
|
|
3784
|
+
} catch (error) {
|
|
3785
|
+
return {
|
|
3786
|
+
error,
|
|
3787
|
+
member: ownerOf(file.path),
|
|
3788
|
+
fatal: true
|
|
3789
|
+
};
|
|
3790
|
+
}
|
|
3791
|
+
for (const file of plan.files.filter((file) => extname(file.path) === ".vue")) {
|
|
3792
|
+
const owner = ownerOf(file.path);
|
|
3793
|
+
const preparedVue = owner?.preparedVue ?? members[0].preparedVue;
|
|
3794
|
+
const vueCompiler = preparedVue.compiler ?? members[0].preparedVue.compiler;
|
|
3795
|
+
if (!vueCompiler) throw new Error(`No Vue compiler is available to verify ${file.path}`);
|
|
3796
|
+
try {
|
|
3797
|
+
const includeUnscoped = preparedVue.unscopedPaths.has(file.path);
|
|
3798
|
+
const blocks = verifyVueSource(vueCompiler, file.path, file.source, includeUnscoped);
|
|
3799
|
+
const untouched = /* @__PURE__ */ new Map();
|
|
3800
|
+
for (const block of verifyVueSource(vueCompiler, file.path, snapshots.get(file.path) ?? "", includeUnscoped)) {
|
|
3801
|
+
const key = `${block.syntax}\0${block.content}`;
|
|
3802
|
+
untouched.set(key, (untouched.get(key) ?? 0) + 1);
|
|
3803
|
+
}
|
|
3804
|
+
for (const [index, block] of blocks.entries()) {
|
|
3805
|
+
const key = `${block.syntax}\0${block.content}`;
|
|
3806
|
+
const remaining = untouched.get(key) ?? 0;
|
|
3807
|
+
if (remaining > 0) {
|
|
3808
|
+
untouched.set(key, remaining - 1);
|
|
3809
|
+
continue;
|
|
3810
|
+
}
|
|
3811
|
+
if (block.syntax === "css") validateCss(block.content);
|
|
3812
|
+
else {
|
|
3813
|
+
const compilerOwner = owner ?? members[0];
|
|
3814
|
+
validateCss((await compileStyleEntry(compilerOwner.styleCompilers, compilerOwner.packageRoot, `${file.path}.${index}.${block.syntax}`, block.content, { virtualEntry: true })).css);
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
} catch (error) {
|
|
3818
|
+
return {
|
|
3819
|
+
error,
|
|
3820
|
+
member: owner,
|
|
3821
|
+
fatal: !isMissingStyleCompilerError(error)
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
for (const member of members) for (const stylesheet of member.stylesheets.filter((stylesheet) => isPreprocessorPath(stylesheet.cssPath))) {
|
|
3826
|
+
const changed = plan.files.find((file) => file.path === stylesheet.cssPath);
|
|
3827
|
+
if (!changed && !plan.deletedFiles.includes(stylesheet.cssPath)) continue;
|
|
3828
|
+
const source = changed?.source ?? "";
|
|
3829
|
+
try {
|
|
3830
|
+
validateCss((await compileStyleEntry(member.styleCompilers, member.packageRoot, stylesheet.cssPath, source)).css);
|
|
3831
|
+
} catch (error) {
|
|
3832
|
+
return {
|
|
3833
|
+
error,
|
|
3834
|
+
member,
|
|
3835
|
+
fatal: !isMissingStyleCompilerError(error)
|
|
3836
|
+
};
|
|
3837
|
+
}
|
|
2201
3838
|
}
|
|
3839
|
+
} catch (error) {
|
|
3840
|
+
return {
|
|
3841
|
+
error,
|
|
3842
|
+
fatal: !isMissingStyleCompilerError(error)
|
|
3843
|
+
};
|
|
2202
3844
|
}
|
|
2203
|
-
return plan;
|
|
2204
3845
|
}
|
|
2205
|
-
function
|
|
3846
|
+
function removeGroupHtmlLinks(plan, members) {
|
|
2206
3847
|
const unlinked = new Set(plan.unlinkedFiles);
|
|
2207
3848
|
const linksByFile = /* @__PURE__ */ new Map();
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2212
|
-
|
|
3849
|
+
const originals = /* @__PURE__ */ new Map();
|
|
3850
|
+
for (const member of members) {
|
|
3851
|
+
for (const link of member.preparedHtml.removableLinks) {
|
|
3852
|
+
if (!unlinked.has(link.cssPath)) continue;
|
|
3853
|
+
const links = linksByFile.get(link.filePath) ?? /* @__PURE__ */ new Set();
|
|
3854
|
+
links.add(`${link.href}\0${link.media}`);
|
|
3855
|
+
linksByFile.set(link.filePath, links);
|
|
3856
|
+
}
|
|
3857
|
+
for (const file of member.preparedHtml.files) if (!originals.has(file.path)) originals.set(file.path, file.source);
|
|
2213
3858
|
}
|
|
2214
3859
|
for (const [filePath, removable] of linksByFile) {
|
|
2215
3860
|
const planned = plan.files.find((file) => file.path === filePath);
|
|
2216
|
-
const
|
|
2217
|
-
if (
|
|
2218
|
-
const source = planned?.source ?? original.source;
|
|
3861
|
+
const source = planned?.source ?? originals.get(filePath);
|
|
3862
|
+
if (source === void 0) continue;
|
|
2219
3863
|
const links = parseHtmlSource(filePath, source).links.filter((link) => removable.has(`${link.href}\0${link.media}`)).sort((left, right) => right.tagStart - left.tagStart);
|
|
2220
3864
|
let bytes = Buffer.from(source);
|
|
2221
3865
|
for (const link of links) bytes = Buffer.concat([bytes.subarray(0, link.tagStart), bytes.subarray(link.tagEnd)]);
|