@animus-ui/extract 0.1.20 → 0.1.22
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
|
@@ -2,16 +2,21 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import path, { basename, dirname, join, normalize, posix } from "node:path";
|
|
3
3
|
import { createHash } from "crypto";
|
|
4
4
|
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "fs";
|
|
5
|
-
import { dirname as dirname$1, extname, isAbsolute, join as join$1, relative, resolve } from "path";
|
|
5
|
+
import { dirname as dirname$1, extname, isAbsolute, join as join$1, relative, resolve, sep } from "path";
|
|
6
6
|
import { realpathSync as realpathSync$1 } from "node:fs";
|
|
7
7
|
import { createRequire as createRequire$1 } from "module";
|
|
8
8
|
import browserslist from "browserslist";
|
|
9
9
|
import { browserslistToTargets, transform } from "lightningcss";
|
|
10
|
-
import { dependsOnContext, foldInitialValue, isUnitlessProperty, substitutesValue } from "@animus-ui/properties";
|
|
10
|
+
import { componentValues, dependsOnContext, foldInitialValue, identifierAt, isSpace, isUnitlessProperty, someValue, substitutesValue, tokenize, variableReads } from "@animus-ui/properties";
|
|
11
11
|
//#region \0rolldown/runtime.js
|
|
12
12
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region pipeline/assemble-stylesheet.ts
|
|
15
|
+
/**
|
|
16
|
+
* The cascade layer order, outermost first: its one definition. The engine
|
|
17
|
+
* reads it from `crates/extract-v2/src/layer_order.json`, which
|
|
18
|
+
* `scripts/layer-order.ts --write` writes from here.
|
|
19
|
+
*/
|
|
15
20
|
const ANIMUS_LAYERS = [
|
|
16
21
|
"anm-global",
|
|
17
22
|
"anm-base",
|
|
@@ -159,6 +164,8 @@ function collectSelectorAliasDiagnostics(selectorAliasesJson) {
|
|
|
159
164
|
return diagnostics;
|
|
160
165
|
}
|
|
161
166
|
const UNREADABLE_SOURCE_FILE = "animus.ingestion.unreadable-source-file";
|
|
167
|
+
/** A kit the system extends resolved, yet discovery found none of its files. */
|
|
168
|
+
const NO_KIT_FILES = "animus.discovery.no-kit-files";
|
|
162
169
|
/** What a skipped source file costs: the analysis never sees what it
|
|
163
170
|
* renders. */
|
|
164
171
|
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";
|
|
@@ -182,6 +189,18 @@ function unreadableSourceDiagnostic(relPath, error) {
|
|
|
182
189
|
severity: severityFor(UNREADABLE_SOURCE_FILE)
|
|
183
190
|
};
|
|
184
191
|
}
|
|
192
|
+
/** One warning per kit the system extends that resolved, yet yielded no
|
|
193
|
+
* files: none of its components are extracted. */
|
|
194
|
+
function noKitFilesDiagnostics(outcomes) {
|
|
195
|
+
return outcomes.filter((record) => record.outcome === "empty").map((record) => ({
|
|
196
|
+
file: record.specifier,
|
|
197
|
+
component: "kit",
|
|
198
|
+
kind: "warn",
|
|
199
|
+
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",
|
|
200
|
+
code: NO_KIT_FILES,
|
|
201
|
+
severity: severityFor(NO_KIT_FILES)
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
185
204
|
/** The collision entry code minted by the system package's merge. */
|
|
186
205
|
const VOCABULARY_COLLISION = "animus.vocabulary.collision";
|
|
187
206
|
/** A theme `@property` registration browsers would ignore; it is not emitted. */
|
|
@@ -201,17 +220,28 @@ const PROPERTY_UNREGISTERED_ANIMATION = "animus.property.unregistered-animation"
|
|
|
201
220
|
* it invalid at computed-value time. */
|
|
202
221
|
const PROPERTY_SELF_REFERENCE = "animus.property.self-reference";
|
|
203
222
|
/** A custom property that reads itself only inside another `var()`'s
|
|
204
|
-
* fallback
|
|
223
|
+
* fallback: cyclic wherever that fallback is used. */
|
|
205
224
|
const PROPERTY_FALLBACK_SELF_REFERENCE = "animus.property.fallback-self-reference";
|
|
225
|
+
/** Custom properties that `currentVar` writes in one rule, each reading
|
|
226
|
+
* another directly, so every property in the cycle is invalid at
|
|
227
|
+
* computed-value time. */
|
|
228
|
+
const PROPERTY_CURRENT_VAR_CYCLE = "animus.property.current-var-cycle";
|
|
206
229
|
/** A prefix renamed contextual variables without `prefixContextualVars`, so
|
|
207
230
|
* their declared names no longer resolve. */
|
|
208
231
|
const PREFIX_CONTEXTUAL_VARS_UNPREFIXED = "animus.prefix.contextual-vars-unprefixed";
|
|
209
232
|
/** Under `prefixContextualVars`, a final name that collides with a runtime
|
|
210
233
|
* transport variable or a theme variable of the same spelling. */
|
|
211
234
|
const PREFIX_NAME_CONFLICT = "animus.prefix.name-conflict";
|
|
235
|
+
/** A contextual variable declared on a scale that also holds a token of its
|
|
236
|
+
* name: scale reads resolve to the token. */
|
|
237
|
+
const PROPERTY_LEGACY_TOKEN_COLLISION = "animus.property.legacy-token-collision";
|
|
238
|
+
/** A theme property declared on a scale that also holds a token of its
|
|
239
|
+
* name. */
|
|
240
|
+
const PROPERTY_TOKEN_COLLISION = "animus.property.token-collision";
|
|
212
241
|
const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
|
|
213
242
|
[SELECTOR_UNSUPPORTED_SUBJECT, "error"],
|
|
214
243
|
[UNREADABLE_SOURCE_FILE, "error"],
|
|
244
|
+
[NO_KIT_FILES, "warn"],
|
|
215
245
|
[VOCABULARY_COLLISION, "warn"],
|
|
216
246
|
[VOCABULARY_LEGACY_VERB, "warn"],
|
|
217
247
|
[INVALID_PROPERTY_REGISTRATION, "error"],
|
|
@@ -220,8 +250,11 @@ const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
|
|
|
220
250
|
[PROPERTY_UNREGISTERED_ANIMATION, "info"],
|
|
221
251
|
[PROPERTY_SELF_REFERENCE, "warn"],
|
|
222
252
|
[PROPERTY_FALLBACK_SELF_REFERENCE, "warn"],
|
|
253
|
+
[PROPERTY_CURRENT_VAR_CYCLE, "warn"],
|
|
223
254
|
[PREFIX_CONTEXTUAL_VARS_UNPREFIXED, "warn"],
|
|
224
|
-
[PREFIX_NAME_CONFLICT, "error"]
|
|
255
|
+
[PREFIX_NAME_CONFLICT, "error"],
|
|
256
|
+
[PROPERTY_LEGACY_TOKEN_COLLISION, "warn"],
|
|
257
|
+
[PROPERTY_TOKEN_COLLISION, "error"]
|
|
225
258
|
]);
|
|
226
259
|
/** An unlisted code is `warn`: a witness kind from a newer system package
|
|
227
260
|
* must not fail the strict build of a host that predates its writer. */
|
|
@@ -294,10 +327,40 @@ function systemLoadDiagnostics(system) {
|
|
|
294
327
|
...vocabularyWitnessDiagnostics(system.vocabularyWitnessesJson),
|
|
295
328
|
...registrations,
|
|
296
329
|
...unprefixed,
|
|
297
|
-
...conflicts
|
|
330
|
+
...conflicts,
|
|
331
|
+
...tokenCollisionDiagnostics(system)
|
|
298
332
|
];
|
|
299
333
|
}
|
|
300
334
|
/**
|
|
335
|
+
* Each declared property whose name is also a token on a scale it is
|
|
336
|
+
* declared on. A scale read of that key, a prop value or a `{scale.key}`
|
|
337
|
+
* reference, resolves to the token, so the property is unreachable through
|
|
338
|
+
* the scale. A legacy declaration warns and the token stays the winner; any
|
|
339
|
+
* other is an error in every mode.
|
|
340
|
+
*/
|
|
341
|
+
function tokenCollisionDiagnostics(system) {
|
|
342
|
+
if (!system.propertyRecordsJson) return [];
|
|
343
|
+
const declared = parseInternalWire(system.propertyRecordsJson, "propertyRecordsJson (the theme's properties)").flatMap((record) => record.scales.map((scale) => ({
|
|
344
|
+
record,
|
|
345
|
+
scale,
|
|
346
|
+
path: `${scale}.${record.name}`
|
|
347
|
+
})));
|
|
348
|
+
if (declared.length === 0) return [];
|
|
349
|
+
const tokens = parseInternalWire(system.scalesJson, "scalesJson (the theme's scale tokens)");
|
|
350
|
+
return declared.filter(({ path }) => Object.hasOwn(tokens, path)).map(({ record, scale, path }) => {
|
|
351
|
+
const code = record.legacy ? PROPERTY_LEGACY_TOKEN_COLLISION : PROPERTY_TOKEN_COLLISION;
|
|
352
|
+
const declaration = record.legacy ? `the contextual variable ${record.name}, which declareContextualVars declares on ${scale},` : `the theme property ${record.name}, declared on ${scale},`;
|
|
353
|
+
return {
|
|
354
|
+
file: "system",
|
|
355
|
+
component: path,
|
|
356
|
+
kind: record.legacy ? "warn" : "error",
|
|
357
|
+
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})`,
|
|
358
|
+
code,
|
|
359
|
+
severity: severityFor(code)
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
301
364
|
* The single strict-escalation policy point for both extraction plugins:
|
|
302
365
|
* error-severity diagnostics throw together under `strict`, else warn.
|
|
303
366
|
*/
|
|
@@ -1789,6 +1852,188 @@ function createSourceIdentity(rootDir) {
|
|
|
1789
1852
|
};
|
|
1790
1853
|
}
|
|
1791
1854
|
//#endregion
|
|
1855
|
+
//#region pipeline/tsconfig-paths.ts
|
|
1856
|
+
/**
|
|
1857
|
+
* tsconfig `paths` → alias pairs, for drivers with no live bundler config.
|
|
1858
|
+
* The nearest config declaring `paths` wins wholesale — TS never merges.
|
|
1859
|
+
*/
|
|
1860
|
+
function stripJsonc(text) {
|
|
1861
|
+
let out = "";
|
|
1862
|
+
let inString = false;
|
|
1863
|
+
let inLine = false;
|
|
1864
|
+
let inBlock = false;
|
|
1865
|
+
for (let i = 0; i < text.length; i++) {
|
|
1866
|
+
const ch = text[i];
|
|
1867
|
+
const next = text[i + 1];
|
|
1868
|
+
if (inLine) {
|
|
1869
|
+
if (ch === "\n") {
|
|
1870
|
+
inLine = false;
|
|
1871
|
+
out += ch;
|
|
1872
|
+
}
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1875
|
+
if (inBlock) {
|
|
1876
|
+
if (ch === "*" && next === "/") {
|
|
1877
|
+
inBlock = false;
|
|
1878
|
+
i++;
|
|
1879
|
+
}
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1882
|
+
if (inString) {
|
|
1883
|
+
out += ch;
|
|
1884
|
+
if (ch === "\\") {
|
|
1885
|
+
out += next ?? "";
|
|
1886
|
+
i++;
|
|
1887
|
+
} else if (ch === "\"") inString = false;
|
|
1888
|
+
continue;
|
|
1889
|
+
}
|
|
1890
|
+
if (ch === "\"") {
|
|
1891
|
+
inString = true;
|
|
1892
|
+
out += ch;
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
if (ch === "/" && next === "/") {
|
|
1896
|
+
inLine = true;
|
|
1897
|
+
i++;
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
if (ch === "/" && next === "*") {
|
|
1901
|
+
inBlock = true;
|
|
1902
|
+
i++;
|
|
1903
|
+
continue;
|
|
1904
|
+
}
|
|
1905
|
+
out += ch;
|
|
1906
|
+
}
|
|
1907
|
+
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
1908
|
+
}
|
|
1909
|
+
/** `Object(value) === value` holds for exactly the blocks and lists
|
|
1910
|
+
* `JSON.parse` produces; the `[object Object]` tag separates the two. */
|
|
1911
|
+
function isJsonBlock(value) {
|
|
1912
|
+
return Object(value) === value && Object.prototype.toString.call(value) === "[object Object]";
|
|
1913
|
+
}
|
|
1914
|
+
/** A block or a list. A declared `paths` OWNS the setting and blocks its
|
|
1915
|
+
* parents' even when it contributes nothing usable. */
|
|
1916
|
+
function isJsonKeyed(value) {
|
|
1917
|
+
return Object(value) === value;
|
|
1918
|
+
}
|
|
1919
|
+
/** A JSON string, excluding the boxed `String` object, which no path join
|
|
1920
|
+
* accepts. */
|
|
1921
|
+
function isJsonString(value) {
|
|
1922
|
+
return Object(value) !== value && Object.prototype.toString.call(value) === "[object String]";
|
|
1923
|
+
}
|
|
1924
|
+
function decodePaths(value) {
|
|
1925
|
+
if (!isJsonKeyed(value)) return null;
|
|
1926
|
+
const decoded = /* @__PURE__ */ new Map();
|
|
1927
|
+
for (const [pattern, targets] of Object.entries(value)) {
|
|
1928
|
+
const [first] = Array.isArray(targets) ? targets : [];
|
|
1929
|
+
if (isJsonString(first)) decoded.set(pattern, first);
|
|
1930
|
+
}
|
|
1931
|
+
return decoded;
|
|
1932
|
+
}
|
|
1933
|
+
/** TypeScript accepts one specifier or an array; a non-string member names
|
|
1934
|
+
* no config and is dropped here. */
|
|
1935
|
+
function decodeExtends(value) {
|
|
1936
|
+
return (Array.isArray(value) ? value : [value]).filter(isJsonString);
|
|
1937
|
+
}
|
|
1938
|
+
function readConfig(path) {
|
|
1939
|
+
let raw;
|
|
1940
|
+
try {
|
|
1941
|
+
raw = readFileSync(path, "utf-8");
|
|
1942
|
+
} catch {
|
|
1943
|
+
return null;
|
|
1944
|
+
}
|
|
1945
|
+
let parsed;
|
|
1946
|
+
try {
|
|
1947
|
+
parsed = JSON.parse(stripJsonc(raw));
|
|
1948
|
+
} catch {
|
|
1949
|
+
return null;
|
|
1950
|
+
}
|
|
1951
|
+
const root = isJsonBlock(parsed) ? parsed : {};
|
|
1952
|
+
const compilerOptions = isJsonBlock(root.compilerOptions) ? root.compilerOptions : {};
|
|
1953
|
+
return {
|
|
1954
|
+
dir: dirname$1(path),
|
|
1955
|
+
baseUrl: isJsonString(compilerOptions.baseUrl) ? compilerOptions.baseUrl : null,
|
|
1956
|
+
paths: decodePaths(compilerOptions.paths),
|
|
1957
|
+
extends: decodeExtends(root.extends)
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
function resolveExtendsTarget(specifier, fromDir) {
|
|
1961
|
+
if (specifier.startsWith(".") || isAbsolute(specifier)) {
|
|
1962
|
+
const base = resolve(fromDir, specifier);
|
|
1963
|
+
for (const candidate of [
|
|
1964
|
+
base,
|
|
1965
|
+
`${base}.json`,
|
|
1966
|
+
join$1(base, "tsconfig.json")
|
|
1967
|
+
]) if (existsSync(candidate)) return candidate;
|
|
1968
|
+
return null;
|
|
1969
|
+
}
|
|
1970
|
+
try {
|
|
1971
|
+
return __require.resolve(specifier.endsWith(".json") ? specifier : `${specifier}/tsconfig.json`, { paths: [fromDir] });
|
|
1972
|
+
} catch {
|
|
1973
|
+
try {
|
|
1974
|
+
return __require.resolve(specifier, { paths: [fromDir] });
|
|
1975
|
+
} catch {
|
|
1976
|
+
return null;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
/** Child-first flattened extends chain starting at `entryPath`. */
|
|
1981
|
+
function loadChain(entryPath) {
|
|
1982
|
+
const chain = [];
|
|
1983
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1984
|
+
const queue = [entryPath];
|
|
1985
|
+
while (queue.length > 0) {
|
|
1986
|
+
const path = queue.shift();
|
|
1987
|
+
const key = resolve(path);
|
|
1988
|
+
if (visited.has(key)) continue;
|
|
1989
|
+
visited.add(key);
|
|
1990
|
+
const node = readConfig(key);
|
|
1991
|
+
if (!node) continue;
|
|
1992
|
+
chain.push(node);
|
|
1993
|
+
for (const parent of node.extends) {
|
|
1994
|
+
const resolved = resolveExtendsTarget(parent, node.dir);
|
|
1995
|
+
if (resolved) queue.push(resolved);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
return chain;
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* The project's tsconfig path aliases as `buildPathAliasesJson` pairs; empty
|
|
2002
|
+
* when no readable tsconfig, or no usable `paths`, exists.
|
|
2003
|
+
*/
|
|
2004
|
+
function readTsconfigAliasPairs(rootDir) {
|
|
2005
|
+
const chain = loadChain(join$1(rootDir, "tsconfig.json"));
|
|
2006
|
+
if (chain.length === 0) return [];
|
|
2007
|
+
const pathsOwner = chain.find((node) => node.paths !== null);
|
|
2008
|
+
const paths = pathsOwner?.paths ?? null;
|
|
2009
|
+
if (pathsOwner === void 0 || paths === null) return [];
|
|
2010
|
+
const baseOwner = chain.find((node) => node.baseUrl !== null);
|
|
2011
|
+
const baseUrl = baseOwner?.baseUrl ?? null;
|
|
2012
|
+
const base = baseOwner !== void 0 && baseUrl !== null ? resolve(baseOwner.dir, baseUrl) : pathsOwner.dir;
|
|
2013
|
+
const pairs = [];
|
|
2014
|
+
for (const [pattern, target] of paths) {
|
|
2015
|
+
const patternStars = pattern.split("*").length - 1;
|
|
2016
|
+
const targetStars = target.split("*").length - 1;
|
|
2017
|
+
if (pattern === "*" || patternStars > 1 || targetStars > 1) continue;
|
|
2018
|
+
if (pattern.endsWith("/*")) {
|
|
2019
|
+
if (!target.endsWith("/*")) continue;
|
|
2020
|
+
pairs.push({
|
|
2021
|
+
pattern: pattern.slice(0, -2),
|
|
2022
|
+
target: resolve(base, target.slice(0, -2)),
|
|
2023
|
+
kind: "prefix"
|
|
2024
|
+
});
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
if (patternStars > 0 || targetStars > 0) continue;
|
|
2028
|
+
pairs.push({
|
|
2029
|
+
pattern,
|
|
2030
|
+
target: resolve(base, target),
|
|
2031
|
+
kind: "exact"
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
return pairs;
|
|
2035
|
+
}
|
|
2036
|
+
//#endregion
|
|
1792
2037
|
//#region pipeline/discover-packages.ts
|
|
1793
2038
|
function findPackageRoot(absEntryPath) {
|
|
1794
2039
|
let pkgRoot = dirname$1(absEntryPath);
|
|
@@ -1842,6 +2087,77 @@ function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
|
|
|
1842
2087
|
const subpath = specifier.slice(packageName.length + 1);
|
|
1843
2088
|
return resolveAbsolutePathSpecifier(join$1(srcDir, subpath || "index"), extensionsSet);
|
|
1844
2089
|
}
|
|
2090
|
+
/**
|
|
2091
|
+
* A package's `sideEffects` field, decoded the way Vite reads it: a boolean
|
|
2092
|
+
* as written, or the list's globs, one with no `/` matching the basename
|
|
2093
|
+
* anywhere and any other rooted at the package. Undefined without a readable
|
|
2094
|
+
* field, or with glob syntax this matcher lacks (braces, classes, negation,
|
|
2095
|
+
* extglobs), which leaves the bundler's side-effectful default.
|
|
2096
|
+
*/
|
|
2097
|
+
function readPackageSideEffects(pkgRoot) {
|
|
2098
|
+
let manifest;
|
|
2099
|
+
try {
|
|
2100
|
+
manifest = JSON.parse(readFileSync(join$1(pkgRoot, "package.json"), "utf-8"));
|
|
2101
|
+
} catch {
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
const field = isJsonBlock(manifest) ? manifest.sideEffects : void 0;
|
|
2105
|
+
if (field === true || field === false) return field;
|
|
2106
|
+
if (!Array.isArray(field)) return void 0;
|
|
2107
|
+
const globs = field.filter(isJsonString);
|
|
2108
|
+
if (globs.some((glob) => /[{}[\]()!+@]/.test(glob))) return void 0;
|
|
2109
|
+
return globs.map((glob) => globToRegExp(glob.includes("/") ? glob.replace(/^\.?\//, "") : `**/${glob}`));
|
|
2110
|
+
}
|
|
2111
|
+
function matchesSideEffects(globs, pkgRoot, absFiles) {
|
|
2112
|
+
return absFiles.some((file) => {
|
|
2113
|
+
const relPath = relative(pkgRoot, file).split(sep).join("/");
|
|
2114
|
+
return globs.some((glob) => glob.test(relPath));
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
/** An exports target under the `import` condition, then `default`, as the
|
|
2118
|
+
* system loader reads it; an array offers its targets in order. */
|
|
2119
|
+
function exportTarget(value) {
|
|
2120
|
+
if (isJsonString(value)) return value;
|
|
2121
|
+
if (Array.isArray(value)) {
|
|
2122
|
+
for (const entry of value) {
|
|
2123
|
+
const target = exportTarget(entry);
|
|
2124
|
+
if (target !== null) return target;
|
|
2125
|
+
}
|
|
2126
|
+
return null;
|
|
2127
|
+
}
|
|
2128
|
+
if (!isJsonBlock(value)) return null;
|
|
2129
|
+
return exportTarget(value.import) ?? exportTarget(value.default);
|
|
2130
|
+
}
|
|
2131
|
+
/** Each exact `exports` entry of the package at `pkgRoot`, as its subpath
|
|
2132
|
+
* and absolute target; `*` patterns name no single file and are skipped. */
|
|
2133
|
+
function packageExportEntries(pkgRoot) {
|
|
2134
|
+
let manifest;
|
|
2135
|
+
try {
|
|
2136
|
+
manifest = JSON.parse(readFileSync(join$1(pkgRoot, "package.json"), "utf-8"));
|
|
2137
|
+
} catch {
|
|
2138
|
+
return [];
|
|
2139
|
+
}
|
|
2140
|
+
const exports = isJsonBlock(manifest) ? manifest.exports : void 0;
|
|
2141
|
+
if (exports === void 0 || exports === null) return [];
|
|
2142
|
+
const subpaths = isJsonBlock(exports) && Object.keys(exports).some((k) => k.startsWith(".")) ? exports : { ".": exports };
|
|
2143
|
+
const entries = [];
|
|
2144
|
+
for (const [subpath, value] of Object.entries(subpaths)) {
|
|
2145
|
+
if (!subpath.startsWith(".") || subpath.includes("*")) continue;
|
|
2146
|
+
const target = exportTarget(value);
|
|
2147
|
+
if (target !== null) entries.push([subpath, resolve(pkgRoot, target)]);
|
|
2148
|
+
}
|
|
2149
|
+
return entries;
|
|
2150
|
+
}
|
|
2151
|
+
/** Installed content is release content, and unpacking can leave any file
|
|
2152
|
+
* times, so only a package whose real path is outside `node_modules`, such
|
|
2153
|
+
* as a workspace link, is judged stale by them. */
|
|
2154
|
+
function isInstalledPackage(pkgRoot) {
|
|
2155
|
+
let realRoot = pkgRoot;
|
|
2156
|
+
try {
|
|
2157
|
+
realRoot = realpathSync(pkgRoot);
|
|
2158
|
+
} catch {}
|
|
2159
|
+
return realRoot.split(/[\\/]/).includes("node_modules");
|
|
2160
|
+
}
|
|
1845
2161
|
function distEntryIsStale(absEntry, srcDir, srcFiles) {
|
|
1846
2162
|
if (isPathWithinRoot(srcDir, absEntry)) return false;
|
|
1847
2163
|
let distMtime;
|
|
@@ -1862,6 +2178,20 @@ async function collectExternalPackageSources(opts) {
|
|
|
1862
2178
|
const pushed = /* @__PURE__ */ new Set();
|
|
1863
2179
|
const packageMap = {};
|
|
1864
2180
|
const sourceEntries = /* @__PURE__ */ new Map();
|
|
2181
|
+
const sourceEntrySideEffects = /* @__PURE__ */ new Map();
|
|
2182
|
+
/** `replaced` names the entry the redirect stands in for; it is only
|
|
2183
|
+
* needed, and so only resolved, for a glob list. */
|
|
2184
|
+
const redirect = async (specifier, srcEntry, pkgRoot, replaced) => {
|
|
2185
|
+
sourceEntries.set(specifier, srcEntry);
|
|
2186
|
+
const field = readPackageSideEffects(pkgRoot);
|
|
2187
|
+
if (field === void 0) return;
|
|
2188
|
+
if (field === true || field === false) {
|
|
2189
|
+
sourceEntrySideEffects.set(specifier, field);
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
const replacedEntry = await replaced();
|
|
2193
|
+
sourceEntrySideEffects.set(specifier, replacedEntry === null || matchesSideEffects(field, pkgRoot, [srcEntry, replacedEntry]));
|
|
2194
|
+
};
|
|
1865
2195
|
const packageDirs = [];
|
|
1866
2196
|
const dirOwnerSets = {};
|
|
1867
2197
|
const dirExtensions = {};
|
|
@@ -1899,7 +2229,8 @@ async function collectExternalPackageSources(opts) {
|
|
|
1899
2229
|
const srcEntry = sourceEntryForSpecifier(specifier, srcDir, extensionsSet);
|
|
1900
2230
|
if (srcEntry) {
|
|
1901
2231
|
packageMap[specifier] = relative(rootDir, srcEntry);
|
|
1902
|
-
|
|
2232
|
+
const replaced = absEntry;
|
|
2233
|
+
await redirect(specifier, srcEntry, pkgRoot, async () => replaced);
|
|
1903
2234
|
} else packageMap[specifier] = relative(rootDir, absEntry);
|
|
1904
2235
|
if (!isAbsolute(specifier)) {
|
|
1905
2236
|
const packageName = bareSpecifierPackageName(specifier);
|
|
@@ -1907,12 +2238,18 @@ async function collectExternalPackageSources(opts) {
|
|
|
1907
2238
|
const rootEntry = sourceEntryForSpecifier(packageName, srcDir, extensionsSet);
|
|
1908
2239
|
if (rootEntry) {
|
|
1909
2240
|
packageMap[packageName] = relative(rootDir, rootEntry);
|
|
1910
|
-
|
|
2241
|
+
await redirect(packageName, rootEntry, pkgRoot, async () => {
|
|
2242
|
+
try {
|
|
2243
|
+
return await resolveSpecifier(packageName);
|
|
2244
|
+
} catch {
|
|
2245
|
+
return null;
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
1911
2248
|
}
|
|
1912
2249
|
}
|
|
1913
2250
|
}
|
|
1914
2251
|
const pkgFiles = walkPackageSources(srcDir, extensionsSet);
|
|
1915
|
-
staleDist = distEntryIsStale(absEntry, srcDir, pkgFiles);
|
|
2252
|
+
staleDist = !isInstalledPackage(pkgRoot) && distEntryIsStale(absEntry, srcDir, pkgFiles);
|
|
1916
2253
|
for (const pkgFile of pkgFiles) {
|
|
1917
2254
|
const relPath = relative(rootDir, pkgFile);
|
|
1918
2255
|
if (alreadyIngested(relPath)) {
|
|
@@ -1972,6 +2309,15 @@ async function collectExternalPackageSources(opts) {
|
|
|
1972
2309
|
fileCount++;
|
|
1973
2310
|
}
|
|
1974
2311
|
}
|
|
2312
|
+
if (!isAbsolute(specifier)) {
|
|
2313
|
+
const packageName = bareSpecifierPackageName(specifier);
|
|
2314
|
+
for (const [subpath, target] of packageExportEntries(pkgRoot)) {
|
|
2315
|
+
const entrySpecifier = packageName + subpath.slice(1);
|
|
2316
|
+
const targetRelPath = relative(rootDir, target);
|
|
2317
|
+
if (entrySpecifier in packageMap || !alreadyIngested(targetRelPath)) continue;
|
|
2318
|
+
packageMap[entrySpecifier] = targetRelPath;
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
1975
2321
|
outcomes.push({
|
|
1976
2322
|
specifier,
|
|
1977
2323
|
outcome: staleDist ? "stale-dist" : fileCount > 0 ? "resolved" : "empty",
|
|
@@ -1982,6 +2328,7 @@ async function collectExternalPackageSources(opts) {
|
|
|
1982
2328
|
entries,
|
|
1983
2329
|
packageMap,
|
|
1984
2330
|
sourceEntries,
|
|
2331
|
+
sourceEntrySideEffects,
|
|
1985
2332
|
packageDirs,
|
|
1986
2333
|
dirOwnerSets,
|
|
1987
2334
|
dirExtensions,
|
|
@@ -2005,9 +2352,12 @@ function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
|
|
|
2005
2352
|
packageMap[specifier] = relTarget;
|
|
2006
2353
|
}
|
|
2007
2354
|
const sourceEntries = /* @__PURE__ */ new Map();
|
|
2355
|
+
const sourceEntrySideEffects = /* @__PURE__ */ new Map();
|
|
2008
2356
|
for (const [specifier, absEntry] of collected.sourceEntries) {
|
|
2009
2357
|
if (targetRejected(specifier, absEntry)) continue;
|
|
2010
2358
|
sourceEntries.set(specifier, absEntry);
|
|
2359
|
+
const sideEffects = collected.sourceEntrySideEffects.get(specifier);
|
|
2360
|
+
if (sideEffects !== void 0) sourceEntrySideEffects.set(specifier, sideEffects);
|
|
2011
2361
|
}
|
|
2012
2362
|
const dirOwnerSets = {};
|
|
2013
2363
|
for (const [dir, specs] of Object.entries(collected.dirOwnerSets)) {
|
|
@@ -2029,6 +2379,7 @@ function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
|
|
|
2029
2379
|
entries: collected.entries.filter((entry) => !rejectedSpecifiers.has(collected.fileOwners[entry.path])),
|
|
2030
2380
|
packageMap,
|
|
2031
2381
|
sourceEntries,
|
|
2382
|
+
sourceEntrySideEffects,
|
|
2032
2383
|
packageDirs: collected.packageDirs.filter((dir) => !rejectedDirs.includes(dir)),
|
|
2033
2384
|
dirOwnerSets,
|
|
2034
2385
|
dirExtensions,
|
|
@@ -2048,15 +2399,50 @@ function staleDistIncludesMessage(outcomes) {
|
|
|
2048
2399
|
if (stale.length === 0) return null;
|
|
2049
2400
|
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`;
|
|
2050
2401
|
}
|
|
2051
|
-
|
|
2402
|
+
/** The engine's own parse of a module's imports, through its
|
|
2403
|
+
* `extractFacts`; undefined for an engine without one. A parse that
|
|
2404
|
+
* panicked reports no imports, so it counts as no parse. */
|
|
2405
|
+
function engineImportParser(engine) {
|
|
2406
|
+
const { extractFacts } = engine;
|
|
2407
|
+
if (!extractFacts) return void 0;
|
|
2408
|
+
return (source, path) => {
|
|
2409
|
+
try {
|
|
2410
|
+
const facts = parseInternalWire(extractFacts(JSON.stringify([{
|
|
2411
|
+
path,
|
|
2412
|
+
source
|
|
2413
|
+
}])), "extractFacts").files[path];
|
|
2414
|
+
return facts && !facts.parsePanicked ? facts.imports : null;
|
|
2415
|
+
} catch {
|
|
2416
|
+
return null;
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
/** A pattern matching a call of any of `callees` as a whole name, or null
|
|
2421
|
+
* when there is none to anchor on. */
|
|
2422
|
+
function rootCallPattern(callees) {
|
|
2423
|
+
if (callees.length === 0) return null;
|
|
2424
|
+
return `(?<![a-zA-Z0-9_$.])(?:${callees.map((callee) => callee.replace(/[.$]/g, "\\$&")).join("|")})`;
|
|
2425
|
+
}
|
|
2426
|
+
function extractSystemFilePackages(systemFilePath, parseImports) {
|
|
2052
2427
|
let source;
|
|
2053
2428
|
try {
|
|
2054
2429
|
source = readFileSync(systemFilePath, "utf-8");
|
|
2055
2430
|
} catch {
|
|
2056
2431
|
return [];
|
|
2057
2432
|
}
|
|
2433
|
+
const parsed = parseImports?.(source, systemFilePath) ?? null;
|
|
2434
|
+
const parsedImports = parsed && parsed.length > 0 ? parsed : null;
|
|
2435
|
+
let rootCall = "createSystem";
|
|
2436
|
+
if (parsedImports) {
|
|
2437
|
+
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);
|
|
2438
|
+
rootCall = rootCallPattern([.../* @__PURE__ */ new Set([
|
|
2439
|
+
...parsedImports.filter((binding) => binding.imported === "createSystem").map((binding) => binding.local),
|
|
2440
|
+
...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`),
|
|
2441
|
+
...shadowed ? [] : ["createSystem"]
|
|
2442
|
+
])]);
|
|
2443
|
+
}
|
|
2058
2444
|
const identifiers = /* @__PURE__ */ new Set();
|
|
2059
|
-
const constructorRegex =
|
|
2445
|
+
const constructorRegex = rootCall === null ? null : new RegExp(`${rootCall}\\s*\\(\\s*\\{[^}]*?\\bincludes\\s*:\\s*\\[([^\\]]*)\\]`, "gs");
|
|
2060
2446
|
const chainRegex = /\.includes\s*\(\s*\[([^\]]*)\]\s*\)/gs;
|
|
2061
2447
|
const collectIdentifiers = (regex) => {
|
|
2062
2448
|
let match;
|
|
@@ -2068,7 +2454,7 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
2068
2454
|
}
|
|
2069
2455
|
}
|
|
2070
2456
|
};
|
|
2071
|
-
collectIdentifiers(constructorRegex);
|
|
2457
|
+
if (constructorRegex) collectIdentifiers(constructorRegex);
|
|
2072
2458
|
collectIdentifiers(chainRegex);
|
|
2073
2459
|
const IDENT_START_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*/;
|
|
2074
2460
|
const skipTrivia = (from) => {
|
|
@@ -2088,6 +2474,38 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
2088
2474
|
return pos;
|
|
2089
2475
|
}
|
|
2090
2476
|
};
|
|
2477
|
+
/** The index after the string literal opening at `from`. */
|
|
2478
|
+
const skipString = (from) => {
|
|
2479
|
+
const quote = source[from];
|
|
2480
|
+
let pos = from + 1;
|
|
2481
|
+
while (pos < source.length && source[pos] !== quote) pos += source[pos] === "\\" ? 2 : 1;
|
|
2482
|
+
return pos + 1;
|
|
2483
|
+
};
|
|
2484
|
+
/** The index after the bracketed span opening at `from`, balanced across
|
|
2485
|
+
* `<>`, `()`, `[]` and `{}`, or -1 when it never closes. An arrow type's
|
|
2486
|
+
* `=>` closes nothing. */
|
|
2487
|
+
const skipBalanced = (from) => {
|
|
2488
|
+
let depth = 0;
|
|
2489
|
+
let pos = from;
|
|
2490
|
+
while (pos < source.length) {
|
|
2491
|
+
const ch = source[pos];
|
|
2492
|
+
if (ch === "'" || ch === "\"" || ch === "`") {
|
|
2493
|
+
pos = skipString(pos);
|
|
2494
|
+
continue;
|
|
2495
|
+
}
|
|
2496
|
+
if (source.startsWith("//", pos) || source.startsWith("/*", pos)) {
|
|
2497
|
+
pos = skipTrivia(pos);
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
2500
|
+
if ("<([{".includes(ch)) depth++;
|
|
2501
|
+
else if (")]}".includes(ch) || ch === ">" && source[pos - 1] !== "=") {
|
|
2502
|
+
depth--;
|
|
2503
|
+
if (depth === 0) return pos + 1;
|
|
2504
|
+
}
|
|
2505
|
+
pos++;
|
|
2506
|
+
}
|
|
2507
|
+
return -1;
|
|
2508
|
+
};
|
|
2091
2509
|
const consumeChainLinks = (from) => {
|
|
2092
2510
|
let pos = from;
|
|
2093
2511
|
for (;;) {
|
|
@@ -2097,6 +2515,11 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
2097
2515
|
const method = IDENT_START_RE.exec(source.slice(cursor))?.[0];
|
|
2098
2516
|
if (method !== "extend" && method !== "from") return pos;
|
|
2099
2517
|
cursor = skipTrivia(cursor + method.length);
|
|
2518
|
+
if (source[cursor] === "<") {
|
|
2519
|
+
const afterTypeArguments = skipBalanced(cursor);
|
|
2520
|
+
if (afterTypeArguments === -1) return pos;
|
|
2521
|
+
cursor = skipTrivia(afterTypeArguments);
|
|
2522
|
+
}
|
|
2100
2523
|
if (source[cursor] !== "(") return pos;
|
|
2101
2524
|
cursor = skipTrivia(cursor + 1);
|
|
2102
2525
|
const base = IDENT_START_RE.exec(source.slice(cursor))?.[0];
|
|
@@ -2114,14 +2537,47 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
2114
2537
|
pos = cursor + 1;
|
|
2115
2538
|
}
|
|
2116
2539
|
};
|
|
2540
|
+
const bindingAt = /* @__PURE__ */ new Map();
|
|
2541
|
+
const aliasOf = /* @__PURE__ */ new Map();
|
|
2542
|
+
const declarationRe = /\b(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
2543
|
+
let declaration;
|
|
2544
|
+
while ((declaration = declarationRe.exec(source)) !== null) {
|
|
2545
|
+
const name = declaration[1];
|
|
2546
|
+
let cursor = skipTrivia(declaration.index + declaration[0].length);
|
|
2547
|
+
if (source[cursor] === ":") {
|
|
2548
|
+
cursor++;
|
|
2549
|
+
while (cursor < source.length) {
|
|
2550
|
+
const ch = source[cursor];
|
|
2551
|
+
if (ch === ";" || ch === ",") break;
|
|
2552
|
+
if (ch === "\n" && /^\s*(?:export\s+)?(?:const|let|var|function|class|type|interface|import)\b/.test(source.slice(cursor + 1, cursor + 80))) break;
|
|
2553
|
+
if (ch === "=" && source[cursor + 1] !== ">") break;
|
|
2554
|
+
if ("<([{".includes(ch) || ch === "'" || ch === "\"" || ch === "`") {
|
|
2555
|
+
const after = "<([{".includes(ch) ? skipBalanced(cursor) : skipString(cursor);
|
|
2556
|
+
if (after === -1) break;
|
|
2557
|
+
cursor = after;
|
|
2558
|
+
continue;
|
|
2559
|
+
}
|
|
2560
|
+
cursor = ch === "=" ? cursor + 2 : cursor + 1;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
if (source[cursor] !== "=" || source[cursor + 1] === "=") continue;
|
|
2564
|
+
const initializer = skipTrivia(cursor + 1);
|
|
2565
|
+
bindingAt.set(initializer, name);
|
|
2566
|
+
const target = IDENT_START_RE.exec(source.slice(initializer))?.[0];
|
|
2567
|
+
if (!target) continue;
|
|
2568
|
+
const rest = source.slice(initializer + target.length);
|
|
2569
|
+
if (/^[ \t]*(?:(?:as|satisfies)\s[^;\n]*)?(?:\/\/[^\n]*)?(?:;|\r?\n(?!\s*[.([?`])|$)/.test(rest)) aliasOf.set(name, target);
|
|
2570
|
+
}
|
|
2117
2571
|
const boundIdentifierBefore = (index) => {
|
|
2572
|
+
const declared = bindingAt.get(index);
|
|
2573
|
+
if (declared) return declared;
|
|
2118
2574
|
const match = /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*$/.exec(source.slice(0, index));
|
|
2119
2575
|
return match ? match[1] : null;
|
|
2120
2576
|
};
|
|
2121
2577
|
const chainRootIdentifiers = /* @__PURE__ */ new Set();
|
|
2122
|
-
const createSystemAnchor =
|
|
2578
|
+
const createSystemAnchor = rootCall === null ? null : new RegExp(`${rootCall}\\s*\\(`, "g");
|
|
2123
2579
|
let anchorMatch;
|
|
2124
|
-
while ((anchorMatch = createSystemAnchor
|
|
2580
|
+
while ((anchorMatch = createSystemAnchor?.exec(source) ?? null) !== null) {
|
|
2125
2581
|
let pos = anchorMatch.index + anchorMatch[0].length;
|
|
2126
2582
|
let depth = 1;
|
|
2127
2583
|
while (pos < source.length && depth > 0) {
|
|
@@ -2152,22 +2608,37 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
2152
2608
|
}
|
|
2153
2609
|
if (identifiers.size === 0) return [];
|
|
2154
2610
|
const importMap = /* @__PURE__ */ new Map();
|
|
2611
|
+
for (const binding of parsedImports ?? []) importMap.set(binding.local, binding.source);
|
|
2155
2612
|
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;
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
const
|
|
2163
|
-
|
|
2164
|
-
|
|
2613
|
+
if (!parsedImports) {
|
|
2614
|
+
let importMatch;
|
|
2615
|
+
while ((importMatch = importRegex.exec(source)) !== null) {
|
|
2616
|
+
const [, comboDefault, namedImports, defaultImport, specifier] = importMatch;
|
|
2617
|
+
if (comboDefault) importMap.set(comboDefault, specifier);
|
|
2618
|
+
if (defaultImport) importMap.set(defaultImport, specifier);
|
|
2619
|
+
if (namedImports) for (const binding of namedImports.split(",")) {
|
|
2620
|
+
const parts = binding.trim().split(/\s+as\s+/);
|
|
2621
|
+
const localName = (parts[1] || parts[0]).trim();
|
|
2622
|
+
if (localName) importMap.set(localName, specifier);
|
|
2623
|
+
}
|
|
2165
2624
|
}
|
|
2166
2625
|
}
|
|
2167
2626
|
const systemFileDir = dirname$1(systemFilePath);
|
|
2168
2627
|
const packages = /* @__PURE__ */ new Set();
|
|
2628
|
+
/** The import a local alias chain ends at, or the identifier itself. */
|
|
2629
|
+
const throughAliases = (id) => {
|
|
2630
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2631
|
+
let current = id;
|
|
2632
|
+
while (!importMap.has(current) && !seen.has(current)) {
|
|
2633
|
+
seen.add(current);
|
|
2634
|
+
const target = aliasOf.get(current);
|
|
2635
|
+
if (!target) break;
|
|
2636
|
+
current = target;
|
|
2637
|
+
}
|
|
2638
|
+
return current;
|
|
2639
|
+
};
|
|
2169
2640
|
for (const id of identifiers) {
|
|
2170
|
-
const specifier = importMap.get(id);
|
|
2641
|
+
const specifier = importMap.get(throughAliases(id));
|
|
2171
2642
|
if (!specifier) continue;
|
|
2172
2643
|
if (specifier.startsWith(".")) packages.add(resolve(systemFileDir, specifier));
|
|
2173
2644
|
else packages.add(specifier);
|
|
@@ -2230,7 +2701,7 @@ const URL_OPEN_RE = /url\(\s*(['"]?)/gi;
|
|
|
2230
2701
|
const IMAGE_SET_OPEN_RE = /(?:-webkit-)?image-set\(/gi;
|
|
2231
2702
|
const BARE_SPECIFIER_RE = /^[^'")\s]+/;
|
|
2232
2703
|
/** The index of the `)` that closes the `(` at `open`, outside strings. */
|
|
2233
|
-
function closingParen
|
|
2704
|
+
function closingParen(css, open) {
|
|
2234
2705
|
let depth = 0;
|
|
2235
2706
|
let quote = null;
|
|
2236
2707
|
for (let i = open; i < css.length; i += 1) {
|
|
@@ -2279,7 +2750,7 @@ function assetReferences(css) {
|
|
|
2279
2750
|
}
|
|
2280
2751
|
for (const open of css.matchAll(IMAGE_SET_OPEN_RE)) {
|
|
2281
2752
|
const paren = (open.index ?? 0) + open[0].length - 1;
|
|
2282
|
-
const close = closingParen
|
|
2753
|
+
const close = closingParen(css, paren);
|
|
2283
2754
|
let depth = 0;
|
|
2284
2755
|
let candidateStart = true;
|
|
2285
2756
|
for (let i = paren + 1; i < close; i += 1) {
|
|
@@ -2324,7 +2795,7 @@ function placeholdersInAssetCalls(css) {
|
|
|
2324
2795
|
if (!css.includes("animus-asset:")) return [];
|
|
2325
2796
|
const calls = [...css.matchAll(URL_OPEN_RE), ...css.matchAll(IMAGE_SET_OPEN_RE)].map((open) => {
|
|
2326
2797
|
const paren = css.indexOf("(", open.index ?? 0);
|
|
2327
|
-
return [paren, closingParen
|
|
2798
|
+
return [paren, closingParen(css, paren)];
|
|
2328
2799
|
});
|
|
2329
2800
|
const found = /* @__PURE__ */ new Set();
|
|
2330
2801
|
for (let at = css.indexOf(ASSET_PLACEHOLDER_PREFIX); at !== -1; at = css.indexOf(ASSET_PLACEHOLDER_PREFIX, at + 1)) {
|
|
@@ -2566,14 +3037,9 @@ function buildPathAliasesJson(pairs, rootDir) {
|
|
|
2566
3037
|
//#endregion
|
|
2567
3038
|
//#region pipeline/post-process-css.ts
|
|
2568
3039
|
function resolveLightningTargets(explicitTargets, rootDir) {
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
const detected = browserslist(void 0, { path: rootDir });
|
|
2573
|
-
queries = detected.length > 0 ? detected : browserslist("defaults");
|
|
2574
|
-
}
|
|
2575
|
-
const [firstQuery] = queries;
|
|
2576
|
-
return browserslistToTargets(firstQuery !== void 0 && firstQuery.includes(" ") ? browserslist(queries) : queries);
|
|
3040
|
+
if (explicitTargets) return browserslistToTargets(browserslist(explicitTargets));
|
|
3041
|
+
const detected = browserslist(void 0, { path: rootDir });
|
|
3042
|
+
return browserslistToTargets(detected.length > 0 ? detected : browserslist("defaults"));
|
|
2577
3043
|
}
|
|
2578
3044
|
function postProcessCss(css, opts) {
|
|
2579
3045
|
if (!css) return css;
|
|
@@ -2591,125 +3057,28 @@ function postProcessCss(css, opts) {
|
|
|
2591
3057
|
}
|
|
2592
3058
|
}
|
|
2593
3059
|
//#endregion
|
|
2594
|
-
//#region pipeline/property-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
const
|
|
2601
|
-
for (const
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
* The name starting at `from`, read as CSS tokenizes an identifier: ASCII
|
|
2614
|
-
* letters, digits, `-` and `_`, every non-ASCII code point, and escapes.
|
|
2615
|
-
*/
|
|
2616
|
-
function readCustomPropertyName(css, from) {
|
|
2617
|
-
let end = from;
|
|
2618
|
-
let name = "";
|
|
2619
|
-
while (end < css.length) {
|
|
2620
|
-
if (isNameChar(css[end])) {
|
|
2621
|
-
name += css[end];
|
|
2622
|
-
end += 1;
|
|
2623
|
-
continue;
|
|
2624
|
-
}
|
|
2625
|
-
const escape = escapeAt(css, end);
|
|
2626
|
-
if (escape === null) break;
|
|
2627
|
-
name += escape.codePoint;
|
|
2628
|
-
end += escape.length;
|
|
2629
|
-
}
|
|
2630
|
-
return {
|
|
2631
|
-
end,
|
|
2632
|
-
name
|
|
2633
|
-
};
|
|
2634
|
-
}
|
|
2635
|
-
/** A valid escape at `at`: up to six hex digits and one following
|
|
2636
|
-
* whitespace, or any other code point but a newline. */
|
|
2637
|
-
function escapeAt(css, at) {
|
|
2638
|
-
if (css[at] !== "\\") return null;
|
|
2639
|
-
const next = css.codePointAt(at + 1);
|
|
2640
|
-
if (next === void 0 || next === 10 || next === 13 || next === 12) return null;
|
|
2641
|
-
const digits = /^[0-9a-fA-F]{1,6}/.exec(css.slice(at + 1, at + 7))?.[0];
|
|
2642
|
-
if (digits === void 0) return {
|
|
2643
|
-
codePoint: String.fromCodePoint(next),
|
|
2644
|
-
length: 1 + String.fromCodePoint(next).length
|
|
2645
|
-
};
|
|
2646
|
-
const after = css.slice(at + 1 + digits.length);
|
|
2647
|
-
const space = after.startsWith("\r\n") ? 2 : /^[ \t\n\r\f]/.test(after) ? 1 : 0;
|
|
2648
|
-
const value = parseInt(digits, 16);
|
|
2649
|
-
const valid = value !== 0 && value <= 1114111 && (value < 55296 || value > 57343);
|
|
2650
|
-
return {
|
|
2651
|
-
codePoint: String.fromCodePoint(valid ? value : 65533),
|
|
2652
|
-
length: 1 + digits.length + space
|
|
2653
|
-
};
|
|
2654
|
-
}
|
|
2655
|
-
/**
|
|
2656
|
-
* Gives every custom-property token that names a managed property its final
|
|
2657
|
-
* name, in one pass: declaration keys, `var()` reads at any depth (fallbacks
|
|
2658
|
-
* included), `@property` names, transition-property lists and style queries.
|
|
2659
|
-
* A name is read whole, escapes decoded, so an undeclared name that starts
|
|
2660
|
-
* with a managed one is never renamed in part. Quoted strings, `url()` and
|
|
2661
|
-
* comments are copied unchanged.
|
|
2662
|
-
*/
|
|
2663
|
-
function renameCustomProperties(css, names) {
|
|
2664
|
-
let out = "";
|
|
2665
|
-
let i = 0;
|
|
2666
|
-
while (i < css.length) {
|
|
2667
|
-
const c = css[i];
|
|
2668
|
-
if (c === "\"" || c === "'") {
|
|
2669
|
-
const end = closingQuote$1(css, i);
|
|
2670
|
-
out += css.slice(i, end);
|
|
2671
|
-
i = end;
|
|
2672
|
-
} else if (css.startsWith("/*", i)) {
|
|
2673
|
-
const close = css.indexOf("*/", i + 2);
|
|
2674
|
-
const end = close === -1 ? css.length : close + 2;
|
|
2675
|
-
out += css.slice(i, end);
|
|
2676
|
-
i = end;
|
|
2677
|
-
} else if (/^url\(/i.test(css.slice(i, i + 4)) && !isNameChar(css[i - 1])) {
|
|
2678
|
-
const end = closingUrl(css, i + 4);
|
|
2679
|
-
out += css.slice(i, end);
|
|
2680
|
-
i = end;
|
|
2681
|
-
} else if (css.startsWith("--", i) && !isNameChar(css[i - 1])) {
|
|
2682
|
-
const { end, name } = readCustomPropertyName(css, i + 2);
|
|
2683
|
-
const identity = names.identity(name);
|
|
2684
|
-
const final = identity === void 0 ? void 0 : names.finalName(identity);
|
|
2685
|
-
out += final === void 0 ? css.slice(i, end) : `--${final}`;
|
|
2686
|
-
i = end;
|
|
2687
|
-
} else {
|
|
2688
|
-
out += c;
|
|
2689
|
-
i += 1;
|
|
2690
|
-
}
|
|
3060
|
+
//#region pipeline/property-registrations.ts
|
|
3061
|
+
const PROPERTY_AT_RULE = /@property\s+/gi;
|
|
3062
|
+
const RULE_BODY = /\s*\{[^}]*\}\n?/y;
|
|
3063
|
+
/** Each `@property` rule, through one newline after it, with its name read
|
|
3064
|
+
* as CSS tokenizes an identifier: escapes decoded, non-ASCII included. */
|
|
3065
|
+
function propertyRules(css) {
|
|
3066
|
+
const rules = [];
|
|
3067
|
+
for (const match of css.matchAll(PROPERTY_AT_RULE)) {
|
|
3068
|
+
if (match.index < (rules.at(-1)?.end ?? 0)) continue;
|
|
3069
|
+
const name = identifierAt(css, match.index + match[0].length);
|
|
3070
|
+
if (!name?.name.startsWith("--")) continue;
|
|
3071
|
+
RULE_BODY.lastIndex = name.end;
|
|
3072
|
+
const body = RULE_BODY.exec(css);
|
|
3073
|
+
if (body === null) continue;
|
|
3074
|
+
rules.push({
|
|
3075
|
+
start: match.index,
|
|
3076
|
+
end: name.end + body[0].length,
|
|
3077
|
+
name: name.name
|
|
3078
|
+
});
|
|
2691
3079
|
}
|
|
2692
|
-
return
|
|
2693
|
-
}
|
|
2694
|
-
/** A name code point, or a UTF-16 unit of a non-ASCII one. */
|
|
2695
|
-
function isNameChar(c) {
|
|
2696
|
-
return c !== void 0 && (NAME_CHAR.test(c) || c.charCodeAt(0) >= 128);
|
|
3080
|
+
return rules;
|
|
2697
3081
|
}
|
|
2698
|
-
function closingQuote$1(css, open) {
|
|
2699
|
-
const quote = css[open];
|
|
2700
|
-
let i = open + 1;
|
|
2701
|
-
while (i < css.length && css[i] !== quote) i += css[i] === "\\" ? 2 : 1;
|
|
2702
|
-
return Math.min(i + 1, css.length);
|
|
2703
|
-
}
|
|
2704
|
-
/** The index after the `)` that closes an unquoted or quoted `url(`. */
|
|
2705
|
-
function closingUrl(css, from) {
|
|
2706
|
-
let i = from;
|
|
2707
|
-
while (i < css.length && css[i] !== ")") i = css[i] === "\"" || css[i] === "'" ? closingQuote$1(css, i) : i + 1;
|
|
2708
|
-
return Math.min(i + 1, css.length);
|
|
2709
|
-
}
|
|
2710
|
-
//#endregion
|
|
2711
|
-
//#region pipeline/property-registrations.ts
|
|
2712
|
-
const PROPERTY_RULE = /@property\s+(--[\w-]+)\s*\{[^}]*\}\n?/g;
|
|
2713
3082
|
/** The data types a registration's `syntax` may name. */
|
|
2714
3083
|
const SYNTAX_TYPES = /* @__PURE__ */ new Set([
|
|
2715
3084
|
"angle",
|
|
@@ -2734,30 +3103,35 @@ const MATH_FUNCTION = /\b(?:calc|min|max|clamp)\(/i;
|
|
|
2734
3103
|
/**
|
|
2735
3104
|
* Removes the `@property` rules browsers would ignore — an unknown syntax
|
|
2736
3105
|
* component, a syntax other than `*` without an initial value, an initial
|
|
2737
|
-
* value that substitutes another value or depends on context,
|
|
2738
|
-
*
|
|
2739
|
-
*
|
|
2740
|
-
* computed value, which
|
|
3106
|
+
* value that substitutes another value or depends on context, math that
|
|
3107
|
+
* breaks calculation syntax, or a value the syntax does not accept.
|
|
3108
|
+
* Minifying a stylesheet that contains such a rule can also fail. An
|
|
3109
|
+
* all-absolute math initial value is written as its computed value, which
|
|
3110
|
+
* the minifier reads.
|
|
2741
3111
|
*/
|
|
2742
3112
|
function splitInvalidPropertyRegistrations(css) {
|
|
2743
3113
|
const invalid = [];
|
|
3114
|
+
let kept = "";
|
|
3115
|
+
let copied = 0;
|
|
3116
|
+
for (const { start, end, name } of propertyRules(css)) {
|
|
3117
|
+
const checked = checkRegistration(css.slice(start, end));
|
|
3118
|
+
kept += css.slice(copied, start);
|
|
3119
|
+
if ("rule" in checked) kept += checked.rule;
|
|
3120
|
+
else invalid.push({
|
|
3121
|
+
name,
|
|
3122
|
+
reason: checked.reason
|
|
3123
|
+
});
|
|
3124
|
+
copied = end;
|
|
3125
|
+
}
|
|
2744
3126
|
return {
|
|
2745
|
-
css: css.
|
|
2746
|
-
const checked = checkRegistration(rule);
|
|
2747
|
-
if ("rule" in checked) return checked.rule;
|
|
2748
|
-
invalid.push({
|
|
2749
|
-
name,
|
|
2750
|
-
reason: checked.reason
|
|
2751
|
-
});
|
|
2752
|
-
return "";
|
|
2753
|
-
}),
|
|
3127
|
+
css: kept + css.slice(copied),
|
|
2754
3128
|
invalid
|
|
2755
3129
|
};
|
|
2756
3130
|
}
|
|
2757
3131
|
/** The registrations in `css`, by custom-property name. */
|
|
2758
3132
|
function propertyRegistrations(css) {
|
|
2759
3133
|
const registrations = /* @__PURE__ */ new Map();
|
|
2760
|
-
for (const
|
|
3134
|
+
for (const { start, end, name } of propertyRules(css)) registrations.set(name, readRegistration(css.slice(start, end)));
|
|
2761
3135
|
return registrations;
|
|
2762
3136
|
}
|
|
2763
3137
|
function readRegistration(rule) {
|
|
@@ -2772,6 +3146,7 @@ function checkRegistration(rule) {
|
|
|
2772
3146
|
const { syntax, initialValue } = readRegistration(rule);
|
|
2773
3147
|
if (initialValue !== void 0 && substitutesValue(initialValue)) return { reason: `initialValue "${initialValue}" substitutes another value; var(), env() and attr() are not allowed in an initial value` };
|
|
2774
3148
|
let kept = rule;
|
|
3149
|
+
let folded;
|
|
2775
3150
|
if (syntax !== "*") {
|
|
2776
3151
|
const unknown = syntax.split("|").map((component) => ({
|
|
2777
3152
|
text: component.trim(),
|
|
@@ -2780,9 +3155,10 @@ function checkRegistration(rule) {
|
|
|
2780
3155
|
if (unknown !== void 0) return { reason: `syntax "${syntax}" has the unknown component "${unknown.text}"` };
|
|
2781
3156
|
if (initialValue === void 0) return { reason: `syntax "${syntax}" needs an initialValue` };
|
|
2782
3157
|
if (dependsOnContext(initialValue)) return { reason: `initialValue "${initialValue}" depends on context; font- and container-relative units are not allowed` };
|
|
2783
|
-
|
|
2784
|
-
if (folded
|
|
2785
|
-
if (folded
|
|
3158
|
+
folded = foldInitialValue(syntax, initialValue);
|
|
3159
|
+
if (folded?.kind === "invalid") return { reason: `initialValue "${initialValue}" is not a valid calculation: ${folded.reason}` };
|
|
3160
|
+
if (folded?.kind === "rejected") return { reason: `initialValue "${initialValue}" computes to the ${folded.type} ${folded.text}, which syntax "${syntax}" does not accept` };
|
|
3161
|
+
if (folded?.kind === "folded") kept = rule.replace(INITIAL_VALUE, `$1${folded.text}`);
|
|
2786
3162
|
}
|
|
2787
3163
|
try {
|
|
2788
3164
|
transform({
|
|
@@ -2792,6 +3168,7 @@ function checkRegistration(rule) {
|
|
|
2792
3168
|
return { rule: kept };
|
|
2793
3169
|
} catch (error) {
|
|
2794
3170
|
const detail = error instanceof Error ? error.message : String(error);
|
|
3171
|
+
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})` };
|
|
2795
3172
|
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` };
|
|
2796
3173
|
return { reason: `the CSS parser rejects \`${kept.trim()}\` (${detail})` };
|
|
2797
3174
|
}
|
|
@@ -2801,13 +3178,18 @@ function checkRegistration(rule) {
|
|
|
2801
3178
|
/**
|
|
2802
3179
|
* Reports custom properties that will not behave as their CSS suggests: a
|
|
2803
3180
|
* property that refers to itself, a fallback a registered initial value
|
|
2804
|
-
* applies ahead of, and an animated property that cannot interpolate.
|
|
2805
|
-
* are read
|
|
2806
|
-
*
|
|
3181
|
+
* applies ahead of, and an animated property that cannot interpolate. Sheets
|
|
3182
|
+
* are read through the shared CSS tokenizer, as the prefix pass reads them:
|
|
3183
|
+
* comments are trivia, quoted text holds no names, and escapes and function
|
|
3184
|
+
* case are decoded. The CSS itself is never changed.
|
|
2807
3185
|
*/
|
|
2808
3186
|
function checkCustomProperties(input) {
|
|
2809
3187
|
const registered = propertyRegistrations(input.system.variableCss);
|
|
2810
|
-
const
|
|
3188
|
+
const declaredNames = input.system.contextualProperties ?? [];
|
|
3189
|
+
const declared = new Set(declaredNames);
|
|
3190
|
+
const withInitialValue = [...registered].filter(([, registration]) => registration.initialValue !== void 0).map(([name]) => name);
|
|
3191
|
+
const writers = currentVarWriters(input.system.propConfigJson);
|
|
3192
|
+
const currentVars = new Set(writers.keys());
|
|
2811
3193
|
const findings = [];
|
|
2812
3194
|
const sheets = [{
|
|
2813
3195
|
css: input.componentCss,
|
|
@@ -2817,9 +3199,20 @@ function checkCustomProperties(input) {
|
|
|
2817
3199
|
owner: globalOwner
|
|
2818
3200
|
}];
|
|
2819
3201
|
for (const { css, owner } of sheets) {
|
|
2820
|
-
if (
|
|
3202
|
+
if (!mayHoldFinding(css, withInitialValue, declaredNames, currentVars)) continue;
|
|
2821
3203
|
const parsed = declarations(css);
|
|
2822
|
-
const
|
|
3204
|
+
for (const cycle of currentVarCycles(parsed, currentVars)) {
|
|
3205
|
+
const [first] = cycle;
|
|
3206
|
+
const names = cycle.map((d) => d.property);
|
|
3207
|
+
const props = [...new Set(cycle.flatMap((d) => writers.get(d.property) ?? []))];
|
|
3208
|
+
findings.push({
|
|
3209
|
+
code: PROPERTY_CURRENT_VAR_CYCLE,
|
|
3210
|
+
property: first.property,
|
|
3211
|
+
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`,
|
|
3212
|
+
owner: owner(first)
|
|
3213
|
+
});
|
|
3214
|
+
}
|
|
3215
|
+
const discreteRules = new Set(parsed.filter((d) => d.property === "transition-behavior" && topLevelItems(d.values).some(isDiscrete)).map((d) => d.rule));
|
|
2823
3216
|
for (const declaration of parsed) {
|
|
2824
3217
|
const found = (code, property, message) => findings.push({
|
|
2825
3218
|
code,
|
|
@@ -2827,16 +3220,26 @@ function checkCustomProperties(input) {
|
|
|
2827
3220
|
message,
|
|
2828
3221
|
owner: owner(declaration)
|
|
2829
3222
|
});
|
|
2830
|
-
|
|
3223
|
+
const reads = variableReads(declaration.values);
|
|
3224
|
+
if (isSelfReference(declaration, reads)) {
|
|
2831
3225
|
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`);
|
|
2832
3226
|
continue;
|
|
2833
3227
|
}
|
|
2834
|
-
if (
|
|
2835
|
-
|
|
3228
|
+
if (readsItselfDirectly(declaration, reads)) {
|
|
3229
|
+
const { property, value } = declaration;
|
|
3230
|
+
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`);
|
|
3231
|
+
continue;
|
|
3232
|
+
}
|
|
3233
|
+
if (readsItselfOnlyInFallback(declaration, reads)) {
|
|
3234
|
+
const { property, value } = declaration;
|
|
3235
|
+
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`);
|
|
3236
|
+
}
|
|
3237
|
+
for (const read of reads) {
|
|
2836
3238
|
const registration = registered.get(read.name);
|
|
2837
3239
|
if (read.fallback === void 0 || registration?.initialValue === void 0) continue;
|
|
2838
|
-
const
|
|
2839
|
-
|
|
3240
|
+
const { fallback } = read;
|
|
3241
|
+
const chain = reads.some((other) => other.start === fallback.first);
|
|
3242
|
+
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`);
|
|
2840
3243
|
}
|
|
2841
3244
|
for (const animated of animatedProperties(declaration, discreteRules)) {
|
|
2842
3245
|
if (animated.discrete || !declared.has(animated.name)) continue;
|
|
@@ -2848,195 +3251,244 @@ function checkCustomProperties(input) {
|
|
|
2848
3251
|
}
|
|
2849
3252
|
return toDiagnostics(findings);
|
|
2850
3253
|
}
|
|
2851
|
-
/**
|
|
3254
|
+
/**
|
|
3255
|
+
* Whether a sheet can hold a finding, shown by its text before anything is
|
|
3256
|
+
* parsed: a custom property reading its own name, a `var(` with a fallback
|
|
3257
|
+
* of a registration that has an initial value, a transition or keyframes
|
|
3258
|
+
* and a declared contextual variable's name, or a `var(` of a currentVar name
|
|
3259
|
+
* when two or more props write one. An escape or a comment can hide
|
|
3260
|
+
* a name or a function from the text, so a sheet with either is always
|
|
3261
|
+
* parsed.
|
|
3262
|
+
*/
|
|
3263
|
+
function mayHoldFinding(css, withInitialValue, declared, currentVars) {
|
|
3264
|
+
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));
|
|
3265
|
+
}
|
|
3266
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3267
|
+
/** A custom-property declaration key, as the text shows one: anything but
|
|
3268
|
+
* CSS whitespace and the characters that end a name, so a non-ASCII space
|
|
3269
|
+
* stays part of the name, as CSS reads it. */
|
|
3270
|
+
const CUSTOM_PROPERTY_KEY = /(--[^\t\n\f\r :;{}()"'\\/]+)[\t\n\f\r ]*:/g;
|
|
3271
|
+
const VAR_OPEN = /var\(\s*/iy;
|
|
3272
|
+
const isNameCode = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 45 || c === 95 || c >= 128;
|
|
3273
|
+
/**
|
|
3274
|
+
* Whether a custom-property declaration has `var(` of its own whole name in
|
|
3275
|
+
* its value, bounded as the declaration scanner bounds one: by a `;`, `{` or
|
|
3276
|
+
* `}` outside parentheses, brackets and quoted text. `var` matches in any
|
|
3277
|
+
* case, so this errs toward parsing.
|
|
3278
|
+
*/
|
|
3279
|
+
function readsOwnName(css) {
|
|
3280
|
+
for (const match of css.matchAll(CUSTOM_PROPERTY_KEY)) {
|
|
3281
|
+
const name = match[1];
|
|
3282
|
+
let depth = 0;
|
|
3283
|
+
let quote = -1;
|
|
3284
|
+
for (let i = match.index + match[0].length; i < css.length; i += 1) {
|
|
3285
|
+
const c = css.charCodeAt(i);
|
|
3286
|
+
if (quote !== -1) {
|
|
3287
|
+
if (c === quote || c === 10 || c === 13 || c === 12) quote = -1;
|
|
3288
|
+
} else if (c === 34 || c === 39) quote = c;
|
|
3289
|
+
else if (c === 40 || c === 91) depth += 1;
|
|
3290
|
+
else if (c === 41 || c === 93) depth = Math.max(0, depth - 1);
|
|
3291
|
+
else if (depth === 0 && (c === 59 || c === 123 || c === 125)) break;
|
|
3292
|
+
else if (c === 118 || c === 86) {
|
|
3293
|
+
VAR_OPEN.lastIndex = i;
|
|
3294
|
+
const at = VAR_OPEN.test(css) ? VAR_OPEN.lastIndex : -1;
|
|
3295
|
+
if (at !== -1 && css.startsWith(name, at) && !isNameCode(css.charCodeAt(at + name.length))) return true;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
return false;
|
|
3300
|
+
}
|
|
3301
|
+
/** `items` as text without comments, at-keywords decoded. */
|
|
3302
|
+
function textOf(css, items) {
|
|
3303
|
+
let text = "";
|
|
3304
|
+
const add = (token) => {
|
|
3305
|
+
if (token.type === "whitespace") text += " ";
|
|
3306
|
+
else if (token.type === "at-keyword") text += `@${token.value.toLowerCase()}`;
|
|
3307
|
+
else text += css.slice(token.start, token.end);
|
|
3308
|
+
};
|
|
3309
|
+
const walk = (list) => {
|
|
3310
|
+
for (const item of list) if (item.kind === "token") add(item.token);
|
|
3311
|
+
else {
|
|
3312
|
+
add(item.open);
|
|
3313
|
+
walk(item.children);
|
|
3314
|
+
if (item.close) add(item.close);
|
|
3315
|
+
}
|
|
3316
|
+
};
|
|
3317
|
+
walk(items);
|
|
3318
|
+
return text.trim();
|
|
3319
|
+
}
|
|
3320
|
+
/** Declarations in a sheet: one `property: value` per `;` inside a `{}`
|
|
3321
|
+
* block, read from component values, so comments separate nothing. */
|
|
2852
3322
|
function declarations(css) {
|
|
2853
3323
|
const found = [];
|
|
2854
|
-
const stack = [];
|
|
2855
3324
|
let rules = 0;
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
found.push({
|
|
2865
|
-
property: text.slice(0, colon).trim(),
|
|
2866
|
-
value: text.slice(colon + 1).replace(/;$/, "").trim(),
|
|
2867
|
-
start: segment + (text.length - text.trimStart().length),
|
|
2868
|
-
end,
|
|
2869
|
-
rule: current.rule,
|
|
2870
|
-
context: stack.map((entry) => entry.prelude)
|
|
2871
|
-
});
|
|
2872
|
-
};
|
|
2873
|
-
for (let i = 0; i < css.length; i += 1) {
|
|
2874
|
-
const c = css[i];
|
|
2875
|
-
if (quote !== null) {
|
|
2876
|
-
if (c === "\\") i += 1;
|
|
2877
|
-
else if (c === quote) quote = null;
|
|
2878
|
-
continue;
|
|
2879
|
-
}
|
|
2880
|
-
if (c === "\"" || c === "'") quote = c;
|
|
2881
|
-
else if (c === "(") parens += 1;
|
|
2882
|
-
else if (c === ")") parens -= 1;
|
|
2883
|
-
else if (parens === 0 && c === "{") {
|
|
2884
|
-
stack.push({
|
|
2885
|
-
prelude: css.slice(segment, i).trim(),
|
|
2886
|
-
rule: rules
|
|
3325
|
+
const visit = (items, context, rule) => {
|
|
3326
|
+
let segment = [];
|
|
3327
|
+
const take = () => {
|
|
3328
|
+
const declaration = declarationOf(css, segment);
|
|
3329
|
+
if (rule !== void 0 && declaration !== void 0) found.push({
|
|
3330
|
+
...declaration,
|
|
3331
|
+
rule,
|
|
3332
|
+
context
|
|
2887
3333
|
});
|
|
3334
|
+
segment = [];
|
|
3335
|
+
};
|
|
3336
|
+
for (const item of items) if (item.kind === "block" && item.open.type === "{") {
|
|
3337
|
+
const prelude = textOf(css, segment);
|
|
3338
|
+
segment = [];
|
|
2888
3339
|
rules += 1;
|
|
2889
|
-
|
|
2890
|
-
} else if (
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
stack.pop();
|
|
2896
|
-
segment = i + 1;
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
3340
|
+
visit(item.children, [...context, prelude], rules - 1);
|
|
3341
|
+
} else if (item.kind === "token" && item.token.type === ";") take();
|
|
3342
|
+
else segment.push(item);
|
|
3343
|
+
take();
|
|
3344
|
+
};
|
|
3345
|
+
visit(componentValues(tokenize(css)), [], void 0);
|
|
2899
3346
|
return found;
|
|
2900
3347
|
}
|
|
2901
|
-
/**
|
|
2902
|
-
*
|
|
2903
|
-
function
|
|
2904
|
-
|
|
2905
|
-
const
|
|
2906
|
-
|
|
3348
|
+
/** The declaration a `;`-separated segment holds, if any. A bad string or
|
|
3349
|
+
* url makes a declaration invalid, so browsers drop it, and so does this. */
|
|
3350
|
+
function declarationOf(css, segment) {
|
|
3351
|
+
const colon = segment.findIndex((item) => item.kind === "token" && item.token.type === ":");
|
|
3352
|
+
const named = segment.slice(0, Math.max(colon, 0)).filter((i) => !isSpace(i));
|
|
3353
|
+
const values = trimSpace(segment.slice(colon + 1));
|
|
3354
|
+
if (colon === -1 || named.length === 0 || someValue(values, (v) => v.kind === "token" && (v.token.type === "bad-url" || v.token.type === "bad-string"))) return;
|
|
3355
|
+
const [only] = named;
|
|
3356
|
+
const ident = named.length === 1 && only.kind === "token" && only.token.type === "ident" ? only.token.value : void 0;
|
|
3357
|
+
const custom = ident?.startsWith("--") === true;
|
|
3358
|
+
return {
|
|
3359
|
+
property: custom && ident !== void 0 ? ident : (ident ?? textOf(css, named)).toLowerCase(),
|
|
3360
|
+
custom,
|
|
3361
|
+
value: values.length > 0 ? css.slice(values[0].start, values[values.length - 1].end) : "",
|
|
3362
|
+
values
|
|
3363
|
+
};
|
|
2907
3364
|
}
|
|
2908
|
-
function
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
3365
|
+
function trimSpace(items) {
|
|
3366
|
+
let start = 0;
|
|
3367
|
+
let end = items.length;
|
|
3368
|
+
while (start < end && isSpace(items[start])) start += 1;
|
|
3369
|
+
while (end > start && isSpace(items[end - 1])) end -= 1;
|
|
3370
|
+
return items.slice(start, end);
|
|
3371
|
+
}
|
|
3372
|
+
/** Whether the value is exactly `var()` of the declared property. */
|
|
3373
|
+
function isSelfReference(declaration, reads) {
|
|
3374
|
+
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;
|
|
3375
|
+
}
|
|
3376
|
+
/** Whether a read of the declared property sits outside every other read's
|
|
3377
|
+
* fallback, as in `--a: var(--a, red)` or `--a: calc(var(--a) + 1px)`: cyclic
|
|
3378
|
+
* whatever the fallback, as the bare `var(--a)` is. */
|
|
3379
|
+
function readsItselfDirectly(declaration, reads) {
|
|
3380
|
+
return declaration.custom && reads.some((read) => read.name === declaration.property && isDirectRead(read, reads));
|
|
3381
|
+
}
|
|
3382
|
+
/** Whether `read` sits outside every other read's fallback, so it is taken
|
|
3383
|
+
* whenever the declaration is. */
|
|
3384
|
+
function isDirectRead(read, reads) {
|
|
3385
|
+
return !reads.some((outer) => outer !== read && outer.fallback !== void 0 && outer.fallback.start <= read.start && read.end <= outer.fallback.end);
|
|
3386
|
+
}
|
|
3387
|
+
/** Each custom property a `currentVar` writes, with the props writing it. */
|
|
3388
|
+
function currentVarWriters(propConfigJson) {
|
|
3389
|
+
const writers = /* @__PURE__ */ new Map();
|
|
3390
|
+
if (propConfigJson === void 0) return writers;
|
|
3391
|
+
const config = JSON.parse(propConfigJson);
|
|
3392
|
+
for (const [prop, entry] of Object.entries(config)) {
|
|
3393
|
+
if (!entry.currentVar) continue;
|
|
3394
|
+
writers.set(entry.currentVar, [...writers.get(entry.currentVar) ?? [], prop]);
|
|
3395
|
+
}
|
|
3396
|
+
return writers;
|
|
2912
3397
|
}
|
|
2913
|
-
/**
|
|
2914
|
-
*
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
}
|
|
2936
|
-
if (value.slice(at, at + 4).toLowerCase() !== "var(" || /[\w-]/.test(value[at - 1] ?? "")) {
|
|
2937
|
-
at += 1;
|
|
2938
|
-
continue;
|
|
2939
|
-
}
|
|
2940
|
-
const nameAt = skipSpace(value, at + 4);
|
|
2941
|
-
if (!value.startsWith("--", nameAt)) {
|
|
2942
|
-
at += 4;
|
|
2943
|
-
continue;
|
|
3398
|
+
/**
|
|
3399
|
+
* Cycles among the `currentVar` writes of one rule, read directly. One rule
|
|
3400
|
+
* is one element under one set of conditions, so its writes are always
|
|
3401
|
+
* active together; writes in different rules, or a read only inside another
|
|
3402
|
+
* read's fallback, are left alone. Each cycle is listed once, in declaration
|
|
3403
|
+
* order.
|
|
3404
|
+
*/
|
|
3405
|
+
function currentVarCycles(parsed, currentVars) {
|
|
3406
|
+
if (currentVars.size === 0) return [];
|
|
3407
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
3408
|
+
for (const declaration of parsed) {
|
|
3409
|
+
if (!declaration.custom || !currentVars.has(declaration.property)) continue;
|
|
3410
|
+
const writes = byRule.get(declaration.rule) ?? /* @__PURE__ */ new Map();
|
|
3411
|
+
writes.set(declaration.property, declaration);
|
|
3412
|
+
byRule.set(declaration.rule, writes);
|
|
3413
|
+
}
|
|
3414
|
+
const cycles = [];
|
|
3415
|
+
for (const writes of byRule.values()) {
|
|
3416
|
+
const edges = /* @__PURE__ */ new Map();
|
|
3417
|
+
for (const [name, declaration] of writes) {
|
|
3418
|
+
const reads = variableReads(declaration.values);
|
|
3419
|
+
edges.set(name, reads.filter((read) => read.name !== name && writes.has(read.name) && isDirectRead(read, reads)).map((read) => read.name));
|
|
2944
3420
|
}
|
|
2945
|
-
const
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
reads.push({
|
|
2949
|
-
name: `--${name}`,
|
|
2950
|
-
fallback: value[after] === "," ? value.slice(after + 1, close - 1).trim() : void 0,
|
|
2951
|
-
start: at,
|
|
2952
|
-
end: close
|
|
2953
|
-
});
|
|
2954
|
-
at = after;
|
|
2955
|
-
}
|
|
2956
|
-
return reads;
|
|
2957
|
-
}
|
|
2958
|
-
/** The index after the `)` that closes the call whose arguments start at
|
|
2959
|
-
* `from`. Quoted text and comments are skipped. */
|
|
2960
|
-
function closingParen(value, from) {
|
|
2961
|
-
let depth = 1;
|
|
2962
|
-
let at = from;
|
|
2963
|
-
while (at < value.length && depth > 0) {
|
|
2964
|
-
const c = value[at];
|
|
2965
|
-
if (c === "\"" || c === "'") at = closingQuote(value, at);
|
|
2966
|
-
else if (value.startsWith("/*", at)) at = skipSpace(value, at);
|
|
2967
|
-
else {
|
|
2968
|
-
if (c === "(") depth += 1;
|
|
2969
|
-
else if (c === ")") depth -= 1;
|
|
2970
|
-
at += 1;
|
|
3421
|
+
for (const component of stronglyConnected(edges)) {
|
|
3422
|
+
if (component.length < 2) continue;
|
|
3423
|
+
cycles.push([...writes.values()].filter((d) => component.includes(d.property)));
|
|
2971
3424
|
}
|
|
2972
3425
|
}
|
|
2973
|
-
return
|
|
2974
|
-
}
|
|
2975
|
-
function closingQuote(value, open) {
|
|
2976
|
-
let at = open + 1;
|
|
2977
|
-
while (at < value.length && value[at] !== value[open]) at += value[at] === "\\" ? 2 : 1;
|
|
2978
|
-
return Math.min(at + 1, value.length);
|
|
3426
|
+
return cycles;
|
|
2979
3427
|
}
|
|
2980
|
-
/**
|
|
2981
|
-
function
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
3428
|
+
/** Tarjan's strongly connected components of a small graph. */
|
|
3429
|
+
function stronglyConnected(edges) {
|
|
3430
|
+
const index = /* @__PURE__ */ new Map();
|
|
3431
|
+
const low = /* @__PURE__ */ new Map();
|
|
3432
|
+
const stack = [];
|
|
3433
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
3434
|
+
const components = [];
|
|
3435
|
+
const visit = (node) => {
|
|
3436
|
+
index.set(node, index.size);
|
|
3437
|
+
low.set(node, index.get(node));
|
|
3438
|
+
stack.push(node);
|
|
3439
|
+
onStack.add(node);
|
|
3440
|
+
for (const next of edges.get(node) ?? []) if (!index.has(next)) {
|
|
3441
|
+
visit(next);
|
|
3442
|
+
low.set(node, Math.min(low.get(node), low.get(next)));
|
|
3443
|
+
} else if (onStack.has(next)) low.set(node, Math.min(low.get(node), index.get(next)));
|
|
3444
|
+
if (low.get(node) === index.get(node)) {
|
|
3445
|
+
const component = [];
|
|
3446
|
+
let member;
|
|
3447
|
+
do {
|
|
3448
|
+
member = stack.pop();
|
|
3449
|
+
onStack.delete(member);
|
|
3450
|
+
component.push(member);
|
|
3451
|
+
} while (member !== node);
|
|
3452
|
+
components.push(component);
|
|
3453
|
+
}
|
|
3454
|
+
};
|
|
3455
|
+
for (const node of edges.keys()) if (!index.has(node)) visit(node);
|
|
3456
|
+
return components;
|
|
2989
3457
|
}
|
|
2990
|
-
|
|
2991
|
-
|
|
3458
|
+
/** Whether every read of the declared property sits inside another read's
|
|
3459
|
+
* fallback, as in `--a: var(--b, var(--a))`. */
|
|
3460
|
+
function readsItselfOnlyInFallback(declaration, reads) {
|
|
3461
|
+
if (!declaration.custom) return false;
|
|
3462
|
+
const own = reads.filter((read) => read.name === declaration.property);
|
|
3463
|
+
return own.length > 0 && own.every((read) => reads.some((outer) => outer.fallback !== void 0 && outer.fallback.start <= read.start && read.end <= outer.fallback.end));
|
|
2992
3464
|
}
|
|
2993
3465
|
/** Custom properties this declaration sets in a keyframe or names in a
|
|
2994
3466
|
* transition. */
|
|
2995
3467
|
function animatedProperties(declaration, discreteRules) {
|
|
2996
3468
|
const keyframes = declaration.context.find((prelude) => prelude.startsWith("@keyframes"));
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
name: set,
|
|
3469
|
+
if (keyframes !== void 0 && declaration.custom) return [{
|
|
3470
|
+
name: declaration.property,
|
|
3000
3471
|
how: `set in ${keyframes}`,
|
|
3001
3472
|
discrete: false
|
|
3002
3473
|
}];
|
|
3003
3474
|
if (declaration.property !== "transition" && declaration.property !== "transition-property") return [];
|
|
3004
|
-
return topLevelItems(declaration.
|
|
3005
|
-
const
|
|
3006
|
-
return
|
|
3007
|
-
name:
|
|
3475
|
+
return topLevelItems(declaration.values).flatMap((item) => {
|
|
3476
|
+
const named = item.find((value) => value.kind === "token" && value.token.type === "ident" && value.token.value.startsWith("--"));
|
|
3477
|
+
return named?.kind === "token" ? [{
|
|
3478
|
+
name: named.token.value,
|
|
3008
3479
|
how: "transitioned",
|
|
3009
|
-
discrete:
|
|
3480
|
+
discrete: isDiscrete(item) || discreteRules.has(declaration.rule)
|
|
3010
3481
|
}] : [];
|
|
3011
3482
|
});
|
|
3012
3483
|
}
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
if (depth === 0 && /\s/.test(c)) {
|
|
3022
|
-
if (token) tokens.push(token);
|
|
3023
|
-
token = "";
|
|
3024
|
-
} else token += c;
|
|
3025
|
-
}
|
|
3026
|
-
if (token) tokens.push(token);
|
|
3027
|
-
return tokens;
|
|
3028
|
-
}
|
|
3029
|
-
function topLevelItems(value) {
|
|
3030
|
-
const items = [];
|
|
3031
|
-
let depth = 0;
|
|
3032
|
-
let start = 0;
|
|
3033
|
-
for (let i = 0; i < value.length; i += 1) if (value[i] === "(") depth += 1;
|
|
3034
|
-
else if (value[i] === ")") depth -= 1;
|
|
3035
|
-
else if (value[i] === "," && depth === 0) {
|
|
3036
|
-
items.push(value.slice(start, i));
|
|
3037
|
-
start = i + 1;
|
|
3038
|
-
}
|
|
3039
|
-
items.push(value.slice(start));
|
|
3484
|
+
function isDiscrete(item) {
|
|
3485
|
+
return item.some((value) => value.kind === "token" && value.token.type === "ident" && value.token.value.toLowerCase() === "allow-discrete");
|
|
3486
|
+
}
|
|
3487
|
+
/** The comma-separated items of a value, each its top-level values. */
|
|
3488
|
+
function topLevelItems(values) {
|
|
3489
|
+
const items = [[]];
|
|
3490
|
+
for (const value of values) if (value.kind === "token" && value.token.type === ",") items.push([]);
|
|
3491
|
+
else items[items.length - 1].push(value);
|
|
3040
3492
|
return items;
|
|
3041
3493
|
}
|
|
3042
3494
|
/** Why a property cannot interpolate, or `null` when it can. */
|
|
@@ -3284,6 +3736,47 @@ function serializeStaticCss(config) {
|
|
|
3284
3736
|
return json === "{}" ? null : json;
|
|
3285
3737
|
}
|
|
3286
3738
|
//#endregion
|
|
3739
|
+
//#region pipeline/property-names.ts
|
|
3740
|
+
function createPropertyNames(declared, prefix, themeNames = []) {
|
|
3741
|
+
const finals = /* @__PURE__ */ new Map();
|
|
3742
|
+
for (const name of [...new Set(declared)].sort()) finals.set(name, `${prefix}-${name}`);
|
|
3743
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
3744
|
+
for (const [name, final] of finals) if (!finals.has(final)) aliases.set(final, name);
|
|
3745
|
+
const others = /* @__PURE__ */ new Map();
|
|
3746
|
+
for (const name of [...new Set(themeNames)].sort()) if (!finals.has(name)) others.set(name, `${prefix}-${name}`);
|
|
3747
|
+
return {
|
|
3748
|
+
identity: (spelling) => finals.has(spelling) ? spelling : aliases.get(spelling) ?? (others.has(spelling) ? spelling : void 0),
|
|
3749
|
+
finalName: (name) => finals.get(name) ?? others.get(name),
|
|
3750
|
+
ambiguous: () => [...others.keys()].filter((name) => aliases.has(name)).map((name) => ({
|
|
3751
|
+
name,
|
|
3752
|
+
contextual: aliases.get(name) ?? name
|
|
3753
|
+
}))
|
|
3754
|
+
};
|
|
3755
|
+
}
|
|
3756
|
+
/**
|
|
3757
|
+
* Gives every custom-property token that names a managed property its final
|
|
3758
|
+
* name, in one pass: declaration keys, `var()` reads at any depth (fallbacks
|
|
3759
|
+
* included), `@property` names, transition-property lists and style queries.
|
|
3760
|
+
* Names are read through the shared CSS tokenizer, whole and with escapes
|
|
3761
|
+
* decoded, escaped leading dashes included, so an undeclared identifier that
|
|
3762
|
+
* contains or starts with a managed name is never renamed in part. Quoted
|
|
3763
|
+
* strings, `url()` and comments are copied unchanged.
|
|
3764
|
+
*/
|
|
3765
|
+
function renameCustomProperties(css, names) {
|
|
3766
|
+
let out = "";
|
|
3767
|
+
let copied = 0;
|
|
3768
|
+
for (const token of tokenize(css)) {
|
|
3769
|
+
if (token.type !== "ident" || !token.value.startsWith("--")) continue;
|
|
3770
|
+
const spelling = token.value.slice(2);
|
|
3771
|
+
const identity = names.identity(spelling);
|
|
3772
|
+
const final = identity === void 0 ? void 0 : names.finalName(identity);
|
|
3773
|
+
if (final === void 0 || final === spelling) continue;
|
|
3774
|
+
out += `${css.slice(copied, token.start)}--${final}`;
|
|
3775
|
+
copied = token.end;
|
|
3776
|
+
}
|
|
3777
|
+
return out + css.slice(copied);
|
|
3778
|
+
}
|
|
3779
|
+
//#endregion
|
|
3287
3780
|
//#region pipeline/prefix.ts
|
|
3288
3781
|
/** Prefix variable references without changing their matching rules. */
|
|
3289
3782
|
function prefixVariableReferences(prefix, value) {
|
|
@@ -3449,7 +3942,7 @@ function runStructuralSelfCheck(input) {
|
|
|
3449
3942
|
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`);
|
|
3450
3943
|
for (const { specifier, outcome } of input.externalOutcomes ?? []) if (outcome === "empty") failures.push(`include '${specifier}' resolved but discovered no component sources`);
|
|
3451
3944
|
else if (outcome === "unresolvable") failures.push(`include '${specifier}' could not be resolved`);
|
|
3452
|
-
if (!input.variableCss.includes(":root")) failures.push("No :root variable block found in variable CSS");
|
|
3945
|
+
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");
|
|
3453
3946
|
if ((input.assembledCss ?? `${input.variableCss}\n${input.globalCss}\n${input.componentCss}`).includes("__TRANSFORM__")) failures.push("Unresolved __TRANSFORM__ placeholders found in CSS output");
|
|
3454
3947
|
if (input.componentCount > 0 && (input.componentCss.length > 0 || input.assembledCss !== void 0)) {
|
|
3455
3948
|
const assembled = input.assembledCss ?? assembleStylesheet({
|
|
@@ -3458,9 +3951,16 @@ function runStructuralSelfCheck(input) {
|
|
|
3458
3951
|
globalCss: input.globalCss,
|
|
3459
3952
|
componentCss: input.componentCss
|
|
3460
3953
|
});
|
|
3461
|
-
const
|
|
3462
|
-
|
|
3463
|
-
|
|
3954
|
+
const blocks = ANIMUS_LAYERS.map((layer) => ({
|
|
3955
|
+
layer,
|
|
3956
|
+
offset: assembled.search(new RegExp(`@layer\\s+${layer}\\s*\\{`))
|
|
3957
|
+
})).filter(({ offset }) => offset !== -1);
|
|
3958
|
+
const misplaced = blocks.findIndex(({ offset }, index) => index > 0 && offset <= blocks[index - 1].offset);
|
|
3959
|
+
if (misplaced !== -1) {
|
|
3960
|
+
const earlier = blocks[misplaced - 1];
|
|
3961
|
+
const later = blocks[misplaced];
|
|
3962
|
+
failures.push(`CSS layer ordering violated — @layer ${earlier.layer} (offset ${earlier.offset}) must precede @layer ${later.layer} (offset ${later.offset})`);
|
|
3963
|
+
}
|
|
3464
3964
|
}
|
|
3465
3965
|
return failures;
|
|
3466
3966
|
}
|
|
@@ -3523,188 +4023,6 @@ function buildSystemPropsModule(opts) {
|
|
|
3523
4023
|
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` : "");
|
|
3524
4024
|
}
|
|
3525
4025
|
//#endregion
|
|
3526
|
-
//#region pipeline/tsconfig-paths.ts
|
|
3527
|
-
/**
|
|
3528
|
-
* tsconfig `paths` → alias pairs, for drivers with no live bundler config.
|
|
3529
|
-
* The nearest config declaring `paths` wins wholesale — TS never merges.
|
|
3530
|
-
*/
|
|
3531
|
-
function stripJsonc(text) {
|
|
3532
|
-
let out = "";
|
|
3533
|
-
let inString = false;
|
|
3534
|
-
let inLine = false;
|
|
3535
|
-
let inBlock = false;
|
|
3536
|
-
for (let i = 0; i < text.length; i++) {
|
|
3537
|
-
const ch = text[i];
|
|
3538
|
-
const next = text[i + 1];
|
|
3539
|
-
if (inLine) {
|
|
3540
|
-
if (ch === "\n") {
|
|
3541
|
-
inLine = false;
|
|
3542
|
-
out += ch;
|
|
3543
|
-
}
|
|
3544
|
-
continue;
|
|
3545
|
-
}
|
|
3546
|
-
if (inBlock) {
|
|
3547
|
-
if (ch === "*" && next === "/") {
|
|
3548
|
-
inBlock = false;
|
|
3549
|
-
i++;
|
|
3550
|
-
}
|
|
3551
|
-
continue;
|
|
3552
|
-
}
|
|
3553
|
-
if (inString) {
|
|
3554
|
-
out += ch;
|
|
3555
|
-
if (ch === "\\") {
|
|
3556
|
-
out += next ?? "";
|
|
3557
|
-
i++;
|
|
3558
|
-
} else if (ch === "\"") inString = false;
|
|
3559
|
-
continue;
|
|
3560
|
-
}
|
|
3561
|
-
if (ch === "\"") {
|
|
3562
|
-
inString = true;
|
|
3563
|
-
out += ch;
|
|
3564
|
-
continue;
|
|
3565
|
-
}
|
|
3566
|
-
if (ch === "/" && next === "/") {
|
|
3567
|
-
inLine = true;
|
|
3568
|
-
i++;
|
|
3569
|
-
continue;
|
|
3570
|
-
}
|
|
3571
|
-
if (ch === "/" && next === "*") {
|
|
3572
|
-
inBlock = true;
|
|
3573
|
-
i++;
|
|
3574
|
-
continue;
|
|
3575
|
-
}
|
|
3576
|
-
out += ch;
|
|
3577
|
-
}
|
|
3578
|
-
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
3579
|
-
}
|
|
3580
|
-
/** `Object(value) === value` holds for exactly the blocks and lists
|
|
3581
|
-
* `JSON.parse` produces; the `[object Object]` tag separates the two. */
|
|
3582
|
-
function isJsonBlock(value) {
|
|
3583
|
-
return Object(value) === value && Object.prototype.toString.call(value) === "[object Object]";
|
|
3584
|
-
}
|
|
3585
|
-
/** A block or a list. A declared `paths` OWNS the setting and blocks its
|
|
3586
|
-
* parents' even when it contributes nothing usable. */
|
|
3587
|
-
function isJsonKeyed(value) {
|
|
3588
|
-
return Object(value) === value;
|
|
3589
|
-
}
|
|
3590
|
-
/** A JSON string, excluding the boxed `String` object, which no path join
|
|
3591
|
-
* accepts. */
|
|
3592
|
-
function isJsonString(value) {
|
|
3593
|
-
return Object(value) !== value && Object.prototype.toString.call(value) === "[object String]";
|
|
3594
|
-
}
|
|
3595
|
-
function decodePaths(value) {
|
|
3596
|
-
if (!isJsonKeyed(value)) return null;
|
|
3597
|
-
const decoded = /* @__PURE__ */ new Map();
|
|
3598
|
-
for (const [pattern, targets] of Object.entries(value)) {
|
|
3599
|
-
const [first] = Array.isArray(targets) ? targets : [];
|
|
3600
|
-
if (isJsonString(first)) decoded.set(pattern, first);
|
|
3601
|
-
}
|
|
3602
|
-
return decoded;
|
|
3603
|
-
}
|
|
3604
|
-
/** TypeScript accepts one specifier or an array; a non-string member names
|
|
3605
|
-
* no config and is dropped here. */
|
|
3606
|
-
function decodeExtends(value) {
|
|
3607
|
-
return (Array.isArray(value) ? value : [value]).filter(isJsonString);
|
|
3608
|
-
}
|
|
3609
|
-
function readConfig(path) {
|
|
3610
|
-
let raw;
|
|
3611
|
-
try {
|
|
3612
|
-
raw = readFileSync(path, "utf-8");
|
|
3613
|
-
} catch {
|
|
3614
|
-
return null;
|
|
3615
|
-
}
|
|
3616
|
-
let parsed;
|
|
3617
|
-
try {
|
|
3618
|
-
parsed = JSON.parse(stripJsonc(raw));
|
|
3619
|
-
} catch {
|
|
3620
|
-
return null;
|
|
3621
|
-
}
|
|
3622
|
-
const root = isJsonBlock(parsed) ? parsed : {};
|
|
3623
|
-
const compilerOptions = isJsonBlock(root.compilerOptions) ? root.compilerOptions : {};
|
|
3624
|
-
return {
|
|
3625
|
-
dir: dirname$1(path),
|
|
3626
|
-
baseUrl: isJsonString(compilerOptions.baseUrl) ? compilerOptions.baseUrl : null,
|
|
3627
|
-
paths: decodePaths(compilerOptions.paths),
|
|
3628
|
-
extends: decodeExtends(root.extends)
|
|
3629
|
-
};
|
|
3630
|
-
}
|
|
3631
|
-
function resolveExtendsTarget(specifier, fromDir) {
|
|
3632
|
-
if (specifier.startsWith(".") || isAbsolute(specifier)) {
|
|
3633
|
-
const base = resolve(fromDir, specifier);
|
|
3634
|
-
for (const candidate of [
|
|
3635
|
-
base,
|
|
3636
|
-
`${base}.json`,
|
|
3637
|
-
join$1(base, "tsconfig.json")
|
|
3638
|
-
]) if (existsSync(candidate)) return candidate;
|
|
3639
|
-
return null;
|
|
3640
|
-
}
|
|
3641
|
-
try {
|
|
3642
|
-
return __require.resolve(specifier.endsWith(".json") ? specifier : `${specifier}/tsconfig.json`, { paths: [fromDir] });
|
|
3643
|
-
} catch {
|
|
3644
|
-
try {
|
|
3645
|
-
return __require.resolve(specifier, { paths: [fromDir] });
|
|
3646
|
-
} catch {
|
|
3647
|
-
return null;
|
|
3648
|
-
}
|
|
3649
|
-
}
|
|
3650
|
-
}
|
|
3651
|
-
/** Child-first flattened extends chain starting at `entryPath`. */
|
|
3652
|
-
function loadChain(entryPath) {
|
|
3653
|
-
const chain = [];
|
|
3654
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3655
|
-
const queue = [entryPath];
|
|
3656
|
-
while (queue.length > 0) {
|
|
3657
|
-
const path = queue.shift();
|
|
3658
|
-
const key = resolve(path);
|
|
3659
|
-
if (visited.has(key)) continue;
|
|
3660
|
-
visited.add(key);
|
|
3661
|
-
const node = readConfig(key);
|
|
3662
|
-
if (!node) continue;
|
|
3663
|
-
chain.push(node);
|
|
3664
|
-
for (const parent of node.extends) {
|
|
3665
|
-
const resolved = resolveExtendsTarget(parent, node.dir);
|
|
3666
|
-
if (resolved) queue.push(resolved);
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
return chain;
|
|
3670
|
-
}
|
|
3671
|
-
/**
|
|
3672
|
-
* The project's tsconfig path aliases as `buildPathAliasesJson` pairs; empty
|
|
3673
|
-
* when no readable tsconfig, or no usable `paths`, exists.
|
|
3674
|
-
*/
|
|
3675
|
-
function readTsconfigAliasPairs(rootDir) {
|
|
3676
|
-
const chain = loadChain(join$1(rootDir, "tsconfig.json"));
|
|
3677
|
-
if (chain.length === 0) return [];
|
|
3678
|
-
const pathsOwner = chain.find((node) => node.paths !== null);
|
|
3679
|
-
const paths = pathsOwner?.paths ?? null;
|
|
3680
|
-
if (pathsOwner === void 0 || paths === null) return [];
|
|
3681
|
-
const baseOwner = chain.find((node) => node.baseUrl !== null);
|
|
3682
|
-
const baseUrl = baseOwner?.baseUrl ?? null;
|
|
3683
|
-
const base = baseOwner !== void 0 && baseUrl !== null ? resolve(baseOwner.dir, baseUrl) : pathsOwner.dir;
|
|
3684
|
-
const pairs = [];
|
|
3685
|
-
for (const [pattern, target] of paths) {
|
|
3686
|
-
const patternStars = pattern.split("*").length - 1;
|
|
3687
|
-
const targetStars = target.split("*").length - 1;
|
|
3688
|
-
if (pattern === "*" || patternStars > 1 || targetStars > 1) continue;
|
|
3689
|
-
if (pattern.endsWith("/*")) {
|
|
3690
|
-
if (!target.endsWith("/*")) continue;
|
|
3691
|
-
pairs.push({
|
|
3692
|
-
pattern: pattern.slice(0, -2),
|
|
3693
|
-
target: resolve(base, target.slice(0, -2)),
|
|
3694
|
-
kind: "prefix"
|
|
3695
|
-
});
|
|
3696
|
-
continue;
|
|
3697
|
-
}
|
|
3698
|
-
if (patternStars > 0 || targetStars > 0) continue;
|
|
3699
|
-
pairs.push({
|
|
3700
|
-
pattern,
|
|
3701
|
-
target: resolve(base, target),
|
|
3702
|
-
kind: "exact"
|
|
3703
|
-
});
|
|
3704
|
-
}
|
|
3705
|
-
return pairs;
|
|
3706
|
-
}
|
|
3707
|
-
//#endregion
|
|
3708
4026
|
//#region pipeline/error-diagnostics.ts
|
|
3709
4027
|
function assertNoErrorDiagnostics(diagnostics) {
|
|
3710
4028
|
const errors = (diagnostics ?? []).filter((diagnostic) => diagnostic.kind === "error");
|
|
@@ -3789,4 +4107,4 @@ function realpathThroughNearestExistingAncestor(path) {
|
|
|
3789
4107
|
}
|
|
3790
4108
|
}
|
|
3791
4109
|
//#endregion
|
|
3792
|
-
export {
|
|
4110
|
+
export { DRIVER_NAMESPACES as $, createSourceCorpus as A, contentHash as At, unresolvableIncludesMessage as B, resolveThroughPathAliases as C, noKitFilesDiagnostics as Ct, reportSurvivingAssetPlaceholders as D, unresolvedParentName as Dt, generatedModuleCode as E, unreadableSourceDiagnostic as Et, findPackageRoot as F, validateLayerOrder as Ft, createSourceIdentity as G, isJsonBlock as H, firstOwners as I, __require as It, compareDiscoveryOrder as J, isPathWithinRoot as K, isExcludedPackageRelativePath as L, engineImportParser as M, ANIMUS_LAYERS as Mt, excludeCollectedPackages as N, assembleStylesheet as Nt, substituteAssetPlaceholders as O, vocabularyWitnessDiagnostics as Ot, extractSystemFilePackages as P, stripLeadingLayerDeclaration as Pt, DEFAULT_EXCLUDE as Q, resolveAbsolutePathSpecifier as R, resolveAssetFile as S, isUnresolvedParentDrop as St, findSheetAssetSpecifiers as T, systemLoadDiagnostics as Tt, isJsonString as U, walkPackageSources as V, readTsconfigAliasPairs as W, AnimusConfigError as X, discoverFiles as Y, CORE_OPTION_KEYS as Z, applyUnitFallback as _, preprocessMdx as _t, assertNoErrorDiagnostics as a, resolveVerbosity as at, buildPathAliasesJson as b, VOCABULARY_LEGACY_VERB as bt, runStructuralSelfCheck as c, createV2EngineApi as ct, serializeStaticCss as d, parseFilesJson as dt, REPLACEABLE_DEFAULT_EXCLUDE as et, camelToKebab as f, projectExternalFileOwners as ft, runProjectAnalysis as g, isEngineTransformExtension as gt, clearEngineCache as h, ENGINE_TRANSFORM_EXTENSIONS as ht, snapshotFilePlans as i, resolveMode as it, collectExternalPackageSources as j, buildAnalyzeProjectArgs as jt, substituteSheetAssets as k, parseInternalWire as kt, loadSystemConfig as l, ingestSourceEntries as lt, buildAnalysisInputs as m, DEFAULT_EXTENSIONS as mt, diffFilePlans as n, assertKnownOptionKeys as nt, buildSystemPropsModule as o, RETIRED_ENGINE_MESSAGE as ot, stableStringify as p, adaptSvelteSource as pt, sharesVolumeRoot as q, hashReplacementPlans as r, createExcludeMatcher as rt, buildDynamicPropConfig as s, assertNoRetiredEngineSelection as st, toWatchKeys as t, STRUCTURAL_EXCLUDE as tt, applyPrefix as u, isAdvisorySourceDiagnostic as ut, postProcessCss as v, INVALID_PROPERTY_REGISTRATION as vt, findAssetSpecifiers as w, surfaceManifestDiagnostics as wt, enforceExternalTokenContracts as x, isDeletedSource as xt, resolveLightningTargets as y, VOCABULARY_COLLISION as yt, staleDistIncludesMessage as z };
|