@animus-ui/extract 0.1.20 → 0.1.21
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/crates/extract-v2/animus-extract-v2.darwin-arm64.node +0 -0
- package/crates/extract-v2/animus-extract-v2.linux-arm64-gnu.node +0 -0
- package/crates/extract-v2/animus-extract-v2.linux-x64-gnu.node +0 -0
- package/crates/extract-v2/index.d.ts +7 -0
- package/dist/index.cjs +3 -1
- package/dist/index.mjs +2 -2
- package/dist/pipeline/assemble-stylesheet.d.ts +5 -0
- package/dist/pipeline/assemble-stylesheet.d.ts.map +1 -1
- package/dist/pipeline/core-options.d.ts +2 -0
- package/dist/pipeline/core-options.d.ts.map +1 -1
- package/dist/pipeline/discover-packages.d.ts +15 -1
- package/dist/pipeline/discover-packages.d.ts.map +1 -1
- package/dist/pipeline/index.d.ts +3 -3
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/manifest-diagnostics.d.ts +18 -2
- package/dist/pipeline/manifest-diagnostics.d.ts.map +1 -1
- package/dist/pipeline/manifest-schema.d.ts +6 -0
- package/dist/pipeline/manifest-schema.d.ts.map +1 -1
- package/dist/pipeline/post-process-css.d.ts.map +1 -1
- package/dist/pipeline/property-diagnostics.d.ts +5 -4
- package/dist/pipeline/property-diagnostics.d.ts.map +1 -1
- package/dist/pipeline/property-names.d.ts +4 -14
- package/dist/pipeline/property-names.d.ts.map +1 -1
- package/dist/pipeline/property-registrations.d.ts +5 -4
- package/dist/pipeline/property-registrations.d.ts.map +1 -1
- package/dist/pipeline/structural-self-check.d.ts +3 -0
- package/dist/pipeline/structural-self-check.d.ts.map +1 -1
- package/dist/pipeline/tsconfig-paths.d.ts +14 -0
- package/dist/pipeline/tsconfig-paths.d.ts.map +1 -1
- package/dist/{pipeline-6wmD00rr.mjs → pipeline-BXY7Pf_D.mjs} +846 -528
- package/dist/{pipeline-A54wEQEg.cjs → pipeline-D7B1z0-1.cjs} +875 -527
- package/dist/session/extraction-session.d.ts +5 -0
- package/dist/session/extraction-session.d.ts.map +1 -1
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/resolve-packages.d.ts.map +1 -1
- package/dist/session/singleton.d.ts +5 -1
- package/dist/session/singleton.d.ts.map +1 -1
- package/dist/session.cjs +90 -3
- package/dist/session.mjs +91 -5
- package/package.json +4 -4
|
@@ -32,6 +32,11 @@ browserslist = __toESM(browserslist);
|
|
|
32
32
|
let lightningcss = require("lightningcss");
|
|
33
33
|
let _animus_ui_properties = require("@animus-ui/properties");
|
|
34
34
|
//#region pipeline/assemble-stylesheet.ts
|
|
35
|
+
/**
|
|
36
|
+
* The cascade layer order, outermost first: its one definition. The engine
|
|
37
|
+
* reads it from `crates/extract-v2/src/layer_order.json`, which
|
|
38
|
+
* `scripts/layer-order.ts --write` writes from here.
|
|
39
|
+
*/
|
|
35
40
|
const ANIMUS_LAYERS = [
|
|
36
41
|
"anm-global",
|
|
37
42
|
"anm-base",
|
|
@@ -179,6 +184,8 @@ function collectSelectorAliasDiagnostics(selectorAliasesJson) {
|
|
|
179
184
|
return diagnostics;
|
|
180
185
|
}
|
|
181
186
|
const UNREADABLE_SOURCE_FILE = "animus.ingestion.unreadable-source-file";
|
|
187
|
+
/** A kit the system extends resolved, yet discovery found none of its files. */
|
|
188
|
+
const NO_KIT_FILES = "animus.discovery.no-kit-files";
|
|
182
189
|
/** What a skipped source file costs: the analysis never sees what it
|
|
183
190
|
* renders. */
|
|
184
191
|
const SKIPPED_SOURCE_COST = "skipped: its renders are not seen, so nothing is pruned and every component, variant option and state is kept until it is analyzed";
|
|
@@ -202,6 +209,18 @@ function unreadableSourceDiagnostic(relPath, error) {
|
|
|
202
209
|
severity: severityFor(UNREADABLE_SOURCE_FILE)
|
|
203
210
|
};
|
|
204
211
|
}
|
|
212
|
+
/** One warning per kit the system extends that resolved, yet yielded no
|
|
213
|
+
* files: none of its components are extracted. */
|
|
214
|
+
function noKitFilesDiagnostics(outcomes) {
|
|
215
|
+
return outcomes.filter((record) => record.outcome === "empty").map((record) => ({
|
|
216
|
+
file: record.specifier,
|
|
217
|
+
component: "kit",
|
|
218
|
+
kind: "warn",
|
|
219
|
+
message: "resolved, but discovery found none of its files, so none of its components are extracted — check that the package ships its src/ directory or a readable entry",
|
|
220
|
+
code: NO_KIT_FILES,
|
|
221
|
+
severity: severityFor(NO_KIT_FILES)
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
205
224
|
/** The collision entry code minted by the system package's merge. */
|
|
206
225
|
const VOCABULARY_COLLISION = "animus.vocabulary.collision";
|
|
207
226
|
/** A theme `@property` registration browsers would ignore; it is not emitted. */
|
|
@@ -221,17 +240,28 @@ const PROPERTY_UNREGISTERED_ANIMATION = "animus.property.unregistered-animation"
|
|
|
221
240
|
* it invalid at computed-value time. */
|
|
222
241
|
const PROPERTY_SELF_REFERENCE = "animus.property.self-reference";
|
|
223
242
|
/** A custom property that reads itself only inside another `var()`'s
|
|
224
|
-
* fallback
|
|
243
|
+
* fallback: cyclic wherever that fallback is used. */
|
|
225
244
|
const PROPERTY_FALLBACK_SELF_REFERENCE = "animus.property.fallback-self-reference";
|
|
245
|
+
/** Custom properties that `currentVar` writes in one rule, each reading
|
|
246
|
+
* another directly, so every property in the cycle is invalid at
|
|
247
|
+
* computed-value time. */
|
|
248
|
+
const PROPERTY_CURRENT_VAR_CYCLE = "animus.property.current-var-cycle";
|
|
226
249
|
/** A prefix renamed contextual variables without `prefixContextualVars`, so
|
|
227
250
|
* their declared names no longer resolve. */
|
|
228
251
|
const PREFIX_CONTEXTUAL_VARS_UNPREFIXED = "animus.prefix.contextual-vars-unprefixed";
|
|
229
252
|
/** Under `prefixContextualVars`, a final name that collides with a runtime
|
|
230
253
|
* transport variable or a theme variable of the same spelling. */
|
|
231
254
|
const PREFIX_NAME_CONFLICT = "animus.prefix.name-conflict";
|
|
255
|
+
/** A contextual variable declared on a scale that also holds a token of its
|
|
256
|
+
* name: scale reads resolve to the token. */
|
|
257
|
+
const PROPERTY_LEGACY_TOKEN_COLLISION = "animus.property.legacy-token-collision";
|
|
258
|
+
/** A theme property declared on a scale that also holds a token of its
|
|
259
|
+
* name. */
|
|
260
|
+
const PROPERTY_TOKEN_COLLISION = "animus.property.token-collision";
|
|
232
261
|
const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
|
|
233
262
|
[SELECTOR_UNSUPPORTED_SUBJECT, "error"],
|
|
234
263
|
[UNREADABLE_SOURCE_FILE, "error"],
|
|
264
|
+
[NO_KIT_FILES, "warn"],
|
|
235
265
|
[VOCABULARY_COLLISION, "warn"],
|
|
236
266
|
[VOCABULARY_LEGACY_VERB, "warn"],
|
|
237
267
|
[INVALID_PROPERTY_REGISTRATION, "error"],
|
|
@@ -240,8 +270,11 @@ const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
|
|
|
240
270
|
[PROPERTY_UNREGISTERED_ANIMATION, "info"],
|
|
241
271
|
[PROPERTY_SELF_REFERENCE, "warn"],
|
|
242
272
|
[PROPERTY_FALLBACK_SELF_REFERENCE, "warn"],
|
|
273
|
+
[PROPERTY_CURRENT_VAR_CYCLE, "warn"],
|
|
243
274
|
[PREFIX_CONTEXTUAL_VARS_UNPREFIXED, "warn"],
|
|
244
|
-
[PREFIX_NAME_CONFLICT, "error"]
|
|
275
|
+
[PREFIX_NAME_CONFLICT, "error"],
|
|
276
|
+
[PROPERTY_LEGACY_TOKEN_COLLISION, "warn"],
|
|
277
|
+
[PROPERTY_TOKEN_COLLISION, "error"]
|
|
245
278
|
]);
|
|
246
279
|
/** An unlisted code is `warn`: a witness kind from a newer system package
|
|
247
280
|
* must not fail the strict build of a host that predates its writer. */
|
|
@@ -314,10 +347,40 @@ function systemLoadDiagnostics(system) {
|
|
|
314
347
|
...vocabularyWitnessDiagnostics(system.vocabularyWitnessesJson),
|
|
315
348
|
...registrations,
|
|
316
349
|
...unprefixed,
|
|
317
|
-
...conflicts
|
|
350
|
+
...conflicts,
|
|
351
|
+
...tokenCollisionDiagnostics(system)
|
|
318
352
|
];
|
|
319
353
|
}
|
|
320
354
|
/**
|
|
355
|
+
* Each declared property whose name is also a token on a scale it is
|
|
356
|
+
* declared on. A scale read of that key, a prop value or a `{scale.key}`
|
|
357
|
+
* reference, resolves to the token, so the property is unreachable through
|
|
358
|
+
* the scale. A legacy declaration warns and the token stays the winner; any
|
|
359
|
+
* other is an error in every mode.
|
|
360
|
+
*/
|
|
361
|
+
function tokenCollisionDiagnostics(system) {
|
|
362
|
+
if (!system.propertyRecordsJson) return [];
|
|
363
|
+
const declared = parseInternalWire(system.propertyRecordsJson, "propertyRecordsJson (the theme's properties)").flatMap((record) => record.scales.map((scale) => ({
|
|
364
|
+
record,
|
|
365
|
+
scale,
|
|
366
|
+
path: `${scale}.${record.name}`
|
|
367
|
+
})));
|
|
368
|
+
if (declared.length === 0) return [];
|
|
369
|
+
const tokens = parseInternalWire(system.scalesJson, "scalesJson (the theme's scale tokens)");
|
|
370
|
+
return declared.filter(({ path }) => Object.hasOwn(tokens, path)).map(({ record, scale, path }) => {
|
|
371
|
+
const code = record.legacy ? PROPERTY_LEGACY_TOKEN_COLLISION : PROPERTY_TOKEN_COLLISION;
|
|
372
|
+
const declaration = record.legacy ? `the contextual variable ${record.name}, which declareContextualVars declares on ${scale},` : `the theme property ${record.name}, declared on ${scale},`;
|
|
373
|
+
return {
|
|
374
|
+
file: "system",
|
|
375
|
+
component: path,
|
|
376
|
+
kind: record.legacy ? "warn" : "error",
|
|
377
|
+
message: `the ${scale} token ${record.name} (${tokens[path]}) and ${declaration} share this key. A scale read of it, such as {${path}} or a prop on the ${scale} scale set to '${record.name}', resolves to the token, so the ${record.legacy ? "contextual variable" : "property"} is unreachable through ${scale}. Rename one of them (${code})`,
|
|
378
|
+
code,
|
|
379
|
+
severity: severityFor(code)
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
321
384
|
* The single strict-escalation policy point for both extraction plugins:
|
|
322
385
|
* error-severity diagnostics throw together under `strict`, else warn.
|
|
323
386
|
*/
|
|
@@ -1809,6 +1872,188 @@ function createSourceIdentity(rootDir) {
|
|
|
1809
1872
|
};
|
|
1810
1873
|
}
|
|
1811
1874
|
//#endregion
|
|
1875
|
+
//#region pipeline/tsconfig-paths.ts
|
|
1876
|
+
/**
|
|
1877
|
+
* tsconfig `paths` → alias pairs, for drivers with no live bundler config.
|
|
1878
|
+
* The nearest config declaring `paths` wins wholesale — TS never merges.
|
|
1879
|
+
*/
|
|
1880
|
+
function stripJsonc(text) {
|
|
1881
|
+
let out = "";
|
|
1882
|
+
let inString = false;
|
|
1883
|
+
let inLine = false;
|
|
1884
|
+
let inBlock = false;
|
|
1885
|
+
for (let i = 0; i < text.length; i++) {
|
|
1886
|
+
const ch = text[i];
|
|
1887
|
+
const next = text[i + 1];
|
|
1888
|
+
if (inLine) {
|
|
1889
|
+
if (ch === "\n") {
|
|
1890
|
+
inLine = false;
|
|
1891
|
+
out += ch;
|
|
1892
|
+
}
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
if (inBlock) {
|
|
1896
|
+
if (ch === "*" && next === "/") {
|
|
1897
|
+
inBlock = false;
|
|
1898
|
+
i++;
|
|
1899
|
+
}
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
if (inString) {
|
|
1903
|
+
out += ch;
|
|
1904
|
+
if (ch === "\\") {
|
|
1905
|
+
out += next ?? "";
|
|
1906
|
+
i++;
|
|
1907
|
+
} else if (ch === "\"") inString = false;
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
if (ch === "\"") {
|
|
1911
|
+
inString = true;
|
|
1912
|
+
out += ch;
|
|
1913
|
+
continue;
|
|
1914
|
+
}
|
|
1915
|
+
if (ch === "/" && next === "/") {
|
|
1916
|
+
inLine = true;
|
|
1917
|
+
i++;
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
if (ch === "/" && next === "*") {
|
|
1921
|
+
inBlock = true;
|
|
1922
|
+
i++;
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
out += ch;
|
|
1926
|
+
}
|
|
1927
|
+
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
1928
|
+
}
|
|
1929
|
+
/** `Object(value) === value` holds for exactly the blocks and lists
|
|
1930
|
+
* `JSON.parse` produces; the `[object Object]` tag separates the two. */
|
|
1931
|
+
function isJsonBlock(value) {
|
|
1932
|
+
return Object(value) === value && Object.prototype.toString.call(value) === "[object Object]";
|
|
1933
|
+
}
|
|
1934
|
+
/** A block or a list. A declared `paths` OWNS the setting and blocks its
|
|
1935
|
+
* parents' even when it contributes nothing usable. */
|
|
1936
|
+
function isJsonKeyed(value) {
|
|
1937
|
+
return Object(value) === value;
|
|
1938
|
+
}
|
|
1939
|
+
/** A JSON string, excluding the boxed `String` object, which no path join
|
|
1940
|
+
* accepts. */
|
|
1941
|
+
function isJsonString(value) {
|
|
1942
|
+
return Object(value) !== value && Object.prototype.toString.call(value) === "[object String]";
|
|
1943
|
+
}
|
|
1944
|
+
function decodePaths(value) {
|
|
1945
|
+
if (!isJsonKeyed(value)) return null;
|
|
1946
|
+
const decoded = /* @__PURE__ */ new Map();
|
|
1947
|
+
for (const [pattern, targets] of Object.entries(value)) {
|
|
1948
|
+
const [first] = Array.isArray(targets) ? targets : [];
|
|
1949
|
+
if (isJsonString(first)) decoded.set(pattern, first);
|
|
1950
|
+
}
|
|
1951
|
+
return decoded;
|
|
1952
|
+
}
|
|
1953
|
+
/** TypeScript accepts one specifier or an array; a non-string member names
|
|
1954
|
+
* no config and is dropped here. */
|
|
1955
|
+
function decodeExtends(value) {
|
|
1956
|
+
return (Array.isArray(value) ? value : [value]).filter(isJsonString);
|
|
1957
|
+
}
|
|
1958
|
+
function readConfig(path$4) {
|
|
1959
|
+
let raw;
|
|
1960
|
+
try {
|
|
1961
|
+
raw = (0, fs.readFileSync)(path$4, "utf-8");
|
|
1962
|
+
} catch {
|
|
1963
|
+
return null;
|
|
1964
|
+
}
|
|
1965
|
+
let parsed;
|
|
1966
|
+
try {
|
|
1967
|
+
parsed = JSON.parse(stripJsonc(raw));
|
|
1968
|
+
} catch {
|
|
1969
|
+
return null;
|
|
1970
|
+
}
|
|
1971
|
+
const root = isJsonBlock(parsed) ? parsed : {};
|
|
1972
|
+
const compilerOptions = isJsonBlock(root.compilerOptions) ? root.compilerOptions : {};
|
|
1973
|
+
return {
|
|
1974
|
+
dir: (0, path.dirname)(path$4),
|
|
1975
|
+
baseUrl: isJsonString(compilerOptions.baseUrl) ? compilerOptions.baseUrl : null,
|
|
1976
|
+
paths: decodePaths(compilerOptions.paths),
|
|
1977
|
+
extends: decodeExtends(root.extends)
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
function resolveExtendsTarget(specifier, fromDir) {
|
|
1981
|
+
if (specifier.startsWith(".") || (0, path.isAbsolute)(specifier)) {
|
|
1982
|
+
const base = (0, path.resolve)(fromDir, specifier);
|
|
1983
|
+
for (const candidate of [
|
|
1984
|
+
base,
|
|
1985
|
+
`${base}.json`,
|
|
1986
|
+
(0, path.join)(base, "tsconfig.json")
|
|
1987
|
+
]) if ((0, fs.existsSync)(candidate)) return candidate;
|
|
1988
|
+
return null;
|
|
1989
|
+
}
|
|
1990
|
+
try {
|
|
1991
|
+
return require.resolve(specifier.endsWith(".json") ? specifier : `${specifier}/tsconfig.json`, { paths: [fromDir] });
|
|
1992
|
+
} catch {
|
|
1993
|
+
try {
|
|
1994
|
+
return require.resolve(specifier, { paths: [fromDir] });
|
|
1995
|
+
} catch {
|
|
1996
|
+
return null;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
/** Child-first flattened extends chain starting at `entryPath`. */
|
|
2001
|
+
function loadChain(entryPath) {
|
|
2002
|
+
const chain = [];
|
|
2003
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2004
|
+
const queue = [entryPath];
|
|
2005
|
+
while (queue.length > 0) {
|
|
2006
|
+
const path$5 = queue.shift();
|
|
2007
|
+
const key = (0, path.resolve)(path$5);
|
|
2008
|
+
if (visited.has(key)) continue;
|
|
2009
|
+
visited.add(key);
|
|
2010
|
+
const node = readConfig(key);
|
|
2011
|
+
if (!node) continue;
|
|
2012
|
+
chain.push(node);
|
|
2013
|
+
for (const parent of node.extends) {
|
|
2014
|
+
const resolved = resolveExtendsTarget(parent, node.dir);
|
|
2015
|
+
if (resolved) queue.push(resolved);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
return chain;
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* The project's tsconfig path aliases as `buildPathAliasesJson` pairs; empty
|
|
2022
|
+
* when no readable tsconfig, or no usable `paths`, exists.
|
|
2023
|
+
*/
|
|
2024
|
+
function readTsconfigAliasPairs(rootDir) {
|
|
2025
|
+
const chain = loadChain((0, path.join)(rootDir, "tsconfig.json"));
|
|
2026
|
+
if (chain.length === 0) return [];
|
|
2027
|
+
const pathsOwner = chain.find((node) => node.paths !== null);
|
|
2028
|
+
const paths = pathsOwner?.paths ?? null;
|
|
2029
|
+
if (pathsOwner === void 0 || paths === null) return [];
|
|
2030
|
+
const baseOwner = chain.find((node) => node.baseUrl !== null);
|
|
2031
|
+
const baseUrl = baseOwner?.baseUrl ?? null;
|
|
2032
|
+
const base = baseOwner !== void 0 && baseUrl !== null ? (0, path.resolve)(baseOwner.dir, baseUrl) : pathsOwner.dir;
|
|
2033
|
+
const pairs = [];
|
|
2034
|
+
for (const [pattern, target] of paths) {
|
|
2035
|
+
const patternStars = pattern.split("*").length - 1;
|
|
2036
|
+
const targetStars = target.split("*").length - 1;
|
|
2037
|
+
if (pattern === "*" || patternStars > 1 || targetStars > 1) continue;
|
|
2038
|
+
if (pattern.endsWith("/*")) {
|
|
2039
|
+
if (!target.endsWith("/*")) continue;
|
|
2040
|
+
pairs.push({
|
|
2041
|
+
pattern: pattern.slice(0, -2),
|
|
2042
|
+
target: (0, path.resolve)(base, target.slice(0, -2)),
|
|
2043
|
+
kind: "prefix"
|
|
2044
|
+
});
|
|
2045
|
+
continue;
|
|
2046
|
+
}
|
|
2047
|
+
if (patternStars > 0 || targetStars > 0) continue;
|
|
2048
|
+
pairs.push({
|
|
2049
|
+
pattern,
|
|
2050
|
+
target: (0, path.resolve)(base, target),
|
|
2051
|
+
kind: "exact"
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
return pairs;
|
|
2055
|
+
}
|
|
2056
|
+
//#endregion
|
|
1812
2057
|
//#region pipeline/discover-packages.ts
|
|
1813
2058
|
function findPackageRoot(absEntryPath) {
|
|
1814
2059
|
let pkgRoot = (0, path.dirname)(absEntryPath);
|
|
@@ -1834,9 +2079,9 @@ const PACKAGE_OUTPUT_EXCLUDES = [
|
|
|
1834
2079
|
".d.ts",
|
|
1835
2080
|
".map"
|
|
1836
2081
|
];
|
|
1837
|
-
const isFile = (path$
|
|
2082
|
+
const isFile = (path$2) => {
|
|
1838
2083
|
try {
|
|
1839
|
-
return (0, fs.statSync)(path$
|
|
2084
|
+
return (0, fs.statSync)(path$2).isFile();
|
|
1840
2085
|
} catch {
|
|
1841
2086
|
return false;
|
|
1842
2087
|
}
|
|
@@ -1862,6 +2107,77 @@ function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
|
|
|
1862
2107
|
const subpath = specifier.slice(packageName.length + 1);
|
|
1863
2108
|
return resolveAbsolutePathSpecifier((0, path.join)(srcDir, subpath || "index"), extensionsSet);
|
|
1864
2109
|
}
|
|
2110
|
+
/**
|
|
2111
|
+
* A package's `sideEffects` field, decoded the way Vite reads it: a boolean
|
|
2112
|
+
* as written, or the list's globs, one with no `/` matching the basename
|
|
2113
|
+
* anywhere and any other rooted at the package. Undefined without a readable
|
|
2114
|
+
* field, or with glob syntax this matcher lacks (braces, classes, negation,
|
|
2115
|
+
* extglobs), which leaves the bundler's side-effectful default.
|
|
2116
|
+
*/
|
|
2117
|
+
function readPackageSideEffects(pkgRoot) {
|
|
2118
|
+
let manifest;
|
|
2119
|
+
try {
|
|
2120
|
+
manifest = JSON.parse((0, fs.readFileSync)((0, path.join)(pkgRoot, "package.json"), "utf-8"));
|
|
2121
|
+
} catch {
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
const field = isJsonBlock(manifest) ? manifest.sideEffects : void 0;
|
|
2125
|
+
if (field === true || field === false) return field;
|
|
2126
|
+
if (!Array.isArray(field)) return void 0;
|
|
2127
|
+
const globs = field.filter(isJsonString);
|
|
2128
|
+
if (globs.some((glob) => /[{}[\]()!+@]/.test(glob))) return void 0;
|
|
2129
|
+
return globs.map((glob) => globToRegExp(glob.includes("/") ? glob.replace(/^\.?\//, "") : `**/${glob}`));
|
|
2130
|
+
}
|
|
2131
|
+
function matchesSideEffects(globs, pkgRoot, absFiles) {
|
|
2132
|
+
return absFiles.some((file) => {
|
|
2133
|
+
const relPath = (0, path.relative)(pkgRoot, file).split(path.sep).join("/");
|
|
2134
|
+
return globs.some((glob) => glob.test(relPath));
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
/** An exports target under the `import` condition, then `default`, as the
|
|
2138
|
+
* system loader reads it; an array offers its targets in order. */
|
|
2139
|
+
function exportTarget(value) {
|
|
2140
|
+
if (isJsonString(value)) return value;
|
|
2141
|
+
if (Array.isArray(value)) {
|
|
2142
|
+
for (const entry of value) {
|
|
2143
|
+
const target = exportTarget(entry);
|
|
2144
|
+
if (target !== null) return target;
|
|
2145
|
+
}
|
|
2146
|
+
return null;
|
|
2147
|
+
}
|
|
2148
|
+
if (!isJsonBlock(value)) return null;
|
|
2149
|
+
return exportTarget(value.import) ?? exportTarget(value.default);
|
|
2150
|
+
}
|
|
2151
|
+
/** Each exact `exports` entry of the package at `pkgRoot`, as its subpath
|
|
2152
|
+
* and absolute target; `*` patterns name no single file and are skipped. */
|
|
2153
|
+
function packageExportEntries(pkgRoot) {
|
|
2154
|
+
let manifest;
|
|
2155
|
+
try {
|
|
2156
|
+
manifest = JSON.parse((0, fs.readFileSync)((0, path.join)(pkgRoot, "package.json"), "utf-8"));
|
|
2157
|
+
} catch {
|
|
2158
|
+
return [];
|
|
2159
|
+
}
|
|
2160
|
+
const exports = isJsonBlock(manifest) ? manifest.exports : void 0;
|
|
2161
|
+
if (exports === void 0 || exports === null) return [];
|
|
2162
|
+
const subpaths = isJsonBlock(exports) && Object.keys(exports).some((k) => k.startsWith(".")) ? exports : { ".": exports };
|
|
2163
|
+
const entries = [];
|
|
2164
|
+
for (const [subpath, value] of Object.entries(subpaths)) {
|
|
2165
|
+
if (!subpath.startsWith(".") || subpath.includes("*")) continue;
|
|
2166
|
+
const target = exportTarget(value);
|
|
2167
|
+
if (target !== null) entries.push([subpath, (0, path.resolve)(pkgRoot, target)]);
|
|
2168
|
+
}
|
|
2169
|
+
return entries;
|
|
2170
|
+
}
|
|
2171
|
+
/** Installed content is release content, and unpacking can leave any file
|
|
2172
|
+
* times, so only a package whose real path is outside `node_modules`, such
|
|
2173
|
+
* as a workspace link, is judged stale by them. */
|
|
2174
|
+
function isInstalledPackage(pkgRoot) {
|
|
2175
|
+
let realRoot = pkgRoot;
|
|
2176
|
+
try {
|
|
2177
|
+
realRoot = (0, fs.realpathSync)(pkgRoot);
|
|
2178
|
+
} catch {}
|
|
2179
|
+
return realRoot.split(/[\\/]/).includes("node_modules");
|
|
2180
|
+
}
|
|
1865
2181
|
function distEntryIsStale(absEntry, srcDir, srcFiles) {
|
|
1866
2182
|
if (isPathWithinRoot(srcDir, absEntry)) return false;
|
|
1867
2183
|
let distMtime;
|
|
@@ -1882,6 +2198,20 @@ async function collectExternalPackageSources(opts) {
|
|
|
1882
2198
|
const pushed = /* @__PURE__ */ new Set();
|
|
1883
2199
|
const packageMap = {};
|
|
1884
2200
|
const sourceEntries = /* @__PURE__ */ new Map();
|
|
2201
|
+
const sourceEntrySideEffects = /* @__PURE__ */ new Map();
|
|
2202
|
+
/** `replaced` names the entry the redirect stands in for; it is only
|
|
2203
|
+
* needed, and so only resolved, for a glob list. */
|
|
2204
|
+
const redirect = async (specifier, srcEntry, pkgRoot, replaced) => {
|
|
2205
|
+
sourceEntries.set(specifier, srcEntry);
|
|
2206
|
+
const field = readPackageSideEffects(pkgRoot);
|
|
2207
|
+
if (field === void 0) return;
|
|
2208
|
+
if (field === true || field === false) {
|
|
2209
|
+
sourceEntrySideEffects.set(specifier, field);
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
const replacedEntry = await replaced();
|
|
2213
|
+
sourceEntrySideEffects.set(specifier, replacedEntry === null || matchesSideEffects(field, pkgRoot, [srcEntry, replacedEntry]));
|
|
2214
|
+
};
|
|
1885
2215
|
const packageDirs = [];
|
|
1886
2216
|
const dirOwnerSets = {};
|
|
1887
2217
|
const dirExtensions = {};
|
|
@@ -1919,7 +2249,8 @@ async function collectExternalPackageSources(opts) {
|
|
|
1919
2249
|
const srcEntry = sourceEntryForSpecifier(specifier, srcDir, extensionsSet);
|
|
1920
2250
|
if (srcEntry) {
|
|
1921
2251
|
packageMap[specifier] = (0, path.relative)(rootDir, srcEntry);
|
|
1922
|
-
|
|
2252
|
+
const replaced = absEntry;
|
|
2253
|
+
await redirect(specifier, srcEntry, pkgRoot, async () => replaced);
|
|
1923
2254
|
} else packageMap[specifier] = (0, path.relative)(rootDir, absEntry);
|
|
1924
2255
|
if (!(0, path.isAbsolute)(specifier)) {
|
|
1925
2256
|
const packageName = bareSpecifierPackageName(specifier);
|
|
@@ -1927,12 +2258,18 @@ async function collectExternalPackageSources(opts) {
|
|
|
1927
2258
|
const rootEntry = sourceEntryForSpecifier(packageName, srcDir, extensionsSet);
|
|
1928
2259
|
if (rootEntry) {
|
|
1929
2260
|
packageMap[packageName] = (0, path.relative)(rootDir, rootEntry);
|
|
1930
|
-
|
|
2261
|
+
await redirect(packageName, rootEntry, pkgRoot, async () => {
|
|
2262
|
+
try {
|
|
2263
|
+
return await resolveSpecifier(packageName);
|
|
2264
|
+
} catch {
|
|
2265
|
+
return null;
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
1931
2268
|
}
|
|
1932
2269
|
}
|
|
1933
2270
|
}
|
|
1934
2271
|
const pkgFiles = walkPackageSources(srcDir, extensionsSet);
|
|
1935
|
-
staleDist = distEntryIsStale(absEntry, srcDir, pkgFiles);
|
|
2272
|
+
staleDist = !isInstalledPackage(pkgRoot) && distEntryIsStale(absEntry, srcDir, pkgFiles);
|
|
1936
2273
|
for (const pkgFile of pkgFiles) {
|
|
1937
2274
|
const relPath = (0, path.relative)(rootDir, pkgFile);
|
|
1938
2275
|
if (alreadyIngested(relPath)) {
|
|
@@ -1992,6 +2329,15 @@ async function collectExternalPackageSources(opts) {
|
|
|
1992
2329
|
fileCount++;
|
|
1993
2330
|
}
|
|
1994
2331
|
}
|
|
2332
|
+
if (!(0, path.isAbsolute)(specifier)) {
|
|
2333
|
+
const packageName = bareSpecifierPackageName(specifier);
|
|
2334
|
+
for (const [subpath, target] of packageExportEntries(pkgRoot)) {
|
|
2335
|
+
const entrySpecifier = packageName + subpath.slice(1);
|
|
2336
|
+
const targetRelPath = (0, path.relative)(rootDir, target);
|
|
2337
|
+
if (entrySpecifier in packageMap || !alreadyIngested(targetRelPath)) continue;
|
|
2338
|
+
packageMap[entrySpecifier] = targetRelPath;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
1995
2341
|
outcomes.push({
|
|
1996
2342
|
specifier,
|
|
1997
2343
|
outcome: staleDist ? "stale-dist" : fileCount > 0 ? "resolved" : "empty",
|
|
@@ -2002,6 +2348,7 @@ async function collectExternalPackageSources(opts) {
|
|
|
2002
2348
|
entries,
|
|
2003
2349
|
packageMap,
|
|
2004
2350
|
sourceEntries,
|
|
2351
|
+
sourceEntrySideEffects,
|
|
2005
2352
|
packageDirs,
|
|
2006
2353
|
dirOwnerSets,
|
|
2007
2354
|
dirExtensions,
|
|
@@ -2025,9 +2372,12 @@ function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
|
|
|
2025
2372
|
packageMap[specifier] = relTarget;
|
|
2026
2373
|
}
|
|
2027
2374
|
const sourceEntries = /* @__PURE__ */ new Map();
|
|
2375
|
+
const sourceEntrySideEffects = /* @__PURE__ */ new Map();
|
|
2028
2376
|
for (const [specifier, absEntry] of collected.sourceEntries) {
|
|
2029
2377
|
if (targetRejected(specifier, absEntry)) continue;
|
|
2030
2378
|
sourceEntries.set(specifier, absEntry);
|
|
2379
|
+
const sideEffects = collected.sourceEntrySideEffects.get(specifier);
|
|
2380
|
+
if (sideEffects !== void 0) sourceEntrySideEffects.set(specifier, sideEffects);
|
|
2031
2381
|
}
|
|
2032
2382
|
const dirOwnerSets = {};
|
|
2033
2383
|
for (const [dir, specs] of Object.entries(collected.dirOwnerSets)) {
|
|
@@ -2049,6 +2399,7 @@ function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
|
|
|
2049
2399
|
entries: collected.entries.filter((entry) => !rejectedSpecifiers.has(collected.fileOwners[entry.path])),
|
|
2050
2400
|
packageMap,
|
|
2051
2401
|
sourceEntries,
|
|
2402
|
+
sourceEntrySideEffects,
|
|
2052
2403
|
packageDirs: collected.packageDirs.filter((dir) => !rejectedDirs.includes(dir)),
|
|
2053
2404
|
dirOwnerSets,
|
|
2054
2405
|
dirExtensions,
|
|
@@ -2068,15 +2419,50 @@ function staleDistIncludesMessage(outcomes) {
|
|
|
2068
2419
|
if (stale.length === 0) return null;
|
|
2069
2420
|
return `[animus-extract] stale dist for include specifier(s): ${stale.join(", ")} — dist entry is older than the newest src/ file; rebuild the package(s) before extracting`;
|
|
2070
2421
|
}
|
|
2071
|
-
|
|
2422
|
+
/** The engine's own parse of a module's imports, through its
|
|
2423
|
+
* `extractFacts`; undefined for an engine without one. A parse that
|
|
2424
|
+
* panicked reports no imports, so it counts as no parse. */
|
|
2425
|
+
function engineImportParser(engine) {
|
|
2426
|
+
const { extractFacts } = engine;
|
|
2427
|
+
if (!extractFacts) return void 0;
|
|
2428
|
+
return (source, path$3) => {
|
|
2429
|
+
try {
|
|
2430
|
+
const facts = parseInternalWire(extractFacts(JSON.stringify([{
|
|
2431
|
+
path: path$3,
|
|
2432
|
+
source
|
|
2433
|
+
}])), "extractFacts").files[path$3];
|
|
2434
|
+
return facts && !facts.parsePanicked ? facts.imports : null;
|
|
2435
|
+
} catch {
|
|
2436
|
+
return null;
|
|
2437
|
+
}
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
/** A pattern matching a call of any of `callees` as a whole name, or null
|
|
2441
|
+
* when there is none to anchor on. */
|
|
2442
|
+
function rootCallPattern(callees) {
|
|
2443
|
+
if (callees.length === 0) return null;
|
|
2444
|
+
return `(?<![a-zA-Z0-9_$.])(?:${callees.map((callee) => callee.replace(/[.$]/g, "\\$&")).join("|")})`;
|
|
2445
|
+
}
|
|
2446
|
+
function extractSystemFilePackages(systemFilePath, parseImports) {
|
|
2072
2447
|
let source;
|
|
2073
2448
|
try {
|
|
2074
2449
|
source = (0, fs.readFileSync)(systemFilePath, "utf-8");
|
|
2075
2450
|
} catch {
|
|
2076
2451
|
return [];
|
|
2077
2452
|
}
|
|
2453
|
+
const parsed = parseImports?.(source, systemFilePath) ?? null;
|
|
2454
|
+
const parsedImports = parsed && parsed.length > 0 ? parsed : null;
|
|
2455
|
+
let rootCall = "createSystem";
|
|
2456
|
+
if (parsedImports) {
|
|
2457
|
+
const shadowed = parsedImports.some((binding) => binding.local === "createSystem" && binding.imported !== "createSystem") || /\b(?:function\*?|class)\s+createSystem\b|\b(?:const|let|var)\s+createSystem\b/.test(source);
|
|
2458
|
+
rootCall = rootCallPattern([.../* @__PURE__ */ new Set([
|
|
2459
|
+
...parsedImports.filter((binding) => binding.imported === "createSystem").map((binding) => binding.local),
|
|
2460
|
+
...Array.from(source.matchAll(/\bimport\s+(?:[a-zA-Z_$][a-zA-Z0-9_$]*\s*,\s*)?\*\s*as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s+from\s+['"]@animus-ui\/system(?:\/[^'"]*)?['"]/g), (match) => `${match[1]}.createSystem`),
|
|
2461
|
+
...shadowed ? [] : ["createSystem"]
|
|
2462
|
+
])]);
|
|
2463
|
+
}
|
|
2078
2464
|
const identifiers = /* @__PURE__ */ new Set();
|
|
2079
|
-
const constructorRegex =
|
|
2465
|
+
const constructorRegex = rootCall === null ? null : new RegExp(`${rootCall}\\s*\\(\\s*\\{[^}]*?\\bincludes\\s*:\\s*\\[([^\\]]*)\\]`, "gs");
|
|
2080
2466
|
const chainRegex = /\.includes\s*\(\s*\[([^\]]*)\]\s*\)/gs;
|
|
2081
2467
|
const collectIdentifiers = (regex) => {
|
|
2082
2468
|
let match;
|
|
@@ -2088,7 +2474,7 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
2088
2474
|
}
|
|
2089
2475
|
}
|
|
2090
2476
|
};
|
|
2091
|
-
collectIdentifiers(constructorRegex);
|
|
2477
|
+
if (constructorRegex) collectIdentifiers(constructorRegex);
|
|
2092
2478
|
collectIdentifiers(chainRegex);
|
|
2093
2479
|
const IDENT_START_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*/;
|
|
2094
2480
|
const skipTrivia = (from) => {
|
|
@@ -2108,6 +2494,38 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
2108
2494
|
return pos;
|
|
2109
2495
|
}
|
|
2110
2496
|
};
|
|
2497
|
+
/** The index after the string literal opening at `from`. */
|
|
2498
|
+
const skipString = (from) => {
|
|
2499
|
+
const quote = source[from];
|
|
2500
|
+
let pos = from + 1;
|
|
2501
|
+
while (pos < source.length && source[pos] !== quote) pos += source[pos] === "\\" ? 2 : 1;
|
|
2502
|
+
return pos + 1;
|
|
2503
|
+
};
|
|
2504
|
+
/** The index after the bracketed span opening at `from`, balanced across
|
|
2505
|
+
* `<>`, `()`, `[]` and `{}`, or -1 when it never closes. An arrow type's
|
|
2506
|
+
* `=>` closes nothing. */
|
|
2507
|
+
const skipBalanced = (from) => {
|
|
2508
|
+
let depth = 0;
|
|
2509
|
+
let pos = from;
|
|
2510
|
+
while (pos < source.length) {
|
|
2511
|
+
const ch = source[pos];
|
|
2512
|
+
if (ch === "'" || ch === "\"" || ch === "`") {
|
|
2513
|
+
pos = skipString(pos);
|
|
2514
|
+
continue;
|
|
2515
|
+
}
|
|
2516
|
+
if (source.startsWith("//", pos) || source.startsWith("/*", pos)) {
|
|
2517
|
+
pos = skipTrivia(pos);
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
if ("<([{".includes(ch)) depth++;
|
|
2521
|
+
else if (")]}".includes(ch) || ch === ">" && source[pos - 1] !== "=") {
|
|
2522
|
+
depth--;
|
|
2523
|
+
if (depth === 0) return pos + 1;
|
|
2524
|
+
}
|
|
2525
|
+
pos++;
|
|
2526
|
+
}
|
|
2527
|
+
return -1;
|
|
2528
|
+
};
|
|
2111
2529
|
const consumeChainLinks = (from) => {
|
|
2112
2530
|
let pos = from;
|
|
2113
2531
|
for (;;) {
|
|
@@ -2117,6 +2535,11 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
2117
2535
|
const method = IDENT_START_RE.exec(source.slice(cursor))?.[0];
|
|
2118
2536
|
if (method !== "extend" && method !== "from") return pos;
|
|
2119
2537
|
cursor = skipTrivia(cursor + method.length);
|
|
2538
|
+
if (source[cursor] === "<") {
|
|
2539
|
+
const afterTypeArguments = skipBalanced(cursor);
|
|
2540
|
+
if (afterTypeArguments === -1) return pos;
|
|
2541
|
+
cursor = skipTrivia(afterTypeArguments);
|
|
2542
|
+
}
|
|
2120
2543
|
if (source[cursor] !== "(") return pos;
|
|
2121
2544
|
cursor = skipTrivia(cursor + 1);
|
|
2122
2545
|
const base = IDENT_START_RE.exec(source.slice(cursor))?.[0];
|
|
@@ -2134,14 +2557,47 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
2134
2557
|
pos = cursor + 1;
|
|
2135
2558
|
}
|
|
2136
2559
|
};
|
|
2560
|
+
const bindingAt = /* @__PURE__ */ new Map();
|
|
2561
|
+
const aliasOf = /* @__PURE__ */ new Map();
|
|
2562
|
+
const declarationRe = /\b(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
2563
|
+
let declaration;
|
|
2564
|
+
while ((declaration = declarationRe.exec(source)) !== null) {
|
|
2565
|
+
const name = declaration[1];
|
|
2566
|
+
let cursor = skipTrivia(declaration.index + declaration[0].length);
|
|
2567
|
+
if (source[cursor] === ":") {
|
|
2568
|
+
cursor++;
|
|
2569
|
+
while (cursor < source.length) {
|
|
2570
|
+
const ch = source[cursor];
|
|
2571
|
+
if (ch === ";" || ch === ",") break;
|
|
2572
|
+
if (ch === "\n" && /^\s*(?:export\s+)?(?:const|let|var|function|class|type|interface|import)\b/.test(source.slice(cursor + 1, cursor + 80))) break;
|
|
2573
|
+
if (ch === "=" && source[cursor + 1] !== ">") break;
|
|
2574
|
+
if ("<([{".includes(ch) || ch === "'" || ch === "\"" || ch === "`") {
|
|
2575
|
+
const after = "<([{".includes(ch) ? skipBalanced(cursor) : skipString(cursor);
|
|
2576
|
+
if (after === -1) break;
|
|
2577
|
+
cursor = after;
|
|
2578
|
+
continue;
|
|
2579
|
+
}
|
|
2580
|
+
cursor = ch === "=" ? cursor + 2 : cursor + 1;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
if (source[cursor] !== "=" || source[cursor + 1] === "=") continue;
|
|
2584
|
+
const initializer = skipTrivia(cursor + 1);
|
|
2585
|
+
bindingAt.set(initializer, name);
|
|
2586
|
+
const target = IDENT_START_RE.exec(source.slice(initializer))?.[0];
|
|
2587
|
+
if (!target) continue;
|
|
2588
|
+
const rest = source.slice(initializer + target.length);
|
|
2589
|
+
if (/^[ \t]*(?:(?:as|satisfies)\s[^;\n]*)?(?:\/\/[^\n]*)?(?:;|\r?\n(?!\s*[.([?`])|$)/.test(rest)) aliasOf.set(name, target);
|
|
2590
|
+
}
|
|
2137
2591
|
const boundIdentifierBefore = (index) => {
|
|
2592
|
+
const declared = bindingAt.get(index);
|
|
2593
|
+
if (declared) return declared;
|
|
2138
2594
|
const match = /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*$/.exec(source.slice(0, index));
|
|
2139
2595
|
return match ? match[1] : null;
|
|
2140
2596
|
};
|
|
2141
2597
|
const chainRootIdentifiers = /* @__PURE__ */ new Set();
|
|
2142
|
-
const createSystemAnchor =
|
|
2598
|
+
const createSystemAnchor = rootCall === null ? null : new RegExp(`${rootCall}\\s*\\(`, "g");
|
|
2143
2599
|
let anchorMatch;
|
|
2144
|
-
while ((anchorMatch = createSystemAnchor
|
|
2600
|
+
while ((anchorMatch = createSystemAnchor?.exec(source) ?? null) !== null) {
|
|
2145
2601
|
let pos = anchorMatch.index + anchorMatch[0].length;
|
|
2146
2602
|
let depth = 1;
|
|
2147
2603
|
while (pos < source.length && depth > 0) {
|
|
@@ -2172,22 +2628,37 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
2172
2628
|
}
|
|
2173
2629
|
if (identifiers.size === 0) return [];
|
|
2174
2630
|
const importMap = /* @__PURE__ */ new Map();
|
|
2631
|
+
for (const binding of parsedImports ?? []) importMap.set(binding.local, binding.source);
|
|
2175
2632
|
const importRegex = /^\s*import\s+(?:([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,\s*)?(?:\{([^}]*)\}|([a-zA-Z_$][a-zA-Z0-9_$]*))\s+from\s+['"]([^'"]+)['"]/gm;
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
const
|
|
2183
|
-
|
|
2184
|
-
|
|
2633
|
+
if (!parsedImports) {
|
|
2634
|
+
let importMatch;
|
|
2635
|
+
while ((importMatch = importRegex.exec(source)) !== null) {
|
|
2636
|
+
const [, comboDefault, namedImports, defaultImport, specifier] = importMatch;
|
|
2637
|
+
if (comboDefault) importMap.set(comboDefault, specifier);
|
|
2638
|
+
if (defaultImport) importMap.set(defaultImport, specifier);
|
|
2639
|
+
if (namedImports) for (const binding of namedImports.split(",")) {
|
|
2640
|
+
const parts = binding.trim().split(/\s+as\s+/);
|
|
2641
|
+
const localName = (parts[1] || parts[0]).trim();
|
|
2642
|
+
if (localName) importMap.set(localName, specifier);
|
|
2643
|
+
}
|
|
2185
2644
|
}
|
|
2186
2645
|
}
|
|
2187
2646
|
const systemFileDir = (0, path.dirname)(systemFilePath);
|
|
2188
2647
|
const packages = /* @__PURE__ */ new Set();
|
|
2648
|
+
/** The import a local alias chain ends at, or the identifier itself. */
|
|
2649
|
+
const throughAliases = (id) => {
|
|
2650
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2651
|
+
let current = id;
|
|
2652
|
+
while (!importMap.has(current) && !seen.has(current)) {
|
|
2653
|
+
seen.add(current);
|
|
2654
|
+
const target = aliasOf.get(current);
|
|
2655
|
+
if (!target) break;
|
|
2656
|
+
current = target;
|
|
2657
|
+
}
|
|
2658
|
+
return current;
|
|
2659
|
+
};
|
|
2189
2660
|
for (const id of identifiers) {
|
|
2190
|
-
const specifier = importMap.get(id);
|
|
2661
|
+
const specifier = importMap.get(throughAliases(id));
|
|
2191
2662
|
if (!specifier) continue;
|
|
2192
2663
|
if (specifier.startsWith(".")) packages.add((0, path.resolve)(systemFileDir, specifier));
|
|
2193
2664
|
else packages.add(specifier);
|
|
@@ -2250,7 +2721,7 @@ const URL_OPEN_RE = /url\(\s*(['"]?)/gi;
|
|
|
2250
2721
|
const IMAGE_SET_OPEN_RE = /(?:-webkit-)?image-set\(/gi;
|
|
2251
2722
|
const BARE_SPECIFIER_RE = /^[^'")\s]+/;
|
|
2252
2723
|
/** The index of the `)` that closes the `(` at `open`, outside strings. */
|
|
2253
|
-
function closingParen
|
|
2724
|
+
function closingParen(css, open) {
|
|
2254
2725
|
let depth = 0;
|
|
2255
2726
|
let quote = null;
|
|
2256
2727
|
for (let i = open; i < css.length; i += 1) {
|
|
@@ -2299,7 +2770,7 @@ function assetReferences(css) {
|
|
|
2299
2770
|
}
|
|
2300
2771
|
for (const open of css.matchAll(IMAGE_SET_OPEN_RE)) {
|
|
2301
2772
|
const paren = (open.index ?? 0) + open[0].length - 1;
|
|
2302
|
-
const close = closingParen
|
|
2773
|
+
const close = closingParen(css, paren);
|
|
2303
2774
|
let depth = 0;
|
|
2304
2775
|
let candidateStart = true;
|
|
2305
2776
|
for (let i = paren + 1; i < close; i += 1) {
|
|
@@ -2344,7 +2815,7 @@ function placeholdersInAssetCalls(css) {
|
|
|
2344
2815
|
if (!css.includes("animus-asset:")) return [];
|
|
2345
2816
|
const calls = [...css.matchAll(URL_OPEN_RE), ...css.matchAll(IMAGE_SET_OPEN_RE)].map((open) => {
|
|
2346
2817
|
const paren = css.indexOf("(", open.index ?? 0);
|
|
2347
|
-
return [paren, closingParen
|
|
2818
|
+
return [paren, closingParen(css, paren)];
|
|
2348
2819
|
});
|
|
2349
2820
|
const found = /* @__PURE__ */ new Set();
|
|
2350
2821
|
for (let at = css.indexOf(ASSET_PLACEHOLDER_PREFIX); at !== -1; at = css.indexOf(ASSET_PLACEHOLDER_PREFIX, at + 1)) {
|
|
@@ -2586,14 +3057,9 @@ function buildPathAliasesJson(pairs, rootDir) {
|
|
|
2586
3057
|
//#endregion
|
|
2587
3058
|
//#region pipeline/post-process-css.ts
|
|
2588
3059
|
function resolveLightningTargets(explicitTargets, rootDir) {
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
const detected = (0, browserslist.default)(void 0, { path: rootDir });
|
|
2593
|
-
queries = detected.length > 0 ? detected : (0, browserslist.default)("defaults");
|
|
2594
|
-
}
|
|
2595
|
-
const [firstQuery] = queries;
|
|
2596
|
-
return (0, lightningcss.browserslistToTargets)(firstQuery !== void 0 && firstQuery.includes(" ") ? (0, browserslist.default)(queries) : queries);
|
|
3060
|
+
if (explicitTargets) return (0, lightningcss.browserslistToTargets)((0, browserslist.default)(explicitTargets));
|
|
3061
|
+
const detected = (0, browserslist.default)(void 0, { path: rootDir });
|
|
3062
|
+
return (0, lightningcss.browserslistToTargets)(detected.length > 0 ? detected : (0, browserslist.default)("defaults"));
|
|
2597
3063
|
}
|
|
2598
3064
|
function postProcessCss(css, opts) {
|
|
2599
3065
|
if (!css) return css;
|
|
@@ -2611,125 +3077,28 @@ function postProcessCss(css, opts) {
|
|
|
2611
3077
|
}
|
|
2612
3078
|
}
|
|
2613
3079
|
//#endregion
|
|
2614
|
-
//#region pipeline/property-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
const
|
|
2621
|
-
for (const
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
* The name starting at `from`, read as CSS tokenizes an identifier: ASCII
|
|
2634
|
-
* letters, digits, `-` and `_`, every non-ASCII code point, and escapes.
|
|
2635
|
-
*/
|
|
2636
|
-
function readCustomPropertyName(css, from) {
|
|
2637
|
-
let end = from;
|
|
2638
|
-
let name = "";
|
|
2639
|
-
while (end < css.length) {
|
|
2640
|
-
if (isNameChar(css[end])) {
|
|
2641
|
-
name += css[end];
|
|
2642
|
-
end += 1;
|
|
2643
|
-
continue;
|
|
2644
|
-
}
|
|
2645
|
-
const escape = escapeAt(css, end);
|
|
2646
|
-
if (escape === null) break;
|
|
2647
|
-
name += escape.codePoint;
|
|
2648
|
-
end += escape.length;
|
|
2649
|
-
}
|
|
2650
|
-
return {
|
|
2651
|
-
end,
|
|
2652
|
-
name
|
|
2653
|
-
};
|
|
2654
|
-
}
|
|
2655
|
-
/** A valid escape at `at`: up to six hex digits and one following
|
|
2656
|
-
* whitespace, or any other code point but a newline. */
|
|
2657
|
-
function escapeAt(css, at) {
|
|
2658
|
-
if (css[at] !== "\\") return null;
|
|
2659
|
-
const next = css.codePointAt(at + 1);
|
|
2660
|
-
if (next === void 0 || next === 10 || next === 13 || next === 12) return null;
|
|
2661
|
-
const digits = /^[0-9a-fA-F]{1,6}/.exec(css.slice(at + 1, at + 7))?.[0];
|
|
2662
|
-
if (digits === void 0) return {
|
|
2663
|
-
codePoint: String.fromCodePoint(next),
|
|
2664
|
-
length: 1 + String.fromCodePoint(next).length
|
|
2665
|
-
};
|
|
2666
|
-
const after = css.slice(at + 1 + digits.length);
|
|
2667
|
-
const space = after.startsWith("\r\n") ? 2 : /^[ \t\n\r\f]/.test(after) ? 1 : 0;
|
|
2668
|
-
const value = parseInt(digits, 16);
|
|
2669
|
-
const valid = value !== 0 && value <= 1114111 && (value < 55296 || value > 57343);
|
|
2670
|
-
return {
|
|
2671
|
-
codePoint: String.fromCodePoint(valid ? value : 65533),
|
|
2672
|
-
length: 1 + digits.length + space
|
|
2673
|
-
};
|
|
2674
|
-
}
|
|
2675
|
-
/**
|
|
2676
|
-
* Gives every custom-property token that names a managed property its final
|
|
2677
|
-
* name, in one pass: declaration keys, `var()` reads at any depth (fallbacks
|
|
2678
|
-
* included), `@property` names, transition-property lists and style queries.
|
|
2679
|
-
* A name is read whole, escapes decoded, so an undeclared name that starts
|
|
2680
|
-
* with a managed one is never renamed in part. Quoted strings, `url()` and
|
|
2681
|
-
* comments are copied unchanged.
|
|
2682
|
-
*/
|
|
2683
|
-
function renameCustomProperties(css, names) {
|
|
2684
|
-
let out = "";
|
|
2685
|
-
let i = 0;
|
|
2686
|
-
while (i < css.length) {
|
|
2687
|
-
const c = css[i];
|
|
2688
|
-
if (c === "\"" || c === "'") {
|
|
2689
|
-
const end = closingQuote$1(css, i);
|
|
2690
|
-
out += css.slice(i, end);
|
|
2691
|
-
i = end;
|
|
2692
|
-
} else if (css.startsWith("/*", i)) {
|
|
2693
|
-
const close = css.indexOf("*/", i + 2);
|
|
2694
|
-
const end = close === -1 ? css.length : close + 2;
|
|
2695
|
-
out += css.slice(i, end);
|
|
2696
|
-
i = end;
|
|
2697
|
-
} else if (/^url\(/i.test(css.slice(i, i + 4)) && !isNameChar(css[i - 1])) {
|
|
2698
|
-
const end = closingUrl(css, i + 4);
|
|
2699
|
-
out += css.slice(i, end);
|
|
2700
|
-
i = end;
|
|
2701
|
-
} else if (css.startsWith("--", i) && !isNameChar(css[i - 1])) {
|
|
2702
|
-
const { end, name } = readCustomPropertyName(css, i + 2);
|
|
2703
|
-
const identity = names.identity(name);
|
|
2704
|
-
const final = identity === void 0 ? void 0 : names.finalName(identity);
|
|
2705
|
-
out += final === void 0 ? css.slice(i, end) : `--${final}`;
|
|
2706
|
-
i = end;
|
|
2707
|
-
} else {
|
|
2708
|
-
out += c;
|
|
2709
|
-
i += 1;
|
|
2710
|
-
}
|
|
3080
|
+
//#region pipeline/property-registrations.ts
|
|
3081
|
+
const PROPERTY_AT_RULE = /@property\s+/gi;
|
|
3082
|
+
const RULE_BODY = /\s*\{[^}]*\}\n?/y;
|
|
3083
|
+
/** Each `@property` rule, through one newline after it, with its name read
|
|
3084
|
+
* as CSS tokenizes an identifier: escapes decoded, non-ASCII included. */
|
|
3085
|
+
function propertyRules(css) {
|
|
3086
|
+
const rules = [];
|
|
3087
|
+
for (const match of css.matchAll(PROPERTY_AT_RULE)) {
|
|
3088
|
+
if (match.index < (rules.at(-1)?.end ?? 0)) continue;
|
|
3089
|
+
const name = (0, _animus_ui_properties.identifierAt)(css, match.index + match[0].length);
|
|
3090
|
+
if (!name?.name.startsWith("--")) continue;
|
|
3091
|
+
RULE_BODY.lastIndex = name.end;
|
|
3092
|
+
const body = RULE_BODY.exec(css);
|
|
3093
|
+
if (body === null) continue;
|
|
3094
|
+
rules.push({
|
|
3095
|
+
start: match.index,
|
|
3096
|
+
end: name.end + body[0].length,
|
|
3097
|
+
name: name.name
|
|
3098
|
+
});
|
|
2711
3099
|
}
|
|
2712
|
-
return
|
|
2713
|
-
}
|
|
2714
|
-
/** A name code point, or a UTF-16 unit of a non-ASCII one. */
|
|
2715
|
-
function isNameChar(c) {
|
|
2716
|
-
return c !== void 0 && (NAME_CHAR.test(c) || c.charCodeAt(0) >= 128);
|
|
2717
|
-
}
|
|
2718
|
-
function closingQuote$1(css, open) {
|
|
2719
|
-
const quote = css[open];
|
|
2720
|
-
let i = open + 1;
|
|
2721
|
-
while (i < css.length && css[i] !== quote) i += css[i] === "\\" ? 2 : 1;
|
|
2722
|
-
return Math.min(i + 1, css.length);
|
|
3100
|
+
return rules;
|
|
2723
3101
|
}
|
|
2724
|
-
/** The index after the `)` that closes an unquoted or quoted `url(`. */
|
|
2725
|
-
function closingUrl(css, from) {
|
|
2726
|
-
let i = from;
|
|
2727
|
-
while (i < css.length && css[i] !== ")") i = css[i] === "\"" || css[i] === "'" ? closingQuote$1(css, i) : i + 1;
|
|
2728
|
-
return Math.min(i + 1, css.length);
|
|
2729
|
-
}
|
|
2730
|
-
//#endregion
|
|
2731
|
-
//#region pipeline/property-registrations.ts
|
|
2732
|
-
const PROPERTY_RULE = /@property\s+(--[\w-]+)\s*\{[^}]*\}\n?/g;
|
|
2733
3102
|
/** The data types a registration's `syntax` may name. */
|
|
2734
3103
|
const SYNTAX_TYPES = /* @__PURE__ */ new Set([
|
|
2735
3104
|
"angle",
|
|
@@ -2754,30 +3123,35 @@ const MATH_FUNCTION = /\b(?:calc|min|max|clamp)\(/i;
|
|
|
2754
3123
|
/**
|
|
2755
3124
|
* Removes the `@property` rules browsers would ignore — an unknown syntax
|
|
2756
3125
|
* component, a syntax other than `*` without an initial value, an initial
|
|
2757
|
-
* value that substitutes another value or depends on context,
|
|
2758
|
-
*
|
|
2759
|
-
*
|
|
2760
|
-
* computed value, which
|
|
3126
|
+
* value that substitutes another value or depends on context, math that
|
|
3127
|
+
* breaks calculation syntax, or a value the syntax does not accept.
|
|
3128
|
+
* Minifying a stylesheet that contains such a rule can also fail. An
|
|
3129
|
+
* all-absolute math initial value is written as its computed value, which
|
|
3130
|
+
* the minifier reads.
|
|
2761
3131
|
*/
|
|
2762
3132
|
function splitInvalidPropertyRegistrations(css) {
|
|
2763
3133
|
const invalid = [];
|
|
3134
|
+
let kept = "";
|
|
3135
|
+
let copied = 0;
|
|
3136
|
+
for (const { start, end, name } of propertyRules(css)) {
|
|
3137
|
+
const checked = checkRegistration(css.slice(start, end));
|
|
3138
|
+
kept += css.slice(copied, start);
|
|
3139
|
+
if ("rule" in checked) kept += checked.rule;
|
|
3140
|
+
else invalid.push({
|
|
3141
|
+
name,
|
|
3142
|
+
reason: checked.reason
|
|
3143
|
+
});
|
|
3144
|
+
copied = end;
|
|
3145
|
+
}
|
|
2764
3146
|
return {
|
|
2765
|
-
css: css.
|
|
2766
|
-
const checked = checkRegistration(rule);
|
|
2767
|
-
if ("rule" in checked) return checked.rule;
|
|
2768
|
-
invalid.push({
|
|
2769
|
-
name,
|
|
2770
|
-
reason: checked.reason
|
|
2771
|
-
});
|
|
2772
|
-
return "";
|
|
2773
|
-
}),
|
|
3147
|
+
css: kept + css.slice(copied),
|
|
2774
3148
|
invalid
|
|
2775
3149
|
};
|
|
2776
3150
|
}
|
|
2777
3151
|
/** The registrations in `css`, by custom-property name. */
|
|
2778
3152
|
function propertyRegistrations(css) {
|
|
2779
3153
|
const registrations = /* @__PURE__ */ new Map();
|
|
2780
|
-
for (const
|
|
3154
|
+
for (const { start, end, name } of propertyRules(css)) registrations.set(name, readRegistration(css.slice(start, end)));
|
|
2781
3155
|
return registrations;
|
|
2782
3156
|
}
|
|
2783
3157
|
function readRegistration(rule) {
|
|
@@ -2792,6 +3166,7 @@ function checkRegistration(rule) {
|
|
|
2792
3166
|
const { syntax, initialValue } = readRegistration(rule);
|
|
2793
3167
|
if (initialValue !== void 0 && (0, _animus_ui_properties.substitutesValue)(initialValue)) return { reason: `initialValue "${initialValue}" substitutes another value; var(), env() and attr() are not allowed in an initial value` };
|
|
2794
3168
|
let kept = rule;
|
|
3169
|
+
let folded;
|
|
2795
3170
|
if (syntax !== "*") {
|
|
2796
3171
|
const unknown = syntax.split("|").map((component) => ({
|
|
2797
3172
|
text: component.trim(),
|
|
@@ -2800,9 +3175,10 @@ function checkRegistration(rule) {
|
|
|
2800
3175
|
if (unknown !== void 0) return { reason: `syntax "${syntax}" has the unknown component "${unknown.text}"` };
|
|
2801
3176
|
if (initialValue === void 0) return { reason: `syntax "${syntax}" needs an initialValue` };
|
|
2802
3177
|
if ((0, _animus_ui_properties.dependsOnContext)(initialValue)) return { reason: `initialValue "${initialValue}" depends on context; font- and container-relative units are not allowed` };
|
|
2803
|
-
|
|
2804
|
-
if (folded
|
|
2805
|
-
if (folded
|
|
3178
|
+
folded = (0, _animus_ui_properties.foldInitialValue)(syntax, initialValue);
|
|
3179
|
+
if (folded?.kind === "invalid") return { reason: `initialValue "${initialValue}" is not a valid calculation: ${folded.reason}` };
|
|
3180
|
+
if (folded?.kind === "rejected") return { reason: `initialValue "${initialValue}" computes to the ${folded.type} ${folded.text}, which syntax "${syntax}" does not accept` };
|
|
3181
|
+
if (folded?.kind === "folded") kept = rule.replace(INITIAL_VALUE, `$1${folded.text}`);
|
|
2806
3182
|
}
|
|
2807
3183
|
try {
|
|
2808
3184
|
(0, lightningcss.transform)({
|
|
@@ -2812,6 +3188,7 @@ function checkRegistration(rule) {
|
|
|
2812
3188
|
return { rule: kept };
|
|
2813
3189
|
} catch (error) {
|
|
2814
3190
|
const detail = error instanceof Error ? error.message : String(error);
|
|
3191
|
+
if (folded?.kind === "not-finite") return { reason: `initialValue "${initialValue}" computes to an infinite or NaN value, which browsers clamp and which has no plain CSS value to write instead, and the CSS parser that builds the stylesheet cannot read the math (${detail})` };
|
|
2815
3192
|
if (initialValue !== void 0 && MATH_FUNCTION.test(initialValue)) return { reason: `the CSS parser that builds the stylesheet cannot read the math in initialValue "${initialValue}" (${detail}); write its computed value instead` };
|
|
2816
3193
|
return { reason: `the CSS parser rejects \`${kept.trim()}\` (${detail})` };
|
|
2817
3194
|
}
|
|
@@ -2821,13 +3198,18 @@ function checkRegistration(rule) {
|
|
|
2821
3198
|
/**
|
|
2822
3199
|
* Reports custom properties that will not behave as their CSS suggests: a
|
|
2823
3200
|
* property that refers to itself, a fallback a registered initial value
|
|
2824
|
-
* applies ahead of, and an animated property that cannot interpolate.
|
|
2825
|
-
* are read
|
|
2826
|
-
*
|
|
3201
|
+
* applies ahead of, and an animated property that cannot interpolate. Sheets
|
|
3202
|
+
* are read through the shared CSS tokenizer, as the prefix pass reads them:
|
|
3203
|
+
* comments are trivia, quoted text holds no names, and escapes and function
|
|
3204
|
+
* case are decoded. The CSS itself is never changed.
|
|
2827
3205
|
*/
|
|
2828
3206
|
function checkCustomProperties(input) {
|
|
2829
3207
|
const registered = propertyRegistrations(input.system.variableCss);
|
|
2830
|
-
const
|
|
3208
|
+
const declaredNames = input.system.contextualProperties ?? [];
|
|
3209
|
+
const declared = new Set(declaredNames);
|
|
3210
|
+
const withInitialValue = [...registered].filter(([, registration]) => registration.initialValue !== void 0).map(([name]) => name);
|
|
3211
|
+
const writers = currentVarWriters(input.system.propConfigJson);
|
|
3212
|
+
const currentVars = new Set(writers.keys());
|
|
2831
3213
|
const findings = [];
|
|
2832
3214
|
const sheets = [{
|
|
2833
3215
|
css: input.componentCss,
|
|
@@ -2837,9 +3219,20 @@ function checkCustomProperties(input) {
|
|
|
2837
3219
|
owner: globalOwner
|
|
2838
3220
|
}];
|
|
2839
3221
|
for (const { css, owner } of sheets) {
|
|
2840
|
-
if (
|
|
3222
|
+
if (!mayHoldFinding(css, withInitialValue, declaredNames, currentVars)) continue;
|
|
2841
3223
|
const parsed = declarations(css);
|
|
2842
|
-
const
|
|
3224
|
+
for (const cycle of currentVarCycles(parsed, currentVars)) {
|
|
3225
|
+
const [first] = cycle;
|
|
3226
|
+
const names = cycle.map((d) => d.property);
|
|
3227
|
+
const props = [...new Set(cycle.flatMap((d) => writers.get(d.property) ?? []))];
|
|
3228
|
+
findings.push({
|
|
3229
|
+
code: PROPERTY_CURRENT_VAR_CYCLE,
|
|
3230
|
+
property: first.property,
|
|
3231
|
+
message: `${cycle.map((d) => `${d.property}: ${d.value}`).join("; ")} are written by the currentVar of ${props.join(", ")} in one rule and read each other, a cycle, so ${names.join(", ")} are invalid at computed-value time wherever this rule applies. Give one of the props a value that does not read another's current variable`,
|
|
3232
|
+
owner: owner(first)
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
const discreteRules = new Set(parsed.filter((d) => d.property === "transition-behavior" && topLevelItems(d.values).some(isDiscrete)).map((d) => d.rule));
|
|
2843
3236
|
for (const declaration of parsed) {
|
|
2844
3237
|
const found = (code, property, message) => findings.push({
|
|
2845
3238
|
code,
|
|
@@ -2847,16 +3240,26 @@ function checkCustomProperties(input) {
|
|
|
2847
3240
|
message,
|
|
2848
3241
|
owner: owner(declaration)
|
|
2849
3242
|
});
|
|
2850
|
-
|
|
3243
|
+
const reads = (0, _animus_ui_properties.variableReads)(declaration.values);
|
|
3244
|
+
if (isSelfReference(declaration, reads)) {
|
|
2851
3245
|
found(PROPERTY_SELF_REFERENCE, declaration.property, `${declaration.property} resolves to var(${declaration.property}), a reference to itself, so it is invalid at computed-value time. Give it a value other than its own name`);
|
|
2852
3246
|
continue;
|
|
2853
3247
|
}
|
|
2854
|
-
if (
|
|
2855
|
-
|
|
3248
|
+
if (readsItselfDirectly(declaration, reads)) {
|
|
3249
|
+
const { property, value } = declaration;
|
|
3250
|
+
found(PROPERTY_SELF_REFERENCE, property, `${property}: ${value} reads ${property} directly, a reference to itself, so it is invalid at computed-value time; a fallback inside that var() does not break the cycle. Give it a value that does not read its own name`);
|
|
3251
|
+
continue;
|
|
3252
|
+
}
|
|
3253
|
+
if (readsItselfOnlyInFallback(declaration, reads)) {
|
|
3254
|
+
const { property, value } = declaration;
|
|
3255
|
+
found(PROPERTY_FALLBACK_SELF_REFERENCE, property, `${property}: ${value} reads ${property} inside another var()'s fallback. Wherever that fallback is used, ${property} refers to itself, a cycle in every browser, so ${property} is invalid at computed-value time, or takes its registered initial value, and so is every property that reads it; where it is not used, browsers differ on whether the cycle still counts. Without this declaration, descendants read the inherited ${property} instead, also where the outer variable is set, which is what a prop with currentVar does for such a value`);
|
|
3256
|
+
}
|
|
3257
|
+
for (const read of reads) {
|
|
2856
3258
|
const registration = registered.get(read.name);
|
|
2857
3259
|
if (read.fallback === void 0 || registration?.initialValue === void 0) continue;
|
|
2858
|
-
const
|
|
2859
|
-
|
|
3260
|
+
const { fallback } = read;
|
|
3261
|
+
const chain = reads.some((other) => other.start === fallback.first);
|
|
3262
|
+
found(chain ? PROPERTY_FALLBACK_CHAIN_SUPPRESSED : PROPERTY_FALLBACK_SUPPRESSED, read.name, `var(${read.name}, ${css.slice(fallback.start, fallback.end).trim()}): where its registration applies, ${read.name}'s initial-value ${registration.initialValue} is used ahead of the ${chain ? "fallback chain" : "fallback"} whenever ${read.name} is unset; the fallback still serves browsers without the registration`);
|
|
2860
3263
|
}
|
|
2861
3264
|
for (const animated of animatedProperties(declaration, discreteRules)) {
|
|
2862
3265
|
if (animated.discrete || !declared.has(animated.name)) continue;
|
|
@@ -2868,195 +3271,244 @@ function checkCustomProperties(input) {
|
|
|
2868
3271
|
}
|
|
2869
3272
|
return toDiagnostics(findings);
|
|
2870
3273
|
}
|
|
2871
|
-
/**
|
|
3274
|
+
/**
|
|
3275
|
+
* Whether a sheet can hold a finding, shown by its text before anything is
|
|
3276
|
+
* parsed: a custom property reading its own name, a `var(` with a fallback
|
|
3277
|
+
* of a registration that has an initial value, a transition or keyframes
|
|
3278
|
+
* and a declared contextual variable's name, or a `var(` of a currentVar name
|
|
3279
|
+
* when two or more props write one. An escape or a comment can hide
|
|
3280
|
+
* a name or a function from the text, so a sheet with either is always
|
|
3281
|
+
* parsed.
|
|
3282
|
+
*/
|
|
3283
|
+
function mayHoldFinding(css, withInitialValue, declared, currentVars) {
|
|
3284
|
+
return css.includes("\\") || css.includes("/*") || readsOwnName(css) || withInitialValue.some((name) => new RegExp(String.raw`var\(\s*${escapeRegExp(name)}\s*,`, "i").test(css)) || declared.some((name) => css.includes(name)) && /transition|@keyframes/i.test(css) || currentVars.size > 1 && [...currentVars].some((name) => new RegExp(String.raw`var\(\s*${escapeRegExp(name)}`, "i").test(css));
|
|
3285
|
+
}
|
|
3286
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3287
|
+
/** A custom-property declaration key, as the text shows one: anything but
|
|
3288
|
+
* CSS whitespace and the characters that end a name, so a non-ASCII space
|
|
3289
|
+
* stays part of the name, as CSS reads it. */
|
|
3290
|
+
const CUSTOM_PROPERTY_KEY = /(--[^\t\n\f\r :;{}()"'\\/]+)[\t\n\f\r ]*:/g;
|
|
3291
|
+
const VAR_OPEN = /var\(\s*/iy;
|
|
3292
|
+
const isNameCode = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 45 || c === 95 || c >= 128;
|
|
3293
|
+
/**
|
|
3294
|
+
* Whether a custom-property declaration has `var(` of its own whole name in
|
|
3295
|
+
* its value, bounded as the declaration scanner bounds one: by a `;`, `{` or
|
|
3296
|
+
* `}` outside parentheses, brackets and quoted text. `var` matches in any
|
|
3297
|
+
* case, so this errs toward parsing.
|
|
3298
|
+
*/
|
|
3299
|
+
function readsOwnName(css) {
|
|
3300
|
+
for (const match of css.matchAll(CUSTOM_PROPERTY_KEY)) {
|
|
3301
|
+
const name = match[1];
|
|
3302
|
+
let depth = 0;
|
|
3303
|
+
let quote = -1;
|
|
3304
|
+
for (let i = match.index + match[0].length; i < css.length; i += 1) {
|
|
3305
|
+
const c = css.charCodeAt(i);
|
|
3306
|
+
if (quote !== -1) {
|
|
3307
|
+
if (c === quote || c === 10 || c === 13 || c === 12) quote = -1;
|
|
3308
|
+
} else if (c === 34 || c === 39) quote = c;
|
|
3309
|
+
else if (c === 40 || c === 91) depth += 1;
|
|
3310
|
+
else if (c === 41 || c === 93) depth = Math.max(0, depth - 1);
|
|
3311
|
+
else if (depth === 0 && (c === 59 || c === 123 || c === 125)) break;
|
|
3312
|
+
else if (c === 118 || c === 86) {
|
|
3313
|
+
VAR_OPEN.lastIndex = i;
|
|
3314
|
+
const at = VAR_OPEN.test(css) ? VAR_OPEN.lastIndex : -1;
|
|
3315
|
+
if (at !== -1 && css.startsWith(name, at) && !isNameCode(css.charCodeAt(at + name.length))) return true;
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
return false;
|
|
3320
|
+
}
|
|
3321
|
+
/** `items` as text without comments, at-keywords decoded. */
|
|
3322
|
+
function textOf(css, items) {
|
|
3323
|
+
let text = "";
|
|
3324
|
+
const add = (token) => {
|
|
3325
|
+
if (token.type === "whitespace") text += " ";
|
|
3326
|
+
else if (token.type === "at-keyword") text += `@${token.value.toLowerCase()}`;
|
|
3327
|
+
else text += css.slice(token.start, token.end);
|
|
3328
|
+
};
|
|
3329
|
+
const walk = (list) => {
|
|
3330
|
+
for (const item of list) if (item.kind === "token") add(item.token);
|
|
3331
|
+
else {
|
|
3332
|
+
add(item.open);
|
|
3333
|
+
walk(item.children);
|
|
3334
|
+
if (item.close) add(item.close);
|
|
3335
|
+
}
|
|
3336
|
+
};
|
|
3337
|
+
walk(items);
|
|
3338
|
+
return text.trim();
|
|
3339
|
+
}
|
|
3340
|
+
/** Declarations in a sheet: one `property: value` per `;` inside a `{}`
|
|
3341
|
+
* block, read from component values, so comments separate nothing. */
|
|
2872
3342
|
function declarations(css) {
|
|
2873
3343
|
const found = [];
|
|
2874
|
-
const stack = [];
|
|
2875
3344
|
let rules = 0;
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
found.push({
|
|
2885
|
-
property: text.slice(0, colon).trim(),
|
|
2886
|
-
value: text.slice(colon + 1).replace(/;$/, "").trim(),
|
|
2887
|
-
start: segment + (text.length - text.trimStart().length),
|
|
2888
|
-
end,
|
|
2889
|
-
rule: current.rule,
|
|
2890
|
-
context: stack.map((entry) => entry.prelude)
|
|
2891
|
-
});
|
|
2892
|
-
};
|
|
2893
|
-
for (let i = 0; i < css.length; i += 1) {
|
|
2894
|
-
const c = css[i];
|
|
2895
|
-
if (quote !== null) {
|
|
2896
|
-
if (c === "\\") i += 1;
|
|
2897
|
-
else if (c === quote) quote = null;
|
|
2898
|
-
continue;
|
|
2899
|
-
}
|
|
2900
|
-
if (c === "\"" || c === "'") quote = c;
|
|
2901
|
-
else if (c === "(") parens += 1;
|
|
2902
|
-
else if (c === ")") parens -= 1;
|
|
2903
|
-
else if (parens === 0 && c === "{") {
|
|
2904
|
-
stack.push({
|
|
2905
|
-
prelude: css.slice(segment, i).trim(),
|
|
2906
|
-
rule: rules
|
|
3345
|
+
const visit = (items, context, rule) => {
|
|
3346
|
+
let segment = [];
|
|
3347
|
+
const take = () => {
|
|
3348
|
+
const declaration = declarationOf(css, segment);
|
|
3349
|
+
if (rule !== void 0 && declaration !== void 0) found.push({
|
|
3350
|
+
...declaration,
|
|
3351
|
+
rule,
|
|
3352
|
+
context
|
|
2907
3353
|
});
|
|
3354
|
+
segment = [];
|
|
3355
|
+
};
|
|
3356
|
+
for (const item of items) if (item.kind === "block" && item.open.type === "{") {
|
|
3357
|
+
const prelude = textOf(css, segment);
|
|
3358
|
+
segment = [];
|
|
2908
3359
|
rules += 1;
|
|
2909
|
-
|
|
2910
|
-
} else if (
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
stack.pop();
|
|
2916
|
-
segment = i + 1;
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
3360
|
+
visit(item.children, [...context, prelude], rules - 1);
|
|
3361
|
+
} else if (item.kind === "token" && item.token.type === ";") take();
|
|
3362
|
+
else segment.push(item);
|
|
3363
|
+
take();
|
|
3364
|
+
};
|
|
3365
|
+
visit((0, _animus_ui_properties.componentValues)((0, _animus_ui_properties.tokenize)(css)), [], void 0);
|
|
2919
3366
|
return found;
|
|
2920
3367
|
}
|
|
2921
|
-
/**
|
|
2922
|
-
*
|
|
2923
|
-
function
|
|
2924
|
-
|
|
2925
|
-
const
|
|
2926
|
-
|
|
3368
|
+
/** The declaration a `;`-separated segment holds, if any. A bad string or
|
|
3369
|
+
* url makes a declaration invalid, so browsers drop it, and so does this. */
|
|
3370
|
+
function declarationOf(css, segment) {
|
|
3371
|
+
const colon = segment.findIndex((item) => item.kind === "token" && item.token.type === ":");
|
|
3372
|
+
const named = segment.slice(0, Math.max(colon, 0)).filter((i) => !(0, _animus_ui_properties.isSpace)(i));
|
|
3373
|
+
const values = trimSpace(segment.slice(colon + 1));
|
|
3374
|
+
if (colon === -1 || named.length === 0 || (0, _animus_ui_properties.someValue)(values, (v) => v.kind === "token" && (v.token.type === "bad-url" || v.token.type === "bad-string"))) return;
|
|
3375
|
+
const [only] = named;
|
|
3376
|
+
const ident = named.length === 1 && only.kind === "token" && only.token.type === "ident" ? only.token.value : void 0;
|
|
3377
|
+
const custom = ident?.startsWith("--") === true;
|
|
3378
|
+
return {
|
|
3379
|
+
property: custom && ident !== void 0 ? ident : (ident ?? textOf(css, named)).toLowerCase(),
|
|
3380
|
+
custom,
|
|
3381
|
+
value: values.length > 0 ? css.slice(values[0].start, values[values.length - 1].end) : "",
|
|
3382
|
+
values
|
|
3383
|
+
};
|
|
2927
3384
|
}
|
|
2928
|
-
function
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
3385
|
+
function trimSpace(items) {
|
|
3386
|
+
let start = 0;
|
|
3387
|
+
let end = items.length;
|
|
3388
|
+
while (start < end && (0, _animus_ui_properties.isSpace)(items[start])) start += 1;
|
|
3389
|
+
while (end > start && (0, _animus_ui_properties.isSpace)(items[end - 1])) end -= 1;
|
|
3390
|
+
return items.slice(start, end);
|
|
3391
|
+
}
|
|
3392
|
+
/** Whether the value is exactly `var()` of the declared property. */
|
|
3393
|
+
function isSelfReference(declaration, reads) {
|
|
3394
|
+
return declaration.custom && reads.length === 1 && reads[0].name === declaration.property && reads[0].fallback === void 0 && declaration.values.length === 1 && declaration.values[0].start === reads[0].start;
|
|
3395
|
+
}
|
|
3396
|
+
/** Whether a read of the declared property sits outside every other read's
|
|
3397
|
+
* fallback, as in `--a: var(--a, red)` or `--a: calc(var(--a) + 1px)`: cyclic
|
|
3398
|
+
* whatever the fallback, as the bare `var(--a)` is. */
|
|
3399
|
+
function readsItselfDirectly(declaration, reads) {
|
|
3400
|
+
return declaration.custom && reads.some((read) => read.name === declaration.property && isDirectRead(read, reads));
|
|
3401
|
+
}
|
|
3402
|
+
/** Whether `read` sits outside every other read's fallback, so it is taken
|
|
3403
|
+
* whenever the declaration is. */
|
|
3404
|
+
function isDirectRead(read, reads) {
|
|
3405
|
+
return !reads.some((outer) => outer !== read && outer.fallback !== void 0 && outer.fallback.start <= read.start && read.end <= outer.fallback.end);
|
|
3406
|
+
}
|
|
3407
|
+
/** Each custom property a `currentVar` writes, with the props writing it. */
|
|
3408
|
+
function currentVarWriters(propConfigJson) {
|
|
3409
|
+
const writers = /* @__PURE__ */ new Map();
|
|
3410
|
+
if (propConfigJson === void 0) return writers;
|
|
3411
|
+
const config = JSON.parse(propConfigJson);
|
|
3412
|
+
for (const [prop, entry] of Object.entries(config)) {
|
|
3413
|
+
if (!entry.currentVar) continue;
|
|
3414
|
+
writers.set(entry.currentVar, [...writers.get(entry.currentVar) ?? [], prop]);
|
|
3415
|
+
}
|
|
3416
|
+
return writers;
|
|
2932
3417
|
}
|
|
2933
|
-
/**
|
|
2934
|
-
*
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
}
|
|
2956
|
-
if (value.slice(at, at + 4).toLowerCase() !== "var(" || /[\w-]/.test(value[at - 1] ?? "")) {
|
|
2957
|
-
at += 1;
|
|
2958
|
-
continue;
|
|
2959
|
-
}
|
|
2960
|
-
const nameAt = skipSpace(value, at + 4);
|
|
2961
|
-
if (!value.startsWith("--", nameAt)) {
|
|
2962
|
-
at += 4;
|
|
2963
|
-
continue;
|
|
3418
|
+
/**
|
|
3419
|
+
* Cycles among the `currentVar` writes of one rule, read directly. One rule
|
|
3420
|
+
* is one element under one set of conditions, so its writes are always
|
|
3421
|
+
* active together; writes in different rules, or a read only inside another
|
|
3422
|
+
* read's fallback, are left alone. Each cycle is listed once, in declaration
|
|
3423
|
+
* order.
|
|
3424
|
+
*/
|
|
3425
|
+
function currentVarCycles(parsed, currentVars) {
|
|
3426
|
+
if (currentVars.size === 0) return [];
|
|
3427
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
3428
|
+
for (const declaration of parsed) {
|
|
3429
|
+
if (!declaration.custom || !currentVars.has(declaration.property)) continue;
|
|
3430
|
+
const writes = byRule.get(declaration.rule) ?? /* @__PURE__ */ new Map();
|
|
3431
|
+
writes.set(declaration.property, declaration);
|
|
3432
|
+
byRule.set(declaration.rule, writes);
|
|
3433
|
+
}
|
|
3434
|
+
const cycles = [];
|
|
3435
|
+
for (const writes of byRule.values()) {
|
|
3436
|
+
const edges = /* @__PURE__ */ new Map();
|
|
3437
|
+
for (const [name, declaration] of writes) {
|
|
3438
|
+
const reads = (0, _animus_ui_properties.variableReads)(declaration.values);
|
|
3439
|
+
edges.set(name, reads.filter((read) => read.name !== name && writes.has(read.name) && isDirectRead(read, reads)).map((read) => read.name));
|
|
2964
3440
|
}
|
|
2965
|
-
const
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
reads.push({
|
|
2969
|
-
name: `--${name}`,
|
|
2970
|
-
fallback: value[after] === "," ? value.slice(after + 1, close - 1).trim() : void 0,
|
|
2971
|
-
start: at,
|
|
2972
|
-
end: close
|
|
2973
|
-
});
|
|
2974
|
-
at = after;
|
|
2975
|
-
}
|
|
2976
|
-
return reads;
|
|
2977
|
-
}
|
|
2978
|
-
/** The index after the `)` that closes the call whose arguments start at
|
|
2979
|
-
* `from`. Quoted text and comments are skipped. */
|
|
2980
|
-
function closingParen(value, from) {
|
|
2981
|
-
let depth = 1;
|
|
2982
|
-
let at = from;
|
|
2983
|
-
while (at < value.length && depth > 0) {
|
|
2984
|
-
const c = value[at];
|
|
2985
|
-
if (c === "\"" || c === "'") at = closingQuote(value, at);
|
|
2986
|
-
else if (value.startsWith("/*", at)) at = skipSpace(value, at);
|
|
2987
|
-
else {
|
|
2988
|
-
if (c === "(") depth += 1;
|
|
2989
|
-
else if (c === ")") depth -= 1;
|
|
2990
|
-
at += 1;
|
|
3441
|
+
for (const component of stronglyConnected(edges)) {
|
|
3442
|
+
if (component.length < 2) continue;
|
|
3443
|
+
cycles.push([...writes.values()].filter((d) => component.includes(d.property)));
|
|
2991
3444
|
}
|
|
2992
3445
|
}
|
|
2993
|
-
return
|
|
3446
|
+
return cycles;
|
|
2994
3447
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3448
|
+
/** Tarjan's strongly connected components of a small graph. */
|
|
3449
|
+
function stronglyConnected(edges) {
|
|
3450
|
+
const index = /* @__PURE__ */ new Map();
|
|
3451
|
+
const low = /* @__PURE__ */ new Map();
|
|
3452
|
+
const stack = [];
|
|
3453
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
3454
|
+
const components = [];
|
|
3455
|
+
const visit = (node) => {
|
|
3456
|
+
index.set(node, index.size);
|
|
3457
|
+
low.set(node, index.get(node));
|
|
3458
|
+
stack.push(node);
|
|
3459
|
+
onStack.add(node);
|
|
3460
|
+
for (const next of edges.get(node) ?? []) if (!index.has(next)) {
|
|
3461
|
+
visit(next);
|
|
3462
|
+
low.set(node, Math.min(low.get(node), low.get(next)));
|
|
3463
|
+
} else if (onStack.has(next)) low.set(node, Math.min(low.get(node), index.get(next)));
|
|
3464
|
+
if (low.get(node) === index.get(node)) {
|
|
3465
|
+
const component = [];
|
|
3466
|
+
let member;
|
|
3467
|
+
do {
|
|
3468
|
+
member = stack.pop();
|
|
3469
|
+
onStack.delete(member);
|
|
3470
|
+
component.push(member);
|
|
3471
|
+
} while (member !== node);
|
|
3472
|
+
components.push(component);
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3475
|
+
for (const node of edges.keys()) if (!index.has(node)) visit(node);
|
|
3476
|
+
return components;
|
|
3009
3477
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3478
|
+
/** Whether every read of the declared property sits inside another read's
|
|
3479
|
+
* fallback, as in `--a: var(--b, var(--a))`. */
|
|
3480
|
+
function readsItselfOnlyInFallback(declaration, reads) {
|
|
3481
|
+
if (!declaration.custom) return false;
|
|
3482
|
+
const own = reads.filter((read) => read.name === declaration.property);
|
|
3483
|
+
return own.length > 0 && own.every((read) => reads.some((outer) => outer.fallback !== void 0 && outer.fallback.start <= read.start && read.end <= outer.fallback.end));
|
|
3012
3484
|
}
|
|
3013
3485
|
/** Custom properties this declaration sets in a keyframe or names in a
|
|
3014
3486
|
* transition. */
|
|
3015
3487
|
function animatedProperties(declaration, discreteRules) {
|
|
3016
3488
|
const keyframes = declaration.context.find((prelude) => prelude.startsWith("@keyframes"));
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
name: set,
|
|
3489
|
+
if (keyframes !== void 0 && declaration.custom) return [{
|
|
3490
|
+
name: declaration.property,
|
|
3020
3491
|
how: `set in ${keyframes}`,
|
|
3021
3492
|
discrete: false
|
|
3022
3493
|
}];
|
|
3023
3494
|
if (declaration.property !== "transition" && declaration.property !== "transition-property") return [];
|
|
3024
|
-
return topLevelItems(declaration.
|
|
3025
|
-
const
|
|
3026
|
-
return
|
|
3027
|
-
name:
|
|
3495
|
+
return topLevelItems(declaration.values).flatMap((item) => {
|
|
3496
|
+
const named = item.find((value) => value.kind === "token" && value.token.type === "ident" && value.token.value.startsWith("--"));
|
|
3497
|
+
return named?.kind === "token" ? [{
|
|
3498
|
+
name: named.token.value,
|
|
3028
3499
|
how: "transitioned",
|
|
3029
|
-
discrete:
|
|
3500
|
+
discrete: isDiscrete(item) || discreteRules.has(declaration.rule)
|
|
3030
3501
|
}] : [];
|
|
3031
3502
|
});
|
|
3032
3503
|
}
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
if (depth === 0 && /\s/.test(c)) {
|
|
3042
|
-
if (token) tokens.push(token);
|
|
3043
|
-
token = "";
|
|
3044
|
-
} else token += c;
|
|
3045
|
-
}
|
|
3046
|
-
if (token) tokens.push(token);
|
|
3047
|
-
return tokens;
|
|
3048
|
-
}
|
|
3049
|
-
function topLevelItems(value) {
|
|
3050
|
-
const items = [];
|
|
3051
|
-
let depth = 0;
|
|
3052
|
-
let start = 0;
|
|
3053
|
-
for (let i = 0; i < value.length; i += 1) if (value[i] === "(") depth += 1;
|
|
3054
|
-
else if (value[i] === ")") depth -= 1;
|
|
3055
|
-
else if (value[i] === "," && depth === 0) {
|
|
3056
|
-
items.push(value.slice(start, i));
|
|
3057
|
-
start = i + 1;
|
|
3058
|
-
}
|
|
3059
|
-
items.push(value.slice(start));
|
|
3504
|
+
function isDiscrete(item) {
|
|
3505
|
+
return item.some((value) => value.kind === "token" && value.token.type === "ident" && value.token.value.toLowerCase() === "allow-discrete");
|
|
3506
|
+
}
|
|
3507
|
+
/** The comma-separated items of a value, each its top-level values. */
|
|
3508
|
+
function topLevelItems(values) {
|
|
3509
|
+
const items = [[]];
|
|
3510
|
+
for (const value of values) if (value.kind === "token" && value.token.type === ",") items.push([]);
|
|
3511
|
+
else items[items.length - 1].push(value);
|
|
3060
3512
|
return items;
|
|
3061
3513
|
}
|
|
3062
3514
|
/** Why a property cannot interpolate, or `null` when it can. */
|
|
@@ -3304,6 +3756,47 @@ function serializeStaticCss(config) {
|
|
|
3304
3756
|
return json === "{}" ? null : json;
|
|
3305
3757
|
}
|
|
3306
3758
|
//#endregion
|
|
3759
|
+
//#region pipeline/property-names.ts
|
|
3760
|
+
function createPropertyNames(declared, prefix, themeNames = []) {
|
|
3761
|
+
const finals = /* @__PURE__ */ new Map();
|
|
3762
|
+
for (const name of [...new Set(declared)].sort()) finals.set(name, `${prefix}-${name}`);
|
|
3763
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
3764
|
+
for (const [name, final] of finals) if (!finals.has(final)) aliases.set(final, name);
|
|
3765
|
+
const others = /* @__PURE__ */ new Map();
|
|
3766
|
+
for (const name of [...new Set(themeNames)].sort()) if (!finals.has(name)) others.set(name, `${prefix}-${name}`);
|
|
3767
|
+
return {
|
|
3768
|
+
identity: (spelling) => finals.has(spelling) ? spelling : aliases.get(spelling) ?? (others.has(spelling) ? spelling : void 0),
|
|
3769
|
+
finalName: (name) => finals.get(name) ?? others.get(name),
|
|
3770
|
+
ambiguous: () => [...others.keys()].filter((name) => aliases.has(name)).map((name) => ({
|
|
3771
|
+
name,
|
|
3772
|
+
contextual: aliases.get(name) ?? name
|
|
3773
|
+
}))
|
|
3774
|
+
};
|
|
3775
|
+
}
|
|
3776
|
+
/**
|
|
3777
|
+
* Gives every custom-property token that names a managed property its final
|
|
3778
|
+
* name, in one pass: declaration keys, `var()` reads at any depth (fallbacks
|
|
3779
|
+
* included), `@property` names, transition-property lists and style queries.
|
|
3780
|
+
* Names are read through the shared CSS tokenizer, whole and with escapes
|
|
3781
|
+
* decoded, escaped leading dashes included, so an undeclared identifier that
|
|
3782
|
+
* contains or starts with a managed name is never renamed in part. Quoted
|
|
3783
|
+
* strings, `url()` and comments are copied unchanged.
|
|
3784
|
+
*/
|
|
3785
|
+
function renameCustomProperties(css, names) {
|
|
3786
|
+
let out = "";
|
|
3787
|
+
let copied = 0;
|
|
3788
|
+
for (const token of (0, _animus_ui_properties.tokenize)(css)) {
|
|
3789
|
+
if (token.type !== "ident" || !token.value.startsWith("--")) continue;
|
|
3790
|
+
const spelling = token.value.slice(2);
|
|
3791
|
+
const identity = names.identity(spelling);
|
|
3792
|
+
const final = identity === void 0 ? void 0 : names.finalName(identity);
|
|
3793
|
+
if (final === void 0 || final === spelling) continue;
|
|
3794
|
+
out += `${css.slice(copied, token.start)}--${final}`;
|
|
3795
|
+
copied = token.end;
|
|
3796
|
+
}
|
|
3797
|
+
return out + css.slice(copied);
|
|
3798
|
+
}
|
|
3799
|
+
//#endregion
|
|
3307
3800
|
//#region pipeline/prefix.ts
|
|
3308
3801
|
/** Prefix variable references without changing their matching rules. */
|
|
3309
3802
|
function prefixVariableReferences(prefix, value) {
|
|
@@ -3469,7 +3962,7 @@ function runStructuralSelfCheck(input) {
|
|
|
3469
3962
|
else if (input.assembledCss !== void 0 ? !/@layer\s+anm-base\s*\{/.test(input.assembledCss) : input.componentCss.trim().length === 0) failures.push(`${input.componentCount} component(s) discovered but the emitted component CSS is empty`);
|
|
3470
3963
|
for (const { specifier, outcome } of input.externalOutcomes ?? []) if (outcome === "empty") failures.push(`include '${specifier}' resolved but discovered no component sources`);
|
|
3471
3964
|
else if (outcome === "unresolvable") failures.push(`include '${specifier}' could not be resolved`);
|
|
3472
|
-
if (!input.variableCss.includes(":root")) failures.push("No :root variable block found in variable CSS");
|
|
3965
|
+
if ((input.variableMapJson == null || Object.keys(parseInternalWire(input.variableMapJson, "variableMapJson (the theme's token → variable map)")).length > 0) && !input.variableCss.includes(":root")) failures.push("No :root variable block found in variable CSS");
|
|
3473
3966
|
if ((input.assembledCss ?? `${input.variableCss}\n${input.globalCss}\n${input.componentCss}`).includes("__TRANSFORM__")) failures.push("Unresolved __TRANSFORM__ placeholders found in CSS output");
|
|
3474
3967
|
if (input.componentCount > 0 && (input.componentCss.length > 0 || input.assembledCss !== void 0)) {
|
|
3475
3968
|
const assembled = input.assembledCss ?? assembleStylesheet({
|
|
@@ -3478,9 +3971,16 @@ function runStructuralSelfCheck(input) {
|
|
|
3478
3971
|
globalCss: input.globalCss,
|
|
3479
3972
|
componentCss: input.componentCss
|
|
3480
3973
|
});
|
|
3481
|
-
const
|
|
3482
|
-
|
|
3483
|
-
|
|
3974
|
+
const blocks = ANIMUS_LAYERS.map((layer) => ({
|
|
3975
|
+
layer,
|
|
3976
|
+
offset: assembled.search(new RegExp(`@layer\\s+${layer}\\s*\\{`))
|
|
3977
|
+
})).filter(({ offset }) => offset !== -1);
|
|
3978
|
+
const misplaced = blocks.findIndex(({ offset }, index) => index > 0 && offset <= blocks[index - 1].offset);
|
|
3979
|
+
if (misplaced !== -1) {
|
|
3980
|
+
const earlier = blocks[misplaced - 1];
|
|
3981
|
+
const later = blocks[misplaced];
|
|
3982
|
+
failures.push(`CSS layer ordering violated — @layer ${earlier.layer} (offset ${earlier.offset}) must precede @layer ${later.layer} (offset ${later.offset})`);
|
|
3983
|
+
}
|
|
3484
3984
|
}
|
|
3485
3985
|
return failures;
|
|
3486
3986
|
}
|
|
@@ -3543,188 +4043,6 @@ function buildSystemPropsModule(opts) {
|
|
|
3543
4043
|
return `export const systemPropMap = ${opts.systemPropMapJson};\nexport const systemPropGroups = ${opts.groupRegistryJson};\nexport const dynamicPropConfig = ${JSON.stringify(dynamicPropConfig)};\nexport const transforms = ${transformsSource(opts.admittedTransforms)};\n` + (opts.typedSystemProps.length > 0 ? `export const typedSystemProps = ${JSON.stringify(opts.typedSystemProps)};\n` : "");
|
|
3544
4044
|
}
|
|
3545
4045
|
//#endregion
|
|
3546
|
-
//#region pipeline/tsconfig-paths.ts
|
|
3547
|
-
/**
|
|
3548
|
-
* tsconfig `paths` → alias pairs, for drivers with no live bundler config.
|
|
3549
|
-
* The nearest config declaring `paths` wins wholesale — TS never merges.
|
|
3550
|
-
*/
|
|
3551
|
-
function stripJsonc(text) {
|
|
3552
|
-
let out = "";
|
|
3553
|
-
let inString = false;
|
|
3554
|
-
let inLine = false;
|
|
3555
|
-
let inBlock = false;
|
|
3556
|
-
for (let i = 0; i < text.length; i++) {
|
|
3557
|
-
const ch = text[i];
|
|
3558
|
-
const next = text[i + 1];
|
|
3559
|
-
if (inLine) {
|
|
3560
|
-
if (ch === "\n") {
|
|
3561
|
-
inLine = false;
|
|
3562
|
-
out += ch;
|
|
3563
|
-
}
|
|
3564
|
-
continue;
|
|
3565
|
-
}
|
|
3566
|
-
if (inBlock) {
|
|
3567
|
-
if (ch === "*" && next === "/") {
|
|
3568
|
-
inBlock = false;
|
|
3569
|
-
i++;
|
|
3570
|
-
}
|
|
3571
|
-
continue;
|
|
3572
|
-
}
|
|
3573
|
-
if (inString) {
|
|
3574
|
-
out += ch;
|
|
3575
|
-
if (ch === "\\") {
|
|
3576
|
-
out += next ?? "";
|
|
3577
|
-
i++;
|
|
3578
|
-
} else if (ch === "\"") inString = false;
|
|
3579
|
-
continue;
|
|
3580
|
-
}
|
|
3581
|
-
if (ch === "\"") {
|
|
3582
|
-
inString = true;
|
|
3583
|
-
out += ch;
|
|
3584
|
-
continue;
|
|
3585
|
-
}
|
|
3586
|
-
if (ch === "/" && next === "/") {
|
|
3587
|
-
inLine = true;
|
|
3588
|
-
i++;
|
|
3589
|
-
continue;
|
|
3590
|
-
}
|
|
3591
|
-
if (ch === "/" && next === "*") {
|
|
3592
|
-
inBlock = true;
|
|
3593
|
-
i++;
|
|
3594
|
-
continue;
|
|
3595
|
-
}
|
|
3596
|
-
out += ch;
|
|
3597
|
-
}
|
|
3598
|
-
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
3599
|
-
}
|
|
3600
|
-
/** `Object(value) === value` holds for exactly the blocks and lists
|
|
3601
|
-
* `JSON.parse` produces; the `[object Object]` tag separates the two. */
|
|
3602
|
-
function isJsonBlock(value) {
|
|
3603
|
-
return Object(value) === value && Object.prototype.toString.call(value) === "[object Object]";
|
|
3604
|
-
}
|
|
3605
|
-
/** A block or a list. A declared `paths` OWNS the setting and blocks its
|
|
3606
|
-
* parents' even when it contributes nothing usable. */
|
|
3607
|
-
function isJsonKeyed(value) {
|
|
3608
|
-
return Object(value) === value;
|
|
3609
|
-
}
|
|
3610
|
-
/** A JSON string, excluding the boxed `String` object, which no path join
|
|
3611
|
-
* accepts. */
|
|
3612
|
-
function isJsonString(value) {
|
|
3613
|
-
return Object(value) !== value && Object.prototype.toString.call(value) === "[object String]";
|
|
3614
|
-
}
|
|
3615
|
-
function decodePaths(value) {
|
|
3616
|
-
if (!isJsonKeyed(value)) return null;
|
|
3617
|
-
const decoded = /* @__PURE__ */ new Map();
|
|
3618
|
-
for (const [pattern, targets] of Object.entries(value)) {
|
|
3619
|
-
const [first] = Array.isArray(targets) ? targets : [];
|
|
3620
|
-
if (isJsonString(first)) decoded.set(pattern, first);
|
|
3621
|
-
}
|
|
3622
|
-
return decoded;
|
|
3623
|
-
}
|
|
3624
|
-
/** TypeScript accepts one specifier or an array; a non-string member names
|
|
3625
|
-
* no config and is dropped here. */
|
|
3626
|
-
function decodeExtends(value) {
|
|
3627
|
-
return (Array.isArray(value) ? value : [value]).filter(isJsonString);
|
|
3628
|
-
}
|
|
3629
|
-
function readConfig(path$2) {
|
|
3630
|
-
let raw;
|
|
3631
|
-
try {
|
|
3632
|
-
raw = (0, fs.readFileSync)(path$2, "utf-8");
|
|
3633
|
-
} catch {
|
|
3634
|
-
return null;
|
|
3635
|
-
}
|
|
3636
|
-
let parsed;
|
|
3637
|
-
try {
|
|
3638
|
-
parsed = JSON.parse(stripJsonc(raw));
|
|
3639
|
-
} catch {
|
|
3640
|
-
return null;
|
|
3641
|
-
}
|
|
3642
|
-
const root = isJsonBlock(parsed) ? parsed : {};
|
|
3643
|
-
const compilerOptions = isJsonBlock(root.compilerOptions) ? root.compilerOptions : {};
|
|
3644
|
-
return {
|
|
3645
|
-
dir: (0, path.dirname)(path$2),
|
|
3646
|
-
baseUrl: isJsonString(compilerOptions.baseUrl) ? compilerOptions.baseUrl : null,
|
|
3647
|
-
paths: decodePaths(compilerOptions.paths),
|
|
3648
|
-
extends: decodeExtends(root.extends)
|
|
3649
|
-
};
|
|
3650
|
-
}
|
|
3651
|
-
function resolveExtendsTarget(specifier, fromDir) {
|
|
3652
|
-
if (specifier.startsWith(".") || (0, path.isAbsolute)(specifier)) {
|
|
3653
|
-
const base = (0, path.resolve)(fromDir, specifier);
|
|
3654
|
-
for (const candidate of [
|
|
3655
|
-
base,
|
|
3656
|
-
`${base}.json`,
|
|
3657
|
-
(0, path.join)(base, "tsconfig.json")
|
|
3658
|
-
]) if ((0, fs.existsSync)(candidate)) return candidate;
|
|
3659
|
-
return null;
|
|
3660
|
-
}
|
|
3661
|
-
try {
|
|
3662
|
-
return require.resolve(specifier.endsWith(".json") ? specifier : `${specifier}/tsconfig.json`, { paths: [fromDir] });
|
|
3663
|
-
} catch {
|
|
3664
|
-
try {
|
|
3665
|
-
return require.resolve(specifier, { paths: [fromDir] });
|
|
3666
|
-
} catch {
|
|
3667
|
-
return null;
|
|
3668
|
-
}
|
|
3669
|
-
}
|
|
3670
|
-
}
|
|
3671
|
-
/** Child-first flattened extends chain starting at `entryPath`. */
|
|
3672
|
-
function loadChain(entryPath) {
|
|
3673
|
-
const chain = [];
|
|
3674
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3675
|
-
const queue = [entryPath];
|
|
3676
|
-
while (queue.length > 0) {
|
|
3677
|
-
const path$3 = queue.shift();
|
|
3678
|
-
const key = (0, path.resolve)(path$3);
|
|
3679
|
-
if (visited.has(key)) continue;
|
|
3680
|
-
visited.add(key);
|
|
3681
|
-
const node = readConfig(key);
|
|
3682
|
-
if (!node) continue;
|
|
3683
|
-
chain.push(node);
|
|
3684
|
-
for (const parent of node.extends) {
|
|
3685
|
-
const resolved = resolveExtendsTarget(parent, node.dir);
|
|
3686
|
-
if (resolved) queue.push(resolved);
|
|
3687
|
-
}
|
|
3688
|
-
}
|
|
3689
|
-
return chain;
|
|
3690
|
-
}
|
|
3691
|
-
/**
|
|
3692
|
-
* The project's tsconfig path aliases as `buildPathAliasesJson` pairs; empty
|
|
3693
|
-
* when no readable tsconfig, or no usable `paths`, exists.
|
|
3694
|
-
*/
|
|
3695
|
-
function readTsconfigAliasPairs(rootDir) {
|
|
3696
|
-
const chain = loadChain((0, path.join)(rootDir, "tsconfig.json"));
|
|
3697
|
-
if (chain.length === 0) return [];
|
|
3698
|
-
const pathsOwner = chain.find((node) => node.paths !== null);
|
|
3699
|
-
const paths = pathsOwner?.paths ?? null;
|
|
3700
|
-
if (pathsOwner === void 0 || paths === null) return [];
|
|
3701
|
-
const baseOwner = chain.find((node) => node.baseUrl !== null);
|
|
3702
|
-
const baseUrl = baseOwner?.baseUrl ?? null;
|
|
3703
|
-
const base = baseOwner !== void 0 && baseUrl !== null ? (0, path.resolve)(baseOwner.dir, baseUrl) : pathsOwner.dir;
|
|
3704
|
-
const pairs = [];
|
|
3705
|
-
for (const [pattern, target] of paths) {
|
|
3706
|
-
const patternStars = pattern.split("*").length - 1;
|
|
3707
|
-
const targetStars = target.split("*").length - 1;
|
|
3708
|
-
if (pattern === "*" || patternStars > 1 || targetStars > 1) continue;
|
|
3709
|
-
if (pattern.endsWith("/*")) {
|
|
3710
|
-
if (!target.endsWith("/*")) continue;
|
|
3711
|
-
pairs.push({
|
|
3712
|
-
pattern: pattern.slice(0, -2),
|
|
3713
|
-
target: (0, path.resolve)(base, target.slice(0, -2)),
|
|
3714
|
-
kind: "prefix"
|
|
3715
|
-
});
|
|
3716
|
-
continue;
|
|
3717
|
-
}
|
|
3718
|
-
if (patternStars > 0 || targetStars > 0) continue;
|
|
3719
|
-
pairs.push({
|
|
3720
|
-
pattern,
|
|
3721
|
-
target: (0, path.resolve)(base, target),
|
|
3722
|
-
kind: "exact"
|
|
3723
|
-
});
|
|
3724
|
-
}
|
|
3725
|
-
return pairs;
|
|
3726
|
-
}
|
|
3727
|
-
//#endregion
|
|
3728
4046
|
//#region pipeline/error-diagnostics.ts
|
|
3729
4047
|
function assertNoErrorDiagnostics(diagnostics) {
|
|
3730
4048
|
const errors = (diagnostics ?? []).filter((diagnostic) => diagnostic.kind === "error");
|
|
@@ -4031,6 +4349,12 @@ Object.defineProperty(exports, "enforceExternalTokenContracts", {
|
|
|
4031
4349
|
return enforceExternalTokenContracts;
|
|
4032
4350
|
}
|
|
4033
4351
|
});
|
|
4352
|
+
Object.defineProperty(exports, "engineImportParser", {
|
|
4353
|
+
enumerable: true,
|
|
4354
|
+
get: function() {
|
|
4355
|
+
return engineImportParser;
|
|
4356
|
+
}
|
|
4357
|
+
});
|
|
4034
4358
|
Object.defineProperty(exports, "excludeCollectedPackages", {
|
|
4035
4359
|
enumerable: true,
|
|
4036
4360
|
get: function() {
|
|
@@ -4109,6 +4433,18 @@ Object.defineProperty(exports, "isExcludedPackageRelativePath", {
|
|
|
4109
4433
|
return isExcludedPackageRelativePath;
|
|
4110
4434
|
}
|
|
4111
4435
|
});
|
|
4436
|
+
Object.defineProperty(exports, "isJsonBlock", {
|
|
4437
|
+
enumerable: true,
|
|
4438
|
+
get: function() {
|
|
4439
|
+
return isJsonBlock;
|
|
4440
|
+
}
|
|
4441
|
+
});
|
|
4442
|
+
Object.defineProperty(exports, "isJsonString", {
|
|
4443
|
+
enumerable: true,
|
|
4444
|
+
get: function() {
|
|
4445
|
+
return isJsonString;
|
|
4446
|
+
}
|
|
4447
|
+
});
|
|
4112
4448
|
Object.defineProperty(exports, "isPathWithinRoot", {
|
|
4113
4449
|
enumerable: true,
|
|
4114
4450
|
get: function() {
|
|
@@ -4127,12 +4463,24 @@ Object.defineProperty(exports, "loadSystemConfig", {
|
|
|
4127
4463
|
return loadSystemConfig;
|
|
4128
4464
|
}
|
|
4129
4465
|
});
|
|
4466
|
+
Object.defineProperty(exports, "noKitFilesDiagnostics", {
|
|
4467
|
+
enumerable: true,
|
|
4468
|
+
get: function() {
|
|
4469
|
+
return noKitFilesDiagnostics;
|
|
4470
|
+
}
|
|
4471
|
+
});
|
|
4130
4472
|
Object.defineProperty(exports, "parseFilesJson", {
|
|
4131
4473
|
enumerable: true,
|
|
4132
4474
|
get: function() {
|
|
4133
4475
|
return parseFilesJson;
|
|
4134
4476
|
}
|
|
4135
4477
|
});
|
|
4478
|
+
Object.defineProperty(exports, "parseInternalWire", {
|
|
4479
|
+
enumerable: true,
|
|
4480
|
+
get: function() {
|
|
4481
|
+
return parseInternalWire;
|
|
4482
|
+
}
|
|
4483
|
+
});
|
|
4136
4484
|
Object.defineProperty(exports, "postProcessCss", {
|
|
4137
4485
|
enumerable: true,
|
|
4138
4486
|
get: function() {
|