@svelte-vitals/core 0.55.1 → 0.55.3
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -2
- package/dist/internal.d.ts +559 -21
- package/dist/internal.js +994 -117
- package/dist/{json-TIGneC1p.d.ts → json-DmLgFEwV.d.ts} +235 -99
- package/dist/{markdown-BOH3FPQl.js → markdown-CsQNXeWj.js} +1421 -355
- package/package.json +4 -4
package/dist/internal.js
CHANGED
|
@@ -1,51 +1,40 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
//#region src/
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"application/x-ecmascript",
|
|
7
|
-
"application/x-javascript",
|
|
8
|
-
"text/ecmascript",
|
|
9
|
-
"text/javascript",
|
|
10
|
-
"text/javascript1.0",
|
|
11
|
-
"text/javascript1.1",
|
|
12
|
-
"text/javascript1.2",
|
|
13
|
-
"text/javascript1.3",
|
|
14
|
-
"text/javascript1.4",
|
|
15
|
-
"text/javascript1.5",
|
|
16
|
-
"text/jscript",
|
|
17
|
-
"text/livescript",
|
|
18
|
-
"text/x-ecmascript",
|
|
19
|
-
"text/x-javascript"
|
|
20
|
-
]);
|
|
21
|
-
/** HTML strips only ASCII whitespace from a `type` before matching — `trim()` would also strip
|
|
22
|
-
* U+00A0 etc. and misread a data block as a classic script. */
|
|
23
|
-
const ASCII_WHITESPACE_EDGES = /^[\t\n\f\r ]+|[\t\n\f\r ]+$/g;
|
|
24
|
-
/**
|
|
25
|
-
* Whether a `type` attribute (undefined = absent) makes a `<script>` a classic,
|
|
26
|
-
* render-blocking-capable script. Per the HTML spec, absent and the literal empty string are
|
|
27
|
-
* classic; any other value is ASCII-whitespace-stripped and must match a JavaScript MIME type —
|
|
28
|
-
* so a whitespace-only `type` is a data block, not a classic script.
|
|
29
|
-
*/
|
|
30
|
-
function isClassicScriptType(type) {
|
|
31
|
-
if (type === void 0 || type === "") return true;
|
|
32
|
-
return JS_MIME_TYPES.has(type.replace(ASCII_WHITESPACE_EDGES, "").toLowerCase());
|
|
1
|
+
import { $ as performanceLoadWaterfall, $n as shouldSkipRangeCheck, $t as securityJavascriptUrl, A as a11yPatternTitle, An as splitTokens, Ar as seoTitlePresence, At as parseModuleProgram, B as a11yPlaceholderLabelOption, Bn as seoJsonLdDateFormat, Bt as attrTextOf, C as a11yPermittedContents, Cn as IDREF_ATTRS, Cr as seoCanonicalUrl, Ct as collectBrowserGuardImports, D as a11yDeprecatedAttr, Dn as foldOccurrences, Dr as layoutNeverRenders, Dt as collectSuppressions, E as a11yDisallowedAriaProps, En as decodeFragmentId, Er as seoDescriptionPresence, Et as collectProgramBindings, F as a11yPositiveTabindex, Fn as seoCharset, Fr as docsUrlFor, Ft as walkEstree, G as a11yInteractiveNesting, Gn as seoViewport, Gt as parseSvelte, H as a11yLabelHasControl, Hn as seoJsonLdDeprecatedType, Ht as attrValueOf, I as isInteractiveContainer, In as seoDescriptionLength, Ir as isPenalized, It as walkEvalScope, J as a11yUnknownAriaAttribute, Jn as seoTwitterCard, Jt as architecturePropCount, K as a11yInvalidAriaValue, Kn as seoOgUrl, Kt as textFromNodes, L as isInteractiveElement, Ln as seoTitleLength, Lt as walkScoped, M as a11yNoDuplicateDt, Mn as resolveRole, Mr as effectiveSeverity, Mt as scopeIntroducedNames, N as a11yNoAutofocus, Nn as seoHreflang, Nr as hasFailureAtOrAbove, Nt as unwrapExport, O as a11yDeprecatedElement, On as isTopFragment, Or as pageRendersOnlyWhenBuilding, Ot as collectSvelteLifecycleImports, P as a11yNoAccesskey, Pn as seoImageAlt, Pr as summarize, Pt as unwrapTs, Q as performanceSequentialAwaits, Qn as resolveRuleOptions, Qt as securityHandlerStateWrite, R as literalTabindexValue, Rn as seoJsonLdRequiredProps, Rr as defaultConfig, Rt as CHILD_NODE_KEYS, S as a11yRequiredElement, Sn as ASIDE_DEMOTING_TAGS, Sr as seoOgImage, St as collectBrowserGlobalRefs, T as a11yDeprecatedAria, Tn as SECTIONING_TAGS, Tr as isClassicScriptType, Tt as collectNamedImportAliases, U as a11yAccessibleName, Un as seoJsonLdValidity, Ut as findAttr, V as a11yUseList, Vn as seoJsonLdRelativeUrl, Vt as attrValue, W as a11yAriaHiddenFocus, Wn as seoSitemapInRobots, Wt as lineOf, X as performanceIframeLoading, Xn as performancePreconnect, Xt as securitySharedStateImport, Y as a11yInvalidRole, Yn as seoIndexability, Yt as architectureComponentSize, Z as performanceStateRaw, Zn as isPlainObject, Zt as securityServerModuleState, _ as a11yNoMissingIdRef, _n as seoHeadingLevelSkip, _r as seoHtmlLang, _t as parseKitModuleFacts, a as formatJsonReport, an as correctnessOrphanEffect, ar as formatFailedRuleWarning, at as architectureReservedNamePlacement, b as a11yDuplicateLandmark, bn as seoSingleH1, br as seoJsonLd, bt as WALK_IGNORED_KEYS, c as computeScore, cn as correctnessNonreactiveBuiltinState, cr as withFailedRulesOff, ct as architectureUnitEntryFile, d as messageText, dn as correctnessUnmutatedState, dr as performanceLcpImage, dt as collectFalseExports, en as securityRawHtml, er as validateRuleSetting, et as performanceMinifyDisabled, f as ruleMetaById, fn as correctnessEffectAsOnMount, fr as performanceFontPreloadCrossorigin, ft as collectTopLevelBindings, g as a11yUnverifiedIdRef, gn as seoSsrDisabled, gr as performanceImageDimensions, gt as parseInMemoryExports, h as explainRule, hn as correctnessEachKey, hr as performanceImageLoadingHint, ht as parseFalseExports, i as buildJsonReport, in as correctnessOrphanLifecycle, ir as compileOverrides, it as architectureRouteComponentImport, j as a11yAbbrTitle, jn as stripTextDirective, jr as classify, jt as rootObjectName, k as HTML_SPEC, kn as resolveLandmark, kr as routeNeverSsr, kt as matchLifecycleCall, l as formatGithubReport, ln as correctnessStalePropDerivation, lr as withIndexableOff, lt as architecturePrivateScopeImport, m as allRules, mn as correctnessEachIndexKey, mr as performanceResponsiveImage, mt as parseExitFactoryExports, n as mdEscape, nn as correctnessServerBrowserGlobal, nr as applyOverrides, nt as performanceHeavyImport, o as formatJsonReportChunks, on as correctnessAutoplayMuted, or as selectRules, ot as architectureReservedDirectoryNames, p as severityToSarifLevel, pn as correctnessEffectAsDerived, pr as performancePreloadMissingAs, pt as parseClientExports, q as a11yRequiredAriaProps, qn as seoOgDescription, qt as valueFromNodes, r as terminalSafe, rn as correctnessBasePathNavigation, rr as applyRuleSeverities, rt as architectureDocLinkTarget, s as computeHealth, sn as correctnessCheckableBindValue, sr as settingSeverity, st as architectureDirectoryNaming, t as formatMarkdownReport, tn as correctnessInstanceBrowserGlobal, tr as SEARCH_RESULT_RULES, tt as performanceNamespaceImport, u as SEVERITY_RANK, un as correctnessPropMutation, ur as performanceRenderBlockingScript, ut as DEFAULT_KIT_ALIASES, v as a11yIdDuplication, vn as seoDuplicateDescription, vr as seoSitemapXml, vt as resolveRepoLocalPath, w as a11yDisallowedElement, wn as NAMING_ATTRS, wr as hasRel, wt as collectDerivedGuardBindings, x as a11yDoctype, xn as ANCESTRY_DEPENDENT_TAGS, xr as seoOgTitle, xt as addBoundNames, y as a11yTopLevelLandmark, yn as seoDuplicateTitle, yr as seoRobotsTxt, yt as isRootRelativePath, z as a11yRequireDatetime, zn as seoJsonLdPlaceholder, zr as defaultProject, zt as attrText } from "./markdown-CsQNXeWj.js";
|
|
2
|
+
//#region src/images.ts
|
|
3
|
+
/** A `.svg` path (query and fragment ignored) or an inline `data:image/svg+xml` URI. */
|
|
4
|
+
function isSvgSrc(src) {
|
|
5
|
+
return /^data:image\/svg\+xml[;,]/i.test(src) || /\.svg$/i.test(src.split(/[?#]/, 1)[0]);
|
|
33
6
|
}
|
|
34
7
|
//#endregion
|
|
35
8
|
//#region src/component-parse.ts
|
|
36
9
|
const KNOWN_TAGS = new Set(Object.keys(HTML_SPEC.elements).map((k) => k.replace(/^svg:/, "").toLowerCase()));
|
|
37
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Whether `expr` is a length-only list constructor: `Array(n)` / `new Array(n)` (single argument =
|
|
12
|
+
* length semantics), `Array.from({ length: n }, …)`, or `Array.from(<any of these>, …)`, optionally
|
|
13
|
+
* followed by `.fill(x)` (every item the same value) or `.keys()` (the indices themselves).
|
|
14
|
+
*/
|
|
38
15
|
function isLengthOnlyArrayCall(expr) {
|
|
39
16
|
const e = unwrapTs(expr);
|
|
40
17
|
if (!e) return false;
|
|
18
|
+
if (e.type === "CallExpression" && e.callee?.type === "MemberExpression" && !e.callee.computed && e.callee.object.type !== "Super" && e.callee.property.type === "Identifier" && (e.callee.property.name === "fill" || e.callee.property.name === "keys")) {
|
|
19
|
+
const receiver = unwrapTs(e.callee.object);
|
|
20
|
+
if (receiver?.type === "ArrayExpression") return receiver.elements.every((el) => el?.type !== "SpreadElement" || isLengthOnlyArrayCall(el.argument));
|
|
21
|
+
return isLengthOnlyArrayCall(e.callee.object);
|
|
22
|
+
}
|
|
41
23
|
if ((e.type === "CallExpression" || e.type === "NewExpression") && e.callee?.type === "Identifier" && e.callee.name === "Array") return (e.arguments?.length ?? 0) === 1;
|
|
42
|
-
if (e.type === "CallExpression" && e.callee?.type === "MemberExpression" && !e.callee.computed && e.callee.object?.type === "Identifier" && e.callee.object.name === "Array" && e.callee.property.type === "Identifier" && e.callee.property.name === "from"
|
|
24
|
+
if (e.type === "CallExpression" && e.callee?.type === "MemberExpression" && !e.callee.computed && e.callee.object?.type === "Identifier" && e.callee.object.name === "Array" && e.callee.property.type === "Identifier" && e.callee.property.name === "from") {
|
|
25
|
+
const source = e.arguments?.[0];
|
|
26
|
+
if (source?.type === "ObjectExpression") return (source.properties ?? []).some(isLengthProperty);
|
|
27
|
+
return source !== void 0 && source.type !== "SpreadElement" && isLengthOnlyArrayCall(source);
|
|
28
|
+
}
|
|
43
29
|
return false;
|
|
44
30
|
}
|
|
31
|
+
function isLengthProperty(p) {
|
|
32
|
+
return p?.type === "Property" && !p.computed && (p.key?.name === "length" || p.key?.value === "length");
|
|
33
|
+
}
|
|
45
34
|
/**
|
|
46
35
|
* Whether the each expression yields no item identity to key on: a constant
|
|
47
36
|
* inline array literal (fixed length, never reorders), a length-only list
|
|
48
|
-
* (`Array(n)`, `new Array(n)`, `[...Array(n)]`, `Array.from({ length: n })` —
|
|
37
|
+
* (`Array(n)`, `new Array(n)`, `[...Array(n)]`, `Array.from({ length: n })`, `Array(n).fill(x)`, `{ length: n }` —
|
|
49
38
|
* placeholder/skeleton lists), or a spread array whose every element spreads a
|
|
50
39
|
* length-only list. Such blocks are skipped entirely — neither each-key nor
|
|
51
40
|
* each-index-key can give useful advice on them.
|
|
@@ -53,6 +42,7 @@ function isLengthOnlyArrayCall(expr) {
|
|
|
53
42
|
function isIdentityFreeEach(node) {
|
|
54
43
|
const expr = unwrapTs(node.expression);
|
|
55
44
|
if (expr.type === "ArrayExpression" && Array.isArray(expr.elements)) return expr.elements.every((el) => el?.type !== "SpreadElement" || isLengthOnlyArrayCall(el.argument));
|
|
45
|
+
if (expr.type === "ObjectExpression") return expr.properties.length === 1 && isLengthProperty(expr.properties[0]);
|
|
56
46
|
return isLengthOnlyArrayCall(expr);
|
|
57
47
|
}
|
|
58
48
|
/**
|
|
@@ -97,19 +87,194 @@ function isIndexKey(each) {
|
|
|
97
87
|
if (typeof each.index !== "string" || each.key == null) return false;
|
|
98
88
|
return isIndexExpression(each.key, each.index);
|
|
99
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* TS nodes that wrap value-space code; every other `TS*` node is a type position. Enums,
|
|
92
|
+
* namespaces and parameter properties are left out: Svelte's own TS support rejects them.
|
|
93
|
+
*/
|
|
94
|
+
const TS_VALUE_NODES = /* @__PURE__ */ new Set([
|
|
95
|
+
"TSAsExpression",
|
|
96
|
+
"TSSatisfiesExpression",
|
|
97
|
+
"TSNonNullExpression",
|
|
98
|
+
"TSInstantiationExpression",
|
|
99
|
+
"TSTypeAssertion"
|
|
100
|
+
]);
|
|
101
|
+
/**
|
|
102
|
+
* Names of `const X = [ … ]` array literals (script top level) that nothing can reorder: every
|
|
103
|
+
* other mention of `X` in the file is a member read, an `{#each X …}`, a `for (… of X)` or a copy into an array literal
|
|
104
|
+
* (`[...X]`). Such a list is the inline
|
|
105
|
+
* literal the each rules already skip, just given a name. Anything else — a mutating call, an
|
|
106
|
+
* assignment through it, passing or spreading `X` — keeps the list keyed. A component's value
|
|
107
|
+
* imports are held to the same uses (`imports`), since the module behind one may be such a list;
|
|
108
|
+
* a module's `export const` is a candidate only with `exported`, because a component's `export`
|
|
109
|
+
* is written by its parent.
|
|
110
|
+
*/
|
|
111
|
+
function collectConstantLists(programs, fragment, opts = {}) {
|
|
112
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
113
|
+
const safe = /* @__PURE__ */ new Set();
|
|
114
|
+
const iterated = opts.imports ? eachListRoots(fragment) : void 0;
|
|
115
|
+
for (const program of programs) for (const top of program.body ?? []) {
|
|
116
|
+
if (iterated?.size && top?.type === "ImportDeclaration" && top.importKind !== "type") for (const s of top.specifiers ?? []) {
|
|
117
|
+
if (s?.importKind === "type" || s?.type === "ImportDefaultSpecifier" || !iterated.has(s?.local?.name)) continue;
|
|
118
|
+
const imported = s.type === "ImportNamespaceSpecifier" ? "*" : s.imported?.name ?? s.imported?.value;
|
|
119
|
+
if (typeof imported !== "string") continue;
|
|
120
|
+
opts.imports.set(s.local.name, {
|
|
121
|
+
source: String(top.source.value),
|
|
122
|
+
imported
|
|
123
|
+
});
|
|
124
|
+
candidates.set(s.local.name, s.local);
|
|
125
|
+
safe.add(s.imported);
|
|
126
|
+
}
|
|
127
|
+
if (opts.exported && top?.type === "ExportNamedDeclaration" && !top.source) for (const s of top.specifiers ?? []) safe.add(s.local).add(s.exported);
|
|
128
|
+
const stmt = opts.exported ? unwrapExport(top) : top;
|
|
129
|
+
if (stmt?.type !== "VariableDeclaration" || stmt.kind !== "const") continue;
|
|
130
|
+
for (const d of stmt.declarations ?? []) {
|
|
131
|
+
const init = opts.sets ? setLiteralList(unwrapTs(d?.init)) : unwrapTs(d?.init);
|
|
132
|
+
if (d?.id?.type === "Identifier" && init?.type === "ArrayExpression") {
|
|
133
|
+
if (!(init.elements ?? []).some((el) => el?.type === "SpreadElement")) candidates.set(d.id.name, d.id);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (candidates.size === 0) return /* @__PURE__ */ new Set();
|
|
138
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
139
|
+
if (opts.exported) for (const program of programs) walkEstree(program, (n) => {
|
|
140
|
+
if (n.type !== "VariableDeclaration" || n.kind !== "const") return;
|
|
141
|
+
for (const d of n.declarations ?? []) {
|
|
142
|
+
const init = unwrapTs(d?.init);
|
|
143
|
+
const list = (init?.type === "ConditionalExpression" ? [init.consequent, init.alternate] : init?.type === "LogicalExpression" && init.operator !== "&&" ? [init.left, init.right] : [init]).map((b) => unwrapTs(b)).find((b) => b?.type === "Identifier" && candidates.has(b.name));
|
|
144
|
+
if (d?.id?.type !== "Identifier" || !list || candidates.has(d.id.name)) continue;
|
|
145
|
+
safe.add(list);
|
|
146
|
+
aliases.set(d.id.name, list.name);
|
|
147
|
+
candidates.set(d.id.name, d.id);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
for (const node of candidates.values()) safe.add(node);
|
|
151
|
+
const namespaces = new Set([...opts.imports ?? []].filter(([, b]) => b.imported === "*").map(([local]) => local));
|
|
152
|
+
const unsafe = /* @__PURE__ */ new Set();
|
|
153
|
+
const roots = [...programs, fragment];
|
|
154
|
+
const listOf = (n) => n?.type === "Identifier" && candidates.has(n.name) ? n.name : void 0;
|
|
155
|
+
const outerList = listOf;
|
|
156
|
+
const nsMember = (n) => n?.type === "MemberExpression" && !n.computed && namespaces.has(listOf(n.object)) ? `${n.object.name}.${n.property.name}` : void 0;
|
|
157
|
+
const outerMember = nsMember;
|
|
158
|
+
const members = /* @__PURE__ */ new Set();
|
|
159
|
+
const declared = /* @__PURE__ */ new Set();
|
|
160
|
+
for (const program of programs) {
|
|
161
|
+
const imported = /* @__PURE__ */ new Set();
|
|
162
|
+
for (const top of program.body ?? []) if (top?.type === "ImportDeclaration") for (const s of top.specifiers ?? []) imported.add(s?.local?.name);
|
|
163
|
+
for (const name of collectProgramBindings(program)) if (!imported.has(name)) declared.add(name);
|
|
164
|
+
}
|
|
165
|
+
for (const root of roots) walkScoped(root, (n, shadowed) => {
|
|
166
|
+
const listOf = (x) => {
|
|
167
|
+
const name = outerList(x);
|
|
168
|
+
return name && !shadowed.has(name) ? name : void 0;
|
|
169
|
+
};
|
|
170
|
+
const nsMember = (x) => x?.type === "MemberExpression" && listOf(x.object) ? outerMember(x) : void 0;
|
|
171
|
+
if (n.type === "EachBlock" && (listOf(n.expression) || nsMember(n.expression))) safe.add(n.expression);
|
|
172
|
+
if (n.type === "ForOfStatement" && (listOf(n.right) || nsMember(n.right))) safe.add(n.right);
|
|
173
|
+
if (n.type === "ArrayExpression") {
|
|
174
|
+
for (const el of n.elements ?? []) if (el?.type === "SpreadElement" && (listOf(el.argument) || nsMember(el.argument))) safe.add(el.argument);
|
|
175
|
+
}
|
|
176
|
+
if (nsMember(n)) members.add(nsMember(n));
|
|
177
|
+
if (n.type === "MemberExpression") {
|
|
178
|
+
const object = n.object ? unwrapTs(n.object) : n.object;
|
|
179
|
+
if (listOf(object) && !(n.computed && namespaces.has(object.name))) safe.add(object);
|
|
180
|
+
if (nsMember(object)) safe.add(object);
|
|
181
|
+
if (!n.computed) safe.add(n.property);
|
|
182
|
+
}
|
|
183
|
+
const target = n.type === "AssignmentExpression" ? n.left : n.type === "UpdateExpression" ? n.argument : void 0;
|
|
184
|
+
let root = target;
|
|
185
|
+
while (root && (root.type === "MemberExpression" || root.type.startsWith("TS"))) root = root.type === "MemberExpression" ? root.object : root.expression;
|
|
186
|
+
if (target?.type === "MemberExpression" && listOf(root)) unsafe.add(listOf(root));
|
|
187
|
+
const called = n.type === "CallExpression" && n.callee?.type === "MemberExpression" ? unwrapTs(n.callee.object) : void 0;
|
|
188
|
+
const calledList = listOf(called) ?? nsMember(called);
|
|
189
|
+
if (calledList) {
|
|
190
|
+
const { computed, property } = n.callee;
|
|
191
|
+
const method = !computed ? property?.name : property?.type === "Literal" && typeof property.value === "string" ? property.value : void 0;
|
|
192
|
+
if (computed && method === void 0 || method !== void 0 && MUTATING_METHODS.has(method)) unsafe.add(calledList);
|
|
193
|
+
}
|
|
194
|
+
const callee = n.type === "CallExpression" || n.type === "NewExpression" ? rootObjectName(n.callee) : void 0;
|
|
195
|
+
if (opts.exported && callee !== void 0 && !declared.has(callee) && !shadowed.has(callee)) {
|
|
196
|
+
for (const arg of n.arguments ?? []) if (listOf(unwrapTs(arg))) safe.add(unwrapTs(arg));
|
|
197
|
+
}
|
|
198
|
+
if (n.type === "BindDirective" && listOf(rootObjectNode(n.expression))) unsafe.add(listOf(rootObjectNode(n.expression)));
|
|
199
|
+
if (n.type === "Property" && !n.computed && !n.shorthand) safe.add(n.key);
|
|
200
|
+
});
|
|
201
|
+
for (const root of roots) walkEvalScope(root, (n, shadowed) => {
|
|
202
|
+
if (n.type.startsWith("TS") && !TS_VALUE_NODES.has(n.type)) return true;
|
|
203
|
+
const name = listOf(n) ?? nsMember(n);
|
|
204
|
+
if (name && !safe.has(n) && !shadowed.has(name.split(".")[0])) unsafe.add(name);
|
|
205
|
+
return false;
|
|
206
|
+
}, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
|
|
207
|
+
for (const [alias, list] of aliases) if (unsafe.has(alias)) unsafe.add(list);
|
|
208
|
+
return new Set([...candidates.keys(), ...members].filter((key) => !aliases.has(key) && !unsafe.has(key) && !unsafe.has(key.split(".")[0])));
|
|
209
|
+
}
|
|
210
|
+
/** A component's script `const` lists that nothing in its scripts or template can change. */
|
|
211
|
+
function collectComponentConstantLists(ast, opts = {}) {
|
|
212
|
+
return collectConstantLists([ast.module?.content, ast.instance?.content].filter(Boolean), ast.fragment ?? ast, opts);
|
|
213
|
+
}
|
|
214
|
+
/** The array literal of `new Set([…])`, or the node itself. */
|
|
215
|
+
function setLiteralList(n) {
|
|
216
|
+
const arg = n?.type === "NewExpression" && n.callee?.type === "Identifier" && n.callee.name === "Set" ? n.arguments : void 0;
|
|
217
|
+
return arg?.length === 1 && unwrapTs(arg[0])?.type === "ArrayExpression" ? unwrapTs(arg[0]) : n;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Export names of a module's constant lists — `export const X = [ … ]`, or a local one exported by
|
|
221
|
+
* name — that the module itself never writes (correctness/each-key, correctness/each-index-key).
|
|
222
|
+
*/
|
|
223
|
+
function collectConstantListExports(program) {
|
|
224
|
+
const lists = collectConstantLists([program], void 0, { exported: true });
|
|
225
|
+
const out = /* @__PURE__ */ new Set();
|
|
226
|
+
for (const top of program?.body ?? []) {
|
|
227
|
+
if (top?.type !== "ExportNamedDeclaration" || top.exportKind === "type") continue;
|
|
228
|
+
for (const d of top.declaration?.declarations ?? []) if (d?.id?.type === "Identifier" && lists.has(d.id.name)) out.add(d.id.name);
|
|
229
|
+
for (const s of top.source ? [] : top.specifiers ?? []) if (lists.has(s.local.name)) out.add(s.exported.name ?? s.exported.value);
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
/** The root names `{#each}` blocks iterate: `xs` for both `{#each xs …}` and `{#each xs.ys …}`. */
|
|
234
|
+
function eachListRoots(node, acc = /* @__PURE__ */ new Set()) {
|
|
235
|
+
if (Array.isArray(node)) {
|
|
236
|
+
for (const child of node) eachListRoots(child, acc);
|
|
237
|
+
return acc;
|
|
238
|
+
}
|
|
239
|
+
if (!node || typeof node !== "object") return acc;
|
|
240
|
+
if (node.type === "EachBlock") {
|
|
241
|
+
const expr = node.expression?.type === "MemberExpression" ? node.expression.object : node.expression;
|
|
242
|
+
if (expr?.type === "Identifier") acc.add(expr.name);
|
|
243
|
+
}
|
|
244
|
+
for (const key of CHILD_NODE_KEYS) if (key in node) eachListRoots(node[key], acc);
|
|
245
|
+
return acc;
|
|
246
|
+
}
|
|
247
|
+
/** The innermost object of a member chain (`a` for `a.b[c].d`), or the node itself. */
|
|
248
|
+
function rootObjectNode(n) {
|
|
249
|
+
let cur = n;
|
|
250
|
+
while (cur?.type === "MemberExpression") cur = cur.object;
|
|
251
|
+
return cur;
|
|
252
|
+
}
|
|
100
253
|
/** Recursively collect every `{#each}` block in the template (correctness/each-key). */
|
|
101
|
-
function collectEachBlocks(node, source, acc) {
|
|
254
|
+
function collectEachBlocks(node, source, acc, lists, imports) {
|
|
102
255
|
if (Array.isArray(node)) {
|
|
103
|
-
for (const child of node) collectEachBlocks(child, source, acc);
|
|
256
|
+
for (const child of node) collectEachBlocks(child, source, acc, lists, imports);
|
|
104
257
|
return;
|
|
105
258
|
}
|
|
106
259
|
if (!node || typeof node !== "object") return;
|
|
107
|
-
if (node.type === "EachBlock" && node.context != null && !isIdentityFreeEach(node))
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
260
|
+
if (node.type === "EachBlock" && node.context != null && !isIdentityFreeEach(node)) {
|
|
261
|
+
const expr = node.expression;
|
|
262
|
+
const list = expr?.type === "Identifier" ? expr.name : expr?.type === "MemberExpression" && !expr.computed && expr.object?.type === "Identifier" ? `${expr.object.name}.${expr.property.name}` : void 0;
|
|
263
|
+
const constant = list !== void 0 && lists.has(list);
|
|
264
|
+
const [local, member] = list?.split(".") ?? [];
|
|
265
|
+
const binding = constant ? imports.get(local) : void 0;
|
|
266
|
+
const indexKey = isIndexKey(node);
|
|
267
|
+
if (!constant || binding) acc.push({
|
|
268
|
+
hasKey: node.key != null,
|
|
269
|
+
line: lineOf(source, node.start),
|
|
270
|
+
...indexKey ? { indexKey: true } : {},
|
|
271
|
+
...binding && (indexKey || node.key == null) ? { importedList: member ? {
|
|
272
|
+
source: binding.source,
|
|
273
|
+
imported: member
|
|
274
|
+
} : binding } : {}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
for (const key of CHILD_NODE_KEYS) if (key in node) collectEachBlocks(node[key], source, acc, lists, imports);
|
|
113
278
|
}
|
|
114
279
|
/**
|
|
115
280
|
* Whether a CallExpression *creates an effect*: `$effect(...)` or `$effect.pre(...)`.
|
|
@@ -132,6 +297,14 @@ function isEffectRootCall(node) {
|
|
|
132
297
|
* `$state.raw(...)`, or `$state.frozen(...)` — but NOT readers like
|
|
133
298
|
* `$state.snapshot(...)`, which would otherwise pollute the state-name set (correctness/effect-as-derived).
|
|
134
299
|
*/
|
|
300
|
+
/** An absent argument or a literal no reader can write through: `0`, `'x'`, `null`, `-1`, `` `x` ``. */
|
|
301
|
+
function isPrimitiveLiteral(node) {
|
|
302
|
+
if (node === void 0) return true;
|
|
303
|
+
if (node.type === "Literal") return !("regex" in node);
|
|
304
|
+
if (node.type === "TemplateLiteral") return node.expressions.length === 0;
|
|
305
|
+
if (node.type === "UnaryExpression") return node.argument?.type === "Literal";
|
|
306
|
+
return node.type === "Identifier" && node.name === "undefined";
|
|
307
|
+
}
|
|
135
308
|
function isStateDeclaration(node) {
|
|
136
309
|
const c = node?.callee;
|
|
137
310
|
if (c?.type === "Identifier") return c.name === "$state";
|
|
@@ -140,7 +313,18 @@ function isStateDeclaration(node) {
|
|
|
140
313
|
}
|
|
141
314
|
/** True when a function's body does nothing but assign to `$state` identifiers (correctness/effect-as-derived). */
|
|
142
315
|
function bodyOnlyAssignsState(fn, stateNames) {
|
|
143
|
-
const
|
|
316
|
+
const readsItself = (expr) => {
|
|
317
|
+
let reads = false;
|
|
318
|
+
const names = /* @__PURE__ */ new Set();
|
|
319
|
+
walkEstree(expr.right, (n) => {
|
|
320
|
+
if (n?.type === "MemberExpression" && !n.computed) names.add(n.property);
|
|
321
|
+
if (n?.type === "Property" && !n.computed && !n.shorthand) names.add(n.key);
|
|
322
|
+
if (n?.type === "TSTypeQuery") names.add(n.exprName);
|
|
323
|
+
reads ||= n?.type === "Identifier" && n.name === expr.left.name && !names.has(n);
|
|
324
|
+
});
|
|
325
|
+
return reads;
|
|
326
|
+
};
|
|
327
|
+
const isStateAssign = (expr) => expr?.type === "AssignmentExpression" && expr.operator === "=" && expr.left?.type === "Identifier" && stateNames.has(expr.left.name) && !readsItself(expr);
|
|
144
328
|
const body = fn?.body;
|
|
145
329
|
if (!body) return false;
|
|
146
330
|
if (body.type !== "BlockStatement") return isStateAssign(body);
|
|
@@ -154,7 +338,37 @@ function isDerivedDeclaration(node) {
|
|
|
154
338
|
if (c?.type === "MemberExpression" && c.object?.type === "Identifier" && c.object.name === "$derived") return c.property?.type === "Identifier" && c.property.name === "by";
|
|
155
339
|
return false;
|
|
156
340
|
}
|
|
157
|
-
|
|
341
|
+
const OBJECT_TYPES = /* @__PURE__ */ new Set([
|
|
342
|
+
"TSArrayType",
|
|
343
|
+
"TSTupleType",
|
|
344
|
+
"TSTypeLiteral"
|
|
345
|
+
]);
|
|
346
|
+
const OBJECT_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
347
|
+
"Array",
|
|
348
|
+
"ReadonlyArray",
|
|
349
|
+
"Record",
|
|
350
|
+
"Map",
|
|
351
|
+
"Set",
|
|
352
|
+
"WeakMap",
|
|
353
|
+
"WeakSet",
|
|
354
|
+
"Date"
|
|
355
|
+
]);
|
|
356
|
+
const OBJECT_VALUES = /* @__PURE__ */ new Set([
|
|
357
|
+
"ObjectExpression",
|
|
358
|
+
"ArrayExpression",
|
|
359
|
+
"NewExpression"
|
|
360
|
+
]);
|
|
361
|
+
/** A `$state` call whose initial value or type argument is an object, array or class instance. */
|
|
362
|
+
function isObjectState(call) {
|
|
363
|
+
const arg = unwrapTs(call.arguments?.[0]);
|
|
364
|
+
if (isPrimitiveLiteral(arg)) return false;
|
|
365
|
+
if (OBJECT_VALUES.has(arg?.type)) return true;
|
|
366
|
+
if (arg?.type === "LogicalExpression" && arg.operator !== "&&" && !isPrimitiveLiteral(unwrapTs(arg.left)) && OBJECT_VALUES.has(unwrapTs(arg.right)?.type)) return true;
|
|
367
|
+
const type = (call.typeArguments ?? call.typeParameters)?.params?.[0];
|
|
368
|
+
if (type?.type === "TSTypeReference") return OBJECT_TYPE_NAMES.has(type.typeName?.name);
|
|
369
|
+
return OBJECT_TYPES.has(type?.type);
|
|
370
|
+
}
|
|
371
|
+
function collectStateWrites(root, stateNames, acc, kinds, localStates = false) {
|
|
158
372
|
const record = (name, kind) => {
|
|
159
373
|
acc.add(name);
|
|
160
374
|
if (kinds) {
|
|
@@ -163,14 +377,38 @@ function collectStateWrites(root, stateNames, acc, kinds) {
|
|
|
163
377
|
set.add(kind);
|
|
164
378
|
}
|
|
165
379
|
};
|
|
166
|
-
|
|
380
|
+
const primitiveStates = /* @__PURE__ */ new Set();
|
|
381
|
+
const objectStates = /* @__PURE__ */ new Set();
|
|
382
|
+
const heldObjects = /* @__PURE__ */ new Set();
|
|
383
|
+
walkEstree(root, (n) => {
|
|
384
|
+
const init = n?.type === "VariableDeclarator" ? unwrapTs(n.init) : void 0;
|
|
385
|
+
if (n?.id?.type !== "Identifier" || !isStateDeclaration(init)) return;
|
|
386
|
+
(isPrimitiveLiteral(init.arguments?.[0]) ? primitiveStates : objectStates).add(n.id.name);
|
|
387
|
+
if (localStates && isObjectState(init)) heldObjects.add(n.id.name);
|
|
388
|
+
});
|
|
389
|
+
for (const name of objectStates) primitiveStates.delete(name);
|
|
390
|
+
const aliasOf = /* @__PURE__ */ new Map();
|
|
391
|
+
const reassigned = /* @__PURE__ */ new Set();
|
|
392
|
+
if (localStates) walkEstree(root, (n) => {
|
|
393
|
+
const target = n?.type === "AssignmentExpression" ? n.left : n?.type === "UpdateExpression" ? n.argument : void 0;
|
|
394
|
+
if (target?.type === "Identifier") reassigned.add(target.name);
|
|
395
|
+
});
|
|
396
|
+
if (localStates) for (let pass = 0; pass < 2; pass++) walkEstree(root, (n) => {
|
|
397
|
+
const [id, from] = n?.type === "VariableDeclarator" && unwrapTs(n.init)?.type === "MemberExpression" ? [n.id, n.init] : n?.type === "ForOfStatement" && n.left?.type === "VariableDeclaration" ? [n.left.declarations?.[0]?.id, n.right] : [void 0, void 0];
|
|
398
|
+
const r = rootObjectName(unwrapTs(from));
|
|
399
|
+
const owner = r && (stateNames.has(r) ? r : aliasOf.get(r));
|
|
400
|
+
if (id?.type === "Identifier" && owner && !stateNames.has(id.name) && !reassigned.has(id.name)) aliasOf.set(id.name, owner);
|
|
401
|
+
});
|
|
402
|
+
const visit = (stateNames) => (n, scope) => {
|
|
167
403
|
const shadowed = (name) => name === void 0 || scope.has(name);
|
|
404
|
+
const mutated = (r) => {
|
|
405
|
+
if (r && stateNames.has(r) && !shadowed(r)) record(r, "mutate");
|
|
406
|
+
else if (r && stateNames.has(aliasOf.get(r))) record(aliasOf.get(r), "mutate");
|
|
407
|
+
};
|
|
168
408
|
if (n?.type === "AssignmentExpression") {
|
|
169
409
|
if (n.left?.type === "Identifier" && stateNames.has(n.left.name) && !shadowed(n.left.name)) record(n.left.name, "reassign");
|
|
170
|
-
else if (n.left?.type === "MemberExpression")
|
|
171
|
-
|
|
172
|
-
if (r && stateNames.has(r) && !shadowed(r)) record(r, "mutate");
|
|
173
|
-
} else if (n.left?.type === "ObjectPattern" || n.left?.type === "ArrayPattern") {
|
|
410
|
+
else if (n.left?.type === "MemberExpression") mutated(rootObjectName(n.left));
|
|
411
|
+
else if (n.left?.type === "ObjectPattern" || n.left?.type === "ArrayPattern") {
|
|
174
412
|
const bound = /* @__PURE__ */ new Set();
|
|
175
413
|
addBoundNames(n.left, bound);
|
|
176
414
|
for (const name of bound) if (stateNames.has(name) && !shadowed(name)) record(name, "reassign");
|
|
@@ -178,24 +416,41 @@ function collectStateWrites(root, stateNames, acc, kinds) {
|
|
|
178
416
|
} else if (n?.type === "UpdateExpression") {
|
|
179
417
|
if (n.argument?.type === "Identifier") {
|
|
180
418
|
if (stateNames.has(n.argument.name) && !shadowed(n.argument.name)) record(n.argument.name, "reassign");
|
|
181
|
-
} else
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
} else if (n?.type === "UnaryExpression" && n.operator === "delete") {
|
|
186
|
-
const r = rootObjectName(n.argument);
|
|
187
|
-
if (r && stateNames.has(r) && !shadowed(r)) record(r, "mutate");
|
|
188
|
-
} else if (n?.type === "CallExpression") {
|
|
189
|
-
if (n.callee?.type === "MemberExpression") {
|
|
190
|
-
const r = rootObjectName(n.callee);
|
|
191
|
-
if (r && stateNames.has(r) && !shadowed(r)) record(r, "mutate");
|
|
192
|
-
}
|
|
419
|
+
} else mutated(rootObjectName(n.argument));
|
|
420
|
+
} else if (n?.type === "UnaryExpression" && n.operator === "delete") mutated(rootObjectName(n.argument));
|
|
421
|
+
else if (n?.type === "CallExpression") {
|
|
422
|
+
if (n.callee?.type === "MemberExpression") mutated(rootObjectName(n.callee));
|
|
193
423
|
for (const a of n.arguments ?? []) {
|
|
194
424
|
const arg = a?.type === "SpreadElement" ? a.argument : a;
|
|
195
425
|
const r = rootObjectName(arg);
|
|
196
426
|
if (r && stateNames.has(r) && !shadowed(r)) record(r, "escape");
|
|
197
427
|
}
|
|
428
|
+
} else if (n?.type === "ReturnStatement") {
|
|
429
|
+
const out = unwrapTs(n.argument);
|
|
430
|
+
const r = out?.type === "Identifier" ? out.name : void 0;
|
|
431
|
+
if (r && stateNames.has(r) && !shadowed(r) && !primitiveStates.has(r)) record(r, "escape");
|
|
432
|
+
} else if (heldObjects.size > 0 && (n?.type === "ObjectExpression" || n?.type === "ArrayExpression")) {
|
|
433
|
+
const values = n.type === "ObjectExpression" ? (n.properties ?? []).map((p) => p?.value) : n.elements;
|
|
434
|
+
for (const v of values ?? []) {
|
|
435
|
+
const placed = unwrapTs(v);
|
|
436
|
+
const r = placed?.type === "Identifier" ? placed.name : void 0;
|
|
437
|
+
if (r && heldObjects.has(r) && stateNames.has(r) && !shadowed(r)) record(r, "escape");
|
|
438
|
+
}
|
|
439
|
+
} else if ((n?.type === "Property" || n?.type === "MethodDefinition") && n.kind === "get") walkEstree(n.value?.body, (m) => {
|
|
440
|
+
const r = m?.type === "ReturnStatement" ? rootObjectName(m.argument) : void 0;
|
|
441
|
+
if (r && stateNames.has(r) && !shadowed(r) && !primitiveStates.has(r)) record(r, "escape");
|
|
442
|
+
});
|
|
443
|
+
};
|
|
444
|
+
walkScoped(root, visit(stateNames));
|
|
445
|
+
if (!localStates) return;
|
|
446
|
+
walkEstree(root, (n) => {
|
|
447
|
+
if (n.type !== "BlockStatement") return;
|
|
448
|
+
const own = /* @__PURE__ */ new Set();
|
|
449
|
+
for (const stmt of n.body ?? []) {
|
|
450
|
+
if (stmt?.type !== "VariableDeclaration") continue;
|
|
451
|
+
for (const d of stmt.declarations ?? []) if (d?.id?.type === "Identifier" && stateNames.has(d.id.name) && isStateDeclaration(unwrapTs(d.init))) own.add(d.id.name);
|
|
198
452
|
}
|
|
453
|
+
if (own.size > 0) walkScoped(n.body, visit(own));
|
|
199
454
|
});
|
|
200
455
|
}
|
|
201
456
|
/** Function-shaped nodes whose bodies defer evaluation — prop reads inside them stay reactive (compiled to call-time reads). */
|
|
@@ -421,16 +676,17 @@ function collectFragmentAliasRefs(node, names, acc, shadowed = /* @__PURE__ */ n
|
|
|
421
676
|
}
|
|
422
677
|
}
|
|
423
678
|
/**
|
|
424
|
-
* Each-context taint (performance/state-raw condition 5): for `{#each candidate as item}`
|
|
679
|
+
* Each-context taint (performance/state-raw condition 5, correctness/unmutated-state): for `{#each candidate as item}`
|
|
425
680
|
* or `{#each candidate.path as item}`, any mutate/escape of the context binding (or index)
|
|
426
681
|
* inside the block — member writes, method calls, call arguments, `bind:`, component props —
|
|
427
682
|
* disqualifies the candidate over the candidate or a member path of it
|
|
428
683
|
* (`{#each obj.items as item}`): item-level edits stop being reactive under $state.raw.
|
|
429
684
|
* Pure reassignments of the context name are ignored (they don't touch the list's contents).
|
|
685
|
+
* `includeIndex: false` ignores the index binding, a number that cannot carry a write back to the list.
|
|
430
686
|
*/
|
|
431
|
-
function collectEachContextTaint(node, names, acc, shadowed = /* @__PURE__ */ new Set()) {
|
|
687
|
+
function collectEachContextTaint(node, names, acc, shadowed = /* @__PURE__ */ new Set(), includeIndex = true) {
|
|
432
688
|
if (Array.isArray(node)) {
|
|
433
|
-
for (const child of node) collectEachContextTaint(child, names, acc, shadowed);
|
|
689
|
+
for (const child of node) collectEachContextTaint(child, names, acc, shadowed, includeIndex);
|
|
434
690
|
return;
|
|
435
691
|
}
|
|
436
692
|
if (!node || typeof node !== "object" || typeof node.type !== "string") return;
|
|
@@ -442,7 +698,7 @@ function collectEachContextTaint(node, names, acc, shadowed = /* @__PURE__ */ ne
|
|
|
442
698
|
if (target !== void 0 && names.has(target) && !shadowed.has(target)) {
|
|
443
699
|
const ctxNames = /* @__PURE__ */ new Set();
|
|
444
700
|
addBoundNames(node.context, ctxNames);
|
|
445
|
-
if (typeof node.index === "string") ctxNames.add(node.index);
|
|
701
|
+
if (includeIndex && typeof node.index === "string") ctxNames.add(node.index);
|
|
446
702
|
if (ctxNames.size > 0) {
|
|
447
703
|
const union = /* @__PURE__ */ new Set();
|
|
448
704
|
const kinds = /* @__PURE__ */ new Map();
|
|
@@ -457,7 +713,7 @@ function collectEachContextTaint(node, names, acc, shadowed = /* @__PURE__ */ ne
|
|
|
457
713
|
}
|
|
458
714
|
for (const key of Object.keys(node)) {
|
|
459
715
|
if (WALK_IGNORED_KEYS.has(key)) continue;
|
|
460
|
-
collectEachContextTaint(node[key], names, acc, scope);
|
|
716
|
+
collectEachContextTaint(node[key], names, acc, scope, includeIndex);
|
|
461
717
|
}
|
|
462
718
|
}
|
|
463
719
|
/**
|
|
@@ -656,6 +912,30 @@ function collectNewExprLocalNames(program, acc) {
|
|
|
656
912
|
});
|
|
657
913
|
}
|
|
658
914
|
/**
|
|
915
|
+
* Top-level locals declared as a plain alias of an imported binding or a `new …()` local
|
|
916
|
+
* (`const p = presenter`, `const p = presenter as Presenter`), to a fixed point so alias chains resolve in any order
|
|
917
|
+
* (correctness/effect-as-onmount). Top level only: names match by text, not scope, so a
|
|
918
|
+
* function-local `const id = presenter` would otherwise mark every `id` in the component reactive.
|
|
919
|
+
* Runs before rune names are added: an alias of a `$state` primitive is a non-reactive snapshot.
|
|
920
|
+
*/
|
|
921
|
+
function collectAliasLocalNames(program, acc) {
|
|
922
|
+
const declarators = (program?.body ?? []).flatMap((stmt) => {
|
|
923
|
+
const decl = unwrapExport(stmt);
|
|
924
|
+
return decl?.type === "VariableDeclaration" ? decl.declarations : [];
|
|
925
|
+
});
|
|
926
|
+
for (let grew = true; grew;) {
|
|
927
|
+
grew = false;
|
|
928
|
+
for (const d of declarators) {
|
|
929
|
+
if (d.id?.type !== "Identifier" || !d.init || acc.has(d.id.name)) continue;
|
|
930
|
+
const init = unwrapTs(d.init);
|
|
931
|
+
if (init.type === "Identifier" && acc.has(init.name)) {
|
|
932
|
+
acc.add(d.id.name);
|
|
933
|
+
grew = true;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
659
939
|
* Whether an $effect callback body reads a reactive value (correctness/effect-as-onmount, conservative):
|
|
660
940
|
* a reactive name (rune declarator, imported binding, or a local declared with a `new …()`
|
|
661
941
|
* initializer — see `collectImportedLocalNames`/`collectNewExprLocalNames`), a `$`-prefixed
|
|
@@ -851,10 +1131,12 @@ function collectAriaElements(node, source, acc) {
|
|
|
851
1131
|
const hasList = tag === "input" && findAttr(node.attributes, "list") !== void 0;
|
|
852
1132
|
const selectKind = tag === "select" ? selectNativeRole(node.attributes) : void 0;
|
|
853
1133
|
const hasSpread = node.attributes.some((a) => a?.type === "SpreadAttribute");
|
|
1134
|
+
const hasAction = node.attributes.some((a) => a?.type === "UseDirective");
|
|
854
1135
|
acc.push({
|
|
855
1136
|
tag,
|
|
856
1137
|
line: lineOf(source, node.start),
|
|
857
1138
|
...roleAttr ? { role: classifyAttrValue(roleAttr.value) } : {},
|
|
1139
|
+
...hasAction ? { hasAction: true } : {},
|
|
858
1140
|
...inputType !== void 0 ? { inputType: inputType.toLowerCase() } : {},
|
|
859
1141
|
...hasList ? { hasList: true } : {},
|
|
860
1142
|
...selectKind ? { selectKind } : {},
|
|
@@ -989,10 +1271,13 @@ function hasNamingAttr(attributes) {
|
|
|
989
1271
|
"title"
|
|
990
1272
|
].some((name) => hasNamingValue(attributes, name));
|
|
991
1273
|
}
|
|
992
|
-
/** A
|
|
993
|
-
*
|
|
1274
|
+
/** A custom element may be form-associated and its shadow root may supply content; a namespaced tag
|
|
1275
|
+
* (`<enhanced:img>`) is rewritten by a preprocessor into markup not visible here. Treated as
|
|
1276
|
+
* unknowable by both a11y scanners. */
|
|
994
1277
|
function isCustomElement(node) {
|
|
995
|
-
|
|
1278
|
+
if (node.type !== "RegularElement" || typeof node.name !== "string") return false;
|
|
1279
|
+
const tag = node.name.toLowerCase();
|
|
1280
|
+
return tag.includes("-") || !KNOWN_TAGS.has(tag);
|
|
996
1281
|
}
|
|
997
1282
|
function scanAccessibleNameSubtree(node, skip) {
|
|
998
1283
|
if (Array.isArray(node)) {
|
|
@@ -1542,6 +1827,7 @@ function isPropsCall(node) {
|
|
|
1542
1827
|
}
|
|
1543
1828
|
/** Whether a CallExpression is a `$bindable(...)` call (a destructured prop's default value). */
|
|
1544
1829
|
function isBindableCall(node) {
|
|
1830
|
+
node = unwrapTs(node);
|
|
1545
1831
|
return node?.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "$bindable";
|
|
1546
1832
|
}
|
|
1547
1833
|
/**
|
|
@@ -1591,6 +1877,7 @@ function collectLegacyPropNames(program) {
|
|
|
1591
1877
|
const names = /* @__PURE__ */ new Set();
|
|
1592
1878
|
for (const stmt of program.body ?? []) {
|
|
1593
1879
|
if (stmt?.type !== "ExportNamedDeclaration" || stmt.declaration?.type !== "VariableDeclaration") continue;
|
|
1880
|
+
if (stmt.declaration.kind === "const") continue;
|
|
1594
1881
|
for (const d of stmt.declaration.declarations ?? []) if (d?.id?.type === "Identifier") names.add(d.id.name);
|
|
1595
1882
|
}
|
|
1596
1883
|
return names;
|
|
@@ -1611,6 +1898,92 @@ const MUTATING_METHODS = /* @__PURE__ */ new Set([
|
|
|
1611
1898
|
"delete",
|
|
1612
1899
|
"clear"
|
|
1613
1900
|
]);
|
|
1901
|
+
/** Built-in types whose own methods include the mutating method names above. */
|
|
1902
|
+
const COLLECTION_TYPES = /* @__PURE__ */ new Set([
|
|
1903
|
+
"Array",
|
|
1904
|
+
"ReadonlyArray",
|
|
1905
|
+
"Set",
|
|
1906
|
+
"ReadonlySet",
|
|
1907
|
+
"Map",
|
|
1908
|
+
"ReadonlyMap",
|
|
1909
|
+
"WeakMap",
|
|
1910
|
+
"WeakSet",
|
|
1911
|
+
"SvelteSet",
|
|
1912
|
+
"SvelteMap",
|
|
1913
|
+
"URLSearchParams",
|
|
1914
|
+
"SvelteURLSearchParams",
|
|
1915
|
+
"FormData",
|
|
1916
|
+
"Headers",
|
|
1917
|
+
"Int8Array",
|
|
1918
|
+
"Uint8Array",
|
|
1919
|
+
"Uint8ClampedArray",
|
|
1920
|
+
"Int16Array",
|
|
1921
|
+
"Uint16Array",
|
|
1922
|
+
"Int32Array",
|
|
1923
|
+
"Uint32Array",
|
|
1924
|
+
"Float16Array",
|
|
1925
|
+
"Float32Array",
|
|
1926
|
+
"Float64Array",
|
|
1927
|
+
"BigInt64Array",
|
|
1928
|
+
"BigUint64Array"
|
|
1929
|
+
]);
|
|
1930
|
+
const NON_COLLECTION_NODES = /* @__PURE__ */ new Set([
|
|
1931
|
+
"TSStringKeyword",
|
|
1932
|
+
"TSNumberKeyword",
|
|
1933
|
+
"TSBooleanKeyword",
|
|
1934
|
+
"TSBigIntKeyword",
|
|
1935
|
+
"TSNullKeyword",
|
|
1936
|
+
"TSUndefinedKeyword",
|
|
1937
|
+
"TSFunctionType",
|
|
1938
|
+
"TSTypeLiteral",
|
|
1939
|
+
"TSLiteralType"
|
|
1940
|
+
]);
|
|
1941
|
+
/**
|
|
1942
|
+
* The props whose declared type, from the `$props()` annotation, is known not to be a collection: a
|
|
1943
|
+
* method called on one is its own API (`queryClient.clear()`), not an array, Set or Map mutation.
|
|
1944
|
+
* A named type counts unless it is a built-in collection or a same-file alias of one.
|
|
1945
|
+
*/
|
|
1946
|
+
function nonCollectionProps(program) {
|
|
1947
|
+
const decls = /* @__PURE__ */ new Map();
|
|
1948
|
+
walkEstree(program, (n) => {
|
|
1949
|
+
if ((n.type === "TSInterfaceDeclaration" || n.type === "TSTypeAliasDeclaration") && n.id?.name) decls.set(n.id.name, n);
|
|
1950
|
+
});
|
|
1951
|
+
const unwrap = (t) => t?.type === "TSTypeAnnotation" ? t.typeAnnotation : t;
|
|
1952
|
+
const nonCollection = (t, depth) => {
|
|
1953
|
+
t = unwrap(t);
|
|
1954
|
+
if (!t || depth > 4) return false;
|
|
1955
|
+
if (NON_COLLECTION_NODES.has(t.type)) return true;
|
|
1956
|
+
if (t.type === "TSUnionType") return t.types.every((u) => nonCollection(u, depth + 1));
|
|
1957
|
+
if (t.type !== "TSTypeReference" || t.typeName?.type !== "Identifier") return false;
|
|
1958
|
+
const name = t.typeName.name;
|
|
1959
|
+
if (COLLECTION_TYPES.has(name)) return false;
|
|
1960
|
+
const decl = decls.get(name);
|
|
1961
|
+
if (decl?.type === "TSTypeAliasDeclaration") return nonCollection(decl.typeAnnotation, depth + 1);
|
|
1962
|
+
if (decl?.type === "TSInterfaceDeclaration") return !(decl.extends ?? []).some((h) => COLLECTION_TYPES.has(h.expression?.name));
|
|
1963
|
+
return !(t.typeArguments ?? t.typeParameters)?.params?.length;
|
|
1964
|
+
};
|
|
1965
|
+
const membersOf = (t, depth) => {
|
|
1966
|
+
t = unwrap(t);
|
|
1967
|
+
if (t?.type === "TSTypeLiteral") return t.members;
|
|
1968
|
+
if (t?.type !== "TSTypeReference" || depth > 4) return void 0;
|
|
1969
|
+
const decl = decls.get(t.typeName?.name);
|
|
1970
|
+
if (decl?.type === "TSInterfaceDeclaration") return decl.body?.body;
|
|
1971
|
+
return decl?.type === "TSTypeAliasDeclaration" ? membersOf(decl.typeAnnotation, depth + 1) : void 0;
|
|
1972
|
+
};
|
|
1973
|
+
const names = /* @__PURE__ */ new Set();
|
|
1974
|
+
walkEstree(program, (n) => {
|
|
1975
|
+
if (n.type !== "VariableDeclarator" || !n.init || !isPropsCall(unwrapTs(n.init))) return;
|
|
1976
|
+
if (n.id?.type !== "ObjectPattern") return;
|
|
1977
|
+
const types = /* @__PURE__ */ new Map();
|
|
1978
|
+
for (const m of membersOf(n.id.typeAnnotation, 0) ?? []) if (m.type === "TSPropertySignature" && m.key?.type === "Identifier") types.set(m.key.name, m.typeAnnotation);
|
|
1979
|
+
for (const p of n.id.properties ?? []) {
|
|
1980
|
+
if (p?.type !== "Property" || p.key?.type !== "Identifier" || !types.has(p.key.name)) continue;
|
|
1981
|
+
const local = p.value?.type === "AssignmentPattern" ? p.value.left : p.value;
|
|
1982
|
+
if (local?.type === "Identifier" && nonCollection(types.get(p.key.name), 0)) names.add(local.name);
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
return names;
|
|
1986
|
+
}
|
|
1614
1987
|
/**
|
|
1615
1988
|
* Flag mutations of a non-`$bindable` prop (correctness/prop-mutation): a member-expression write
|
|
1616
1989
|
* (`prop.x = …`, `prop.x += …`, `prop.x++`), `delete prop.x`, or a call to a mutating
|
|
@@ -1619,9 +1992,43 @@ const MUTATING_METHODS = /* @__PURE__ */ new Set([
|
|
|
1619
1992
|
* reassignment for ephemeral state; only mutation is prohibited. Run over the instance
|
|
1620
1993
|
* program AND the template fragment (inline handlers can mutate props in the template).
|
|
1621
1994
|
* Scope-aware (issue #140): a local that shadows the prop's name is not flagged.
|
|
1995
|
+
* A `legacy` (export let) prop is flagged only for `delete` and mutating method calls — the legacy
|
|
1996
|
+
* compiler turns a member write or update into an invalidating assignment (`prop(prop().x = …, true)`).
|
|
1622
1997
|
*/
|
|
1623
|
-
function collectPropMutations(root, propNames, source, acc) {
|
|
1998
|
+
function collectPropMutations(root, propNames, source, acc, legacy, ownApi = /* @__PURE__ */ new Set()) {
|
|
1624
1999
|
if (propNames.size === 0) return;
|
|
2000
|
+
const exemptCalls = /* @__PURE__ */ new Set();
|
|
2001
|
+
const returnedToProp = /* @__PURE__ */ new Set();
|
|
2002
|
+
if (legacy.size > 0) walkEstree(root, (fn) => {
|
|
2003
|
+
const body = isDeferredBody(fn) ? fn.body : fn.type === "LabeledStatement" && fn.label?.name === "$" ? fn.body : void 0;
|
|
2004
|
+
if (!body) return;
|
|
2005
|
+
const assigned = /* @__PURE__ */ new Set();
|
|
2006
|
+
walkScoped(body, (m, scope) => {
|
|
2007
|
+
if (m.type === "AssignmentExpression" && m.left?.type === "Identifier" && !scope.has(m.left.name)) assigned.add(m.left.name);
|
|
2008
|
+
});
|
|
2009
|
+
walkEstree(body, (m) => {
|
|
2010
|
+
const r = m.type === "CallExpression" ? rootObjectName(m.callee?.object) : m.type === "UnaryExpression" && m.operator === "delete" ? rootObjectName(m.argument) : void 0;
|
|
2011
|
+
if (r && legacy.has(r) && assigned.has(r)) exemptCalls.add(m);
|
|
2012
|
+
});
|
|
2013
|
+
});
|
|
2014
|
+
walkEstree(root, (m) => {
|
|
2015
|
+
const call = m.type === "AssignmentExpression" && m.left?.type === "Identifier" ? m.right : void 0;
|
|
2016
|
+
if (call?.type === "CallExpression" && rootObjectName(call.callee?.object) === m.left.name && call.arguments?.length === 1 && call.arguments[0]?.type === "ObjectExpression") exemptCalls.add(call);
|
|
2017
|
+
const awaited = m.type === "AwaitExpression" ? unwrapTs(m.argument) : void 0;
|
|
2018
|
+
if (awaited?.type === "CallExpression") exemptCalls.add(awaited);
|
|
2019
|
+
const chained = m.type === "CallExpression" && m.callee?.type === "MemberExpression" && !m.callee.computed && [
|
|
2020
|
+
"then",
|
|
2021
|
+
"catch",
|
|
2022
|
+
"finally"
|
|
2023
|
+
].includes(m.callee.property?.name) ? unwrapTs(m.callee.object) : void 0;
|
|
2024
|
+
if (chained?.type === "CallExpression") exemptCalls.add(chained);
|
|
2025
|
+
if (m.type === "AssignmentExpression" && m.left?.type === "Identifier") {
|
|
2026
|
+
let back = unwrapTs(m.right);
|
|
2027
|
+
if (back?.type === "LogicalExpression") back = unwrapTs(back.left);
|
|
2028
|
+
if (back?.type === "ChainExpression") back = back.expression;
|
|
2029
|
+
if (back?.type === "CallExpression" && back.callee.type === "MemberExpression" && rootObjectName(back.callee.object) === m.left.name) returnedToProp.add(back);
|
|
2030
|
+
}
|
|
2031
|
+
});
|
|
1625
2032
|
walkScoped(root, (n, scope) => {
|
|
1626
2033
|
const flag = (r) => {
|
|
1627
2034
|
if (r && propNames.has(r) && !scope.has(r)) acc.push({
|
|
@@ -1629,11 +2036,18 @@ function collectPropMutations(root, propNames, source, acc) {
|
|
|
1629
2036
|
line: lineOf(source, n.start)
|
|
1630
2037
|
});
|
|
1631
2038
|
};
|
|
1632
|
-
if (
|
|
1633
|
-
|
|
1634
|
-
|
|
2039
|
+
if (exemptCalls.has(n)) return;
|
|
2040
|
+
if (n.type === "AssignmentExpression" && n.left?.type === "MemberExpression") {
|
|
2041
|
+
const r = rootObjectName(n.left);
|
|
2042
|
+
if (r && !legacy.has(r)) flag(r);
|
|
2043
|
+
} else if (n.type === "UpdateExpression" && n.argument?.type === "MemberExpression") {
|
|
2044
|
+
const r = rootObjectName(n.argument);
|
|
2045
|
+
if (r && !legacy.has(r)) flag(r);
|
|
2046
|
+
} else if (n.type === "UnaryExpression" && n.operator === "delete") flag(rootObjectName(n.argument));
|
|
1635
2047
|
else if (n.type === "CallExpression" && n.callee?.type === "MemberExpression") {
|
|
1636
2048
|
const method = n.callee.property?.type === "Identifier" ? n.callee.property.name : void 0;
|
|
2049
|
+
const receiver = unwrapTs(n.callee.object);
|
|
2050
|
+
if (receiver?.type === "Identifier" && ownApi.has(receiver.name) && !returnedToProp.has(n)) return;
|
|
1637
2051
|
if (method && MUTATING_METHODS.has(method)) flag(rootObjectName(n.callee.object));
|
|
1638
2052
|
}
|
|
1639
2053
|
});
|
|
@@ -1666,27 +2080,66 @@ function isTypeOnlyImport(n) {
|
|
|
1666
2080
|
const specs = n.specifiers;
|
|
1667
2081
|
return Array.isArray(specs) && specs.length > 0 && specs.every((s) => s?.importKind === "type");
|
|
1668
2082
|
}
|
|
2083
|
+
/** Whether an import declaration binds only named exports — no default, `default as`, or namespace (see `ComponentFacts.importSpans`). */
|
|
2084
|
+
function isNamedOnlyImport(n) {
|
|
2085
|
+
const specs = n.specifiers;
|
|
2086
|
+
return Array.isArray(specs) && specs.length > 0 && specs.every((s) => s?.type === "ImportSpecifier" && (s.imported?.name ?? s.imported?.value) !== "default");
|
|
2087
|
+
}
|
|
1669
2088
|
/** Module specifiers of every `import`, each with its source line (see `ComponentFacts.importSpans`). */
|
|
1670
2089
|
function collectImportSources(program, source, acc) {
|
|
1671
2090
|
walkEstree(program, (n) => {
|
|
1672
2091
|
if (n.type === "ImportDeclaration" && typeof n.source?.value === "string") acc.push({
|
|
1673
2092
|
source: n.source.value,
|
|
1674
2093
|
line: lineOf(source, n.start),
|
|
1675
|
-
...isTypeOnlyImport(n) ? { type: true } : {}
|
|
2094
|
+
...isTypeOnlyImport(n) ? { type: true } : isNamedOnlyImport(n) ? { named: true } : {}
|
|
1676
2095
|
});
|
|
1677
2096
|
});
|
|
1678
2097
|
}
|
|
1679
2098
|
/** A specifier is "bare" (a node_modules package) when it is not relative/absolute/alias-local. */
|
|
1680
2099
|
function isBareSpecifier(s) {
|
|
1681
|
-
return !/^[./$#]/.test(s);
|
|
2100
|
+
return !/^([./$#]|@\/|~\/)/.test(s);
|
|
1682
2101
|
}
|
|
1683
|
-
/**
|
|
1684
|
-
|
|
2102
|
+
/**
|
|
2103
|
+
* Whether `name` is used anywhere in `roots` other than as the object of a static member access
|
|
2104
|
+
* (`X.foo`, `X['foo']`) — passing `X` on, spreading it or indexing it by a variable is what makes
|
|
2105
|
+
* a bundler keep the whole module. `<X.Foo />` in markup is a component name, not an identifier,
|
|
2106
|
+
* so it counts as static. A same-named local (`scopeIntroducedNames`) shadows the namespace.
|
|
2107
|
+
*/
|
|
2108
|
+
function namespaceUsedDynamically(name, roots) {
|
|
2109
|
+
const staticOrDeclaring = /* @__PURE__ */ new Set();
|
|
2110
|
+
for (const root of roots) walkEstree(root, (n) => {
|
|
2111
|
+
if (n.type === "MemberExpression") {
|
|
2112
|
+
const literalKey = n.computed && n.property?.type === "Literal" && typeof n.property.value === "string";
|
|
2113
|
+
if (n.object?.type === "Identifier" && n.object.name === name && (!n.computed || literalKey)) staticOrDeclaring.add(n.object);
|
|
2114
|
+
if (!n.computed) staticOrDeclaring.add(n.property);
|
|
2115
|
+
} else if ((n.type === "Property" && !n.shorthand || n.type === "TSPropertySignature" || n.type === "TSMethodSignature") && !n.computed) staticOrDeclaring.add(n.key);
|
|
2116
|
+
else if (n.type === "ImportNamespaceSpecifier") staticOrDeclaring.add(n.local);
|
|
2117
|
+
else if (n.type === "ImportSpecifier" && n.imported !== n.local) staticOrDeclaring.add(n.imported);
|
|
2118
|
+
else if (n.type === "ExportSpecifier" && n.exported !== n.local) staticOrDeclaring.add(n.exported);
|
|
2119
|
+
else if (n.type === "TSQualifiedName" || n.type === "TSTypeQuery") {
|
|
2120
|
+
const id = n.type === "TSQualifiedName" ? n.left : n.exprName;
|
|
2121
|
+
if (id?.type === "Identifier") staticOrDeclaring.add(id);
|
|
2122
|
+
if (n.type === "TSQualifiedName") staticOrDeclaring.add(n.right);
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
let dynamic = false;
|
|
2126
|
+
for (const root of roots) walkScoped(root, (n, shadowed) => {
|
|
2127
|
+
if (n.type === "Identifier" && n.name === name && !staticOrDeclaring.has(n) && !shadowed.has(name)) dynamic = true;
|
|
2128
|
+
});
|
|
2129
|
+
return dynamic;
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Value `import * as X from '<bare pkg>'` namespace imports (type-only excluded) that `roots` (every
|
|
2133
|
+
* program and template of the file) use dynamically — performance/namespace-import. Static member
|
|
2134
|
+
* accesses tree-shake like named imports, so a namespace used only that way is not reported.
|
|
2135
|
+
*/
|
|
2136
|
+
function collectNamespaceImports(program, source, acc, roots) {
|
|
1685
2137
|
walkEstree(program, (n) => {
|
|
1686
2138
|
if (n.type !== "ImportDeclaration" || n.importKind === "type") return;
|
|
1687
2139
|
const spec = n.source?.value;
|
|
1688
2140
|
if (typeof spec !== "string" || !isBareSpecifier(spec)) return;
|
|
1689
|
-
|
|
2141
|
+
const ns = (n.specifiers ?? []).find((s) => s?.type === "ImportNamespaceSpecifier");
|
|
2142
|
+
if (ns?.local?.type === "Identifier" && namespaceUsedDynamically(ns.local.name, roots)) acc.push({
|
|
1690
2143
|
source: spec,
|
|
1691
2144
|
line: lineOf(source, n.start)
|
|
1692
2145
|
});
|
|
@@ -1931,7 +2384,7 @@ function parseModuleFacts(source, filename) {
|
|
|
1931
2384
|
line: shift(s.line)
|
|
1932
2385
|
});
|
|
1933
2386
|
const rawNamespaceImports = [];
|
|
1934
|
-
collectNamespaceImports(program, wrapped, rawNamespaceImports);
|
|
2387
|
+
collectNamespaceImports(program, wrapped, rawNamespaceImports, [program]);
|
|
1935
2388
|
for (const n of rawNamespaceImports) namespaceImports.push({
|
|
1936
2389
|
...n,
|
|
1937
2390
|
line: shift(n.line)
|
|
@@ -1971,7 +2424,10 @@ function parseComponentFacts(source, filename) {
|
|
|
1971
2424
|
if (MODULE_FILE_RE.test(filename)) return parseModuleFacts(source, filename);
|
|
1972
2425
|
const ast = parseSvelte(source, filename);
|
|
1973
2426
|
const eachBlocks = [];
|
|
1974
|
-
|
|
2427
|
+
const scriptPrograms = [ast.module?.content, ast.instance?.content].filter(Boolean);
|
|
2428
|
+
const listImports = /* @__PURE__ */ new Map();
|
|
2429
|
+
const lists = collectConstantLists(scriptPrograms, ast.fragment ?? ast, { imports: listImports });
|
|
2430
|
+
collectEachBlocks(ast.fragment ?? ast, source, eachBlocks, lists, listImports);
|
|
1975
2431
|
const htmlTags = [];
|
|
1976
2432
|
const javascriptUrls = [];
|
|
1977
2433
|
collectSecurityFacts(ast.fragment ?? ast, source, htmlTags, javascriptUrls);
|
|
@@ -2016,17 +2472,26 @@ function parseComponentFacts(source, filename) {
|
|
|
2016
2472
|
const moduleProgram = ast.module?.content;
|
|
2017
2473
|
const importSpans = [];
|
|
2018
2474
|
const namespaceImports = [];
|
|
2475
|
+
const usageRoots = [
|
|
2476
|
+
moduleProgram,
|
|
2477
|
+
ast.instance?.content,
|
|
2478
|
+
ast.fragment
|
|
2479
|
+
].filter(Boolean);
|
|
2019
2480
|
if (moduleProgram) {
|
|
2020
2481
|
collectImportSources(moduleProgram, source, importSpans);
|
|
2021
|
-
collectNamespaceImports(moduleProgram, source, namespaceImports);
|
|
2482
|
+
collectNamespaceImports(moduleProgram, source, namespaceImports, usageRoots);
|
|
2022
2483
|
}
|
|
2023
2484
|
const orphanEffects = moduleProgram ? collectOrphanEffects(moduleProgram, source) : [];
|
|
2024
2485
|
const orphanLifecycleCalls = moduleProgram ? collectOrphanLifecycleCalls(moduleProgram, source) : [];
|
|
2025
2486
|
const browserGlobalRefs = [];
|
|
2026
|
-
if (moduleProgram)
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2487
|
+
if (moduleProgram) {
|
|
2488
|
+
const hoisted = /* @__PURE__ */ new Set();
|
|
2489
|
+
collectImportedLocalNames(ast.instance?.content, hoisted);
|
|
2490
|
+
for (const r of collectBrowserGlobalRefs(moduleProgram, source, { bound: hoisted })) browserGlobalRefs.push({
|
|
2491
|
+
...r,
|
|
2492
|
+
context: "module"
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2030
2495
|
const effects = [];
|
|
2031
2496
|
const constableStates = [];
|
|
2032
2497
|
const mutatedProps = [];
|
|
@@ -2037,13 +2502,15 @@ function parseComponentFacts(source, filename) {
|
|
|
2037
2502
|
const program = ast.instance?.content;
|
|
2038
2503
|
if (program) {
|
|
2039
2504
|
collectImportSources(program, source, importSpans);
|
|
2040
|
-
collectNamespaceImports(program, source, namespaceImports);
|
|
2505
|
+
collectNamespaceImports(program, source, namespaceImports, usageRoots);
|
|
2041
2506
|
propCount = countProps(program);
|
|
2042
2507
|
const legacyPropNames = collectLegacyPropNames(program);
|
|
2043
2508
|
const nonBindableProps = /* @__PURE__ */ new Set([...collectPropNames(program, false), ...legacyPropNames]);
|
|
2044
2509
|
const rawMutations = [];
|
|
2045
|
-
|
|
2046
|
-
|
|
2510
|
+
const ownApi = nonCollectionProps(program);
|
|
2511
|
+
collectPropMutations(program, nonBindableProps, source, rawMutations, legacyPropNames, ownApi);
|
|
2512
|
+
if (ast.fragment) collectPropMutations(ast.fragment, nonBindableProps, source, rawMutations, legacyPropNames, ownApi);
|
|
2513
|
+
const isLegacy = legacyPropNames.size > 0;
|
|
2047
2514
|
for (const m of rawMutations) mutatedProps.push(legacyPropNames.has(m.name) ? {
|
|
2048
2515
|
...m,
|
|
2049
2516
|
legacy: true
|
|
@@ -2061,7 +2528,6 @@ function parseComponentFacts(source, filename) {
|
|
|
2061
2528
|
}
|
|
2062
2529
|
const referenced = /* @__PURE__ */ new Set();
|
|
2063
2530
|
if (ast.fragment) collectFragmentRefs(ast.fragment, candidateNames, referenced);
|
|
2064
|
-
const isLegacy = legacyPropNames.size > 0;
|
|
2065
2531
|
for (const c of candidates) if (!disqualified.has(c.name) && referenced.has(c.name)) stalePropDerivations.push(isLegacy ? {
|
|
2066
2532
|
...c,
|
|
2067
2533
|
legacy: true
|
|
@@ -2075,7 +2541,9 @@ function parseComponentFacts(source, filename) {
|
|
|
2075
2541
|
if (moduleProgram) {
|
|
2076
2542
|
collectImportedLocalNames(moduleProgram, reactiveNames);
|
|
2077
2543
|
collectNewExprLocalNames(moduleProgram, reactiveNames);
|
|
2544
|
+
collectAliasLocalNames(moduleProgram, reactiveNames);
|
|
2078
2545
|
}
|
|
2546
|
+
collectAliasLocalNames(program, reactiveNames);
|
|
2079
2547
|
const stateDecls = [];
|
|
2080
2548
|
walkEstree(program, (n) => {
|
|
2081
2549
|
if (n.type !== "VariableDeclarator" || !n.init) return;
|
|
@@ -2100,11 +2568,12 @@ function parseComponentFacts(source, filename) {
|
|
|
2100
2568
|
});
|
|
2101
2569
|
});
|
|
2102
2570
|
const writtenOrEscaped = /* @__PURE__ */ new Set();
|
|
2103
|
-
collectStateWrites(program, stateNames, writtenOrEscaped);
|
|
2571
|
+
collectStateWrites(program, stateNames, writtenOrEscaped, void 0, true);
|
|
2104
2572
|
if (ast.fragment) {
|
|
2105
|
-
collectStateWrites(ast.fragment, stateNames, writtenOrEscaped);
|
|
2573
|
+
collectStateWrites(ast.fragment, stateNames, writtenOrEscaped, void 0, true);
|
|
2106
2574
|
collectTemplateEscapes(ast.fragment, stateNames, writtenOrEscaped);
|
|
2107
2575
|
collectDirectiveEscapes(ast.fragment, stateNames, writtenOrEscaped);
|
|
2576
|
+
collectEachContextTaint(ast.fragment, stateNames, writtenOrEscaped, /* @__PURE__ */ new Set(), false);
|
|
2108
2577
|
}
|
|
2109
2578
|
for (const d of stateDecls) if (!writtenOrEscaped.has(d.name)) constableStates.push(d);
|
|
2110
2579
|
const rawableCandidates = [];
|
|
@@ -2458,7 +2927,7 @@ async function collectKitModuleFacts(rt, cwd, aliases) {
|
|
|
2458
2927
|
"src/hooks.server.{ts,js}"
|
|
2459
2928
|
].map((p) => rt.glob(p, cwd)));
|
|
2460
2929
|
const files = [...new Set(lists.flat())];
|
|
2461
|
-
return arbitrateServerStoreWrites(rt, cwd, await Promise.all(files.sort().map(async (rel) => {
|
|
2930
|
+
return decideLoadFactories(rt, cwd, await decideFlagGuards(rt, cwd, await dropClientWrites(rt, cwd, await arbitrateServerStoreWrites(rt, cwd, await Promise.all(files.sort().map(async (rel) => {
|
|
2462
2931
|
const kind = kindOf(rel);
|
|
2463
2932
|
let source;
|
|
2464
2933
|
try {
|
|
@@ -2482,7 +2951,7 @@ async function collectKitModuleFacts(rt, cwd, aliases) {
|
|
|
2482
2951
|
parseFailed: true
|
|
2483
2952
|
};
|
|
2484
2953
|
}
|
|
2485
|
-
})));
|
|
2954
|
+
}))), aliases)));
|
|
2486
2955
|
}
|
|
2487
2956
|
/**
|
|
2488
2957
|
* The files a resolved specifier may name, in resolution order. An extensionless path takes an
|
|
@@ -2500,20 +2969,58 @@ function moduleCandidates(repoPath) {
|
|
|
2500
2969
|
`${repoPath}/index.js`
|
|
2501
2970
|
];
|
|
2502
2971
|
}
|
|
2503
|
-
/**
|
|
2504
|
-
|
|
2505
|
-
* container. Returns an empty set when the file cannot be found or read: unresolvable means
|
|
2506
|
-
* unarbitrated, which leaves the write exempt.
|
|
2507
|
-
*/
|
|
2508
|
-
async function inMemoryExportsOf(rt, cwd, repoPath) {
|
|
2972
|
+
/** Read a module and parse it with `parse`; empty when it cannot be found or read. */
|
|
2973
|
+
async function exportsOf(rt, cwd, repoPath, parse) {
|
|
2509
2974
|
for (const rel of moduleCandidates(repoPath)) try {
|
|
2510
2975
|
if (!await rt.exists(rt.join(cwd, rel))) continue;
|
|
2511
|
-
return
|
|
2976
|
+
return parse(await rt.readFile(rt.join(cwd, rel)), rel);
|
|
2512
2977
|
} catch {
|
|
2513
2978
|
return /* @__PURE__ */ new Set();
|
|
2514
2979
|
}
|
|
2515
2980
|
return /* @__PURE__ */ new Set();
|
|
2516
2981
|
}
|
|
2982
|
+
/** Drop the handler `.set()`/`.update()` writes whose target module exports a persistence client under that name. */
|
|
2983
|
+
async function dropClientWrites(rt, cwd, facts, aliases) {
|
|
2984
|
+
const targets = [...new Set(facts.flatMap((f) => (f.storeWriteTargets ?? []).map((w) => w.resolved)))];
|
|
2985
|
+
if (targets.length === 0) return facts;
|
|
2986
|
+
const byPath = new Map(await Promise.all(targets.map(async (t) => [t, await exportsOf(rt, cwd, t, (src, file) => parseClientExports(src, file, aliases))])));
|
|
2987
|
+
return facts.map((f) => {
|
|
2988
|
+
const clients = (f.storeWriteTargets ?? []).filter((w) => byPath.get(w.resolved)?.has(w.imported));
|
|
2989
|
+
if (clients.length === 0) return f;
|
|
2990
|
+
const drop = (w) => w.via === "set-call" && clients.some((c) => c.name === w.name && c.line === w.line);
|
|
2991
|
+
return {
|
|
2992
|
+
...f,
|
|
2993
|
+
importedStateWrites: f.importedStateWrites.filter((w) => !drop(w))
|
|
2994
|
+
};
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
/** A `load` an imported factory builds never renders when the function the factory returns always exits. */
|
|
2998
|
+
async function decideLoadFactories(rt, cwd, facts) {
|
|
2999
|
+
const targets = [...new Set(facts.flatMap((f) => f.pendingLoadFactory ? [f.pendingLoadFactory.resolved] : []))];
|
|
3000
|
+
if (targets.length === 0) return facts;
|
|
3001
|
+
const byPath = new Map(await Promise.all(targets.map(async (t) => [t, await exportsOf(rt, cwd, t, parseExitFactoryExports)])));
|
|
3002
|
+
return facts.map((f) => {
|
|
3003
|
+
const g = f.pendingLoadFactory;
|
|
3004
|
+
return g && byPath.get(g.resolved)?.has(g.name) ? {
|
|
3005
|
+
...f,
|
|
3006
|
+
loadNeverRenders: true
|
|
3007
|
+
} : f;
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
/** A load whose leading guard exits on an imported flag that is the literal `false` never renders. */
|
|
3011
|
+
async function decideFlagGuards(rt, cwd, facts) {
|
|
3012
|
+
const targets = [...new Set(facts.flatMap((f) => f.pendingFlagGuard ? [f.pendingFlagGuard.resolved] : []))];
|
|
3013
|
+
if (targets.length === 0) return facts;
|
|
3014
|
+
const byPath = new Map(await Promise.all(targets.map(async (t) => [t, await exportsOf(rt, cwd, t, parseFalseExports)])));
|
|
3015
|
+
return facts.map((f) => {
|
|
3016
|
+
const g = f.pendingFlagGuard;
|
|
3017
|
+
const key = g && (g.member ? `${g.name}.${g.member}` : g.name);
|
|
3018
|
+
return g && byPath.get(g.resolved)?.has(key) ? {
|
|
3019
|
+
...f,
|
|
3020
|
+
loadNeverRenders: true
|
|
3021
|
+
} : f;
|
|
3022
|
+
});
|
|
3023
|
+
}
|
|
2517
3024
|
/**
|
|
2518
3025
|
* Decide the `.set()`/`.update()` calls the parse could not: a call on a binding exported from
|
|
2519
3026
|
* under the `$lib` server root is shared module state when that binding is an in-memory
|
|
@@ -2523,7 +3030,7 @@ async function inMemoryExportsOf(rt, cwd, repoPath) {
|
|
|
2523
3030
|
async function arbitrateServerStoreWrites(rt, cwd, facts) {
|
|
2524
3031
|
const targets = [...new Set(facts.flatMap((f) => f.pendingServerStoreWrites.map((w) => w.resolved)))];
|
|
2525
3032
|
if (targets.length === 0) return facts;
|
|
2526
|
-
const byPath = new Map(await Promise.all(targets.map(async (t) => [t, await
|
|
3033
|
+
const byPath = new Map(await Promise.all(targets.map(async (t) => [t, await exportsOf(rt, cwd, t, parseInMemoryExports)])));
|
|
2527
3034
|
return facts.map((f) => {
|
|
2528
3035
|
const promoted = f.pendingServerStoreWrites.filter((w) => byPath.get(w.resolved)?.has(w.imported)).map((w) => ({
|
|
2529
3036
|
name: w.name,
|
|
@@ -2653,20 +3160,51 @@ function findMinifyDisabled(source) {
|
|
|
2653
3160
|
}
|
|
2654
3161
|
//#endregion
|
|
2655
3162
|
//#region src/svelte-config-parse.ts
|
|
3163
|
+
function isServedBase(value) {
|
|
3164
|
+
return typeof value === "string" && value.startsWith("/") && !value.endsWith("/");
|
|
3165
|
+
}
|
|
3166
|
+
/** The top-level bindings declared `const`: a `let` may be reassigned on the way to the export. */
|
|
3167
|
+
function constBindings(program, bindings) {
|
|
3168
|
+
const names = /* @__PURE__ */ new Set();
|
|
3169
|
+
for (const stmt of program.body) {
|
|
3170
|
+
const decl = stmt.type === "ExportNamedDeclaration" ? stmt.declaration : stmt;
|
|
3171
|
+
if (decl?.type !== "VariableDeclaration" || decl.kind !== "const") continue;
|
|
3172
|
+
for (const d of decl.declarations) if (d.id.type === "Identifier") names.add(d.id.name);
|
|
3173
|
+
}
|
|
3174
|
+
return new Map([...bindings].filter(([name]) => names.has(name)));
|
|
3175
|
+
}
|
|
3176
|
+
/** Whether no branch of a `?:` / `||` / `&&` / `??` tree is a literal base an app can be served under. */
|
|
3177
|
+
function neverABase(expr, consts) {
|
|
3178
|
+
const e = resolveBinding(expr, consts);
|
|
3179
|
+
if (e.type === "Literal") return !isServedBase(e.value);
|
|
3180
|
+
if (e.type === "ConditionalExpression") return neverABase(e.consequent, consts) && neverABase(e.alternate, consts);
|
|
3181
|
+
if (e.type === "LogicalExpression") return neverABase(e.left, consts) && neverABase(e.right, consts);
|
|
3182
|
+
return false;
|
|
3183
|
+
}
|
|
3184
|
+
/** `expr` with top-level `const` names followed to their initializers (`paths: { base }` naming `const base = ''`). */
|
|
3185
|
+
function resolveBinding(expr, consts) {
|
|
3186
|
+
let e = unwrapTs(expr);
|
|
3187
|
+
for (let i = 0; i < 4 && e.type === "Identifier"; i++) {
|
|
3188
|
+
const next = consts.get(e.name);
|
|
3189
|
+
if (!next) break;
|
|
3190
|
+
e = unwrapTs(next);
|
|
3191
|
+
}
|
|
3192
|
+
return e;
|
|
3193
|
+
}
|
|
2656
3194
|
/**
|
|
2657
3195
|
* `paths.base` off a resolved Kit-config object: `{ value }` for a non-empty string literal,
|
|
2658
3196
|
* `{}` for any other expression (base exists, value unknowable — the `dev ? '' : '/repo'`
|
|
2659
|
-
* deploy form), and undefined when absent or
|
|
3197
|
+
* deploy form), and undefined when absent or when no value it can take is a base SvelteKit serves under.
|
|
2660
3198
|
*/
|
|
2661
|
-
function basePathOf(kitConfig, bindings) {
|
|
3199
|
+
function basePathOf(kitConfig, bindings, consts) {
|
|
2662
3200
|
const paths = propOf(kitConfig, "paths");
|
|
2663
3201
|
const pathsObj = paths ? unwrapToObjectExpression(paths.value, bindings) : void 0;
|
|
2664
3202
|
if (!pathsObj) return void 0;
|
|
2665
3203
|
const base = propOf(pathsObj, "base");
|
|
2666
3204
|
if (!base) return void 0;
|
|
2667
|
-
const value =
|
|
2668
|
-
if (value.type === "Literal") return
|
|
2669
|
-
return {};
|
|
3205
|
+
const value = resolveBinding(base.value, consts);
|
|
3206
|
+
if (value.type === "Literal") return isServedBase(value.value) ? { value: value.value } : void 0;
|
|
3207
|
+
return neverABase(value, consts) ? void 0 : {};
|
|
2670
3208
|
}
|
|
2671
3209
|
/** A property's key when it is a plain (non-computed) string or identifier key. */
|
|
2672
3210
|
function keyNameOf(p) {
|
|
@@ -2674,10 +3212,46 @@ function keyNameOf(p) {
|
|
|
2674
3212
|
if (p.key.type === "Identifier") return p.key.name;
|
|
2675
3213
|
if (p.key.type === "Literal" && typeof p.key.value === "string") return p.key.value;
|
|
2676
3214
|
}
|
|
2677
|
-
|
|
3215
|
+
function stringLiteralOf(expr) {
|
|
3216
|
+
return expr?.type === "Literal" && typeof expr.value === "string" ? expr.value : void 0;
|
|
3217
|
+
}
|
|
3218
|
+
/** `__dirname`, `import.meta.dirname` or `process.cwd()`: the config's directory, which is the project root. */
|
|
3219
|
+
function isRootDir(expr) {
|
|
3220
|
+
if (expr.type === "Identifier") return expr.name === "__dirname";
|
|
3221
|
+
if (expr.type === "MemberExpression" && expr.property.type === "Identifier") return expr.object.type === "MetaProperty" && expr.property.name === "dirname";
|
|
3222
|
+
return expr.type === "CallExpression" && expr.callee.type === "MemberExpression" && expr.callee.object.type === "Identifier" && expr.callee.object.name === "process" && expr.callee.property.type === "Identifier" && expr.callee.property.name === "cwd";
|
|
3223
|
+
}
|
|
3224
|
+
/**
|
|
3225
|
+
* A config path value, relative to the project root: a string literal, `resolve(…)`/`join(…)`
|
|
3226
|
+
* (bare or `path.`-qualified) of string literals led by an optional root directory, or
|
|
3227
|
+
* `fileURLToPath(new URL('<literal>', import.meta.url))`. Anything else is unreadable.
|
|
3228
|
+
*/
|
|
3229
|
+
function pathValueOf(value) {
|
|
3230
|
+
const v = unwrapTs(value);
|
|
3231
|
+
if (v.type !== "CallExpression") return stringLiteralOf(v);
|
|
3232
|
+
const callee = v.callee.type === "MemberExpression" && !v.callee.computed ? v.callee.property : v.callee;
|
|
3233
|
+
const name = callee.type === "Identifier" ? callee.name : void 0;
|
|
3234
|
+
const args = v.arguments;
|
|
3235
|
+
if (name === "fileURLToPath") {
|
|
3236
|
+
const url = args[0];
|
|
3237
|
+
if (url?.type !== "NewExpression") return void 0;
|
|
3238
|
+
const [href, base] = url.arguments;
|
|
3239
|
+
return base?.type === "MemberExpression" && base.object.type === "MetaProperty" && base.property.type === "Identifier" && base.property.name === "url" ? stringLiteralOf(href) : void 0;
|
|
3240
|
+
}
|
|
3241
|
+
if (name !== "resolve" && name !== "join") return void 0;
|
|
3242
|
+
const parts = [];
|
|
3243
|
+
for (const [i, arg] of args.entries()) {
|
|
3244
|
+
if (i === 0 && isRootDir(arg)) continue;
|
|
3245
|
+
const part = stringLiteralOf(arg);
|
|
3246
|
+
if (part === void 0) return void 0;
|
|
3247
|
+
if (name === "resolve" && part.startsWith("/")) parts.length = 0;
|
|
3248
|
+
parts.push(part);
|
|
3249
|
+
}
|
|
3250
|
+
return parts.join("/");
|
|
3251
|
+
}
|
|
3252
|
+
/** A property's value as a config path (see `pathValueOf`), else undefined. */
|
|
2678
3253
|
function stringValueOf(p) {
|
|
2679
|
-
|
|
2680
|
-
return v.type === "Literal" && typeof v.value === "string" ? v.value : void 0;
|
|
3254
|
+
return pathValueOf(p.value);
|
|
2681
3255
|
}
|
|
2682
3256
|
/**
|
|
2683
3257
|
* `kit.alias`'s entries in source order. Undefined when the key set cannot be known (see
|
|
@@ -2745,7 +3319,7 @@ function findKitAliasesInSvelteConfig(source) {
|
|
|
2745
3319
|
* same irregularities available to it.
|
|
2746
3320
|
*/
|
|
2747
3321
|
function normalizeAliasValue(value) {
|
|
2748
|
-
const posix = value.replace(/\\/g, "/");
|
|
3322
|
+
const posix = value.replace(/\\/g, "/").replace(/^(\.\/)+/, "");
|
|
2749
3323
|
return (posix.endsWith("/*") ? posix.slice(0, -2) : posix).replace(/\/+$/, "");
|
|
2750
3324
|
}
|
|
2751
3325
|
/**
|
|
@@ -2788,6 +3362,57 @@ function resolveKitAliases(viteConfig, svelteConfig) {
|
|
|
2788
3362
|
if (!svelteConfig) return void 0;
|
|
2789
3363
|
return compileKitAliases(findKitAliasesInSvelteConfig(svelteConfig.source));
|
|
2790
3364
|
}
|
|
3365
|
+
/** Conditions every Vite build of a Svelte app matches; `types`/`require` never apply to an ESM import. */
|
|
3366
|
+
const MATCHED_CONDITIONS = /* @__PURE__ */ new Set([
|
|
3367
|
+
"svelte",
|
|
3368
|
+
"import",
|
|
3369
|
+
"module",
|
|
3370
|
+
"default"
|
|
3371
|
+
]);
|
|
3372
|
+
const IGNORED_CONDITIONS = /* @__PURE__ */ new Set(["types", "require"]);
|
|
3373
|
+
/**
|
|
3374
|
+
* The target a package `imports` value resolves to, walking conditions in declaration order as Node
|
|
3375
|
+
* does. A condition that depends on the environment (`node`, `browser`, `development`, …) makes the
|
|
3376
|
+
* answer unknowable here, so it resolves to nothing rather than to a branch that may not apply.
|
|
3377
|
+
*/
|
|
3378
|
+
function importTarget(value) {
|
|
3379
|
+
if (typeof value === "string") return value;
|
|
3380
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
3381
|
+
for (const [condition, branch] of Object.entries(value)) {
|
|
3382
|
+
if (IGNORED_CONDITIONS.has(condition)) continue;
|
|
3383
|
+
return MATCHED_CONDITIONS.has(condition) ? importTarget(branch) : void 0;
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
/**
|
|
3387
|
+
* `aliases` followed by the project's package.json `imports` entries (`"#lib/*": "./src/lib/*"`),
|
|
3388
|
+
* after Kit's own: Vite's alias plugin resolves before Node-style subpath imports. The entries are
|
|
3389
|
+
* ordered by Node's precedence — exact keys, then patterns by longest prefix — because the alias
|
|
3390
|
+
* resolver takes the first match. Only a project-relative target counts; anything else is skipped,
|
|
3391
|
+
* which leaves the specifier unresolved as before.
|
|
3392
|
+
*/
|
|
3393
|
+
function withPackageImports(aliases, packageJsonSource) {
|
|
3394
|
+
let imports;
|
|
3395
|
+
try {
|
|
3396
|
+
imports = JSON.parse(packageJsonSource ?? "").imports;
|
|
3397
|
+
} catch {
|
|
3398
|
+
return aliases;
|
|
3399
|
+
}
|
|
3400
|
+
if (!imports || typeof imports !== "object") return aliases;
|
|
3401
|
+
const entries = [];
|
|
3402
|
+
for (const [key, raw] of Object.entries(imports)) {
|
|
3403
|
+
const target = importTarget(raw);
|
|
3404
|
+
if (target === void 0 || !target.startsWith("./")) continue;
|
|
3405
|
+
const star = key.endsWith("/*");
|
|
3406
|
+
if (star !== target.endsWith("/*") || key.slice(0, star ? -2 : void 0).includes("*")) continue;
|
|
3407
|
+
entries.push({
|
|
3408
|
+
find: star ? key.slice(0, -2) : key,
|
|
3409
|
+
replacement: normalizeAliasValue(target.slice(2)),
|
|
3410
|
+
match: star ? "contents" : "exact"
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3413
|
+
entries.sort((a, b) => a.match === b.match ? a.match === "exact" ? 0 : b.find.length - a.find.length : a.match === "exact" ? -1 : 1);
|
|
3414
|
+
return entries.length > 0 ? [...aliases ?? DEFAULT_KIT_ALIASES, ...entries] : aliases;
|
|
3415
|
+
}
|
|
2791
3416
|
/** Parse a config source to a program, or undefined when it cannot be parsed. */
|
|
2792
3417
|
function programOf(source, filename) {
|
|
2793
3418
|
try {
|
|
@@ -2805,7 +3430,33 @@ function findKitPathsBaseInSvelteConfig(source) {
|
|
|
2805
3430
|
const bindings = collectTopLevelBindings(program);
|
|
2806
3431
|
const kit = propOf(config, "kit");
|
|
2807
3432
|
const kitObj = kit ? unwrapToObjectExpression(kit.value, bindings) : void 0;
|
|
2808
|
-
return kitObj ? basePathOf(kitObj, bindings) : void 0;
|
|
3433
|
+
return kitObj ? basePathOf(kitObj, bindings, constBindings(program, bindings)) : void 0;
|
|
3434
|
+
}
|
|
3435
|
+
/**
|
|
3436
|
+
* `unwrapToObjectExpression`, but only where the literal is the value: a call passing more than one
|
|
3437
|
+
* argument (`mergeConfigs(a, b)`) or a spread could make it something else.
|
|
3438
|
+
*/
|
|
3439
|
+
function knownObject(expr, bindings) {
|
|
3440
|
+
let current = expr;
|
|
3441
|
+
for (let i = 0; i < 4 && current; i++) {
|
|
3442
|
+
const e = unwrapTs(current);
|
|
3443
|
+
if (e.type === "ObjectExpression") return e.properties.some((p) => p.type === "SpreadElement") ? void 0 : e;
|
|
3444
|
+
if (e.type === "Identifier") current = bindings.get(e.name);
|
|
3445
|
+
else if (e.type === "CallExpression" && e.arguments.length === 1) current = e.arguments[0];
|
|
3446
|
+
else return void 0;
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
/** Whether `kit.router.type` is the literal `'hash'` in a config whose value is known. */
|
|
3450
|
+
function findKitHashRouterInSvelteConfig(source) {
|
|
3451
|
+
const program = programOf(source, "svelte.config.js");
|
|
3452
|
+
if (!program) return false;
|
|
3453
|
+
const bindings = collectTopLevelBindings(program);
|
|
3454
|
+
const objectAt = (obj, key) => {
|
|
3455
|
+
const p = obj && propOf(obj, key);
|
|
3456
|
+
return p ? knownObject(p.value, bindings) : void 0;
|
|
3457
|
+
};
|
|
3458
|
+
const router = objectAt(objectAt(knownObject(findExportedExpression(program), bindings), "kit"), "router");
|
|
3459
|
+
return stringLiteralOf(router && propOf(router, "type")?.value) === "hash";
|
|
2809
3460
|
}
|
|
2810
3461
|
/**
|
|
2811
3462
|
* Local names bound to `sveltekit` imported from '@sveltejs/kit/vite'. When no such import is
|
|
@@ -2838,7 +3489,7 @@ function findKitPathsBaseInViteConfig(source) {
|
|
|
2838
3489
|
if (arg === void 0) return none;
|
|
2839
3490
|
const kitConfig = unwrapToObjectExpression(arg, bindings);
|
|
2840
3491
|
if (!kitConfig) return { kind: "unresolvable" };
|
|
2841
|
-
const base = basePathOf(kitConfig, bindings);
|
|
3492
|
+
const base = basePathOf(kitConfig, bindings, constBindings(program, bindings));
|
|
2842
3493
|
return base ? {
|
|
2843
3494
|
kind: "resolved",
|
|
2844
3495
|
base
|
|
@@ -2868,6 +3519,219 @@ function resolveKitPathsBase(viteConfig, svelteConfig) {
|
|
|
2868
3519
|
} : void 0;
|
|
2869
3520
|
}
|
|
2870
3521
|
//#endregion
|
|
3522
|
+
//#region src/workspace-packages.ts
|
|
3523
|
+
const DEPENDENCY_FIELDS = [
|
|
3524
|
+
"dependencies",
|
|
3525
|
+
"devDependencies",
|
|
3526
|
+
"peerDependencies"
|
|
3527
|
+
];
|
|
3528
|
+
/**
|
|
3529
|
+
* The app's dependencies that link a package of the same repository, name → range: a `workspace:`
|
|
3530
|
+
* version range, or a `link:`/`file:` path. A `workspace:` range that renames (`workspace:other@*`)
|
|
3531
|
+
* or names a path is left out, since the specifier would not be the package's own name.
|
|
3532
|
+
*/
|
|
3533
|
+
function declaredLocalPackages(packageJsonSource) {
|
|
3534
|
+
const out = /* @__PURE__ */ new Map();
|
|
3535
|
+
let pkg;
|
|
3536
|
+
try {
|
|
3537
|
+
pkg = JSON.parse(packageJsonSource ?? "");
|
|
3538
|
+
} catch {
|
|
3539
|
+
return out;
|
|
3540
|
+
}
|
|
3541
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
3542
|
+
const deps = pkg?.[field];
|
|
3543
|
+
if (!deps || typeof deps !== "object") continue;
|
|
3544
|
+
for (const [name, range] of Object.entries(deps)) if (typeof range === "string" && /^(?:workspace:(?:[\^~*\d]|$)|link:|file:)/.test(range)) out.set(name, range);
|
|
3545
|
+
}
|
|
3546
|
+
return out;
|
|
3547
|
+
}
|
|
3548
|
+
/** The `packages:` globs of a `pnpm-workspace.yaml`, in block (`- 'apps/*'`) or flow (`[a, b]`) form. */
|
|
3549
|
+
function pnpmWorkspaceGlobs(yaml) {
|
|
3550
|
+
const out = [];
|
|
3551
|
+
const item = (raw) => {
|
|
3552
|
+
const value = raw.trim().replace(/^(['"])(.*)\1$/, "$2");
|
|
3553
|
+
if (value) out.push(value);
|
|
3554
|
+
};
|
|
3555
|
+
let inPackages = false;
|
|
3556
|
+
for (const line of yaml.split(/\r?\n/)) {
|
|
3557
|
+
if (/^\s*(#|$)/.test(line)) continue;
|
|
3558
|
+
const key = /^packages\s*:\s*(.*)$/.exec(line);
|
|
3559
|
+
if (key) {
|
|
3560
|
+
const flow = /^\[(.*)\]/.exec(key[1].replace(/\s+#.*$/, ""));
|
|
3561
|
+
if (flow) flow[1].split(",").forEach(item);
|
|
3562
|
+
inPackages = !flow;
|
|
3563
|
+
continue;
|
|
3564
|
+
}
|
|
3565
|
+
if (!inPackages) continue;
|
|
3566
|
+
if (/^[^\s-]/.test(line)) break;
|
|
3567
|
+
const entry = /^\s*-\s*(.*?)\s*(?:\s#.*)?$/.exec(line);
|
|
3568
|
+
if (entry) item(entry[1]);
|
|
3569
|
+
}
|
|
3570
|
+
return out;
|
|
3571
|
+
}
|
|
3572
|
+
/** A root `package.json`'s `workspaces` globs (an array, or `{ packages }`); undefined when it declares none. */
|
|
3573
|
+
function packageJsonWorkspaceGlobs(source) {
|
|
3574
|
+
let workspaces;
|
|
3575
|
+
try {
|
|
3576
|
+
workspaces = JSON.parse(source ?? "").workspaces;
|
|
3577
|
+
} catch {
|
|
3578
|
+
return;
|
|
3579
|
+
}
|
|
3580
|
+
const list = Array.isArray(workspaces) ? workspaces : workspaces?.packages;
|
|
3581
|
+
return Array.isArray(list) ? list.filter((g) => typeof g === "string") : void 0;
|
|
3582
|
+
}
|
|
3583
|
+
/** A package-relative `svelte` field (`./dist/index.js`, `index.js`); undefined when absent or leaving the package. */
|
|
3584
|
+
function svelteField(manifest) {
|
|
3585
|
+
let field;
|
|
3586
|
+
try {
|
|
3587
|
+
field = JSON.parse(manifest).svelte;
|
|
3588
|
+
} catch {
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
if (typeof field !== "string" || field.startsWith("/") || /^[A-Za-z]:/.test(field)) return void 0;
|
|
3592
|
+
const rel = field.replace(/^\.\//, "");
|
|
3593
|
+
return rel && !rel.split("/").includes("..") ? rel : void 0;
|
|
3594
|
+
}
|
|
3595
|
+
/** `dir/rel`, or `dir` itself for an empty `rel`. */
|
|
3596
|
+
function inDir(dir, rel) {
|
|
3597
|
+
const tail = normalizeAliasValue(rel);
|
|
3598
|
+
return tail ? `${dir}/${tail}` : dir;
|
|
3599
|
+
}
|
|
3600
|
+
/**
|
|
3601
|
+
* One package's entries, as Node resolves a bare specifier against its `exports`: a key without
|
|
3602
|
+
* `*` matches exactly, a `./x/*` key matches the directory's contents (its target may add a suffix
|
|
3603
|
+
* after `*`: `./src/*.js`); exact keys come first, then
|
|
3604
|
+
* patterns by longest prefix. The condition walk is `importTarget`'s, in declaration order over
|
|
3605
|
+
* `svelte`/`import`/`module`/`default`. A target this cannot follow — an environment condition,
|
|
3606
|
+
* a `null` exclusion, a `*` mid-pattern or mid-segment, a path leaving the package — stays as an opaque entry so
|
|
3607
|
+
* a shorter pattern cannot answer in its place. Without `exports`, the bare name resolves through
|
|
3608
|
+
* the `svelte` field and subpaths through the package's file layout.
|
|
3609
|
+
*/
|
|
3610
|
+
function entriesOf({ name, dir, manifest }) {
|
|
3611
|
+
let exports;
|
|
3612
|
+
try {
|
|
3613
|
+
exports = JSON.parse(manifest).exports;
|
|
3614
|
+
} catch {
|
|
3615
|
+
return [];
|
|
3616
|
+
}
|
|
3617
|
+
if (exports === void 0) {
|
|
3618
|
+
const field = svelteField(manifest);
|
|
3619
|
+
return [...field === void 0 ? [] : [{
|
|
3620
|
+
find: name,
|
|
3621
|
+
replacement: inDir(dir, field),
|
|
3622
|
+
match: "exact",
|
|
3623
|
+
root: dir
|
|
3624
|
+
}], {
|
|
3625
|
+
find: name,
|
|
3626
|
+
replacement: dir,
|
|
3627
|
+
match: "prefix",
|
|
3628
|
+
root: dir
|
|
3629
|
+
}];
|
|
3630
|
+
}
|
|
3631
|
+
const keys = exports && typeof exports === "object" && !Array.isArray(exports) ? Object.keys(exports) : [];
|
|
3632
|
+
const dotted = keys.filter((k) => k.startsWith(".")).length;
|
|
3633
|
+
if (dotted > 0 && dotted < keys.length) return [{
|
|
3634
|
+
find: name,
|
|
3635
|
+
replacement: null,
|
|
3636
|
+
match: "prefix",
|
|
3637
|
+
root: dir
|
|
3638
|
+
}];
|
|
3639
|
+
const subpaths = dotted > 0 ? exports : { ".": exports };
|
|
3640
|
+
const out = [];
|
|
3641
|
+
for (const [key, value] of Object.entries(subpaths)) {
|
|
3642
|
+
const star = key.indexOf("*");
|
|
3643
|
+
const target = importTarget(value);
|
|
3644
|
+
const local = target?.startsWith("./") && !target.split("/").includes("..") ? target.slice(2) : void 0;
|
|
3645
|
+
if (star < 0) {
|
|
3646
|
+
const replacement = local === void 0 || local.includes("*") ? null : inDir(dir, local);
|
|
3647
|
+
out.push({
|
|
3648
|
+
find: name + key.slice(1),
|
|
3649
|
+
replacement,
|
|
3650
|
+
match: "exact",
|
|
3651
|
+
root: dir
|
|
3652
|
+
});
|
|
3653
|
+
continue;
|
|
3654
|
+
}
|
|
3655
|
+
const head = key.slice(1, star);
|
|
3656
|
+
const trailing = star === key.length - 1 && head.endsWith("/");
|
|
3657
|
+
const at = local?.indexOf("*") ?? -1;
|
|
3658
|
+
const follows = trailing && at > 0 && local[at - 1] === "/" && !local.includes("*", at + 1);
|
|
3659
|
+
const suffix = follows ? local.slice(at + 1) : "";
|
|
3660
|
+
out.push({
|
|
3661
|
+
find: name + head.slice(0, head.lastIndexOf("/")),
|
|
3662
|
+
replacement: follows ? inDir(dir, local.slice(0, at - 1)) : null,
|
|
3663
|
+
match: "contents",
|
|
3664
|
+
root: dir,
|
|
3665
|
+
...suffix ? { suffix } : {}
|
|
3666
|
+
});
|
|
3667
|
+
}
|
|
3668
|
+
return out.sort((a, b) => a.match === b.match ? a.match === "exact" ? 0 : b.find.length - a.find.length : a.match === "exact" ? -1 : 1);
|
|
3669
|
+
}
|
|
3670
|
+
/**
|
|
3671
|
+
* `aliases` widened with the app's workspace packages, the way the bundler sees them: an alias
|
|
3672
|
+
* whose value points into `node_modules/<package>` names the package's own directory (the link a
|
|
3673
|
+
* workspace install creates, readable whether or not it was installed), and every package's
|
|
3674
|
+
* `exports` follows the aliases, as Vite resolves a bare specifier only after its alias plugin.
|
|
3675
|
+
*/
|
|
3676
|
+
function withWorkspacePackages(aliases, packages) {
|
|
3677
|
+
if (packages.length === 0) return aliases;
|
|
3678
|
+
const linked = (entry) => {
|
|
3679
|
+
for (const { name, dir } of packages) {
|
|
3680
|
+
const from = `node_modules/${name}`;
|
|
3681
|
+
const r = entry.replacement;
|
|
3682
|
+
if (r === from || r?.startsWith(`${from}/`)) return {
|
|
3683
|
+
...entry,
|
|
3684
|
+
replacement: dir + r.slice(from.length),
|
|
3685
|
+
root: dir
|
|
3686
|
+
};
|
|
3687
|
+
}
|
|
3688
|
+
return entry;
|
|
3689
|
+
};
|
|
3690
|
+
return [...(aliases ?? DEFAULT_KIT_ALIASES).map(linked), ...packages.flatMap(entriesOf)];
|
|
3691
|
+
}
|
|
3692
|
+
/**
|
|
3693
|
+
* The app's dependencies that an install puts in `node_modules`: every declared name whose range is
|
|
3694
|
+
* not a local protocol (`workspace:`, `link:`, `file:`, `portal:`). Those are the workspace
|
|
3695
|
+
* resolver's, whose bounds must not be sidestepped through their `node_modules` link.
|
|
3696
|
+
*/
|
|
3697
|
+
function declaredInstalledPackages(packageJsonSource) {
|
|
3698
|
+
let pkg;
|
|
3699
|
+
try {
|
|
3700
|
+
pkg = JSON.parse(packageJsonSource ?? "");
|
|
3701
|
+
} catch {
|
|
3702
|
+
return [];
|
|
3703
|
+
}
|
|
3704
|
+
const out = /* @__PURE__ */ new Set();
|
|
3705
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
3706
|
+
const deps = pkg?.[field];
|
|
3707
|
+
if (!deps || typeof deps !== "object") continue;
|
|
3708
|
+
for (const [name, range] of Object.entries(deps)) if (typeof range === "string" && !/^(?:workspace|link|file|portal):/.test(range)) out.add(name);
|
|
3709
|
+
}
|
|
3710
|
+
return [...out];
|
|
3711
|
+
}
|
|
3712
|
+
function hasSvelteCondition(value) {
|
|
3713
|
+
if (!value || typeof value !== "object") return false;
|
|
3714
|
+
return Object.entries(value).some(([key, branch]) => key === "svelte" || hasSvelteCondition(branch));
|
|
3715
|
+
}
|
|
3716
|
+
/** Whether a manifest publishes Svelte components: a `svelte` field, or a `svelte` condition in `exports`. */
|
|
3717
|
+
function isSveltePackage(manifest) {
|
|
3718
|
+
try {
|
|
3719
|
+
const { svelte, exports } = JSON.parse(manifest);
|
|
3720
|
+
return typeof svelte === "string" || hasSvelteCondition(exports);
|
|
3721
|
+
} catch {
|
|
3722
|
+
return false;
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
/**
|
|
3726
|
+
* `aliases` widened with the Svelte packages installed in `node_modules` (`dir` is the textual
|
|
3727
|
+
* `node_modules/<name>` path, relative to the app), compiled like workspace packages. Packages that
|
|
3728
|
+
* publish no Svelte marker get no entries: nothing they export is a component.
|
|
3729
|
+
*/
|
|
3730
|
+
function withInstalledPackages(aliases, packages) {
|
|
3731
|
+
const svelte = packages.filter((p) => isSveltePackage(p.manifest));
|
|
3732
|
+
return svelte.length === 0 ? aliases : [...aliases ?? DEFAULT_KIT_ALIASES, ...svelte.flatMap(entriesOf)];
|
|
3733
|
+
}
|
|
3734
|
+
//#endregion
|
|
2871
3735
|
//#region src/project-paths.ts
|
|
2872
3736
|
/**
|
|
2873
3737
|
* Source-file locations that satisfy the project-scope rules, shared by every
|
|
@@ -2886,6 +3750,19 @@ const SITEMAP_SOURCE_PATHS = [
|
|
|
2886
3750
|
"src/routes/sitemap.xml/+server.ts",
|
|
2887
3751
|
"src/routes/sitemap.xml/+server.js"
|
|
2888
3752
|
];
|
|
3753
|
+
/** The `+server` endpoints above, wherever (group) directories nest them — filter with `servesRootFile`. */
|
|
3754
|
+
const ROOT_FILE_ENDPOINT_GLOB = "src/routes/**/{robots.txt,sitemap*.xml}/+server.{ts,js}";
|
|
3755
|
+
/**
|
|
3756
|
+
* Whether a `+server` endpoint serves `/<file>`: (group) directories add no URL segment, and an optional
|
|
3757
|
+
* parameter in the name (`sitemap[[page]].xml`) still answers the bare name.
|
|
3758
|
+
*/
|
|
3759
|
+
function servesRootFile(rel, file) {
|
|
3760
|
+
const segments = rel.split("/");
|
|
3761
|
+
const dot = file.lastIndexOf(".");
|
|
3762
|
+
const optional = new RegExp(`^${file.slice(0, dot)}\\[\\[[^\\]]+\\]\\]\\${file.slice(dot)}$`);
|
|
3763
|
+
const dir = segments.at(-2) ?? "";
|
|
3764
|
+
return segments[0] === "src" && segments[1] === "routes" && (dir === file || optional.test(dir)) && /^\+server\.(ts|js)$/.test(segments.at(-1) ?? "") && segments.slice(2, -2).every((s) => /^\(.+\)$/.test(s));
|
|
3765
|
+
}
|
|
2889
3766
|
/** Vite's own config resolution order — only the first existing file is the one Vite loads. */
|
|
2890
3767
|
const VITE_CONFIG_FILES = [
|
|
2891
3768
|
"vite.config.js",
|
|
@@ -2957,7 +3834,7 @@ async function runRules(rules, ctx) {
|
|
|
2957
3834
|
}));
|
|
2958
3835
|
const results = [];
|
|
2959
3836
|
const failedRules = [];
|
|
2960
|
-
for (const outcome of perRule) if (Array.isArray(outcome)) results.push(
|
|
3837
|
+
for (const outcome of perRule) if (Array.isArray(outcome)) for (const r of outcome) results.push(r);
|
|
2961
3838
|
else failedRules.push(outcome);
|
|
2962
3839
|
return {
|
|
2963
3840
|
results,
|
|
@@ -3129,7 +4006,7 @@ function formatConsoleReport(results, config, options = {}) {
|
|
|
3129
4006
|
}
|
|
3130
4007
|
lines.push("");
|
|
3131
4008
|
}
|
|
3132
|
-
if (options.byRoute)
|
|
4009
|
+
if (options.byRoute) for (const line of byRouteTree(p, results, config, options.verbose ?? false)) lines.push(line);
|
|
3133
4010
|
if (options.verbose && summary.dynamic > 0) lines.push(p.dim("↯ = set dynamically (verified at runtime)."));
|
|
3134
4011
|
return lines.join("\n").replace(/\n+$/, "\n");
|
|
3135
4012
|
}
|
|
@@ -3156,7 +4033,7 @@ function formatAgentReport(results, config) {
|
|
|
3156
4033
|
if (!groups.has(key)) groups.set(key, []);
|
|
3157
4034
|
groups.get(key).push(r);
|
|
3158
4035
|
}
|
|
3159
|
-
const groupSeverity = (rs) =>
|
|
4036
|
+
const groupSeverity = (rs) => rs.reduce((min, r) => Math.min(min, SEVERITY_RANK[effectiveSeverity(r, config)]), Infinity);
|
|
3160
4037
|
const orderedGroups = [...groups.entries()].sort((a, b) => groupSeverity(a[1]) - groupSeverity(b[1]) || a[0].localeCompare(b[0]));
|
|
3161
4038
|
for (const [loc, rs] of orderedGroups) {
|
|
3162
4039
|
rs.sort((x, y) => SEVERITY_RANK[effectiveSeverity(x, config)] - SEVERITY_RANK[effectiveSeverity(y, config)] || x.id.localeCompare(y.id));
|
|
@@ -3992,4 +4869,4 @@ function formatHtmlReport(results, config, meta) {
|
|
|
3992
4869
|
return buildHtmlDocument(buildJsonReport(results, config, meta), meta);
|
|
3993
4870
|
}
|
|
3994
4871
|
//#endregion
|
|
3995
|
-
export { ANCESTRY_DEPENDENT_TAGS, APP_SCRIPT, ASIDE_DEMOTING_TAGS, CHILD_NODE_KEYS, IDREF_ATTRS, NAMING_ATTRS, ROBOTS_SOURCE_PATHS, SEARCH_RESULT_RULES, SECTIONING_TAGS, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, VITE_CONFIG_FILES, a11yAbbrTitle, a11yAccessibleName, a11yAriaHiddenFocus, a11yDeprecatedAria, a11yDeprecatedAttr, a11yDeprecatedElement, a11yDisallowedAriaProps, a11yDisallowedElement, a11yDoctype, a11yDuplicateLandmark, a11yIdDuplication, a11yInteractiveNesting, a11yInvalidAriaValue, a11yInvalidRole, a11yLabelHasControl, a11yNoAccesskey, a11yNoAutofocus, a11yNoDuplicateDt, a11yNoMissingIdRef, a11yPatternTitle, a11yPermittedContents, a11yPlaceholderLabelOption, a11yPositiveTabindex, a11yRequireDatetime, a11yRequiredAriaProps, a11yRequiredElement, a11yTopLevelLandmark, a11yUnknownAriaAttribute, a11yUnverifiedIdRef, a11yUseList, addFactsDirectives, allRules, architectureComponentSize, architectureDirectoryNaming, architectureDocLinkTarget, architecturePrivateScopeImport, architecturePropCount, architectureReservedDirectoryNames, architectureReservedNamePlacement, architectureRouteComponentImport, architectureUnitEntryFile, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, collectSuppressions, compileOverrides, computeHealth, computeScore, correctnessAutoplayMuted, correctnessBasePathNavigation, correctnessCheckableBindValue, correctnessEachIndexKey, correctnessEachKey, correctnessEffectAsDerived, correctnessEffectAsOnMount, correctnessInstanceBrowserGlobal, correctnessNonreactiveBuiltinState, correctnessOrphanEffect, correctnessOrphanLifecycle, correctnessPropMutation, correctnessServerBrowserGlobal, correctnessStalePropDerivation, correctnessUnmutatedState, decodeFragmentId, defaultConfig, defaultProject, docsUrlFor, effectiveSeverity, emptyComponentFacts, escapeHtml, explainRule, findAttr, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, isClassicScriptType, isPenalized, isPlainObject, isTopFragment, lineOf, noColorPalette, parseComponentFacts, parseSvelte, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceIframeLoading, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveLandmark, resolveRepoLocalPath, resolveRuleOptions, runAnalysis, runRules, safeHref, scoreColor, securityHandlerStateWrite, securityJavascriptUrl, securityRawHtml, securityServerModuleState, securitySharedStateImport, selectRules, seoCanonicalUrl, seoCharset, seoDescriptionLength, seoDescriptionPresence, seoDuplicateDescription, seoDuplicateTitle, seoHeadingLevelSkip, seoHreflang, seoHtmlLang, seoImageAlt, seoIndexability, seoJsonLd, seoJsonLdDateFormat, seoJsonLdDeprecatedType, seoJsonLdPlaceholder, seoJsonLdRelativeUrl, seoJsonLdRequiredProps, seoJsonLdValidity, seoOgDescription, seoOgImage, seoOgTitle, seoOgUrl, seoRobotsTxt, seoSingleH1, seoSitemapInRobots, seoSitemapXml, seoSsrDisabled, seoTitleLength, seoTitlePresence, seoTwitterCard, seoViewport, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, validateRuleSetting, valueFromNodes, withFailedRulesOff, withIndexableOff, withReadLimit };
|
|
4872
|
+
export { ANCESTRY_DEPENDENT_TAGS, APP_SCRIPT, ASIDE_DEMOTING_TAGS, CHILD_NODE_KEYS, IDREF_ATTRS, NAMING_ATTRS, ROBOTS_SOURCE_PATHS, ROOT_FILE_ENDPOINT_GLOB, SEARCH_RESULT_RULES, SECTIONING_TAGS, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, VITE_CONFIG_FILES, a11yAbbrTitle, a11yAccessibleName, a11yAriaHiddenFocus, a11yDeprecatedAria, a11yDeprecatedAttr, a11yDeprecatedElement, a11yDisallowedAriaProps, a11yDisallowedElement, a11yDoctype, a11yDuplicateLandmark, a11yIdDuplication, a11yInteractiveNesting, a11yInvalidAriaValue, a11yInvalidRole, a11yLabelHasControl, a11yNoAccesskey, a11yNoAutofocus, a11yNoDuplicateDt, a11yNoMissingIdRef, a11yPatternTitle, a11yPermittedContents, a11yPlaceholderLabelOption, a11yPositiveTabindex, a11yRequireDatetime, a11yRequiredAriaProps, a11yRequiredElement, a11yTopLevelLandmark, a11yUnknownAriaAttribute, a11yUnverifiedIdRef, a11yUseList, addFactsDirectives, allRules, architectureComponentSize, architectureDirectoryNaming, architectureDocLinkTarget, architecturePrivateScopeImport, architecturePropCount, architectureReservedDirectoryNames, architectureReservedNamePlacement, architectureRouteComponentImport, architectureUnitEntryFile, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentConstantLists, collectComponentFacts, collectConstantListExports, collectFalseExports, collectKitModuleFacts, collectSourceFiles, collectSuppressions, compileOverrides, computeHealth, computeScore, correctnessAutoplayMuted, correctnessBasePathNavigation, correctnessCheckableBindValue, correctnessEachIndexKey, correctnessEachKey, correctnessEffectAsDerived, correctnessEffectAsOnMount, correctnessInstanceBrowserGlobal, correctnessNonreactiveBuiltinState, correctnessOrphanEffect, correctnessOrphanLifecycle, correctnessPropMutation, correctnessServerBrowserGlobal, correctnessStalePropDerivation, correctnessUnmutatedState, declaredInstalledPackages, declaredLocalPackages, decodeFragmentId, defaultConfig, defaultProject, docsUrlFor, effectiveSeverity, emptyComponentFacts, escapeHtml, explainRule, findAttr, findKitHashRouterInSvelteConfig, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatJsonReportChunks, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, hasRel, isClassicScriptType, isPenalized, isPlainObject, isSvgSrc, isTopFragment, layoutNeverRenders, lineOf, noColorPalette, packageJsonWorkspaceGlobs, pageRendersOnlyWhenBuilding, parseComponentFacts, parseModuleProgram, parseSvelte, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceIframeLoading, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, pnpmWorkspaceGlobs, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveLandmark, resolveRepoLocalPath, resolveRuleOptions, routeNeverSsr, runAnalysis, runRules, safeHref, scoreColor, securityHandlerStateWrite, securityJavascriptUrl, securityRawHtml, securityServerModuleState, securitySharedStateImport, selectRules, seoCanonicalUrl, seoCharset, seoDescriptionLength, seoDescriptionPresence, seoDuplicateDescription, seoDuplicateTitle, seoHeadingLevelSkip, seoHreflang, seoHtmlLang, seoImageAlt, seoIndexability, seoJsonLd, seoJsonLdDateFormat, seoJsonLdDeprecatedType, seoJsonLdPlaceholder, seoJsonLdRelativeUrl, seoJsonLdRequiredProps, seoJsonLdValidity, seoOgDescription, seoOgImage, seoOgTitle, seoOgUrl, seoRobotsTxt, seoSingleH1, seoSitemapInRobots, seoSitemapXml, seoSsrDisabled, seoTitleLength, seoTitlePresence, seoTwitterCard, seoViewport, servesRootFile, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, unwrapTs, validateRuleSetting, valueFromNodes, walkEstree, withFailedRulesOff, withIndexableOff, withInstalledPackages, withPackageImports, withReadLimit, withWorkspacePackages };
|