@animus-ui/extract 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/crates/extract-v2/animus-extract-v2.darwin-arm64.node +0 -0
- package/crates/extract-v2/animus-extract-v2.linux-arm64-gnu.node +0 -0
- package/crates/extract-v2/animus-extract-v2.linux-x64-gnu.node +0 -0
- package/crates/extract-v2/index.d.ts +13 -0
- package/dist/index.cjs +5 -1
- package/dist/index.mjs +2 -2
- package/dist/pipeline/analyze-project-args.d.ts +5 -1
- package/dist/pipeline/analyze-project-args.d.ts.map +1 -1
- package/dist/pipeline/assemble-stylesheet.d.ts +5 -0
- package/dist/pipeline/assemble-stylesheet.d.ts.map +1 -1
- package/dist/pipeline/asset-placeholders.d.ts +14 -0
- package/dist/pipeline/asset-placeholders.d.ts.map +1 -1
- package/dist/pipeline/core-options.d.ts +6 -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/dynamic-prop-config.d.ts +3 -0
- package/dist/pipeline/dynamic-prop-config.d.ts.map +1 -1
- package/dist/pipeline/engine-adapter.d.ts +1 -1
- package/dist/pipeline/engine-adapter.d.ts.map +1 -1
- package/dist/pipeline/index.d.ts +4 -4
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/manifest-diagnostics.d.ts +33 -3
- 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/prefix.d.ts +35 -0
- package/dist/pipeline/prefix.d.ts.map +1 -1
- package/dist/pipeline/property-diagnostics.d.ts +6 -4
- package/dist/pipeline/property-diagnostics.d.ts.map +1 -1
- package/dist/pipeline/property-names.d.ts +32 -0
- package/dist/pipeline/property-names.d.ts.map +1 -0
- package/dist/pipeline/property-registrations.d.ts +5 -2
- package/dist/pipeline/property-registrations.d.ts.map +1 -1
- package/dist/pipeline/run-analysis.d.ts +16 -0
- package/dist/pipeline/run-analysis.d.ts.map +1 -1
- package/dist/pipeline/source-ingestion.d.ts +3 -0
- package/dist/pipeline/source-ingestion.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/system-config.d.ts +9 -0
- package/dist/pipeline/system-config.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-BuiItKT-.mjs → pipeline-BXY7Pf_D.mjs} +1322 -567
- package/dist/{pipeline-CD2qmEEn.cjs → pipeline-D7B1z0-1.cjs} +1363 -566
- package/dist/session/extraction-session.d.ts +21 -1
- package/dist/session/extraction-session.d.ts.map +1 -1
- package/dist/session/index.d.ts +2 -2
- 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 +131 -11
- package/dist/session.mjs +132 -13
- 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 { isUnitlessProperty } 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",
|
|
@@ -85,7 +90,8 @@ function buildAnalyzeProjectArgs(inputs) {
|
|
|
85
90
|
inputs.transformSourcesJson,
|
|
86
91
|
inputs.transformProvenanceJson ?? null
|
|
87
92
|
];
|
|
88
|
-
if (inputs.declarationScalesJson) args.push(inputs.declarationScalesJson);
|
|
93
|
+
if (inputs.declarationScalesJson || inputs.analysisContextJson) args.push(inputs.declarationScalesJson);
|
|
94
|
+
if (inputs.analysisContextJson) args.push(inputs.analysisContextJson);
|
|
89
95
|
return args;
|
|
90
96
|
}
|
|
91
97
|
//#endregion
|
|
@@ -105,6 +111,282 @@ function parseInternalWire(json, wire) {
|
|
|
105
111
|
return parsed;
|
|
106
112
|
}
|
|
107
113
|
//#endregion
|
|
114
|
+
//#region pipeline/manifest-diagnostics.ts
|
|
115
|
+
/** Stable code for selector forms with no substitutable subject; the
|
|
116
|
+
* extraction engine mints the same string. */
|
|
117
|
+
const SELECTOR_UNSUPPORTED_SUBJECT = "animus.selector.unsupported-subject";
|
|
118
|
+
/** The one matcher for the engine's unresolved-parent chain-drop message;
|
|
119
|
+
* consumers match through the exported helpers, never their own copy. */
|
|
120
|
+
const UNRESOLVED_PARENT_RE = /chain dropped: could not resolve parent component '([^']+)'/;
|
|
121
|
+
function isUnresolvedParentDrop(diagnostic) {
|
|
122
|
+
return UNRESOLVED_PARENT_RE.test(diagnostic.message);
|
|
123
|
+
}
|
|
124
|
+
function unresolvedParentName(diagnostic) {
|
|
125
|
+
return UNRESOLVED_PARENT_RE.exec(diagnostic.message)?.[1] ?? null;
|
|
126
|
+
}
|
|
127
|
+
/** True when the selector carries at least one substitutable `&` subject
|
|
128
|
+
* outside quoted text. */
|
|
129
|
+
function hasSelectorSubject(value) {
|
|
130
|
+
let quote = null;
|
|
131
|
+
let escaped = false;
|
|
132
|
+
for (const c of value) {
|
|
133
|
+
if (escaped) {
|
|
134
|
+
escaped = false;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (c === "\\") {
|
|
138
|
+
escaped = true;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (quote !== null) {
|
|
142
|
+
if (c === quote) quote = null;
|
|
143
|
+
} else if (c === "\"" || c === "'") quote = c;
|
|
144
|
+
else if (c === "&") return true;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Coded diagnostics for selector-shaped alias values with no substitutable
|
|
150
|
+
* subject; alias values never reach the evaluator's own key guard.
|
|
151
|
+
*/
|
|
152
|
+
function collectSelectorAliasDiagnostics(selectorAliasesJson) {
|
|
153
|
+
if (!selectorAliasesJson) return [];
|
|
154
|
+
const aliases = parseInternalWire(selectorAliasesJson, "selectorAliasesJson (the system loader's selector-alias registry)");
|
|
155
|
+
const diagnostics = [];
|
|
156
|
+
for (const [name, value] of Object.entries(aliases)) if (value.includes("&") && !hasSelectorSubject(value)) diagnostics.push({
|
|
157
|
+
file: "system",
|
|
158
|
+
component: name,
|
|
159
|
+
kind: "warn",
|
|
160
|
+
message: `selector alias '${name}' value '${value}' has no substitutable '&' subject outside quoted text (${SELECTOR_UNSUPPORTED_SUBJECT})`,
|
|
161
|
+
code: SELECTOR_UNSUPPORTED_SUBJECT,
|
|
162
|
+
severity: severityFor(SELECTOR_UNSUPPORTED_SUBJECT)
|
|
163
|
+
});
|
|
164
|
+
return diagnostics;
|
|
165
|
+
}
|
|
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";
|
|
169
|
+
/** What a skipped source file costs: the analysis never sees what it
|
|
170
|
+
* renders. */
|
|
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";
|
|
172
|
+
/**
|
|
173
|
+
* A configured source file that could not be read is a LOST INPUT: the
|
|
174
|
+
* emitted artifacts silently lack whatever that file declared.
|
|
175
|
+
*/
|
|
176
|
+
/** Whether a failed source read means the file was deleted after discovery
|
|
177
|
+
* (an editor's delete-and-recreate, a branch switch): a deletion, not lost
|
|
178
|
+
* input. */
|
|
179
|
+
function isDeletedSource(error) {
|
|
180
|
+
return error?.code === "ENOENT";
|
|
181
|
+
}
|
|
182
|
+
function unreadableSourceDiagnostic(relPath, error) {
|
|
183
|
+
return {
|
|
184
|
+
file: relPath,
|
|
185
|
+
component: "source",
|
|
186
|
+
kind: "warn",
|
|
187
|
+
message: `configured source file ${relPath} could not be read: ${String(error)} (${SKIPPED_SOURCE_COST})`,
|
|
188
|
+
code: UNREADABLE_SOURCE_FILE,
|
|
189
|
+
severity: severityFor(UNREADABLE_SOURCE_FILE)
|
|
190
|
+
};
|
|
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
|
+
}
|
|
204
|
+
/** The collision entry code minted by the system package's merge. */
|
|
205
|
+
const VOCABULARY_COLLISION = "animus.vocabulary.collision";
|
|
206
|
+
/** A theme `@property` registration browsers would ignore; it is not emitted. */
|
|
207
|
+
const INVALID_PROPERTY_REGISTRATION = "animus.theme.invalid-property-registration";
|
|
208
|
+
/** Minted when a sealed kit with registered vocabulary is consumed through
|
|
209
|
+
* the deprecated `from()`/`includes:` verbs. */
|
|
210
|
+
const VOCABULARY_LEGACY_VERB = "animus.vocabulary.legacy-verb";
|
|
211
|
+
/** A `var()` fallback that never applies: the property is registered with an
|
|
212
|
+
* initial value. */
|
|
213
|
+
const PROPERTY_FALLBACK_SUPPRESSED = "animus.property.fallback-suppressed";
|
|
214
|
+
/** The same, where the fallback heads a chain of further `var()` reads. */
|
|
215
|
+
const PROPERTY_FALLBACK_CHAIN_SUPPRESSED = "animus.property.fallback-chain-suppressed";
|
|
216
|
+
/** A declared property set in keyframes or transitioned without a typed
|
|
217
|
+
* registration, so it does not interpolate. */
|
|
218
|
+
const PROPERTY_UNREGISTERED_ANIMATION = "animus.property.unregistered-animation";
|
|
219
|
+
/** A custom property whose resolved value is `var()` of itself, which makes
|
|
220
|
+
* it invalid at computed-value time. */
|
|
221
|
+
const PROPERTY_SELF_REFERENCE = "animus.property.self-reference";
|
|
222
|
+
/** A custom property that reads itself only inside another `var()`'s
|
|
223
|
+
* fallback: cyclic wherever that fallback is used. */
|
|
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";
|
|
229
|
+
/** A prefix renamed contextual variables without `prefixContextualVars`, so
|
|
230
|
+
* their declared names no longer resolve. */
|
|
231
|
+
const PREFIX_CONTEXTUAL_VARS_UNPREFIXED = "animus.prefix.contextual-vars-unprefixed";
|
|
232
|
+
/** Under `prefixContextualVars`, a final name that collides with a runtime
|
|
233
|
+
* transport variable or a theme variable of the same spelling. */
|
|
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";
|
|
241
|
+
const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
|
|
242
|
+
[SELECTOR_UNSUPPORTED_SUBJECT, "error"],
|
|
243
|
+
[UNREADABLE_SOURCE_FILE, "error"],
|
|
244
|
+
[NO_KIT_FILES, "warn"],
|
|
245
|
+
[VOCABULARY_COLLISION, "warn"],
|
|
246
|
+
[VOCABULARY_LEGACY_VERB, "warn"],
|
|
247
|
+
[INVALID_PROPERTY_REGISTRATION, "error"],
|
|
248
|
+
[PROPERTY_FALLBACK_SUPPRESSED, "info"],
|
|
249
|
+
[PROPERTY_FALLBACK_CHAIN_SUPPRESSED, "warn"],
|
|
250
|
+
[PROPERTY_UNREGISTERED_ANIMATION, "info"],
|
|
251
|
+
[PROPERTY_SELF_REFERENCE, "warn"],
|
|
252
|
+
[PROPERTY_FALLBACK_SELF_REFERENCE, "warn"],
|
|
253
|
+
[PROPERTY_CURRENT_VAR_CYCLE, "warn"],
|
|
254
|
+
[PREFIX_CONTEXTUAL_VARS_UNPREFIXED, "warn"],
|
|
255
|
+
[PREFIX_NAME_CONFLICT, "error"],
|
|
256
|
+
[PROPERTY_LEGACY_TOKEN_COLLISION, "warn"],
|
|
257
|
+
[PROPERTY_TOKEN_COLLISION, "error"]
|
|
258
|
+
]);
|
|
259
|
+
/** An unlisted code is `warn`: a witness kind from a newer system package
|
|
260
|
+
* must not fail the strict build of a host that predates its writer. */
|
|
261
|
+
function severityFor(code) {
|
|
262
|
+
return (code === void 0 ? void 0 : DIAGNOSTIC_SEVERITY.get(code)) ?? "warn";
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The one mapper of a sealed system's witness entries for every host: the
|
|
266
|
+
* loader's evaluation host shims `console`, so the record is the channel.
|
|
267
|
+
*/
|
|
268
|
+
function vocabularyWitnessDiagnostics(vocabularyWitnessesJson) {
|
|
269
|
+
if (!vocabularyWitnessesJson) return [];
|
|
270
|
+
const entries = parseInternalWire(vocabularyWitnessesJson, "vocabularyWitnessesJson (the sealed system's vocabulary witness record)");
|
|
271
|
+
const diagnostics = [];
|
|
272
|
+
for (const entry of entries) if (entry.code === "animus.vocabulary.collision") diagnostics.push({
|
|
273
|
+
file: "system",
|
|
274
|
+
component: entry.name ?? "keyframes",
|
|
275
|
+
kind: "warn",
|
|
276
|
+
message: `keyframes vocabulary "${entry.name}" is registered by both ${entry.loser} and ${entry.winner} — ${entry.winner} wins; rename one collection (${entry.code})`,
|
|
277
|
+
code: entry.code,
|
|
278
|
+
severity: severityFor(entry.code)
|
|
279
|
+
});
|
|
280
|
+
else if (entry.code === "animus.vocabulary.legacy-verb") diagnostics.push({
|
|
281
|
+
file: "system",
|
|
282
|
+
component: "keyframes",
|
|
283
|
+
kind: "warn",
|
|
284
|
+
message: `a sealed system (${entry.source ?? `'${entry.verb}' source`}) with registered vocabulary [${(entry.names ?? []).join(", ")}] was consumed through the deprecated '${entry.verb}' verb, which cannot carry it — those collections do NOT reach this consumer; use createSystem().extend(source) (${entry.code})`,
|
|
285
|
+
code: entry.code,
|
|
286
|
+
severity: severityFor(entry.code)
|
|
287
|
+
});
|
|
288
|
+
else diagnostics.push({
|
|
289
|
+
file: "system",
|
|
290
|
+
component: "vocabulary",
|
|
291
|
+
kind: "warn",
|
|
292
|
+
message: `unrecognized vocabulary witness entry ${JSON.stringify(entry)} — a newer @animus-ui/system may have recorded a witness kind this host predates${entry.code ? ` (${entry.code})` : ""}`,
|
|
293
|
+
code: entry.code,
|
|
294
|
+
severity: severityFor(entry.code)
|
|
295
|
+
});
|
|
296
|
+
return diagnostics;
|
|
297
|
+
}
|
|
298
|
+
/** The diagnostics a loaded system's own records carry: vocabulary
|
|
299
|
+
* witnesses and invalid `@property` registrations. */
|
|
300
|
+
function systemLoadDiagnostics(system) {
|
|
301
|
+
const registrations = (system.invalidPropertyRegistrations ?? []).map(({ name, reason }) => ({
|
|
302
|
+
file: "system",
|
|
303
|
+
component: name,
|
|
304
|
+
kind: "warn",
|
|
305
|
+
message: `@property ${name} was not emitted: ${reason}. Browsers ignore an invalid registration and minifying it fails; fix it where the theme declares ${name} (${INVALID_PROPERTY_REGISTRATION})`,
|
|
306
|
+
code: INVALID_PROPERTY_REGISTRATION,
|
|
307
|
+
severity: severityFor(INVALID_PROPERTY_REGISTRATION)
|
|
308
|
+
}));
|
|
309
|
+
const prefixed = system.legacyPrefixedContextualVars ?? [];
|
|
310
|
+
const unprefixed = prefixed.length === 0 ? [] : [{
|
|
311
|
+
file: "system",
|
|
312
|
+
component: "prefix",
|
|
313
|
+
kind: "warn",
|
|
314
|
+
message: `the prefix renames the contextual variables ${prefixed.join(", ")}, but their declared names are still read and written as written, so scale reads of them fail and their writes miss. Set prefixContextualVars: true to resolve them under the prefix (${PREFIX_CONTEXTUAL_VARS_UNPREFIXED})`,
|
|
315
|
+
code: PREFIX_CONTEXTUAL_VARS_UNPREFIXED,
|
|
316
|
+
severity: severityFor(PREFIX_CONTEXTUAL_VARS_UNPREFIXED)
|
|
317
|
+
}];
|
|
318
|
+
const conflicts = (system.prefixNameConflicts ?? []).map(({ name, final, reason }) => ({
|
|
319
|
+
file: "system",
|
|
320
|
+
component: `--${name}`,
|
|
321
|
+
kind: "error",
|
|
322
|
+
message: reason === "transport" ? `the prefix would emit the contextual variable ${name} as --${final}, inside the --animus- names of the runtime's transport variables, where it can collide with a prop's own variable. Use a different prefix (${PREFIX_NAME_CONFLICT})` : `the theme variable --${name} is spelled like the final name of the contextual variable ${final}, so under prefixContextualVars a reference to --${name} reaches ${final} and the theme variable is unreachable. Rename one of them (${PREFIX_NAME_CONFLICT})`,
|
|
323
|
+
code: PREFIX_NAME_CONFLICT,
|
|
324
|
+
severity: severityFor(PREFIX_NAME_CONFLICT)
|
|
325
|
+
}));
|
|
326
|
+
return [
|
|
327
|
+
...vocabularyWitnessDiagnostics(system.vocabularyWitnessesJson),
|
|
328
|
+
...registrations,
|
|
329
|
+
...unprefixed,
|
|
330
|
+
...conflicts,
|
|
331
|
+
...tokenCollisionDiagnostics(system)
|
|
332
|
+
];
|
|
333
|
+
}
|
|
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
|
+
/**
|
|
364
|
+
* The single strict-escalation policy point for both extraction plugins:
|
|
365
|
+
* error-severity diagnostics throw together under `strict`, else warn.
|
|
366
|
+
*/
|
|
367
|
+
function surfaceManifestDiagnostics(manifest, warn, policy = {}) {
|
|
368
|
+
const errors = [];
|
|
369
|
+
const diagnostics = policy.prepend?.length ? [...policy.prepend, ...manifest.diagnostics ?? []] : manifest.diagnostics ?? [];
|
|
370
|
+
for (const diagnostic of diagnostics) {
|
|
371
|
+
let line = null;
|
|
372
|
+
if (diagnostic.kind === "bail") line = `⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`;
|
|
373
|
+
else if (diagnostic.kind === "skip") line = `⚠ ${diagnostic.component}: skipped ${diagnostic.message}`;
|
|
374
|
+
else if (diagnostic.kind === "warn") line = `${diagnostic.severity === "info" ? "ℹ" : "⚠"} ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`;
|
|
375
|
+
if (line === null) continue;
|
|
376
|
+
if (diagnostic.code && !diagnostic.message.includes(diagnostic.code)) line += ` [${diagnostic.code}]`;
|
|
377
|
+
if (diagnostic.severity === "info") {
|
|
378
|
+
policy.info?.(line);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (policy.strict && diagnostic.severity === "error") {
|
|
382
|
+
errors.push(`${diagnostic.code ?? "error"} — ${diagnostic.component}: ${diagnostic.message}`);
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
warn(line);
|
|
386
|
+
}
|
|
387
|
+
if (errors.length > 0) throw new Error(`[animus] strict: ${errors.length} error diagnostic(s):\n${errors.join("\n")}`);
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
108
390
|
//#region pipeline/mdx-preprocessor.ts
|
|
109
391
|
/**
|
|
110
392
|
* MDX compiled to scanner-consumable JSX, plus the extension sets every
|
|
@@ -1029,7 +1311,8 @@ function withoutInvalidOriginals(result, invalidOriginals) {
|
|
|
1029
1311
|
...result,
|
|
1030
1312
|
originalEntries: result.originalEntries.filter((entry) => !invalidOriginals.has(entry.path)),
|
|
1031
1313
|
analysisEntries: result.analysisEntries.filter((entry) => analysisPaths.has(entry.path)),
|
|
1032
|
-
ownership
|
|
1314
|
+
ownership,
|
|
1315
|
+
skippedOriginals: [...invalidOriginals].sort()
|
|
1033
1316
|
};
|
|
1034
1317
|
}
|
|
1035
1318
|
/**
|
|
@@ -1069,11 +1352,12 @@ function createSourceIngestor(host) {
|
|
|
1069
1352
|
const invalidOriginals = new Set(fatal.map((diagnostic) => diagnostic.originalPath));
|
|
1070
1353
|
if (diagnostics.length === 0) return invalidOriginals;
|
|
1071
1354
|
if (host.strict() && fatal.length > 0) {
|
|
1072
|
-
const lines = fatal.map((diagnostic) => `${diagnostic.code} ${diagnostic.originalPath}: ${diagnostic.message}`);
|
|
1355
|
+
const lines = fatal.map((diagnostic) => `${diagnostic.code} ${diagnostic.originalPath}: ${diagnostic.message} (${SKIPPED_SOURCE_COST})`);
|
|
1073
1356
|
throw new Error(`${host.prefix} ${lines.join(`\n${host.prefix} `)}`);
|
|
1074
1357
|
}
|
|
1075
1358
|
for (const diagnostic of diagnostics) {
|
|
1076
|
-
const
|
|
1359
|
+
const skipped = isAdvisorySourceDiagnostic(diagnostic) ? "" : ` (${SKIPPED_SOURCE_COST})`;
|
|
1360
|
+
const line = `${diagnostic.code} ${diagnostic.originalPath}: ${diagnostic.message}${skipped}`;
|
|
1077
1361
|
let warned = warnedByOriginal.get(diagnostic.originalPath);
|
|
1078
1362
|
if (!warned) {
|
|
1079
1363
|
warned = /* @__PURE__ */ new Set();
|
|
@@ -1108,7 +1392,7 @@ function createV2EngineApi(deps) {
|
|
|
1108
1392
|
return {
|
|
1109
1393
|
loadSystemModule: (...args) => native.loadSystemModule(...args),
|
|
1110
1394
|
extractFacts: (filesJson) => native.extractFacts(filesJson),
|
|
1111
|
-
analyzeProject: (filesJsonRaw, scalesJson, variableMapJson, contextualVarsJson, propConfigJson, groupRegistryJson, packageResolutionJson, devMode, emitterConfigJson, selectorAliasesJson, _selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesJson, staticCssJson, conditionAliasesJson, externalDirsJson, transformSourcesJson, transformProvenanceJson, declarationScalesJson) => {
|
|
1395
|
+
analyzeProject: (filesJsonRaw, scalesJson, variableMapJson, contextualVarsJson, propConfigJson, groupRegistryJson, packageResolutionJson, devMode, emitterConfigJson, selectorAliasesJson, _selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesJson, staticCssJson, conditionAliasesJson, externalDirsJson, transformSourcesJson, transformProvenanceJson, declarationScalesJson, analysisContextJson) => {
|
|
1112
1396
|
const filesJson = deps.rehydrateFilesJson ? deps.rehydrateFilesJson(filesJsonRaw) : filesJsonRaw;
|
|
1113
1397
|
const sent = /* @__PURE__ */ new Map();
|
|
1114
1398
|
for (const entry of parseFilesJson(filesJson, label)) sent.set(entry.path, entry.source);
|
|
@@ -1135,6 +1419,7 @@ function createV2EngineApi(deps) {
|
|
|
1135
1419
|
transformSourcesJson: transformSourcesJson ?? void 0,
|
|
1136
1420
|
transformProvenanceJson: transformProvenanceJson ?? void 0,
|
|
1137
1421
|
declarationScalesJson,
|
|
1422
|
+
analysisContextJson,
|
|
1138
1423
|
devMode
|
|
1139
1424
|
};
|
|
1140
1425
|
const engine = new native.ExtractEngine(config);
|
|
@@ -1207,6 +1492,7 @@ const CORE_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
|
1207
1492
|
"strict",
|
|
1208
1493
|
"verbose",
|
|
1209
1494
|
"prefix",
|
|
1495
|
+
"prefixContextualVars",
|
|
1210
1496
|
"targets",
|
|
1211
1497
|
"minify",
|
|
1212
1498
|
"staticCss",
|
|
@@ -1300,6 +1586,11 @@ const CORE_VALUE_GATES = [
|
|
|
1300
1586
|
ok: isString,
|
|
1301
1587
|
expected: "a string"
|
|
1302
1588
|
},
|
|
1589
|
+
{
|
|
1590
|
+
key: "prefixContextualVars",
|
|
1591
|
+
ok: isBoolean,
|
|
1592
|
+
expected: "a boolean"
|
|
1593
|
+
},
|
|
1303
1594
|
{
|
|
1304
1595
|
key: "targets",
|
|
1305
1596
|
ok: isStringOrStringArray,
|
|
@@ -1561,13 +1852,195 @@ function createSourceIdentity(rootDir) {
|
|
|
1561
1852
|
};
|
|
1562
1853
|
}
|
|
1563
1854
|
//#endregion
|
|
1564
|
-
//#region pipeline/
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
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");
|
|
1569
1908
|
}
|
|
1570
|
-
|
|
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
|
|
2037
|
+
//#region pipeline/discover-packages.ts
|
|
2038
|
+
function findPackageRoot(absEntryPath) {
|
|
2039
|
+
let pkgRoot = dirname$1(absEntryPath);
|
|
2040
|
+
while (pkgRoot !== dirname$1(pkgRoot) && !existsSync(join$1(pkgRoot, "package.json"))) pkgRoot = dirname$1(pkgRoot);
|
|
2041
|
+
return pkgRoot;
|
|
2042
|
+
}
|
|
2043
|
+
const PACKAGE_SRC_EXCLUDES = [
|
|
1571
2044
|
"dist",
|
|
1572
2045
|
"node_modules",
|
|
1573
2046
|
".test.",
|
|
@@ -1614,6 +2087,77 @@ function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
|
|
|
1614
2087
|
const subpath = specifier.slice(packageName.length + 1);
|
|
1615
2088
|
return resolveAbsolutePathSpecifier(join$1(srcDir, subpath || "index"), extensionsSet);
|
|
1616
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
|
+
}
|
|
1617
2161
|
function distEntryIsStale(absEntry, srcDir, srcFiles) {
|
|
1618
2162
|
if (isPathWithinRoot(srcDir, absEntry)) return false;
|
|
1619
2163
|
let distMtime;
|
|
@@ -1634,6 +2178,20 @@ async function collectExternalPackageSources(opts) {
|
|
|
1634
2178
|
const pushed = /* @__PURE__ */ new Set();
|
|
1635
2179
|
const packageMap = {};
|
|
1636
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
|
+
};
|
|
1637
2195
|
const packageDirs = [];
|
|
1638
2196
|
const dirOwnerSets = {};
|
|
1639
2197
|
const dirExtensions = {};
|
|
@@ -1671,7 +2229,8 @@ async function collectExternalPackageSources(opts) {
|
|
|
1671
2229
|
const srcEntry = sourceEntryForSpecifier(specifier, srcDir, extensionsSet);
|
|
1672
2230
|
if (srcEntry) {
|
|
1673
2231
|
packageMap[specifier] = relative(rootDir, srcEntry);
|
|
1674
|
-
|
|
2232
|
+
const replaced = absEntry;
|
|
2233
|
+
await redirect(specifier, srcEntry, pkgRoot, async () => replaced);
|
|
1675
2234
|
} else packageMap[specifier] = relative(rootDir, absEntry);
|
|
1676
2235
|
if (!isAbsolute(specifier)) {
|
|
1677
2236
|
const packageName = bareSpecifierPackageName(specifier);
|
|
@@ -1679,12 +2238,18 @@ async function collectExternalPackageSources(opts) {
|
|
|
1679
2238
|
const rootEntry = sourceEntryForSpecifier(packageName, srcDir, extensionsSet);
|
|
1680
2239
|
if (rootEntry) {
|
|
1681
2240
|
packageMap[packageName] = relative(rootDir, rootEntry);
|
|
1682
|
-
|
|
2241
|
+
await redirect(packageName, rootEntry, pkgRoot, async () => {
|
|
2242
|
+
try {
|
|
2243
|
+
return await resolveSpecifier(packageName);
|
|
2244
|
+
} catch {
|
|
2245
|
+
return null;
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
1683
2248
|
}
|
|
1684
2249
|
}
|
|
1685
2250
|
}
|
|
1686
2251
|
const pkgFiles = walkPackageSources(srcDir, extensionsSet);
|
|
1687
|
-
staleDist = distEntryIsStale(absEntry, srcDir, pkgFiles);
|
|
2252
|
+
staleDist = !isInstalledPackage(pkgRoot) && distEntryIsStale(absEntry, srcDir, pkgFiles);
|
|
1688
2253
|
for (const pkgFile of pkgFiles) {
|
|
1689
2254
|
const relPath = relative(rootDir, pkgFile);
|
|
1690
2255
|
if (alreadyIngested(relPath)) {
|
|
@@ -1744,6 +2309,15 @@ async function collectExternalPackageSources(opts) {
|
|
|
1744
2309
|
fileCount++;
|
|
1745
2310
|
}
|
|
1746
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
|
+
}
|
|
1747
2321
|
outcomes.push({
|
|
1748
2322
|
specifier,
|
|
1749
2323
|
outcome: staleDist ? "stale-dist" : fileCount > 0 ? "resolved" : "empty",
|
|
@@ -1754,6 +2328,7 @@ async function collectExternalPackageSources(opts) {
|
|
|
1754
2328
|
entries,
|
|
1755
2329
|
packageMap,
|
|
1756
2330
|
sourceEntries,
|
|
2331
|
+
sourceEntrySideEffects,
|
|
1757
2332
|
packageDirs,
|
|
1758
2333
|
dirOwnerSets,
|
|
1759
2334
|
dirExtensions,
|
|
@@ -1777,9 +2352,12 @@ function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
|
|
|
1777
2352
|
packageMap[specifier] = relTarget;
|
|
1778
2353
|
}
|
|
1779
2354
|
const sourceEntries = /* @__PURE__ */ new Map();
|
|
2355
|
+
const sourceEntrySideEffects = /* @__PURE__ */ new Map();
|
|
1780
2356
|
for (const [specifier, absEntry] of collected.sourceEntries) {
|
|
1781
2357
|
if (targetRejected(specifier, absEntry)) continue;
|
|
1782
2358
|
sourceEntries.set(specifier, absEntry);
|
|
2359
|
+
const sideEffects = collected.sourceEntrySideEffects.get(specifier);
|
|
2360
|
+
if (sideEffects !== void 0) sourceEntrySideEffects.set(specifier, sideEffects);
|
|
1783
2361
|
}
|
|
1784
2362
|
const dirOwnerSets = {};
|
|
1785
2363
|
for (const [dir, specs] of Object.entries(collected.dirOwnerSets)) {
|
|
@@ -1801,6 +2379,7 @@ function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
|
|
|
1801
2379
|
entries: collected.entries.filter((entry) => !rejectedSpecifiers.has(collected.fileOwners[entry.path])),
|
|
1802
2380
|
packageMap,
|
|
1803
2381
|
sourceEntries,
|
|
2382
|
+
sourceEntrySideEffects,
|
|
1804
2383
|
packageDirs: collected.packageDirs.filter((dir) => !rejectedDirs.includes(dir)),
|
|
1805
2384
|
dirOwnerSets,
|
|
1806
2385
|
dirExtensions,
|
|
@@ -1820,15 +2399,50 @@ function staleDistIncludesMessage(outcomes) {
|
|
|
1820
2399
|
if (stale.length === 0) return null;
|
|
1821
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`;
|
|
1822
2401
|
}
|
|
1823
|
-
|
|
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) {
|
|
1824
2427
|
let source;
|
|
1825
2428
|
try {
|
|
1826
2429
|
source = readFileSync(systemFilePath, "utf-8");
|
|
1827
2430
|
} catch {
|
|
1828
2431
|
return [];
|
|
1829
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
|
+
}
|
|
1830
2444
|
const identifiers = /* @__PURE__ */ new Set();
|
|
1831
|
-
const constructorRegex =
|
|
2445
|
+
const constructorRegex = rootCall === null ? null : new RegExp(`${rootCall}\\s*\\(\\s*\\{[^}]*?\\bincludes\\s*:\\s*\\[([^\\]]*)\\]`, "gs");
|
|
1832
2446
|
const chainRegex = /\.includes\s*\(\s*\[([^\]]*)\]\s*\)/gs;
|
|
1833
2447
|
const collectIdentifiers = (regex) => {
|
|
1834
2448
|
let match;
|
|
@@ -1840,7 +2454,7 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
1840
2454
|
}
|
|
1841
2455
|
}
|
|
1842
2456
|
};
|
|
1843
|
-
collectIdentifiers(constructorRegex);
|
|
2457
|
+
if (constructorRegex) collectIdentifiers(constructorRegex);
|
|
1844
2458
|
collectIdentifiers(chainRegex);
|
|
1845
2459
|
const IDENT_START_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*/;
|
|
1846
2460
|
const skipTrivia = (from) => {
|
|
@@ -1860,6 +2474,38 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
1860
2474
|
return pos;
|
|
1861
2475
|
}
|
|
1862
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
|
+
};
|
|
1863
2509
|
const consumeChainLinks = (from) => {
|
|
1864
2510
|
let pos = from;
|
|
1865
2511
|
for (;;) {
|
|
@@ -1869,6 +2515,11 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
1869
2515
|
const method = IDENT_START_RE.exec(source.slice(cursor))?.[0];
|
|
1870
2516
|
if (method !== "extend" && method !== "from") return pos;
|
|
1871
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
|
+
}
|
|
1872
2523
|
if (source[cursor] !== "(") return pos;
|
|
1873
2524
|
cursor = skipTrivia(cursor + 1);
|
|
1874
2525
|
const base = IDENT_START_RE.exec(source.slice(cursor))?.[0];
|
|
@@ -1886,14 +2537,47 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
1886
2537
|
pos = cursor + 1;
|
|
1887
2538
|
}
|
|
1888
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
|
+
}
|
|
1889
2571
|
const boundIdentifierBefore = (index) => {
|
|
2572
|
+
const declared = bindingAt.get(index);
|
|
2573
|
+
if (declared) return declared;
|
|
1890
2574
|
const match = /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*$/.exec(source.slice(0, index));
|
|
1891
2575
|
return match ? match[1] : null;
|
|
1892
2576
|
};
|
|
1893
2577
|
const chainRootIdentifiers = /* @__PURE__ */ new Set();
|
|
1894
|
-
const createSystemAnchor =
|
|
2578
|
+
const createSystemAnchor = rootCall === null ? null : new RegExp(`${rootCall}\\s*\\(`, "g");
|
|
1895
2579
|
let anchorMatch;
|
|
1896
|
-
while ((anchorMatch = createSystemAnchor
|
|
2580
|
+
while ((anchorMatch = createSystemAnchor?.exec(source) ?? null) !== null) {
|
|
1897
2581
|
let pos = anchorMatch.index + anchorMatch[0].length;
|
|
1898
2582
|
let depth = 1;
|
|
1899
2583
|
while (pos < source.length && depth > 0) {
|
|
@@ -1924,22 +2608,37 @@ function extractSystemFilePackages(systemFilePath) {
|
|
|
1924
2608
|
}
|
|
1925
2609
|
if (identifiers.size === 0) return [];
|
|
1926
2610
|
const importMap = /* @__PURE__ */ new Map();
|
|
2611
|
+
for (const binding of parsedImports ?? []) importMap.set(binding.local, binding.source);
|
|
1927
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;
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
const
|
|
1935
|
-
|
|
1936
|
-
|
|
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
|
+
}
|
|
1937
2624
|
}
|
|
1938
2625
|
}
|
|
1939
2626
|
const systemFileDir = dirname$1(systemFilePath);
|
|
1940
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
|
+
};
|
|
1941
2640
|
for (const id of identifiers) {
|
|
1942
|
-
const specifier = importMap.get(id);
|
|
2641
|
+
const specifier = importMap.get(throughAliases(id));
|
|
1943
2642
|
if (!specifier) continue;
|
|
1944
2643
|
if (specifier.startsWith(".")) packages.add(resolve(systemFileDir, specifier));
|
|
1945
2644
|
else packages.add(specifier);
|
|
@@ -1998,31 +2697,113 @@ function createSourceCorpus(host, ingestor = createSourceIngestor(host)) {
|
|
|
1998
2697
|
//#endregion
|
|
1999
2698
|
//#region pipeline/asset-placeholders.ts
|
|
2000
2699
|
const ASSET_PLACEHOLDER_PREFIX = "animus-asset:";
|
|
2001
|
-
const
|
|
2002
|
-
const
|
|
2003
|
-
const
|
|
2004
|
-
|
|
2005
|
-
function
|
|
2700
|
+
const URL_OPEN_RE = /url\(\s*(['"]?)/gi;
|
|
2701
|
+
const IMAGE_SET_OPEN_RE = /(?:-webkit-)?image-set\(/gi;
|
|
2702
|
+
const BARE_SPECIFIER_RE = /^[^'")\s]+/;
|
|
2703
|
+
/** The index of the `)` that closes the `(` at `open`, outside strings. */
|
|
2704
|
+
function closingParen(css, open) {
|
|
2705
|
+
let depth = 0;
|
|
2706
|
+
let quote = null;
|
|
2707
|
+
for (let i = open; i < css.length; i += 1) {
|
|
2708
|
+
const c = css[i];
|
|
2709
|
+
if (quote !== null) {
|
|
2710
|
+
if (c === quote) quote = null;
|
|
2711
|
+
} else if (c === "\"" || c === "'") quote = c;
|
|
2712
|
+
else if (c === "(") depth += 1;
|
|
2713
|
+
else if (c === ")") {
|
|
2714
|
+
depth -= 1;
|
|
2715
|
+
if (depth === 0) return i;
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
return css.length;
|
|
2719
|
+
}
|
|
2720
|
+
function quotedReference(css, quoteAt) {
|
|
2721
|
+
const start = quoteAt + 1;
|
|
2722
|
+
if (!css.startsWith("animus-asset:", start)) return null;
|
|
2723
|
+
const end = css.indexOf(css[quoteAt], start);
|
|
2724
|
+
if (end === -1) return null;
|
|
2725
|
+
return {
|
|
2726
|
+
start,
|
|
2727
|
+
end,
|
|
2728
|
+
specifier: css.slice(start + 13, end)
|
|
2729
|
+
};
|
|
2730
|
+
}
|
|
2731
|
+
function assetReferences(css) {
|
|
2006
2732
|
if (!css.includes("animus-asset:")) return [];
|
|
2007
|
-
const
|
|
2008
|
-
const
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2733
|
+
const references = [];
|
|
2734
|
+
for (const open of css.matchAll(URL_OPEN_RE)) {
|
|
2735
|
+
const at = (open.index ?? 0) + open[0].length;
|
|
2736
|
+
if (open[1]) {
|
|
2737
|
+
const reference = quotedReference(css, at - 1);
|
|
2738
|
+
if (reference) references.push(reference);
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
if (!css.startsWith("animus-asset:", at)) continue;
|
|
2742
|
+
const specifier = BARE_SPECIFIER_RE.exec(css.slice(at + 13))?.[0];
|
|
2743
|
+
if (specifier === void 0) continue;
|
|
2744
|
+
const end = at + 13 + specifier.length;
|
|
2745
|
+
references.push({
|
|
2746
|
+
start: at,
|
|
2747
|
+
end,
|
|
2748
|
+
specifier
|
|
2749
|
+
});
|
|
2750
|
+
}
|
|
2751
|
+
for (const open of css.matchAll(IMAGE_SET_OPEN_RE)) {
|
|
2752
|
+
const paren = (open.index ?? 0) + open[0].length - 1;
|
|
2753
|
+
const close = closingParen(css, paren);
|
|
2754
|
+
let depth = 0;
|
|
2755
|
+
let candidateStart = true;
|
|
2756
|
+
for (let i = paren + 1; i < close; i += 1) {
|
|
2757
|
+
const c = css[i];
|
|
2758
|
+
if (/\s/.test(c)) continue;
|
|
2759
|
+
if (c === "\"" || c === "'") {
|
|
2760
|
+
if (depth === 0 && candidateStart) {
|
|
2761
|
+
const reference = quotedReference(css, i);
|
|
2762
|
+
if (reference) references.push(reference);
|
|
2763
|
+
}
|
|
2764
|
+
const end = css.indexOf(c, i + 1);
|
|
2765
|
+
i = end === -1 ? close : end;
|
|
2766
|
+
} else if (c === "(") depth += 1;
|
|
2767
|
+
else if (c === ")") depth -= 1;
|
|
2768
|
+
else if (c === "," && depth === 0) {
|
|
2769
|
+
candidateStart = true;
|
|
2770
|
+
continue;
|
|
2771
|
+
}
|
|
2772
|
+
candidateStart = false;
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
return references.sort((a, b) => a.start - b.start);
|
|
2776
|
+
}
|
|
2777
|
+
function findAssetSpecifiers(css) {
|
|
2778
|
+
return [...new Set(assetReferences(css).map((reference) => reference.specifier))];
|
|
2014
2779
|
}
|
|
2015
2780
|
function substituteAssetPlaceholders(css, urlBySpecifier) {
|
|
2016
2781
|
if (urlBySpecifier.size === 0) return css;
|
|
2017
|
-
if (!css.includes("animus-asset:")) return css;
|
|
2018
|
-
const specifiers = [...urlBySpecifier.keys()].sort((a, b) => b.length - a.length);
|
|
2019
2782
|
let out = css;
|
|
2020
|
-
for (const
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2783
|
+
for (const reference of assetReferences(css).reverse()) {
|
|
2784
|
+
const url = urlBySpecifier.get(reference.specifier);
|
|
2785
|
+
if (url === void 0) continue;
|
|
2786
|
+
out = out.slice(0, reference.start) + url + out.slice(reference.end);
|
|
2023
2787
|
}
|
|
2024
2788
|
return out;
|
|
2025
2789
|
}
|
|
2790
|
+
/**
|
|
2791
|
+
* Specifiers of placeholder text anywhere inside a `url()` or `image-set()`
|
|
2792
|
+
* call, a reference or not: after substitution any of it is a broken load.
|
|
2793
|
+
*/
|
|
2794
|
+
function placeholdersInAssetCalls(css) {
|
|
2795
|
+
if (!css.includes("animus-asset:")) return [];
|
|
2796
|
+
const calls = [...css.matchAll(URL_OPEN_RE), ...css.matchAll(IMAGE_SET_OPEN_RE)].map((open) => {
|
|
2797
|
+
const paren = css.indexOf("(", open.index ?? 0);
|
|
2798
|
+
return [paren, closingParen(css, paren)];
|
|
2799
|
+
});
|
|
2800
|
+
const found = /* @__PURE__ */ new Set();
|
|
2801
|
+
for (let at = css.indexOf(ASSET_PLACEHOLDER_PREFIX); at !== -1; at = css.indexOf(ASSET_PLACEHOLDER_PREFIX, at + 1)) {
|
|
2802
|
+
if (!calls.some(([open, close]) => open < at && at < close)) continue;
|
|
2803
|
+
found.add(/^[^'")\s,]*/.exec(css.slice(at + 13))[0]);
|
|
2804
|
+
}
|
|
2805
|
+
return [...found];
|
|
2806
|
+
}
|
|
2026
2807
|
function findSheetAssetSpecifiers(sheets) {
|
|
2027
2808
|
return [.../* @__PURE__ */ new Set([
|
|
2028
2809
|
...findAssetSpecifiers(sheets.variableCss),
|
|
@@ -2042,12 +2823,22 @@ const UNSUBSTITUTED_ASSET_CODE = "animus.asset.unsubstituted-placeholder";
|
|
|
2042
2823
|
/** A placeholder in emitted CSS is a broken URL in the browser: it fails a
|
|
2043
2824
|
* strict build and warns otherwise. */
|
|
2044
2825
|
function reportSurvivingAssetPlaceholders(css, report) {
|
|
2045
|
-
const specifiers =
|
|
2826
|
+
const specifiers = placeholdersInAssetCalls(css);
|
|
2046
2827
|
if (specifiers.length === 0) return;
|
|
2047
|
-
const message = `${report.prefix} asset() placeholders reached emitted CSS unsubstituted: ${specifiers.join(", ")} (${UNSUBSTITUTED_ASSET_CODE})`;
|
|
2828
|
+
const message = `${report.prefix} asset() placeholders reached ${report.surface ?? "emitted CSS"} unsubstituted: ${specifiers.join(", ")} (${UNSUBSTITUTED_ASSET_CODE})`;
|
|
2048
2829
|
if (report.strict) throw new Error(message);
|
|
2049
2830
|
report.warn(message);
|
|
2050
2831
|
}
|
|
2832
|
+
/**
|
|
2833
|
+
* The runtime code an analysis generates: each dynamic prop config, which the
|
|
2834
|
+
* shared prop map module carries, and each component's replacement. Extraction
|
|
2835
|
+
* lifts asset() out of them into root variables, so none should hold one.
|
|
2836
|
+
* Both embed values as JSON strings, so their quote and backslash escapes are
|
|
2837
|
+
* undone for scanning: the text is for `findAssetSpecifiers`, not to run.
|
|
2838
|
+
*/
|
|
2839
|
+
function generatedModuleCode(manifest) {
|
|
2840
|
+
return [JSON.stringify(manifest.dynamic_props), ...Object.values(manifest.components).map((component) => component.replacement)].join("\n").replace(/\\(["\\])/g, "$1");
|
|
2841
|
+
}
|
|
2051
2842
|
//#endregion
|
|
2052
2843
|
//#region pipeline/resolve-asset.ts
|
|
2053
2844
|
/**
|
|
@@ -2246,14 +3037,9 @@ function buildPathAliasesJson(pairs, rootDir) {
|
|
|
2246
3037
|
//#endregion
|
|
2247
3038
|
//#region pipeline/post-process-css.ts
|
|
2248
3039
|
function resolveLightningTargets(explicitTargets, rootDir) {
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
const detected = browserslist(void 0, { path: rootDir });
|
|
2253
|
-
queries = detected.length > 0 ? detected : browserslist("defaults");
|
|
2254
|
-
}
|
|
2255
|
-
const [firstQuery] = queries;
|
|
2256
|
-
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"));
|
|
2257
3043
|
}
|
|
2258
3044
|
function postProcessCss(css, opts) {
|
|
2259
3045
|
if (!css) return css;
|
|
@@ -2271,186 +3057,28 @@ function postProcessCss(css, opts) {
|
|
|
2271
3057
|
}
|
|
2272
3058
|
}
|
|
2273
3059
|
//#endregion
|
|
2274
|
-
//#region pipeline/
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
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
|
+
});
|
|
3079
|
+
}
|
|
3080
|
+
return rules;
|
|
2286
3081
|
}
|
|
2287
|
-
/** True when the selector carries at least one substitutable `&` subject
|
|
2288
|
-
* outside quoted text. */
|
|
2289
|
-
function hasSelectorSubject(value) {
|
|
2290
|
-
let quote = null;
|
|
2291
|
-
let escaped = false;
|
|
2292
|
-
for (const c of value) {
|
|
2293
|
-
if (escaped) {
|
|
2294
|
-
escaped = false;
|
|
2295
|
-
continue;
|
|
2296
|
-
}
|
|
2297
|
-
if (c === "\\") {
|
|
2298
|
-
escaped = true;
|
|
2299
|
-
continue;
|
|
2300
|
-
}
|
|
2301
|
-
if (quote !== null) {
|
|
2302
|
-
if (c === quote) quote = null;
|
|
2303
|
-
} else if (c === "\"" || c === "'") quote = c;
|
|
2304
|
-
else if (c === "&") return true;
|
|
2305
|
-
}
|
|
2306
|
-
return false;
|
|
2307
|
-
}
|
|
2308
|
-
/**
|
|
2309
|
-
* Coded diagnostics for selector-shaped alias values with no substitutable
|
|
2310
|
-
* subject; alias values never reach the evaluator's own key guard.
|
|
2311
|
-
*/
|
|
2312
|
-
function collectSelectorAliasDiagnostics(selectorAliasesJson) {
|
|
2313
|
-
if (!selectorAliasesJson) return [];
|
|
2314
|
-
const aliases = parseInternalWire(selectorAliasesJson, "selectorAliasesJson (the system loader's selector-alias registry)");
|
|
2315
|
-
const diagnostics = [];
|
|
2316
|
-
for (const [name, value] of Object.entries(aliases)) if (value.includes("&") && !hasSelectorSubject(value)) diagnostics.push({
|
|
2317
|
-
file: "system",
|
|
2318
|
-
component: name,
|
|
2319
|
-
kind: "warn",
|
|
2320
|
-
message: `selector alias '${name}' value '${value}' has no substitutable '&' subject outside quoted text (${SELECTOR_UNSUPPORTED_SUBJECT})`,
|
|
2321
|
-
code: SELECTOR_UNSUPPORTED_SUBJECT,
|
|
2322
|
-
severity: severityFor(SELECTOR_UNSUPPORTED_SUBJECT)
|
|
2323
|
-
});
|
|
2324
|
-
return diagnostics;
|
|
2325
|
-
}
|
|
2326
|
-
const UNREADABLE_SOURCE_FILE = "animus.ingestion.unreadable-source-file";
|
|
2327
|
-
/**
|
|
2328
|
-
* A configured source file that could not be read is a LOST INPUT: the
|
|
2329
|
-
* emitted artifacts silently lack whatever that file declared.
|
|
2330
|
-
*/
|
|
2331
|
-
function unreadableSourceDiagnostic(relPath, error) {
|
|
2332
|
-
return {
|
|
2333
|
-
file: relPath,
|
|
2334
|
-
component: "source",
|
|
2335
|
-
kind: "warn",
|
|
2336
|
-
message: `configured source file ${relPath} could not be read and was skipped: ${String(error)}`,
|
|
2337
|
-
code: UNREADABLE_SOURCE_FILE,
|
|
2338
|
-
severity: severityFor(UNREADABLE_SOURCE_FILE)
|
|
2339
|
-
};
|
|
2340
|
-
}
|
|
2341
|
-
/** The collision entry code minted by the system package's merge. */
|
|
2342
|
-
const VOCABULARY_COLLISION = "animus.vocabulary.collision";
|
|
2343
|
-
/** A theme `@property` registration browsers would ignore; it is not emitted. */
|
|
2344
|
-
const INVALID_PROPERTY_REGISTRATION = "animus.theme.invalid-property-registration";
|
|
2345
|
-
/** Minted when a sealed kit with registered vocabulary is consumed through
|
|
2346
|
-
* the deprecated `from()`/`includes:` verbs. */
|
|
2347
|
-
const VOCABULARY_LEGACY_VERB = "animus.vocabulary.legacy-verb";
|
|
2348
|
-
/** A `var()` fallback that never applies: the property is registered with an
|
|
2349
|
-
* initial value. */
|
|
2350
|
-
const PROPERTY_FALLBACK_SUPPRESSED = "animus.property.fallback-suppressed";
|
|
2351
|
-
/** The same, where the fallback heads a chain of further `var()` reads. */
|
|
2352
|
-
const PROPERTY_FALLBACK_CHAIN_SUPPRESSED = "animus.property.fallback-chain-suppressed";
|
|
2353
|
-
/** A declared property set in keyframes or transitioned without a typed
|
|
2354
|
-
* registration, so it does not interpolate. */
|
|
2355
|
-
const PROPERTY_UNREGISTERED_ANIMATION = "animus.property.unregistered-animation";
|
|
2356
|
-
/** The same, where `allow-discrete` shows interpolation was intended. */
|
|
2357
|
-
const PROPERTY_DISCRETE_ANIMATION = "animus.property.discrete-animation";
|
|
2358
|
-
/** A custom property whose resolved value is `var()` of itself, which makes
|
|
2359
|
-
* it invalid at computed-value time. */
|
|
2360
|
-
const PROPERTY_SELF_REFERENCE = "animus.property.self-reference";
|
|
2361
|
-
const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
|
|
2362
|
-
[SELECTOR_UNSUPPORTED_SUBJECT, "error"],
|
|
2363
|
-
[UNREADABLE_SOURCE_FILE, "error"],
|
|
2364
|
-
[VOCABULARY_COLLISION, "warn"],
|
|
2365
|
-
[VOCABULARY_LEGACY_VERB, "warn"],
|
|
2366
|
-
[INVALID_PROPERTY_REGISTRATION, "error"],
|
|
2367
|
-
[PROPERTY_FALLBACK_SUPPRESSED, "info"],
|
|
2368
|
-
[PROPERTY_FALLBACK_CHAIN_SUPPRESSED, "warn"],
|
|
2369
|
-
[PROPERTY_UNREGISTERED_ANIMATION, "info"],
|
|
2370
|
-
[PROPERTY_DISCRETE_ANIMATION, "warn"],
|
|
2371
|
-
[PROPERTY_SELF_REFERENCE, "warn"]
|
|
2372
|
-
]);
|
|
2373
|
-
/** An unlisted code is `warn`: a witness kind from a newer system package
|
|
2374
|
-
* must not fail the strict build of a host that predates its writer. */
|
|
2375
|
-
function severityFor(code) {
|
|
2376
|
-
return (code === void 0 ? void 0 : DIAGNOSTIC_SEVERITY.get(code)) ?? "warn";
|
|
2377
|
-
}
|
|
2378
|
-
/**
|
|
2379
|
-
* The one mapper of a sealed system's witness entries for every host: the
|
|
2380
|
-
* loader's evaluation host shims `console`, so the record is the channel.
|
|
2381
|
-
*/
|
|
2382
|
-
function vocabularyWitnessDiagnostics(vocabularyWitnessesJson) {
|
|
2383
|
-
if (!vocabularyWitnessesJson) return [];
|
|
2384
|
-
const entries = parseInternalWire(vocabularyWitnessesJson, "vocabularyWitnessesJson (the sealed system's vocabulary witness record)");
|
|
2385
|
-
const diagnostics = [];
|
|
2386
|
-
for (const entry of entries) if (entry.code === "animus.vocabulary.collision") diagnostics.push({
|
|
2387
|
-
file: "system",
|
|
2388
|
-
component: entry.name ?? "keyframes",
|
|
2389
|
-
kind: "warn",
|
|
2390
|
-
message: `keyframes vocabulary "${entry.name}" is registered by both ${entry.loser} and ${entry.winner} — ${entry.winner} wins; rename one collection (${entry.code})`,
|
|
2391
|
-
code: entry.code,
|
|
2392
|
-
severity: severityFor(entry.code)
|
|
2393
|
-
});
|
|
2394
|
-
else if (entry.code === "animus.vocabulary.legacy-verb") diagnostics.push({
|
|
2395
|
-
file: "system",
|
|
2396
|
-
component: "keyframes",
|
|
2397
|
-
kind: "warn",
|
|
2398
|
-
message: `a sealed system (${entry.source ?? `'${entry.verb}' source`}) with registered vocabulary [${(entry.names ?? []).join(", ")}] was consumed through the deprecated '${entry.verb}' verb, which cannot carry it — those collections do NOT reach this consumer; use createSystem().extend(source) (${entry.code})`,
|
|
2399
|
-
code: entry.code,
|
|
2400
|
-
severity: severityFor(entry.code)
|
|
2401
|
-
});
|
|
2402
|
-
else diagnostics.push({
|
|
2403
|
-
file: "system",
|
|
2404
|
-
component: "vocabulary",
|
|
2405
|
-
kind: "warn",
|
|
2406
|
-
message: `unrecognized vocabulary witness entry ${JSON.stringify(entry)} — a newer @animus-ui/system may have recorded a witness kind this host predates${entry.code ? ` (${entry.code})` : ""}`,
|
|
2407
|
-
code: entry.code,
|
|
2408
|
-
severity: severityFor(entry.code)
|
|
2409
|
-
});
|
|
2410
|
-
return diagnostics;
|
|
2411
|
-
}
|
|
2412
|
-
/** The diagnostics a loaded system's own records carry: vocabulary
|
|
2413
|
-
* witnesses and invalid `@property` registrations. */
|
|
2414
|
-
function systemLoadDiagnostics(system) {
|
|
2415
|
-
const registrations = (system.invalidPropertyRegistrations ?? []).map(({ name, reason }) => ({
|
|
2416
|
-
file: "system",
|
|
2417
|
-
component: name,
|
|
2418
|
-
kind: "warn",
|
|
2419
|
-
message: `@property ${name} was not emitted: ${reason}. Browsers ignore an invalid registration and minifying it fails; fix it where the theme declares ${name} (${INVALID_PROPERTY_REGISTRATION})`,
|
|
2420
|
-
code: INVALID_PROPERTY_REGISTRATION,
|
|
2421
|
-
severity: severityFor(INVALID_PROPERTY_REGISTRATION)
|
|
2422
|
-
}));
|
|
2423
|
-
return [...vocabularyWitnessDiagnostics(system.vocabularyWitnessesJson), ...registrations];
|
|
2424
|
-
}
|
|
2425
|
-
/**
|
|
2426
|
-
* The single strict-escalation policy point for both extraction plugins:
|
|
2427
|
-
* error-severity diagnostics throw together under `strict`, else warn.
|
|
2428
|
-
*/
|
|
2429
|
-
function surfaceManifestDiagnostics(manifest, warn, policy = {}) {
|
|
2430
|
-
const errors = [];
|
|
2431
|
-
const diagnostics = policy.prepend?.length ? [...policy.prepend, ...manifest.diagnostics ?? []] : manifest.diagnostics ?? [];
|
|
2432
|
-
for (const diagnostic of diagnostics) {
|
|
2433
|
-
let line = null;
|
|
2434
|
-
if (diagnostic.kind === "bail") line = `⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`;
|
|
2435
|
-
else if (diagnostic.kind === "skip") line = `⚠ ${diagnostic.component}: skipped ${diagnostic.message}`;
|
|
2436
|
-
else if (diagnostic.kind === "warn") line = `${diagnostic.severity === "info" ? "ℹ" : "⚠"} ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`;
|
|
2437
|
-
if (line === null) continue;
|
|
2438
|
-
if (diagnostic.code && !diagnostic.message.includes(diagnostic.code)) line += ` [${diagnostic.code}]`;
|
|
2439
|
-
if (diagnostic.severity === "info") {
|
|
2440
|
-
policy.info?.(line);
|
|
2441
|
-
continue;
|
|
2442
|
-
}
|
|
2443
|
-
if (policy.strict && diagnostic.severity === "error") {
|
|
2444
|
-
errors.push(`${diagnostic.code ?? "error"} — ${diagnostic.component}: ${diagnostic.message}`);
|
|
2445
|
-
continue;
|
|
2446
|
-
}
|
|
2447
|
-
warn(line);
|
|
2448
|
-
}
|
|
2449
|
-
if (errors.length > 0) throw new Error(`[animus] strict: ${errors.length} error diagnostic(s):\n${errors.join("\n")}`);
|
|
2450
|
-
}
|
|
2451
|
-
//#endregion
|
|
2452
|
-
//#region pipeline/property-registrations.ts
|
|
2453
|
-
const PROPERTY_RULE = /@property\s+(--[\w-]+)\s*\{[^}]*\}\n?/g;
|
|
2454
3082
|
/** The data types a registration's `syntax` may name. */
|
|
2455
3083
|
const SYNTAX_TYPES = /* @__PURE__ */ new Set([
|
|
2456
3084
|
"angle",
|
|
@@ -2470,44 +3098,40 @@ const SYNTAX_TYPES = /* @__PURE__ */ new Set([
|
|
|
2470
3098
|
"url"
|
|
2471
3099
|
]);
|
|
2472
3100
|
const SYNTAX_COMPONENT = /^(?:<([a-z-]+)>[+#]?|-?[a-zA-Z_][\w-]*)$/;
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
const DIMENSION_TYPES = /* @__PURE__ */ new Set([
|
|
2476
|
-
"image",
|
|
2477
|
-
"length",
|
|
2478
|
-
"length-percentage",
|
|
2479
|
-
"transform-function",
|
|
2480
|
-
"transform-list"
|
|
2481
|
-
]);
|
|
2482
|
-
const RELATIVE_UNIT = /(?:\d|\.)(?:r?em|r?ex|r?cap|r?ch|r?ic|r?lh|cq(?:w|h|i|b|min|max))\b/i;
|
|
2483
|
-
const SUBSTITUTION = /\b(?:var|env|attr)\(/i;
|
|
2484
|
-
/** Quoted strings and `url()` hold text, never units or substitutions. */
|
|
2485
|
-
const LITERAL_TEXT = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|url\([^)]*\)/gi;
|
|
3101
|
+
const INITIAL_VALUE = /(initial-value\s*:\s*)([^;}]*)/;
|
|
3102
|
+
const MATH_FUNCTION = /\b(?:calc|min|max|clamp)\(/i;
|
|
2486
3103
|
/**
|
|
2487
3104
|
* Removes the `@property` rules browsers would ignore — an unknown syntax
|
|
2488
3105
|
* component, a syntax other than `*` without an initial value, an initial
|
|
2489
|
-
* value that
|
|
2490
|
-
*
|
|
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.
|
|
2491
3111
|
*/
|
|
2492
3112
|
function splitInvalidPropertyRegistrations(css) {
|
|
2493
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
|
+
}
|
|
2494
3126
|
return {
|
|
2495
|
-
css: css.
|
|
2496
|
-
const reason = registrationFailure(rule);
|
|
2497
|
-
if (reason === null) return rule;
|
|
2498
|
-
invalid.push({
|
|
2499
|
-
name,
|
|
2500
|
-
reason
|
|
2501
|
-
});
|
|
2502
|
-
return "";
|
|
2503
|
-
}),
|
|
3127
|
+
css: kept + css.slice(copied),
|
|
2504
3128
|
invalid
|
|
2505
3129
|
};
|
|
2506
3130
|
}
|
|
2507
3131
|
/** The registrations in `css`, by custom-property name. */
|
|
2508
3132
|
function propertyRegistrations(css) {
|
|
2509
3133
|
const registrations = /* @__PURE__ */ new Map();
|
|
2510
|
-
for (const
|
|
3134
|
+
for (const { start, end, name } of propertyRules(css)) registrations.set(name, readRegistration(css.slice(start, end)));
|
|
2511
3135
|
return registrations;
|
|
2512
3136
|
}
|
|
2513
3137
|
function readRegistration(rule) {
|
|
@@ -2516,42 +3140,56 @@ function readRegistration(rule) {
|
|
|
2516
3140
|
initialValue: /initial-value\s*:\s*([^;}]*)/.exec(rule)?.[1]?.trim()
|
|
2517
3141
|
};
|
|
2518
3142
|
}
|
|
2519
|
-
|
|
3143
|
+
/** The rule to keep, with an all-absolute math initial value folded, or why
|
|
3144
|
+
* a browser would ignore it. */
|
|
3145
|
+
function checkRegistration(rule) {
|
|
2520
3146
|
const { syntax, initialValue } = readRegistration(rule);
|
|
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` };
|
|
3148
|
+
let kept = rule;
|
|
3149
|
+
let folded;
|
|
2521
3150
|
if (syntax !== "*") {
|
|
2522
|
-
const
|
|
3151
|
+
const unknown = syntax.split("|").map((component) => ({
|
|
2523
3152
|
text: component.trim(),
|
|
2524
3153
|
match: SYNTAX_COMPONENT.exec(component.trim())
|
|
2525
|
-
}));
|
|
2526
|
-
|
|
2527
|
-
if (
|
|
2528
|
-
if (initialValue
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
if (
|
|
3154
|
+
})).find(({ match }) => match === null || match[1] !== void 0 && !SYNTAX_TYPES.has(match[1]));
|
|
3155
|
+
if (unknown !== void 0) return { reason: `syntax "${syntax}" has the unknown component "${unknown.text}"` };
|
|
3156
|
+
if (initialValue === void 0) return { reason: `syntax "${syntax}" needs an initialValue` };
|
|
3157
|
+
if (dependsOnContext(initialValue)) return { reason: `initialValue "${initialValue}" depends on context; font- and container-relative units are not allowed` };
|
|
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}`);
|
|
2532
3162
|
}
|
|
2533
3163
|
try {
|
|
2534
3164
|
transform({
|
|
2535
3165
|
filename: "animus-registrations.css",
|
|
2536
|
-
code: Buffer.from(
|
|
3166
|
+
code: Buffer.from(kept)
|
|
2537
3167
|
});
|
|
2538
|
-
return
|
|
3168
|
+
return { rule: kept };
|
|
2539
3169
|
} catch (error) {
|
|
2540
3170
|
const detail = error instanceof Error ? error.message : String(error);
|
|
2541
|
-
return `the CSS parser
|
|
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})` };
|
|
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` };
|
|
3173
|
+
return { reason: `the CSS parser rejects \`${kept.trim()}\` (${detail})` };
|
|
2542
3174
|
}
|
|
2543
3175
|
}
|
|
2544
3176
|
//#endregion
|
|
2545
3177
|
//#region pipeline/property-diagnostics.ts
|
|
2546
3178
|
/**
|
|
2547
3179
|
* Reports custom properties that will not behave as their CSS suggests: a
|
|
2548
|
-
* property that refers to itself
|
|
2549
|
-
*
|
|
2550
|
-
*
|
|
3180
|
+
* property that refers to itself, a fallback a registered initial value
|
|
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.
|
|
2551
3185
|
*/
|
|
2552
3186
|
function checkCustomProperties(input) {
|
|
2553
3187
|
const registered = propertyRegistrations(input.system.variableCss);
|
|
2554
|
-
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());
|
|
2555
3193
|
const findings = [];
|
|
2556
3194
|
const sheets = [{
|
|
2557
3195
|
css: input.componentCss,
|
|
@@ -2561,8 +3199,20 @@ function checkCustomProperties(input) {
|
|
|
2561
3199
|
owner: globalOwner
|
|
2562
3200
|
}];
|
|
2563
3201
|
for (const { css, owner } of sheets) {
|
|
3202
|
+
if (!mayHoldFinding(css, withInitialValue, declaredNames, currentVars)) continue;
|
|
2564
3203
|
const parsed = declarations(css);
|
|
2565
|
-
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));
|
|
2566
3216
|
for (const declaration of parsed) {
|
|
2567
3217
|
const found = (code, property, message) => findings.push({
|
|
2568
3218
|
code,
|
|
@@ -2570,139 +3220,275 @@ function checkCustomProperties(input) {
|
|
|
2570
3220
|
message,
|
|
2571
3221
|
owner: owner(declaration)
|
|
2572
3222
|
});
|
|
2573
|
-
|
|
3223
|
+
const reads = variableReads(declaration.values);
|
|
3224
|
+
if (isSelfReference(declaration, reads)) {
|
|
2574
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`);
|
|
2575
3226
|
continue;
|
|
2576
3227
|
}
|
|
2577
|
-
if (
|
|
2578
|
-
|
|
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) {
|
|
2579
3238
|
const registration = registered.get(read.name);
|
|
2580
3239
|
if (read.fallback === void 0 || registration?.initialValue === void 0) continue;
|
|
2581
|
-
const
|
|
2582
|
-
|
|
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`);
|
|
2583
3243
|
}
|
|
2584
3244
|
for (const animated of animatedProperties(declaration, discreteRules)) {
|
|
2585
|
-
if (!declared.has(animated.name)) continue;
|
|
3245
|
+
if (animated.discrete || !declared.has(animated.name)) continue;
|
|
2586
3246
|
const reason = interpolationGap(registered.get(animated.name));
|
|
2587
3247
|
if (reason === null) continue;
|
|
2588
|
-
|
|
2589
|
-
else found(PROPERTY_UNREGISTERED_ANIMATION, animated.name, `${animated.name} is ${animated.how} but ${reason}, so it changes in one step instead of interpolating. Register it with a typed syntax to animate it`);
|
|
3248
|
+
found(PROPERTY_UNREGISTERED_ANIMATION, animated.name, `${animated.name} is ${animated.how} but ${reason}, so it changes in one step instead of interpolating. Register it with a typed syntax to animate it`);
|
|
2590
3249
|
}
|
|
2591
3250
|
}
|
|
2592
3251
|
}
|
|
2593
3252
|
return toDiagnostics(findings);
|
|
2594
3253
|
}
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
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;
|
|
2599
3300
|
}
|
|
2600
|
-
/**
|
|
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. */
|
|
2601
3322
|
function declarations(css) {
|
|
2602
3323
|
const found = [];
|
|
2603
|
-
const stack = [];
|
|
2604
3324
|
let rules = 0;
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
found.push({
|
|
2614
|
-
property: text.slice(0, colon).trim(),
|
|
2615
|
-
value: text.slice(colon + 1).replace(/;$/, "").trim(),
|
|
2616
|
-
start: segment + (text.length - text.trimStart().length),
|
|
2617
|
-
end,
|
|
2618
|
-
rule: current.rule,
|
|
2619
|
-
context: stack.map((entry) => entry.prelude)
|
|
2620
|
-
});
|
|
2621
|
-
};
|
|
2622
|
-
for (let i = 0; i < css.length; i += 1) {
|
|
2623
|
-
const c = css[i];
|
|
2624
|
-
if (quote !== null) {
|
|
2625
|
-
if (c === "\\") i += 1;
|
|
2626
|
-
else if (c === quote) quote = null;
|
|
2627
|
-
continue;
|
|
2628
|
-
}
|
|
2629
|
-
if (c === "\"" || c === "'") quote = c;
|
|
2630
|
-
else if (c === "(") parens += 1;
|
|
2631
|
-
else if (c === ")") parens -= 1;
|
|
2632
|
-
else if (parens === 0 && c === "{") {
|
|
2633
|
-
stack.push({
|
|
2634
|
-
prelude: css.slice(segment, i).trim(),
|
|
2635
|
-
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
|
|
2636
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 = [];
|
|
2637
3339
|
rules += 1;
|
|
2638
|
-
|
|
2639
|
-
} else if (
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
stack.pop();
|
|
2645
|
-
segment = i + 1;
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
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);
|
|
2648
3346
|
return found;
|
|
2649
3347
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
const
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
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
|
+
};
|
|
3364
|
+
}
|
|
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;
|
|
3397
|
+
}
|
|
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));
|
|
3420
|
+
}
|
|
3421
|
+
for (const component of stronglyConnected(edges)) {
|
|
3422
|
+
if (component.length < 2) continue;
|
|
3423
|
+
cycles.push([...writes.values()].filter((d) => component.includes(d.property)));
|
|
2664
3424
|
}
|
|
2665
|
-
let depth = 1;
|
|
2666
|
-
let close = after + 1;
|
|
2667
|
-
for (; close < value.length && depth > 0; close += 1) if (value[close] === "(") depth += 1;
|
|
2668
|
-
else if (value[close] === ")") depth -= 1;
|
|
2669
|
-
reads.push({
|
|
2670
|
-
name: match[1],
|
|
2671
|
-
fallback: value.slice(after + 1, close - 1).trim()
|
|
2672
|
-
});
|
|
2673
3425
|
}
|
|
2674
|
-
return
|
|
3426
|
+
return cycles;
|
|
3427
|
+
}
|
|
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;
|
|
3457
|
+
}
|
|
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));
|
|
2675
3464
|
}
|
|
2676
3465
|
/** Custom properties this declaration sets in a keyframe or names in a
|
|
2677
3466
|
* transition. */
|
|
2678
3467
|
function animatedProperties(declaration, discreteRules) {
|
|
2679
3468
|
const keyframes = declaration.context.find((prelude) => prelude.startsWith("@keyframes"));
|
|
2680
|
-
if (keyframes !== void 0 && declaration.
|
|
3469
|
+
if (keyframes !== void 0 && declaration.custom) return [{
|
|
2681
3470
|
name: declaration.property,
|
|
2682
3471
|
how: `set in ${keyframes}`,
|
|
2683
3472
|
discrete: false
|
|
2684
3473
|
}];
|
|
2685
3474
|
if (declaration.property !== "transition" && declaration.property !== "transition-property") return [];
|
|
2686
|
-
return topLevelItems(declaration.
|
|
2687
|
-
const
|
|
2688
|
-
return
|
|
2689
|
-
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,
|
|
2690
3479
|
how: "transitioned",
|
|
2691
|
-
discrete:
|
|
3480
|
+
discrete: isDiscrete(item) || discreteRules.has(declaration.rule)
|
|
2692
3481
|
}] : [];
|
|
2693
3482
|
});
|
|
2694
3483
|
}
|
|
2695
|
-
function
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
start = i + 1;
|
|
2704
|
-
}
|
|
2705
|
-
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);
|
|
2706
3492
|
return items;
|
|
2707
3493
|
}
|
|
2708
3494
|
/** Why a property cannot interpolate, or `null` when it can. */
|
|
@@ -2712,7 +3498,7 @@ function interpolationGap(registration) {
|
|
|
2712
3498
|
return null;
|
|
2713
3499
|
}
|
|
2714
3500
|
function componentOwner(manifest) {
|
|
2715
|
-
const components = Object.values(manifest.components);
|
|
3501
|
+
const components = Object.values(manifest.components ?? {});
|
|
2716
3502
|
return (declaration) => {
|
|
2717
3503
|
const selector = [...declaration.context].reverse().find((prelude) => !prelude.startsWith("@"));
|
|
2718
3504
|
const classes = (selector ?? "").match(/\.-?[_a-zA-Z][\w-]*/g) ?? [];
|
|
@@ -2834,12 +3620,33 @@ function buildAnalysisInputs(opts) {
|
|
|
2834
3620
|
externalDirsJson: opts.externalDirs?.length && hasSourceThemeManifests(opts.system) ? JSON.stringify(opts.externalDirs) : null
|
|
2835
3621
|
};
|
|
2836
3622
|
if (opts.system.declarationScalesJson) inputs.declarationScalesJson = opts.system.declarationScalesJson;
|
|
3623
|
+
const context = opts.analysisContext;
|
|
3624
|
+
if (context?.skippedSources?.length || context?.unbundledComputedImports || context?.packageDirs?.length) inputs.analysisContextJson = JSON.stringify({
|
|
3625
|
+
skippedSources: context.skippedSources ?? [],
|
|
3626
|
+
unbundledComputedImports: context.unbundledComputedImports ?? false,
|
|
3627
|
+
packageDirs: context.packageDirs ?? []
|
|
3628
|
+
});
|
|
2837
3629
|
return inputs;
|
|
2838
3630
|
}
|
|
2839
3631
|
function hasSourceThemeManifests(system) {
|
|
2840
3632
|
const json = system.sourceThemeManifestsJson ?? "";
|
|
2841
3633
|
return json.length > 0 && json !== "{}";
|
|
2842
3634
|
}
|
|
3635
|
+
/** Systems whose load diagnostics were surfaced: each loaded system reports
|
|
3636
|
+
* them once, not again on every analysis and hot update. The set is per
|
|
3637
|
+
* process. Next's webpack compilers share one analysis in one process, but
|
|
3638
|
+
* its build worker and Turbopack builds analyze in two processes, which
|
|
3639
|
+
* repeat every diagnostic alike, component ones included. */
|
|
3640
|
+
const surfacedSystems = /* @__PURE__ */ new WeakSet();
|
|
3641
|
+
/** The loaded system's own diagnostics, the first time it is analyzed. A
|
|
3642
|
+
* strict error keeps failing every analysis until the system changes. */
|
|
3643
|
+
function systemDiagnostics(system, strict) {
|
|
3644
|
+
const diagnostics = [...collectSelectorAliasDiagnostics(system.selectorAliasesJson), ...systemLoadDiagnostics(system)];
|
|
3645
|
+
const blocking = strict === true && diagnostics.some((d) => d.severity === "error");
|
|
3646
|
+
if (surfacedSystems.has(system) && !blocking) return [];
|
|
3647
|
+
surfacedSystems.add(system);
|
|
3648
|
+
return diagnostics;
|
|
3649
|
+
}
|
|
2843
3650
|
/**
|
|
2844
3651
|
* The one analysis invocation both plugins share. Error handling stays at
|
|
2845
3652
|
* the call site (strict-mode throw vs warn).
|
|
@@ -2854,18 +3661,20 @@ function runProjectAnalysis(engineApi, opts) {
|
|
|
2854
3661
|
const extractMs = Math.round(performance.now() - t);
|
|
2855
3662
|
t = performance.now();
|
|
2856
3663
|
const manifest = JSON.parse(manifestJson);
|
|
3664
|
+
const systemErrors = systemLoadDiagnostics(opts.system).filter((diagnostic) => diagnostic.kind === "error");
|
|
3665
|
+
if (systemErrors.length > 0) manifest.diagnostics = [...systemErrors, ...manifest.diagnostics ?? []];
|
|
3666
|
+
const componentCss = applyUnitFallback(manifest.css);
|
|
2857
3667
|
const propertyDiagnostics = checkCustomProperties({
|
|
2858
3668
|
system: opts.system,
|
|
2859
3669
|
manifest,
|
|
2860
|
-
componentCss
|
|
3670
|
+
componentCss,
|
|
2861
3671
|
globalCss: manifest.sheets.global
|
|
2862
3672
|
});
|
|
2863
3673
|
surfaceManifestDiagnostics(manifest, opts.warn, {
|
|
2864
3674
|
strict: opts.strict,
|
|
2865
3675
|
info: opts.info,
|
|
2866
3676
|
prepend: [
|
|
2867
|
-
...
|
|
2868
|
-
...systemLoadDiagnostics(opts.system),
|
|
3677
|
+
...systemDiagnostics(opts.system, opts.strict),
|
|
2869
3678
|
...propertyDiagnostics,
|
|
2870
3679
|
...opts.extraDiagnostics ?? []
|
|
2871
3680
|
]
|
|
@@ -2875,7 +3684,7 @@ function runProjectAnalysis(engineApi, opts) {
|
|
|
2875
3684
|
manifest,
|
|
2876
3685
|
manifestJson,
|
|
2877
3686
|
globalCss: manifest.sheets.global,
|
|
2878
|
-
componentCss
|
|
3687
|
+
componentCss,
|
|
2879
3688
|
inputs,
|
|
2880
3689
|
timings: {
|
|
2881
3690
|
serializeMs,
|
|
@@ -2927,6 +3736,47 @@ function serializeStaticCss(config) {
|
|
|
2927
3736
|
return json === "{}" ? null : json;
|
|
2928
3737
|
}
|
|
2929
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
|
|
2930
3780
|
//#region pipeline/prefix.ts
|
|
2931
3781
|
/** Prefix variable references without changing their matching rules. */
|
|
2932
3782
|
function prefixVariableReferences(prefix, value) {
|
|
@@ -2960,6 +3810,61 @@ function applyPrefix(prefix, variableMapJson, variableCss, themeJson, contextual
|
|
|
2960
3810
|
}
|
|
2961
3811
|
return result;
|
|
2962
3812
|
}
|
|
3813
|
+
/** The runtime's transport variables are `--animus-<prop>`. */
|
|
3814
|
+
const TRANSPORT_NAMESPACE = "animus-";
|
|
3815
|
+
/**
|
|
3816
|
+
* The prefix applied through one map: every variable the theme defines and
|
|
3817
|
+
* every declared contextual variable takes `--${prefix}-${name}`, each
|
|
3818
|
+
* reference renamed once. Each contextual entry carries its declared and
|
|
3819
|
+
* final name, so lookups run on the declared identity and the extractor
|
|
3820
|
+
* emits the final name.
|
|
3821
|
+
*/
|
|
3822
|
+
function applyPropertyNames(prefix, artifacts) {
|
|
3823
|
+
const byScale = artifacts.contextualVarsJson ? parseInternalWire(artifacts.contextualVarsJson, "contextualVarsJson (the theme's contextual variable names)") : null;
|
|
3824
|
+
const declared = Object.values(byScale ?? {}).flat();
|
|
3825
|
+
const variableMap = parseInternalWire(artifacts.variableMapJson, "variableMapJson (the theme's token variables)");
|
|
3826
|
+
const managed = createPropertyNames(declared, prefix, [...Object.values(variableMap).map((name) => name.replace(/^--/, "")), ...[...artifacts.variableCss.matchAll(THEME_DEFINITION)].map((m) => m[1])]);
|
|
3827
|
+
const nameConflicts = [...[...new Set(declared)].flatMap((name) => {
|
|
3828
|
+
const final = managed.finalName(name) ?? name;
|
|
3829
|
+
return final.startsWith(TRANSPORT_NAMESPACE) ? [{
|
|
3830
|
+
name,
|
|
3831
|
+
final,
|
|
3832
|
+
reason: "transport"
|
|
3833
|
+
}] : [];
|
|
3834
|
+
}), ...managed.ambiguous().map(({ name, contextual }) => ({
|
|
3835
|
+
name,
|
|
3836
|
+
final: contextual,
|
|
3837
|
+
reason: "theme-variable"
|
|
3838
|
+
}))];
|
|
3839
|
+
const rename = (value) => renameCustomProperties(value, managed);
|
|
3840
|
+
return {
|
|
3841
|
+
variableMapJson: JSON.stringify(Object.fromEntries(Object.entries(variableMap).map(([key, name]) => [key, rename(name)]))),
|
|
3842
|
+
variableCss: rename(artifacts.variableCss),
|
|
3843
|
+
themeJson: renameTokenValues(artifacts.themeJson, rename),
|
|
3844
|
+
contextualVarsJson: byScale === null ? null : JSON.stringify(Object.fromEntries(Object.entries(byScale).map(([scale, names]) => [scale, names.map((name) => ({
|
|
3845
|
+
name,
|
|
3846
|
+
var: managed.finalName(name) ?? name
|
|
3847
|
+
}))]))),
|
|
3848
|
+
declarationScalesJson: artifacts.declarationScalesJson === null ? null : renameDeclarationRecords(artifacts.declarationScalesJson, rename),
|
|
3849
|
+
nameConflicts,
|
|
3850
|
+
contextualProperties: [...new Set(declared)].map((name) => `--${managed.finalName(name) ?? name}`)
|
|
3851
|
+
};
|
|
3852
|
+
}
|
|
3853
|
+
/** A custom property the theme's CSS defines or registers. */
|
|
3854
|
+
const THEME_DEFINITION = /(?:@property\s+|(?:^|[\s;{]))--([\w-]+)(?=\s*[:{])/g;
|
|
3855
|
+
/** Renames inside the token map's values. */
|
|
3856
|
+
function renameTokenValues(themeJson, rename) {
|
|
3857
|
+
const tokens = parseInternalWire(themeJson, "themeJson (the theme's flattened token values)");
|
|
3858
|
+
return JSON.stringify(Object.fromEntries(Object.entries(tokens).map(([path, value]) => [path, rename(value)])));
|
|
3859
|
+
}
|
|
3860
|
+
/** Renames inside every declaration-scale record value. */
|
|
3861
|
+
function renameDeclarationRecords(declarationScalesJson, rename) {
|
|
3862
|
+
const scales = parseInternalWire(declarationScalesJson, "declarationScalesJson (the theme's declaration scales)");
|
|
3863
|
+
return JSON.stringify(Object.fromEntries(Object.entries(scales).map(([name, scale]) => [name, {
|
|
3864
|
+
...scale,
|
|
3865
|
+
values: Object.fromEntries(Object.entries(scale.values).map(([key, record]) => [key, Object.fromEntries(Object.entries(record).map(([property, value]) => [property, rename(value)]))]))
|
|
3866
|
+
}])));
|
|
3867
|
+
}
|
|
2963
3868
|
//#endregion
|
|
2964
3869
|
//#region pipeline/system-config.ts
|
|
2965
3870
|
/**
|
|
@@ -2975,7 +3880,28 @@ function loadSystemConfig(engineApi, opts) {
|
|
|
2975
3880
|
let variableCss = registrations.css;
|
|
2976
3881
|
let contextualVarsJson = config.contextualVarsJson || null;
|
|
2977
3882
|
let declarationScalesJson = config.declarationScalesJson || null;
|
|
2978
|
-
|
|
3883
|
+
const declaredNames = Object.values(parseInternalWire(contextualVarsJson ?? "{}", "contextualVarsJson (the theme's contextual variable names)")).flat();
|
|
3884
|
+
let contextualProperties = declaredNames.map((name) => `--${name}`);
|
|
3885
|
+
let legacyPrefixedContextualVars = [];
|
|
3886
|
+
let prefixNameConflicts = [];
|
|
3887
|
+
if (opts.prefix && opts.prefixContextualVars) {
|
|
3888
|
+
const resolved = applyPropertyNames(opts.prefix, {
|
|
3889
|
+
variableMapJson,
|
|
3890
|
+
variableCss,
|
|
3891
|
+
themeJson: scalesJson,
|
|
3892
|
+
contextualVarsJson,
|
|
3893
|
+
declarationScalesJson
|
|
3894
|
+
});
|
|
3895
|
+
variableMapJson = resolved.variableMapJson;
|
|
3896
|
+
variableCss = resolved.variableCss;
|
|
3897
|
+
scalesJson = resolved.themeJson;
|
|
3898
|
+
contextualVarsJson = resolved.contextualVarsJson;
|
|
3899
|
+
contextualProperties = resolved.contextualProperties;
|
|
3900
|
+
prefixNameConflicts = resolved.nameConflicts;
|
|
3901
|
+
declarationScalesJson = resolved.declarationScalesJson;
|
|
3902
|
+
} else if (opts.prefix) {
|
|
3903
|
+
legacyPrefixedContextualVars = declaredNames;
|
|
3904
|
+
contextualProperties = declaredNames.map((name) => `--${opts.prefix}-${name}`);
|
|
2979
3905
|
const prefixed = applyPrefix(opts.prefix, variableMapJson, variableCss, scalesJson, contextualVarsJson || void 0);
|
|
2980
3906
|
variableMapJson = prefixed.variableMapJson;
|
|
2981
3907
|
variableCss = prefixed.variableCss;
|
|
@@ -2990,6 +3916,7 @@ function loadSystemConfig(engineApi, opts) {
|
|
|
2990
3916
|
variableMapJson,
|
|
2991
3917
|
variableCss,
|
|
2992
3918
|
contextualVarsJson,
|
|
3919
|
+
contextualProperties: [...new Set(contextualProperties)],
|
|
2993
3920
|
selectorAliasesJson: config.selectorAliases || null,
|
|
2994
3921
|
conditionAliasesJson: config.conditionAliases || null,
|
|
2995
3922
|
transformSourcesJson: config.transformSources || null,
|
|
@@ -3003,6 +3930,8 @@ function loadSystemConfig(engineApi, opts) {
|
|
|
3003
3930
|
if (declarationScalesJson) system.declarationScalesJson = declarationScalesJson;
|
|
3004
3931
|
if (config.propertyRecordsJson) system.propertyRecordsJson = config.propertyRecordsJson;
|
|
3005
3932
|
if (registrations.invalid.length > 0) system.invalidPropertyRegistrations = registrations.invalid;
|
|
3933
|
+
if (prefixNameConflicts.length > 0) system.prefixNameConflicts = prefixNameConflicts;
|
|
3934
|
+
if (legacyPrefixedContextualVars.length > 0) system.legacyPrefixedContextualVars = [...new Set(legacyPrefixedContextualVars)];
|
|
3006
3935
|
return system;
|
|
3007
3936
|
}
|
|
3008
3937
|
//#endregion
|
|
@@ -3013,7 +3942,7 @@ function runStructuralSelfCheck(input) {
|
|
|
3013
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`);
|
|
3014
3943
|
for (const { specifier, outcome } of input.externalOutcomes ?? []) if (outcome === "empty") failures.push(`include '${specifier}' resolved but discovered no component sources`);
|
|
3015
3944
|
else if (outcome === "unresolvable") failures.push(`include '${specifier}' could not be resolved`);
|
|
3016
|
-
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");
|
|
3017
3946
|
if ((input.assembledCss ?? `${input.variableCss}\n${input.globalCss}\n${input.componentCss}`).includes("__TRANSFORM__")) failures.push("Unresolved __TRANSFORM__ placeholders found in CSS output");
|
|
3018
3947
|
if (input.componentCount > 0 && (input.componentCss.length > 0 || input.assembledCss !== void 0)) {
|
|
3019
3948
|
const assembled = input.assembledCss ?? assembleStylesheet({
|
|
@@ -3022,9 +3951,16 @@ function runStructuralSelfCheck(input) {
|
|
|
3022
3951
|
globalCss: input.globalCss,
|
|
3023
3952
|
componentCss: input.componentCss
|
|
3024
3953
|
});
|
|
3025
|
-
const
|
|
3026
|
-
|
|
3027
|
-
|
|
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
|
+
}
|
|
3028
3964
|
}
|
|
3029
3965
|
return failures;
|
|
3030
3966
|
}
|
|
@@ -3056,6 +3992,7 @@ function buildDynamicPropConfig(dynamicProps) {
|
|
|
3056
3992
|
if (meta.negative) entry.negative = true;
|
|
3057
3993
|
if (meta.strict) entry.strict = true;
|
|
3058
3994
|
if (meta.keywords?.length) entry.keywords = meta.keywords;
|
|
3995
|
+
if (meta.currentVar) entry.currentVar = meta.currentVar;
|
|
3059
3996
|
configEntries[propName] = entry;
|
|
3060
3997
|
}
|
|
3061
3998
|
return configEntries;
|
|
@@ -3086,188 +4023,6 @@ function buildSystemPropsModule(opts) {
|
|
|
3086
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` : "");
|
|
3087
4024
|
}
|
|
3088
4025
|
//#endregion
|
|
3089
|
-
//#region pipeline/tsconfig-paths.ts
|
|
3090
|
-
/**
|
|
3091
|
-
* tsconfig `paths` → alias pairs, for drivers with no live bundler config.
|
|
3092
|
-
* The nearest config declaring `paths` wins wholesale — TS never merges.
|
|
3093
|
-
*/
|
|
3094
|
-
function stripJsonc(text) {
|
|
3095
|
-
let out = "";
|
|
3096
|
-
let inString = false;
|
|
3097
|
-
let inLine = false;
|
|
3098
|
-
let inBlock = false;
|
|
3099
|
-
for (let i = 0; i < text.length; i++) {
|
|
3100
|
-
const ch = text[i];
|
|
3101
|
-
const next = text[i + 1];
|
|
3102
|
-
if (inLine) {
|
|
3103
|
-
if (ch === "\n") {
|
|
3104
|
-
inLine = false;
|
|
3105
|
-
out += ch;
|
|
3106
|
-
}
|
|
3107
|
-
continue;
|
|
3108
|
-
}
|
|
3109
|
-
if (inBlock) {
|
|
3110
|
-
if (ch === "*" && next === "/") {
|
|
3111
|
-
inBlock = false;
|
|
3112
|
-
i++;
|
|
3113
|
-
}
|
|
3114
|
-
continue;
|
|
3115
|
-
}
|
|
3116
|
-
if (inString) {
|
|
3117
|
-
out += ch;
|
|
3118
|
-
if (ch === "\\") {
|
|
3119
|
-
out += next ?? "";
|
|
3120
|
-
i++;
|
|
3121
|
-
} else if (ch === "\"") inString = false;
|
|
3122
|
-
continue;
|
|
3123
|
-
}
|
|
3124
|
-
if (ch === "\"") {
|
|
3125
|
-
inString = true;
|
|
3126
|
-
out += ch;
|
|
3127
|
-
continue;
|
|
3128
|
-
}
|
|
3129
|
-
if (ch === "/" && next === "/") {
|
|
3130
|
-
inLine = true;
|
|
3131
|
-
i++;
|
|
3132
|
-
continue;
|
|
3133
|
-
}
|
|
3134
|
-
if (ch === "/" && next === "*") {
|
|
3135
|
-
inBlock = true;
|
|
3136
|
-
i++;
|
|
3137
|
-
continue;
|
|
3138
|
-
}
|
|
3139
|
-
out += ch;
|
|
3140
|
-
}
|
|
3141
|
-
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
3142
|
-
}
|
|
3143
|
-
/** `Object(value) === value` holds for exactly the blocks and lists
|
|
3144
|
-
* `JSON.parse` produces; the `[object Object]` tag separates the two. */
|
|
3145
|
-
function isJsonBlock(value) {
|
|
3146
|
-
return Object(value) === value && Object.prototype.toString.call(value) === "[object Object]";
|
|
3147
|
-
}
|
|
3148
|
-
/** A block or a list. A declared `paths` OWNS the setting and blocks its
|
|
3149
|
-
* parents' even when it contributes nothing usable. */
|
|
3150
|
-
function isJsonKeyed(value) {
|
|
3151
|
-
return Object(value) === value;
|
|
3152
|
-
}
|
|
3153
|
-
/** A JSON string, excluding the boxed `String` object, which no path join
|
|
3154
|
-
* accepts. */
|
|
3155
|
-
function isJsonString(value) {
|
|
3156
|
-
return Object(value) !== value && Object.prototype.toString.call(value) === "[object String]";
|
|
3157
|
-
}
|
|
3158
|
-
function decodePaths(value) {
|
|
3159
|
-
if (!isJsonKeyed(value)) return null;
|
|
3160
|
-
const decoded = /* @__PURE__ */ new Map();
|
|
3161
|
-
for (const [pattern, targets] of Object.entries(value)) {
|
|
3162
|
-
const [first] = Array.isArray(targets) ? targets : [];
|
|
3163
|
-
if (isJsonString(first)) decoded.set(pattern, first);
|
|
3164
|
-
}
|
|
3165
|
-
return decoded;
|
|
3166
|
-
}
|
|
3167
|
-
/** TypeScript accepts one specifier or an array; a non-string member names
|
|
3168
|
-
* no config and is dropped here. */
|
|
3169
|
-
function decodeExtends(value) {
|
|
3170
|
-
return (Array.isArray(value) ? value : [value]).filter(isJsonString);
|
|
3171
|
-
}
|
|
3172
|
-
function readConfig(path) {
|
|
3173
|
-
let raw;
|
|
3174
|
-
try {
|
|
3175
|
-
raw = readFileSync(path, "utf-8");
|
|
3176
|
-
} catch {
|
|
3177
|
-
return null;
|
|
3178
|
-
}
|
|
3179
|
-
let parsed;
|
|
3180
|
-
try {
|
|
3181
|
-
parsed = JSON.parse(stripJsonc(raw));
|
|
3182
|
-
} catch {
|
|
3183
|
-
return null;
|
|
3184
|
-
}
|
|
3185
|
-
const root = isJsonBlock(parsed) ? parsed : {};
|
|
3186
|
-
const compilerOptions = isJsonBlock(root.compilerOptions) ? root.compilerOptions : {};
|
|
3187
|
-
return {
|
|
3188
|
-
dir: dirname$1(path),
|
|
3189
|
-
baseUrl: isJsonString(compilerOptions.baseUrl) ? compilerOptions.baseUrl : null,
|
|
3190
|
-
paths: decodePaths(compilerOptions.paths),
|
|
3191
|
-
extends: decodeExtends(root.extends)
|
|
3192
|
-
};
|
|
3193
|
-
}
|
|
3194
|
-
function resolveExtendsTarget(specifier, fromDir) {
|
|
3195
|
-
if (specifier.startsWith(".") || isAbsolute(specifier)) {
|
|
3196
|
-
const base = resolve(fromDir, specifier);
|
|
3197
|
-
for (const candidate of [
|
|
3198
|
-
base,
|
|
3199
|
-
`${base}.json`,
|
|
3200
|
-
join$1(base, "tsconfig.json")
|
|
3201
|
-
]) if (existsSync(candidate)) return candidate;
|
|
3202
|
-
return null;
|
|
3203
|
-
}
|
|
3204
|
-
try {
|
|
3205
|
-
return __require.resolve(specifier.endsWith(".json") ? specifier : `${specifier}/tsconfig.json`, { paths: [fromDir] });
|
|
3206
|
-
} catch {
|
|
3207
|
-
try {
|
|
3208
|
-
return __require.resolve(specifier, { paths: [fromDir] });
|
|
3209
|
-
} catch {
|
|
3210
|
-
return null;
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
/** Child-first flattened extends chain starting at `entryPath`. */
|
|
3215
|
-
function loadChain(entryPath) {
|
|
3216
|
-
const chain = [];
|
|
3217
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3218
|
-
const queue = [entryPath];
|
|
3219
|
-
while (queue.length > 0) {
|
|
3220
|
-
const path = queue.shift();
|
|
3221
|
-
const key = resolve(path);
|
|
3222
|
-
if (visited.has(key)) continue;
|
|
3223
|
-
visited.add(key);
|
|
3224
|
-
const node = readConfig(key);
|
|
3225
|
-
if (!node) continue;
|
|
3226
|
-
chain.push(node);
|
|
3227
|
-
for (const parent of node.extends) {
|
|
3228
|
-
const resolved = resolveExtendsTarget(parent, node.dir);
|
|
3229
|
-
if (resolved) queue.push(resolved);
|
|
3230
|
-
}
|
|
3231
|
-
}
|
|
3232
|
-
return chain;
|
|
3233
|
-
}
|
|
3234
|
-
/**
|
|
3235
|
-
* The project's tsconfig path aliases as `buildPathAliasesJson` pairs; empty
|
|
3236
|
-
* when no readable tsconfig, or no usable `paths`, exists.
|
|
3237
|
-
*/
|
|
3238
|
-
function readTsconfigAliasPairs(rootDir) {
|
|
3239
|
-
const chain = loadChain(join$1(rootDir, "tsconfig.json"));
|
|
3240
|
-
if (chain.length === 0) return [];
|
|
3241
|
-
const pathsOwner = chain.find((node) => node.paths !== null);
|
|
3242
|
-
const paths = pathsOwner?.paths ?? null;
|
|
3243
|
-
if (pathsOwner === void 0 || paths === null) return [];
|
|
3244
|
-
const baseOwner = chain.find((node) => node.baseUrl !== null);
|
|
3245
|
-
const baseUrl = baseOwner?.baseUrl ?? null;
|
|
3246
|
-
const base = baseOwner !== void 0 && baseUrl !== null ? resolve(baseOwner.dir, baseUrl) : pathsOwner.dir;
|
|
3247
|
-
const pairs = [];
|
|
3248
|
-
for (const [pattern, target] of paths) {
|
|
3249
|
-
const patternStars = pattern.split("*").length - 1;
|
|
3250
|
-
const targetStars = target.split("*").length - 1;
|
|
3251
|
-
if (pattern === "*" || patternStars > 1 || targetStars > 1) continue;
|
|
3252
|
-
if (pattern.endsWith("/*")) {
|
|
3253
|
-
if (!target.endsWith("/*")) continue;
|
|
3254
|
-
pairs.push({
|
|
3255
|
-
pattern: pattern.slice(0, -2),
|
|
3256
|
-
target: resolve(base, target.slice(0, -2)),
|
|
3257
|
-
kind: "prefix"
|
|
3258
|
-
});
|
|
3259
|
-
continue;
|
|
3260
|
-
}
|
|
3261
|
-
if (patternStars > 0 || targetStars > 0) continue;
|
|
3262
|
-
pairs.push({
|
|
3263
|
-
pattern,
|
|
3264
|
-
target: resolve(base, target),
|
|
3265
|
-
kind: "exact"
|
|
3266
|
-
});
|
|
3267
|
-
}
|
|
3268
|
-
return pairs;
|
|
3269
|
-
}
|
|
3270
|
-
//#endregion
|
|
3271
4026
|
//#region pipeline/error-diagnostics.ts
|
|
3272
4027
|
function assertNoErrorDiagnostics(diagnostics) {
|
|
3273
4028
|
const errors = (diagnostics ?? []).filter((diagnostic) => diagnostic.kind === "error");
|
|
@@ -3352,4 +4107,4 @@ function realpathThroughNearestExistingAncestor(path) {
|
|
|
3352
4107
|
}
|
|
3353
4108
|
}
|
|
3354
4109
|
//#endregion
|
|
3355
|
-
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 };
|