@svelte-vitals/core 0.50.1 → 0.51.1

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/internal.js CHANGED
@@ -1,5 +1,2236 @@
1
- import { $ as architectureDocLinkTarget, $n as seoRobotsTxt, $t as correctnessEffectAsDerived, A as a11yAbbrTitle, An as shouldSkipRangeCheck, At as lineOf, B as a11yAriaHiddenFocus, Bn as settingSeverity, Bt as securityRawHtml, C as a11yPermittedContents, Cn as seoIndexability, Ct as stripTextDirective, D as a11yDeprecatedAttr, Dn as listOption, Dt as attrValue, E as a11yDisallowedAriaProps, En as isMentionedAnywhere, Et as attrTextOf, F as a11yRequireDatetime, Fn as compileOverrides, Ft as architectureComponentSize, G as a11yInvalidRole, Gn as performancePreloadMissingAs, Gt as correctnessOrphanEffect, H as a11yInvalidAriaValue, Hn as performanceRenderBlockingScript, Ht as correctnessServerBrowserGlobal, I as a11yPlaceholderLabelOption, In as formatFailedRuleWarning, It as securitySharedStateImport, J as performanceSequentialAwaits, Jn as performanceImageLoadingHint, Jt as correctnessNonreactiveBuiltinState, K as performanceIframeLoading, Kn as linkRule, Kt as correctnessAutoplayMuted, L as a11yUseList, Ln as overrideMatches, Lt as securityServerModuleState, M as a11yNoAutofocus, Mn as validateRuleSetting, Mt as textFromNodes, N as a11yNoAccesskey, Nn as applyOverrides, Nt as valueFromNodes, O as a11yDeprecatedElement, On as mapOption, Ot as attrValueOf, P as a11yPositiveTabindex, Pn as applyRuleSeverities, Pt as architecturePropCount, Q as performanceHeavyImport, Qn as seoSitemapXml, Qt as correctnessEffectAsOnMount, R as a11yLabelHasControl, Rn as selectRules, Rt as securityHandlerStateWrite, S as a11yRequiredElement, Sn as seoTwitterCard, St as splitTokens, T as a11yDeprecatedAria, Tn as intOption, Tt as attrText, U as a11yRequiredAriaProps, Un as performanceLcpImage, Ut as correctnessBasePathNavigation, V as a11yInteractiveNesting, Vn as withFailedRulesOff, Vt as correctnessInstanceBrowserGlobal, W as a11yUnknownAriaAttribute, Wn as performanceFontPreloadCrossorigin, Wt as correctnessOrphanLifecycle, X as performanceMinifyDisabled, Xn as imageRule, Xt as correctnessPropMutation, Y as performanceLoadWaterfall, Yn as performanceImageDimensions, Yt as correctnessStalePropDerivation, Z as performanceNamespaceImport, Zn as seoHtmlLang, Zt as correctnessUnmutatedState, _ as a11yNoMissingIdRef, _n as seoJsonLdValidity, _t as IDREF_ATTRS, a as formatJsonReport, an as seoDuplicateTitle, ar as headTagRule, at as architecturePrivateScopeImport, b as a11yDuplicateLandmark, bn as seoOgUrl, bt as foldOccurrences, c as scoresByCategory, cn as seoImageAlt, cr as effectiveSeverity, ct as parseKitModuleFacts, d as messageText, dn as seoTitleLength, dr as docsUrlFor, dt as collectNamedImportAliases, en as correctnessEachIndexKey, er as seoJsonLd, et as architectureRouteComponentImport, f as ruleMetaById, fn as seoJsonLdRequiredProps, fr as isPenalized, ft as collectSuppressions, g as a11yUnverifiedIdRef, gn as seoJsonLdDeprecatedType, gt as resolveRole, h as explainRule, hn as seoJsonLdRelativeUrl, hr as defaultProject, ht as unwrapTs, i as buildJsonReport, in as seoDuplicateDescription, ir as seoDescriptionPresence, it as architectureUnitEntryFile, j as a11yNoDuplicateDt, jn as validateRuleOptions, jt as parseSvelte, k as a11yPatternTitle, kn as resolveRuleOptions, kt as findAttr, l as formatGithubReport, ln as seoCharset, lr as hasFailureAtOrAbove, lt as resolveRepoLocalPath, m as allRules, mn as seoJsonLdDateFormat, mr as defaultConfig, mt as parseModuleProgram, n as mdEscape, nn as seoSsrDisabled, nr as seoOgImage, nt as architectureReservedDirectoryNames, o as computeHealth, on as seoSingleH1, or as seoTitlePresence, ot as collectTopLevelBindings, p as severityToSarifLevel, pn as seoJsonLdPlaceholder, pt as parseComponentFacts, q as performanceStateRaw, qn as performanceResponsiveImage, qt as correctnessCheckableBindValue, r as terminalSafe, rn as seoHeadingLevelSkip, rr as seoCanonicalUrl, rt as architectureDirectoryNaming, s as computeScore, sn as seoHreflang, sr as classify, st as parseInMemoryExports, t as formatMarkdownReport, tn as correctnessEachKey, tr as seoOgTitle, tt as architectureReservedNamePlacement, u as SEVERITY_RANK, un as seoDescriptionLength, ur as summarize, ut as resolveRunesModuleSpecifier, v as a11yIdDuplication, vn as seoSitemapInRobots, vt as LANDMARK_ROLES, w as a11yDisallowedElement, wn as performancePreconnect, wt as CHILD_NODE_KEYS, x as a11yDoctype, xn as seoOgDescription, xt as isTopFragment, y as a11yTopLevelLandmark, yn as seoViewport, yt as decodeFragmentId, z as a11yAccessibleName, zn as settingOptions, zt as securityJavascriptUrl } from "./markdown-BBqqynv7.js";
1
+ import { $ as SECTIONING_TAGS, $n as settingSeverity, $t as valueFromNodes, A as a11yAbbrTitle, An as seoDescriptionLength, At as collectDerivedGuardBindings, B as a11yUseList, Bn as seoOgUrl, Bt as unwrapTs, C as a11yDisallowedElement, Cn as seoHeadingLevelSkip, Ct as parseKitModuleFacts, D as a11yDeprecatedElement, Dn as seoHreflang, Dt as addBoundNames, E as a11yDeprecatedAttr, En as seoSingleH1, Et as WALK_IGNORED_KEYS, F as isInteractiveContainer, Fn as seoJsonLdRelativeUrl, Ft as matchLifecycleCall, G as a11yInvalidAriaValue, Gn as resolveRuleOptions, Gt as attrText, H as a11yAccessibleName, Hn as seoTwitterCard, Ht as walkEvalScope, I as isInteractiveElement, In as seoJsonLdDeprecatedType, It as parseModuleProgram, J as a11yInvalidRole, Jn as applyOverrides, Jt as attrValueOf, K as a11yRequiredAriaProps, Kn as shouldSkipRangeCheck, Kt as attrTextOf, L as literalTabindexValue, Ln as seoJsonLdValidity, Lt as rootObjectName, M as a11yNoAutofocus, Mn as seoJsonLdRequiredProps, Mt as collectProgramBindings, N as a11yNoAccesskey, Nn as seoJsonLdPlaceholder, Nt as collectSuppressions, O as HTML_SPEC, On as seoImageAlt, Ot as collectBrowserGlobalRefs, P as a11yPositiveTabindex, Pn as seoJsonLdDateFormat, Pt as collectSvelteLifecycleImports, Q as NAMING_ATTRS, Qn as selectRules, Qt as textFromNodes, R as a11yRequireDatetime, Rn as seoSitemapInRobots, Rt as scopeIntroducedNames, S as a11yPermittedContents, Sn as seoSsrDisabled, Sr as isPenalized, St as parseInMemoryExports, T as a11yDisallowedAriaProps, Tn as seoDuplicateTitle, Tr as defaultProject, Tt as isRootRelativePath, U as a11yAriaHiddenFocus, Un as seoIndexability, Ut as walkScoped, V as a11yLabelHasControl, Vn as seoOgDescription, Vt as walkEstree, W as a11yInteractiveNesting, Wn as performancePreconnect, Wt as CHILD_NODE_KEYS, X as ASIDE_DEMOTING_TAGS, Xn as compileOverrides, Xt as lineOf, Y as ANCESTRY_DEPENDENT_TAGS, Yn as applyRuleSeverities, Yt as findAttr, Z as IDREF_ATTRS, Zn as formatFailedRuleWarning, Zt as parseSvelte, _ as a11yIdDuplication, _n as correctnessUnmutatedState, _r as classify, _t as architectureReservedDirectoryNames, a as formatJsonReport, an as securityJavascriptUrl, ar as performanceResponsiveImage, at as stripTextDirective, b as a11yDoctype, bn as correctnessEachIndexKey, br as summarize, bt as architecturePrivateScopeImport, c as formatGithubReport, cn as correctnessServerBrowserGlobal, cr as seoHtmlLang, ct as performanceStateRaw, d as ruleMetaById, dn as correctnessOrphanEffect, dr as seoJsonLd, dt as performanceMinifyDisabled, en as architecturePropCount, er as withFailedRulesOff, et as decodeFragmentId, f as severityToSarifLevel, fn as correctnessAutoplayMuted, fr as seoOgTitle, ft as performanceNamespaceImport, g as a11yNoMissingIdRef, gn as correctnessPropMutation, gr as seoTitlePresence, gt as architectureReservedNamePlacement, h as a11yUnverifiedIdRef, hn as correctnessStalePropDerivation, hr as seoDescriptionPresence, ht as architectureRouteComponentImport, i as buildJsonReport, in as securityHandlerStateWrite, ir as performancePreloadMissingAs, it as splitTokens, j as a11yNoDuplicateDt, jn as seoTitleLength, jt as collectNamedImportAliases, k as a11yPatternTitle, kn as seoCharset, kt as collectBrowserGuardImports, l as SEVERITY_RANK, ln as correctnessBasePathNavigation, lr as seoSitemapXml, lt as performanceSequentialAwaits, m as explainRule, mn as correctnessNonreactiveBuiltinState, mr as seoCanonicalUrl, mt as architectureDocLinkTarget, n as mdEscape, nn as securitySharedStateImport, nr as performanceLcpImage, nt as isTopFragment, o as computeHealth, on as securityRawHtml, or as performanceImageLoadingHint, ot as resolveRole, p as allRules, pn as correctnessCheckableBindValue, pr as seoOgImage, pt as performanceHeavyImport, q as a11yUnknownAriaAttribute, qn as validateRuleSetting, qt as attrValue, r as terminalSafe, rn as securityServerModuleState, rr as performanceFontPreloadCrossorigin, rt as resolveLandmark, s as computeScore, sn as correctnessInstanceBrowserGlobal, sr as performanceImageDimensions, st as performanceIframeLoading, t as formatMarkdownReport, tn as architectureComponentSize, tr as performanceRenderBlockingScript, tt as foldOccurrences, u as messageText, un as correctnessOrphanLifecycle, ur as seoRobotsTxt, ut as performanceLoadWaterfall, v as a11yTopLevelLandmark, vn as correctnessEffectAsOnMount, vr as effectiveSeverity, vt as architectureDirectoryNaming, w as a11yDeprecatedAria, wn as seoDuplicateDescription, wr as defaultConfig, wt as resolveRepoLocalPath, x as a11yRequiredElement, xn as correctnessEachKey, xr as docsUrlFor, xt as collectTopLevelBindings, y as a11yDuplicateLandmark, yn as correctnessEffectAsDerived, yr as hasFailureAtOrAbove, yt as architectureUnitEntryFile, z as a11yPlaceholderLabelOption, zn as seoViewport, zt as unwrapExport } from "./markdown-B6TPN1ND.js";
2
+ //#region src/head.ts
3
+ const JS_MIME_TYPES = /* @__PURE__ */ new Set([
4
+ "application/ecmascript",
5
+ "application/javascript",
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());
33
+ }
34
+ //#endregion
35
+ //#region src/component-parse.ts
36
+ const KNOWN_TAGS = new Set(Object.keys(HTML_SPEC.elements).map((k) => k.replace(/^svg:/, "").toLowerCase()));
37
+ /** Whether `expr` is a length-only list constructor: `Array(n)` / `new Array(n)` (single argument = length semantics) or `Array.from({ length: n }, …)`. */
38
+ function isLengthOnlyArrayCall(expr) {
39
+ const e = unwrapTs(expr);
40
+ if (!e) return false;
41
+ 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" && e.arguments?.[0]?.type === "ObjectExpression") return (e.arguments[0].properties ?? []).some((p) => p?.type === "Property" && !p.computed && (p.key?.name === "length" || p.key?.value === "length"));
43
+ return false;
44
+ }
45
+ /**
46
+ * Whether the each expression yields no item identity to key on: a constant
47
+ * inline array literal (fixed length, never reorders), a length-only list
48
+ * (`Array(n)`, `new Array(n)`, `[...Array(n)]`, `Array.from({ length: n })` —
49
+ * placeholder/skeleton lists), or a spread array whose every element spreads a
50
+ * length-only list. Such blocks are skipped entirely — neither each-key nor
51
+ * each-index-key can give useful advice on them.
52
+ */
53
+ function isIdentityFreeEach(node) {
54
+ const expr = unwrapTs(node.expression);
55
+ if (expr.type === "ArrayExpression" && Array.isArray(expr.elements)) return expr.elements.every((el) => el?.type !== "SpreadElement" || isLengthOnlyArrayCall(el.argument));
56
+ return isLengthOnlyArrayCall(expr);
57
+ }
58
+ /**
59
+ * Whether `expr` is the index binding itself or a trivial coercion of it —
60
+ * `i`, `String(i)`, `Number(i)`, `` `${i}` ``, `i.toString()`, `i + ''` — all
61
+ * position-based identity. A template or concatenation with any literal text
62
+ * (`` `row-${i}` ``, `i + '-row'`) or extra expressions is treated as composite
63
+ * and NOT matched: composite keys may be a deliberate uniqueness workaround for
64
+ * duplicate items.
65
+ */
66
+ function isIndexExpression(expr, index) {
67
+ const e = unwrapTs(expr);
68
+ if (e.type === "Identifier") return e.name === index;
69
+ if (e.type === "CallExpression") {
70
+ const callee = e.callee;
71
+ if (callee.type === "Identifier" && (callee.name === "String" || callee.name === "Number") && e.arguments.length === 1) return isIndexExpression(e.arguments[0], index);
72
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier" && callee.property.name === "toString" && e.arguments.length === 0) return isIndexExpression(callee.object, index);
73
+ return false;
74
+ }
75
+ if (e.type === "TemplateLiteral") {
76
+ const exprs = e.expressions;
77
+ if (exprs.length !== 1) return false;
78
+ if (e.quasis.some((q) => (q.value.cooked ?? q.value.raw) !== "")) return false;
79
+ return isIndexExpression(exprs[0], index);
80
+ }
81
+ if (e.type === "BinaryExpression" && e.operator === "+") {
82
+ const emptyString = (n) => n.type === "Literal" && n.value === "";
83
+ if (emptyString(e.left)) return isIndexExpression(e.right, index);
84
+ if (emptyString(e.right)) return isIndexExpression(e.left, index);
85
+ }
86
+ return false;
87
+ }
88
+ /**
89
+ * Whether the block's key expression is its index binding or a trivial
90
+ * stringification of it (`{#each items as item, i (i)}`, `(String(i))`,
91
+ * `` (`${i}`) ``, `(i.toString())`) — position-based identity, the anti-pattern
92
+ * Svelte's docs call out ("do not use the index as a key"). Composite keys that
93
+ * merely CONTAIN the index add uniqueness and are never matched
94
+ * (correctness/each-index-key).
95
+ */
96
+ function isIndexKey(each) {
97
+ if (typeof each.index !== "string" || each.key == null) return false;
98
+ return isIndexExpression(each.key, each.index);
99
+ }
100
+ /** Recursively collect every `{#each}` block in the template (correctness/each-key). */
101
+ function collectEachBlocks(node, source, acc) {
102
+ if (Array.isArray(node)) {
103
+ for (const child of node) collectEachBlocks(child, source, acc);
104
+ return;
105
+ }
106
+ if (!node || typeof node !== "object") return;
107
+ if (node.type === "EachBlock" && node.context != null && !isIdentityFreeEach(node)) acc.push({
108
+ hasKey: node.key != null,
109
+ line: lineOf(source, node.start),
110
+ ...isIndexKey(node) ? { indexKey: true } : {}
111
+ });
112
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectEachBlocks(node[key], source, acc);
113
+ }
114
+ /**
115
+ * Whether a CallExpression *creates an effect*: `$effect(...)` or `$effect.pre(...)`.
116
+ * Excludes the non-effect `$effect.*` readers (`$effect.tracking()`, `$effect.root()`),
117
+ * which would otherwise be recorded as effects and seed spurious correctness/effect-as-derived pass units.
118
+ */
119
+ function isEffectCall(node) {
120
+ const c = node?.callee;
121
+ if (c?.type === "Identifier") return c.name === "$effect";
122
+ if (c?.type === "MemberExpression" && c.object?.type === "Identifier" && c.object.name === "$effect") return c.property?.type === "Identifier" && c.property.name === "pre";
123
+ return false;
124
+ }
125
+ /** Whether a CallExpression is `$effect.root(...)` — a legal standalone reactive scope (correctness/orphan-effect). */
126
+ function isEffectRootCall(node) {
127
+ const c = node?.callee;
128
+ return c?.type === "MemberExpression" && c.object?.type === "Identifier" && c.object.name === "$effect" && c.property?.type === "Identifier" && c.property.name === "root";
129
+ }
130
+ /**
131
+ * Whether a CallExpression is a `$state` *declaration* form: `$state(...)`,
132
+ * `$state.raw(...)`, or `$state.frozen(...)` — but NOT readers like
133
+ * `$state.snapshot(...)`, which would otherwise pollute the state-name set (correctness/effect-as-derived).
134
+ */
135
+ function isStateDeclaration(node) {
136
+ const c = node?.callee;
137
+ if (c?.type === "Identifier") return c.name === "$state";
138
+ if (c?.type === "MemberExpression" && c.object?.type === "Identifier" && c.object.name === "$state") return c.property?.type === "Identifier" && (c.property.name === "raw" || c.property.name === "frozen");
139
+ return false;
140
+ }
141
+ /** True when a function's body does nothing but assign to `$state` identifiers (correctness/effect-as-derived). */
142
+ function bodyOnlyAssignsState(fn, stateNames) {
143
+ const isStateAssign = (expr) => expr?.type === "AssignmentExpression" && expr.operator === "=" && expr.left?.type === "Identifier" && stateNames.has(expr.left.name);
144
+ const body = fn?.body;
145
+ if (!body) return false;
146
+ if (body.type !== "BlockStatement") return isStateAssign(body);
147
+ if (body.body.length === 0) return false;
148
+ return body.body.every((s) => s?.type === "ExpressionStatement" && isStateAssign(s.expression));
149
+ }
150
+ /** `$derived(...)` or `$derived.by(...)` declaration form. */
151
+ function isDerivedDeclaration(node) {
152
+ const c = node?.callee;
153
+ if (c?.type === "Identifier") return c.name === "$derived";
154
+ if (c?.type === "MemberExpression" && c.object?.type === "Identifier" && c.object.name === "$derived") return c.property?.type === "Identifier" && c.property.name === "by";
155
+ return false;
156
+ }
157
+ function collectStateWrites(root, stateNames, acc, kinds) {
158
+ const record = (name, kind) => {
159
+ acc.add(name);
160
+ if (kinds) {
161
+ let set = kinds.get(name);
162
+ if (!set) kinds.set(name, set = /* @__PURE__ */ new Set());
163
+ set.add(kind);
164
+ }
165
+ };
166
+ walkScoped(root, (n, scope) => {
167
+ const shadowed = (name) => name === void 0 || scope.has(name);
168
+ if (n?.type === "AssignmentExpression") {
169
+ 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
+ const r = rootObjectName(n.left);
172
+ if (r && stateNames.has(r) && !shadowed(r)) record(r, "mutate");
173
+ } else if (n.left?.type === "ObjectPattern" || n.left?.type === "ArrayPattern") {
174
+ const bound = /* @__PURE__ */ new Set();
175
+ addBoundNames(n.left, bound);
176
+ for (const name of bound) if (stateNames.has(name) && !shadowed(name)) record(name, "reassign");
177
+ }
178
+ } else if (n?.type === "UpdateExpression") if (n.argument?.type === "Identifier") {
179
+ if (stateNames.has(n.argument.name) && !shadowed(n.argument.name)) record(n.argument.name, "reassign");
180
+ } else {
181
+ const r = rootObjectName(n.argument);
182
+ if (r && stateNames.has(r) && !shadowed(r)) record(r, "mutate");
183
+ }
184
+ else if (n?.type === "UnaryExpression" && n.operator === "delete") {
185
+ const r = rootObjectName(n.argument);
186
+ if (r && stateNames.has(r) && !shadowed(r)) record(r, "mutate");
187
+ } else if (n?.type === "CallExpression") {
188
+ if (n.callee?.type === "MemberExpression") {
189
+ const r = rootObjectName(n.callee);
190
+ if (r && stateNames.has(r) && !shadowed(r)) record(r, "mutate");
191
+ }
192
+ for (const a of n.arguments ?? []) {
193
+ const arg = a?.type === "SpreadElement" ? a.argument : a;
194
+ const r = rootObjectName(arg);
195
+ if (r && stateNames.has(r) && !shadowed(r)) record(r, "escape");
196
+ }
197
+ }
198
+ });
199
+ }
200
+ /** Function-shaped nodes whose bodies defer evaluation — prop reads inside them stay reactive (compiled to call-time reads). */
201
+ function isDeferredBody(n) {
202
+ return n?.type === "FunctionDeclaration" || n?.type === "FunctionExpression" || n?.type === "ArrowFunctionExpression";
203
+ }
204
+ /** A plain `$state(...)` call — bare-Identifier callee, never `$state.raw`/`$state.frozen` (performance/state-raw candidates only; `isStateDeclaration` stays raw-inclusive for `stateNames`). */
205
+ function isPlainStateCall(node) {
206
+ return node?.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "$state";
207
+ }
208
+ /** Built-in classes with reactive drop-ins in svelte/reactivity — plain instances in $state are NOT deep-proxied, so their mutations are untracked (correctness/nonreactive-builtin-state). */
209
+ const BUILTIN_STATE_TYPES = /* @__PURE__ */ new Set([
210
+ "Map",
211
+ "Set",
212
+ "Date",
213
+ "URL",
214
+ "URLSearchParams"
215
+ ]);
216
+ /** Type-specific mutating methods. URL mutates via property writes and deep searchParams calls only. */
217
+ const BUILTIN_MUTATIONS = {
218
+ Map: /* @__PURE__ */ new Set([
219
+ "set",
220
+ "delete",
221
+ "clear"
222
+ ]),
223
+ Set: /* @__PURE__ */ new Set([
224
+ "add",
225
+ "delete",
226
+ "clear"
227
+ ]),
228
+ Date: /* @__PURE__ */ new Set([
229
+ "setTime",
230
+ "setFullYear",
231
+ "setMonth",
232
+ "setDate",
233
+ "setHours",
234
+ "setMinutes",
235
+ "setSeconds",
236
+ "setMilliseconds",
237
+ "setYear",
238
+ "setUTCFullYear",
239
+ "setUTCMonth",
240
+ "setUTCDate",
241
+ "setUTCHours",
242
+ "setUTCMinutes",
243
+ "setUTCSeconds",
244
+ "setUTCMilliseconds"
245
+ ]),
246
+ URL: /* @__PURE__ */ new Set(),
247
+ URLSearchParams: /* @__PURE__ */ new Set([
248
+ "append",
249
+ "set",
250
+ "delete",
251
+ "sort"
252
+ ])
253
+ };
254
+ /**
255
+ * Signals for correctness/nonreactive-builtin-state, per candidate binding
256
+ * (name → constructor type): `mutated` collects type-specific mutations that
257
+ * happen INSIDE a function body (a top-level init mutation runs before first
258
+ * render and can never leave the UI stale); `reassigned` collects whole-binding
259
+ * reassignments ANYWHERE (a fresh reassignment after mutation makes the code
260
+ * work) — except the bare self-assignment `b = b`, a no-op under $state's
261
+ * referential equality in Svelte 5, which must not exempt. Member writes
262
+ * (assignment / update / `delete`) and deep member calls count as mutation
263
+ * only for URL bindings (URL mutates via properties: `u.href = …`,
264
+ * `u.searchParams.set(...)` — final method in the URLSearchParams set); the
265
+ * other types count only direct calls from their own method table, so an
266
+ * expando-property write on a Map never flags (SvelteMap would not track it
267
+ * either). Deep reads (`get`, `has`, …) never count. Shadow-aware; class
268
+ * bodies count as function depth.
269
+ */
270
+ function collectBuiltinStateSignals(node, candidates, mutated, reassigned, shadowed = /* @__PURE__ */ new Set(), inFunction = false) {
271
+ if (Array.isArray(node)) {
272
+ for (const child of node) collectBuiltinStateSignals(child, candidates, mutated, reassigned, shadowed, inFunction);
273
+ return;
274
+ }
275
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
276
+ const introduced = scopeIntroducedNames(node);
277
+ const scope = introduced.size > 0 ? /* @__PURE__ */ new Set([...shadowed, ...introduced]) : shadowed;
278
+ const boundary = isDeferredBody(node) || node.type === "ClassDeclaration" || node.type === "ClassExpression";
279
+ const nextInFunction = inFunction || boundary;
280
+ const hit = (name) => typeof name === "string" && candidates.has(name) && !scope.has(name) ? name : void 0;
281
+ if (node.type === "AssignmentExpression") {
282
+ if (node.left?.type === "Identifier") {
283
+ const n = hit(node.left.name);
284
+ const isBareSelfAssign = node.right?.type === "Identifier" && node.right.name === n;
285
+ if (n && !isBareSelfAssign) reassigned.add(n);
286
+ } else if (node.left?.type === "ObjectPattern" || node.left?.type === "ArrayPattern") {
287
+ const bound = /* @__PURE__ */ new Set();
288
+ addBoundNames(node.left, bound);
289
+ for (const name of bound) {
290
+ const n = hit(name);
291
+ if (n) reassigned.add(n);
292
+ }
293
+ } else if (node.left?.type === "MemberExpression" && inFunction) {
294
+ const n = hit(rootObjectName(node.left));
295
+ if (n && candidates.get(n) === "URL") mutated.add(n);
296
+ }
297
+ } else if (node.type === "UpdateExpression" && node.argument?.type === "MemberExpression" && inFunction) {
298
+ const n = hit(rootObjectName(node.argument));
299
+ if (n && candidates.get(n) === "URL") mutated.add(n);
300
+ } else if (node.type === "UnaryExpression" && node.operator === "delete" && inFunction) {
301
+ const n = hit(rootObjectName(node.argument));
302
+ if (n && candidates.get(n) === "URL") mutated.add(n);
303
+ } else if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && !node.callee.computed && inFunction) {
304
+ const method = node.callee.property?.name;
305
+ if (typeof method === "string") {
306
+ if (node.callee.object?.type === "Identifier") {
307
+ const n = hit(node.callee.object.name);
308
+ if (n && BUILTIN_MUTATIONS[candidates.get(n)]?.has(method)) mutated.add(n);
309
+ } else if (node.callee.object?.type === "MemberExpression") {
310
+ const n = hit(rootObjectName(node.callee));
311
+ if (n && candidates.get(n) === "URL" && BUILTIN_MUTATIONS.URLSearchParams.has(method)) mutated.add(n);
312
+ }
313
+ }
314
+ }
315
+ for (const key of Object.keys(node)) {
316
+ if (WALK_IGNORED_KEYS.has(key)) continue;
317
+ collectBuiltinStateSignals(node[key], candidates, mutated, reassigned, scope, nextInFunction);
318
+ }
319
+ }
320
+ /**
321
+ * Scan a binding pattern (a `VariableDeclarator.id`, or nested inside one) for
322
+ * real references hiding inside it, while skipping the bound identifiers
323
+ * themselves — a binding site is never a read. `const { a } = x` binds `a`,
324
+ * no reference to anything; `const { a = obj } = x` reads `obj` (an
325
+ * `AssignmentPattern`'s default-value RHS is a real expression position);
326
+ * `const { [obj]: a } = x` reads `obj` as a computed key. Both cases hand off
327
+ * to `collectAliasRefs` (normal expression-walk semantics) for the referenced
328
+ * subtree; only the pattern shape itself (`ObjectPattern`/`ArrayPattern`/
329
+ * `RestElement`/`AssignmentPattern` bound side) is walked specially here.
330
+ */
331
+ function collectPatternAliasRefs(node, names, acc, scope, ownRhs) {
332
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
333
+ if (node.type === "Identifier") return;
334
+ if (node.type === "ObjectPattern") {
335
+ for (const prop of node.properties ?? []) if (prop?.type === "RestElement") collectPatternAliasRefs(prop.argument, names, acc, scope, ownRhs);
336
+ else if (prop?.type === "Property") {
337
+ if (prop.computed) collectAliasRefs(prop.key, names, acc, scope, ownRhs);
338
+ collectPatternAliasRefs(prop.value, names, acc, scope, ownRhs);
339
+ }
340
+ return;
341
+ }
342
+ if (node.type === "ArrayPattern") {
343
+ for (const el of node.elements ?? []) collectPatternAliasRefs(el, names, acc, scope, ownRhs);
344
+ return;
345
+ }
346
+ if (node.type === "AssignmentPattern") {
347
+ collectPatternAliasRefs(node.left, names, acc, scope, ownRhs);
348
+ collectAliasRefs(node.right, names, acc, scope, ownRhs);
349
+ return;
350
+ }
351
+ if (node.type === "RestElement") collectPatternAliasRefs(node.argument, names, acc, scope, ownRhs);
352
+ }
353
+ /**
354
+ * Bare references to candidate names outside their own reassignments — aliasing
355
+ * escapes (performance/state-raw condition 4). Nearest-enclosing-assignment
356
+ * semantics: a reference is exempt only while the CLOSEST surrounding
357
+ * AssignmentExpression assigns to that same candidate (`list = [...list, x]`
358
+ * qualifies; `obj = (cache = obj)` does not). Skips the assignment LHS itself
359
+ * and non-computed member properties/keys; shadow-aware. A `VariableDeclarator`
360
+ * routes its `id` through `collectPatternAliasRefs` — bound names in the
361
+ * pattern are never references, but destructuring defaults and computed keys
362
+ * are — so the candidate's own `$state(literal)` declarator contains no
363
+ * self-reference; `let b = $state([...list])` referencing ANOTHER candidate
364
+ * still correctly counts as an alias escape of `list` via `init`. Walks
365
+ * whatever subtree it is given — callers choose the roots.
366
+ */
367
+ function collectAliasRefs(node, names, acc, shadowed = /* @__PURE__ */ new Set(), ownRhs = null) {
368
+ if (Array.isArray(node)) {
369
+ for (const child of node) collectAliasRefs(child, names, acc, shadowed, ownRhs);
370
+ return;
371
+ }
372
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
373
+ const introduced = scopeIntroducedNames(node);
374
+ const scope = introduced.size > 0 ? /* @__PURE__ */ new Set([...shadowed, ...introduced]) : shadowed;
375
+ if (node.type === "AssignmentExpression") {
376
+ const lhsIsCandidate = node.left?.type === "Identifier" && names.has(node.left.name) && !scope.has(node.left.name);
377
+ if (!lhsIsCandidate) collectAliasRefs(node.left, names, acc, scope, null);
378
+ collectAliasRefs(node.right, names, acc, scope, lhsIsCandidate ? node.left.name : null);
379
+ return;
380
+ }
381
+ if (node.type === "VariableDeclarator") {
382
+ collectPatternAliasRefs(node.id, names, acc, scope, ownRhs);
383
+ if (node.init) collectAliasRefs(node.init, names, acc, scope, ownRhs);
384
+ return;
385
+ }
386
+ if (node.type === "Identifier" && names.has(node.name) && !scope.has(node.name) && node.name !== ownRhs) {
387
+ acc.add(node.name);
388
+ return;
389
+ }
390
+ for (const key of Object.keys(node)) {
391
+ if (WALK_IGNORED_KEYS.has(key)) continue;
392
+ if (node.type === "MemberExpression" && key === "property" && !node.computed) continue;
393
+ if (node.type === "Property" && key === "key" && !node.computed) continue;
394
+ collectAliasRefs(node[key], names, acc, scope, ownRhs);
395
+ }
396
+ }
397
+ /**
398
+ * Alias scan over the fragment: template READ positions are exempt, but function
399
+ * bodies inside the template (inline handlers) are not — `onclick={() => (cache = obj)}`
400
+ * escapes. Threads template shadowing (each contexts etc.) into the handler scan.
401
+ */
402
+ function collectFragmentAliasRefs(node, names, acc, shadowed = /* @__PURE__ */ new Set()) {
403
+ if (Array.isArray(node)) {
404
+ for (const child of node) collectFragmentAliasRefs(child, names, acc, shadowed);
405
+ return;
406
+ }
407
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
408
+ if (isDeferredBody(node)) {
409
+ const introduced = scopeIntroducedNames(node);
410
+ const scope = introduced.size > 0 ? /* @__PURE__ */ new Set([...shadowed, ...introduced]) : shadowed;
411
+ collectAliasRefs(node.body, names, acc, scope, null);
412
+ return;
413
+ }
414
+ const introduced = scopeIntroducedNames(node);
415
+ const scope = introduced.size > 0 ? /* @__PURE__ */ new Set([...shadowed, ...introduced]) : shadowed;
416
+ if (Array.isArray(node.attributes)) collectFragmentAliasRefs(node.attributes, names, acc, scope);
417
+ for (const key of Object.keys(node)) {
418
+ if (WALK_IGNORED_KEYS.has(key) || key === "attributes") continue;
419
+ collectFragmentAliasRefs(node[key], names, acc, scope);
420
+ }
421
+ }
422
+ /**
423
+ * Each-context taint (performance/state-raw condition 5): for `{#each candidate as item}`
424
+ * or `{#each candidate.path as item}`, any mutate/escape of the context binding (or index)
425
+ * inside the block — member writes, method calls, call arguments, `bind:`, component props —
426
+ * disqualifies the candidate over the candidate or a member path of it
427
+ * (`{#each obj.items as item}`): item-level edits stop being reactive under $state.raw.
428
+ * Pure reassignments of the context name are ignored (they don't touch the list's contents).
429
+ */
430
+ function collectEachContextTaint(node, names, acc, shadowed = /* @__PURE__ */ new Set()) {
431
+ if (Array.isArray(node)) {
432
+ for (const child of node) collectEachContextTaint(child, names, acc, shadowed);
433
+ return;
434
+ }
435
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
436
+ const introduced = scopeIntroducedNames(node);
437
+ const scope = introduced.size > 0 ? /* @__PURE__ */ new Set([...shadowed, ...introduced]) : shadowed;
438
+ if (node.type === "EachBlock") {
439
+ const expr = unwrapTs(node.expression);
440
+ const target = expr?.type === "Identifier" ? expr.name : expr?.type === "MemberExpression" ? rootObjectName(expr) : void 0;
441
+ if (target !== void 0 && names.has(target) && !shadowed.has(target)) {
442
+ const ctxNames = /* @__PURE__ */ new Set();
443
+ addBoundNames(node.context, ctxNames);
444
+ if (typeof node.index === "string") ctxNames.add(node.index);
445
+ if (ctxNames.size > 0) {
446
+ const union = /* @__PURE__ */ new Set();
447
+ const kinds = /* @__PURE__ */ new Map();
448
+ collectStateWrites(node.body, ctxNames, union, kinds);
449
+ collectTemplateEscapes(node.body, ctxNames, union, kinds);
450
+ if ([...union].some((n) => {
451
+ const k = kinds.get(n);
452
+ return !k || [...k].some((kind) => kind !== "reassign");
453
+ })) acc.add(target);
454
+ }
455
+ }
456
+ }
457
+ for (const key of Object.keys(node)) {
458
+ if (WALK_IGNORED_KEYS.has(key)) continue;
459
+ collectEachContextTaint(node[key], names, acc, scope);
460
+ }
461
+ }
462
+ /**
463
+ * Whether `node` references any of `names` in an EAGER position: nested
464
+ * function/arrow bodies (incl. object getters/methods, whose values are
465
+ * FunctionExpressions) are skipped — the compiler defers those reads to call
466
+ * time, so they stay reactive. Non-computed member properties and object keys
467
+ * are not references. Shadow-aware via `scopeIntroducedNames`.
468
+ */
469
+ function refsNamesEagerly(node, names, shadowed = /* @__PURE__ */ new Set()) {
470
+ if (Array.isArray(node)) return node.some((c) => refsNamesEagerly(c, names, shadowed));
471
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return false;
472
+ if (isDeferredBody(node)) return false;
473
+ const introduced = scopeIntroducedNames(node);
474
+ const scope = introduced.size > 0 ? /* @__PURE__ */ new Set([...shadowed, ...introduced]) : shadowed;
475
+ if (node.type === "Identifier" && names.has(node.name) && !scope.has(node.name)) return true;
476
+ for (const key of Object.keys(node)) {
477
+ if (WALK_IGNORED_KEYS.has(key)) continue;
478
+ if (node.type === "MemberExpression" && key === "property" && !node.computed) continue;
479
+ if (node.type === "Property" && key === "key" && !node.computed) continue;
480
+ if (refsNamesEagerly(node[key], names, scope)) return true;
481
+ }
482
+ return false;
483
+ }
484
+ /** Whether the subtree contains any call, construction, or await — used to keep stale-prop candidates to plain expressions (rune wrappers and helper/service calls are all excluded structurally). */
485
+ function containsCallLike(node) {
486
+ let found = false;
487
+ walkEstree(node, (n) => {
488
+ if (n?.type === "CallExpression" || n?.type === "NewExpression" || n?.type === "AwaitExpression") found = true;
489
+ });
490
+ return found;
491
+ }
492
+ /**
493
+ * Names from `names` referenced in the template fragment in an eager position:
494
+ * expression tags, attribute/directive expressions, and block expressions count;
495
+ * inline-handler function bodies do NOT (deferred reads never render), while
496
+ * `{#snippet}` bodies DO (render content). Shadow-aware for template scopes
497
+ * (each contexts + index, snippet parameters, await value/error).
498
+ */
499
+ function collectFragmentRefs(node, names, acc, shadowed = /* @__PURE__ */ new Set()) {
500
+ if (Array.isArray(node)) {
501
+ for (const c of node) collectFragmentRefs(c, names, acc, shadowed);
502
+ return;
503
+ }
504
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
505
+ if (isDeferredBody(node)) return;
506
+ const introduced = scopeIntroducedNames(node);
507
+ const scope = introduced.size > 0 ? /* @__PURE__ */ new Set([...shadowed, ...introduced]) : shadowed;
508
+ if (node.type === "Identifier" && names.has(node.name) && !scope.has(node.name)) acc.add(node.name);
509
+ if (node.type === "EachBlock" || node.type === "AwaitBlock") {
510
+ collectFragmentRefs(node.expression, names, acc, shadowed);
511
+ for (const key of Object.keys(node)) {
512
+ if (WALK_IGNORED_KEYS.has(key) || key === "expression") continue;
513
+ collectFragmentRefs(node[key], names, acc, scope);
514
+ }
515
+ return;
516
+ }
517
+ if (Array.isArray(node.attributes)) collectFragmentRefs(node.attributes, names, acc, scope);
518
+ for (const key of Object.keys(node)) {
519
+ if (WALK_IGNORED_KEYS.has(key) || key === "attributes") continue;
520
+ if (node.type === "MemberExpression" && key === "property" && !node.computed) continue;
521
+ if (node.type === "Property" && key === "key" && !node.computed) continue;
522
+ collectFragmentRefs(node[key], names, acc, scope);
523
+ }
524
+ }
525
+ /**
526
+ * Stale prop derivations (correctness/stale-prop-derivation): top-level
527
+ * const/let/var Identifier declarators whose CALL-FREE initializer references a
528
+ * prop eagerly. Reassignment/escape and template-reference filtering happen at
529
+ * the call site, where the fragment is available.
530
+ */
531
+ function collectStalePropCandidates(program, propNames, source) {
532
+ const out = [];
533
+ for (const stmt of program.body ?? []) {
534
+ if (stmt?.type !== "VariableDeclaration") continue;
535
+ for (const d of stmt.declarations ?? []) {
536
+ if (d?.id?.type !== "Identifier" || !d.init) continue;
537
+ if (containsCallLike(d.init)) continue;
538
+ if (!refsNamesEagerly(d.init, propNames)) continue;
539
+ out.push({
540
+ name: d.id.name,
541
+ line: lineOf(source, d.start)
542
+ });
543
+ }
544
+ }
545
+ return out;
546
+ }
547
+ /**
548
+ * Component-like nodes whose attributes are props passed INTO another component
549
+ * (an escape), as opposed to `SvelteElement` (`<svelte:element this={...}>`), whose
550
+ * attributes are DOM-attribute reads on a dynamically-named element — not an escape.
551
+ */
552
+ const COMPONENT_LIKE_TYPES = /* @__PURE__ */ new Set([
553
+ "Component",
554
+ "SvelteComponent",
555
+ "SvelteSelf"
556
+ ]);
557
+ /**
558
+ * Add state names ESCAPED via the template (correctness/unmutated-state rules 5–6): a `bind:` on any
559
+ * element, or passed as a prop to a component (static `<Foo>`, or dynamic
560
+ * `<svelte:component>`/`<svelte:self>`). Slot children / DOM-attribute reads do
561
+ * not escape. `CHILD_NODE_KEYS` omits `attributes`, so inspect them explicitly.
562
+ * The optional `kinds` map records every hit as 'escape' for consumers that need
563
+ * the classification (performance/state-raw); the `acc` union contract is unchanged.
564
+ */
565
+ function collectTemplateEscapes(node, stateNames, acc, kinds) {
566
+ const record = (name) => {
567
+ acc.add(name);
568
+ if (kinds) {
569
+ let set = kinds.get(name);
570
+ if (!set) kinds.set(name, set = /* @__PURE__ */ new Set());
571
+ set.add("escape");
572
+ }
573
+ };
574
+ if (Array.isArray(node)) {
575
+ for (const c of node) collectTemplateEscapes(c, stateNames, acc, kinds);
576
+ return;
577
+ }
578
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
579
+ if (Array.isArray(node.attributes)) {
580
+ for (const attr of node.attributes) if (attr?.type === "BindDirective") {
581
+ const r = rootObjectName(attr.expression);
582
+ if (r && stateNames.has(r)) record(r);
583
+ } else if (COMPONENT_LIKE_TYPES.has(node.type)) walkEstree(attr, (m) => {
584
+ if (m?.type === "Identifier" && stateNames.has(m.name)) record(m.name);
585
+ });
586
+ }
587
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectTemplateEscapes(node[key], stateNames, acc, kinds);
588
+ }
589
+ /** Directive types verified against svelte 5 modern-AST output: `use:x={obj}` → `UseDirective`, `transition:x={obj}` → `TransitionDirective`, `animate:x={obj}` → `AnimateDirective`. */
590
+ const DIRECTIVE_ESCAPE_TYPES = /* @__PURE__ */ new Set([
591
+ "UseDirective",
592
+ "TransitionDirective",
593
+ "AnimateDirective"
594
+ ]);
595
+ /**
596
+ * Directive expressions that hand a value to arbitrary code — `use:action={obj}`,
597
+ * `transition:fn={obj}`, `animate:fn={obj}` — are reference handoffs, the same class
598
+ * as a call argument. Serves `performance/state-raw` and `correctness/unmutated-state`
599
+ * (the receiving code may mutate the proxy invisibly, so such state is neither
600
+ * raw-able nor "unused"). The shared template-escape collector deliberately still
601
+ * excludes directives so `correctness/stale-prop-derivation`'s disqualification set
602
+ * is unchanged — a stale prop-derived value handed to an action is still worth flagging.
603
+ */
604
+ function collectDirectiveEscapes(node, names, acc) {
605
+ if (Array.isArray(node)) {
606
+ for (const c of node) collectDirectiveEscapes(c, names, acc);
607
+ return;
608
+ }
609
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
610
+ if (Array.isArray(node.attributes)) {
611
+ for (const attr of node.attributes) if (DIRECTIVE_ESCAPE_TYPES.has(attr?.type) && attr.expression) walkEstree(attr.expression, (m) => {
612
+ if (m?.type === "Identifier" && names.has(m.name)) acc.add(m.name);
613
+ });
614
+ }
615
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectDirectiveEscapes(node[key], names, acc);
616
+ }
617
+ const RUNE_NAMES = /* @__PURE__ */ new Set([
618
+ "$state",
619
+ "$derived",
620
+ "$effect",
621
+ "$props",
622
+ "$bindable",
623
+ "$inspect",
624
+ "$host"
625
+ ]);
626
+ /**
627
+ * Local names bound by any non-type-only import specifier (default, named, or namespace),
628
+ * anywhere in a program (correctness/effect-as-onmount). An imported binding is opaque to this
629
+ * analysis — it may be a runes-module state object, a `svelte/reactivity`/`svelte/reactivity/window`
630
+ * constructor or live binding, or anything else carrying hidden reactivity — so member reads on
631
+ * it are folded into `reactiveNames` rather than risk telling someone to replace a live effect
632
+ * with `onMount`.
633
+ */
634
+ function collectImportedLocalNames(program, acc) {
635
+ for (const stmt of program?.body ?? []) {
636
+ if (stmt?.type !== "ImportDeclaration" || stmt.importKind === "type") continue;
637
+ for (const s of stmt.specifiers ?? []) {
638
+ if (s?.importKind === "type" || s?.local?.type !== "Identifier") continue;
639
+ acc.add(s.local.name);
640
+ }
641
+ }
642
+ }
643
+ /**
644
+ * Local names declared with a `new …()` initializer (`const x = new Foo()`), anywhere in a
645
+ * program (correctness/effect-as-onmount). A class instance is opaque the same way an import is —
646
+ * it may carry `$state` fields — so it's folded into `reactiveNames` alongside imports.
647
+ * Declarator-init only: `let x; x = new Foo();` is not seen (`VariableDeclarator.init` is unset,
648
+ * and the later `AssignmentExpression` isn't a declarator at all) — a rarer style, left
649
+ * undetected rather than adding an assignment-tracking pass for it.
650
+ */
651
+ function collectNewExprLocalNames(program, acc) {
652
+ walkEstree(program, (n) => {
653
+ if (n.type !== "VariableDeclarator" || n.id?.type !== "Identifier" || !n.init) return;
654
+ if (unwrapTs(n.init).type === "NewExpression") acc.add(n.id.name);
655
+ });
656
+ }
657
+ /**
658
+ * Whether an $effect callback body reads a reactive value (correctness/effect-as-onmount, conservative):
659
+ * a reactive name (rune declarator, imported binding, or a local declared with a `new …()`
660
+ * initializer — see `collectImportedLocalNames`/`collectNewExprLocalNames`), a `$`-prefixed
661
+ * store subscription, or any bare-identifier call. Still blind to a reactive value reached only
662
+ * through a plain function call's return value (`const c = createCounter()`) or a
663
+ * prop-drilled/destructured member of one of the above — narrower than "any" reactive read,
664
+ * deliberately: those shapes have no syntactic marker to key off, so treating them as reactive by
665
+ * default would give up detection entirely rather than narrow it.
666
+ */
667
+ function bodyReadsReactive(fn, reactiveNames) {
668
+ let reads = false;
669
+ const visit = (n) => {
670
+ if (reads || !n) return;
671
+ if (Array.isArray(n)) {
672
+ for (const c of n) visit(c);
673
+ return;
674
+ }
675
+ if (typeof n !== "object" || typeof n.type !== "string") return;
676
+ if (n.type === "Identifier") {
677
+ if (reactiveNames.has(n.name) || n.name.startsWith("$") && !RUNE_NAMES.has(n.name)) reads = true;
678
+ return;
679
+ }
680
+ if (n.type === "CallExpression" && n.callee?.type === "Identifier") {
681
+ reads = true;
682
+ return;
683
+ }
684
+ if (n.type === "MemberExpression") {
685
+ visit(n.object);
686
+ if (n.computed) visit(n.property);
687
+ return;
688
+ }
689
+ if (n.type === "Property") {
690
+ if (n.computed) visit(n.key);
691
+ visit(n.value);
692
+ return;
693
+ }
694
+ for (const key of Object.keys(n)) if (!WALK_IGNORED_KEYS.has(key)) visit(n[key]);
695
+ };
696
+ visit(fn.body);
697
+ return reads;
698
+ }
699
+ /** Empty effect callback body (`() => {}` or no body). */
700
+ function bodyIsEmpty(fn) {
701
+ const body = fn?.body;
702
+ if (!body) return true;
703
+ if (body.type === "BlockStatement") return (body.body ?? []).length === 0;
704
+ return false;
705
+ }
706
+ /** Attributes whose value navigates/executes — a literal `javascript:` here is an XSS vector (security/javascript-url). */
707
+ const URL_ATTRS = [
708
+ "href",
709
+ "src",
710
+ "action",
711
+ "formaction"
712
+ ];
713
+ /** Native checkable input types where bind:value binds the wrong DOM property (correctness/checkable-bind-value). */
714
+ const CHECKABLE_INPUT_TYPES = /* @__PURE__ */ new Set(["checkbox", "radio"]);
715
+ /** Recursively collect Security facts: `{@html}` tags and literal `javascript:` URLs (security/raw-html, security/javascript-url). */
716
+ function collectSecurityFacts(node, source, htmlTags, jsUrls) {
717
+ if (Array.isArray(node)) {
718
+ for (const child of node) collectSecurityFacts(child, source, htmlTags, jsUrls);
719
+ return;
720
+ }
721
+ if (!node || typeof node !== "object") return;
722
+ if (node.type === "HtmlTag") htmlTags.push({ line: lineOf(source, node.start) });
723
+ if ((node.type === "RegularElement" || node.type === "SvelteElement") && Array.isArray(node.attributes)) for (const name of URL_ATTRS) {
724
+ const attr = findAttr(node.attributes, name);
725
+ if (!attr) continue;
726
+ const value = attrTextOf(attr);
727
+ if (value !== void 0 && /^\s*javascript:/i.test(value)) jsUrls.push({ line: lineOf(source, attr.start ?? node.start) });
728
+ }
729
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectSecurityFacts(node[key], source, htmlTags, jsUrls);
730
+ }
731
+ /**
732
+ * `<input type="checkbox">` / `<input type="radio">` elements carrying a `bind:value`
733
+ * directive (correctness/checkable-bind-value) — bind:value binds the DOM value property,
734
+ * which checkbox/radio interaction never changes, so the bound state silently never updates.
735
+ * Only `RegularElement` (a static `<input>`) is checked, so a dynamic tag via
736
+ * `<svelte:element this="input" …>` is naturally skipped (it is a different node type). A
737
+ * dynamic `type={expr}` makes `attrTextOf` return `undefined`, which is also skipped.
738
+ */
739
+ function collectCheckableBindValues(node, source, acc) {
740
+ if (Array.isArray(node)) {
741
+ for (const child of node) collectCheckableBindValues(child, source, acc);
742
+ return;
743
+ }
744
+ if (!node || typeof node !== "object") return;
745
+ if (node.type === "RegularElement" && node.name === "input" && Array.isArray(node.attributes)) {
746
+ const typeAttr = findAttr(node.attributes, "type");
747
+ const typeValue = typeAttr ? attrTextOf(typeAttr) : void 0;
748
+ if (typeValue && CHECKABLE_INPUT_TYPES.has(typeValue)) {
749
+ const bindValue = node.attributes.find((a) => a?.type === "BindDirective" && a.name === "value");
750
+ if (bindValue) acc.push({
751
+ kind: typeValue,
752
+ line: lineOf(source, bindValue.start ?? node.start)
753
+ });
754
+ }
755
+ }
756
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectCheckableBindValues(node[key], source, acc);
757
+ }
758
+ /** Classify a single Attribute's raw `value`: bare (`true`) is a literal `''`, a single Text
759
+ * node is its literal text, anything else (an ExpressionTag or a multi-part template) is dynamic. */
760
+ function classifyAttrValue(value) {
761
+ if (value === true) return { literal: "" };
762
+ if (Array.isArray(value) && value.length === 1 && value[0]?.type === "Text") return { literal: String(value[0].data ?? "") };
763
+ return { expression: true };
764
+ }
765
+ /**
766
+ * Every element, namespace-tracked: `inSvg` is set on `<svg>` and everything under it, and cleared
767
+ * again under `<foreignObject>`; a component declaring `<svelte:options namespace="svg" />` starts
768
+ * inside SVG, since a `<g>`-root partial has no `<svg>` of its own. `<svelte:element this="…">` is
769
+ * skipped — the tag is dynamic, so neither its name nor its namespace is known — and its subtree
770
+ * keeps the surrounding namespace. Compared lowercased throughout — the AST keeps source spelling,
771
+ * HTML does not.
772
+ */
773
+ const CHAIN_BREAKS = /* @__PURE__ */ new Set([
774
+ "Component",
775
+ "SvelteComponent",
776
+ "SvelteSelf",
777
+ "SvelteElement",
778
+ "SlotElement",
779
+ "RenderTag",
780
+ "HtmlTag",
781
+ "SnippetBlock",
782
+ "SvelteHead"
783
+ ]);
784
+ const SILENT_BREAKS = /* @__PURE__ */ new Set(["SnippetBlock", "SvelteHead"]);
785
+ function collectElements(node, source, acc, inSvg, parent) {
786
+ if (Array.isArray(node)) {
787
+ for (const child of node) collectElements(child, source, acc, inSvg, parent);
788
+ return;
789
+ }
790
+ if (!node || typeof node !== "object") return;
791
+ let next = inSvg;
792
+ let nextParent = parent;
793
+ if (typeof node.type === "string" && CHAIN_BREAKS.has(node.type)) {
794
+ if (parent !== void 0 && !SILENT_BREAKS.has(node.type)) acc[parent].unknownContent = true;
795
+ nextParent = void 0;
796
+ } else if (node.type === "RegularElement" && typeof node.name === "string" && Array.isArray(node.attributes)) {
797
+ const tag = node.name.toLowerCase();
798
+ const self = inSvg || tag === "svg";
799
+ const hasSpread = node.attributes.some((a) => a?.type === "SpreadAttribute");
800
+ acc.push({
801
+ tag,
802
+ line: lineOf(source, node.start),
803
+ attrs: node.attributes.filter((a) => a?.type === "Attribute" && typeof a.name === "string").map((a) => {
804
+ const cls = classifyAttrValue(a.value);
805
+ const value = "expression" in cls ? void 0 : cls.literal;
806
+ return {
807
+ name: String(a.name).toLowerCase(),
808
+ line: lineOf(source, a.start ?? node.start),
809
+ ...value !== void 0 ? { value } : {}
810
+ };
811
+ }),
812
+ ...self ? { inSvg: true } : {},
813
+ ...parent !== void 0 ? { parent } : {},
814
+ ...hasSpread ? { hasSpread: true } : {}
815
+ });
816
+ if (tag.includes("-") || !KNOWN_TAGS.has(tag)) {
817
+ if (parent !== void 0) acc[parent].unknownContent = true;
818
+ nextParent = void 0;
819
+ } else nextParent = acc.length - 1;
820
+ if (tag === "svg") next = true;
821
+ else if (tag === "foreignobject") next = false;
822
+ }
823
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectElements(node[key], source, acc, next, nextParent);
824
+ }
825
+ /**
826
+ * Elements carrying a `role` and/or `aria-*` attribute(s) (a11y ARIA rules). Only
827
+ * `RegularElement` is checked, so `<svelte:element this="div" role="…">` is out of static reach —
828
+ * same convention as `collectCheckableBindValues`/`collectHrefLinks`.
829
+ */
830
+ /** HTML-AAM: a `<select>` is a combobox unless `multiple` or a display `size` above 1 makes it a listbox. */
831
+ function selectNativeRole(attributes) {
832
+ if (findAttr(attributes, "multiple") !== void 0) return "listbox";
833
+ const size = findAttr(attributes, "size");
834
+ if (size === void 0) return "combobox";
835
+ if (attrValueOf(size) !== "static") return void 0;
836
+ return Number(attrTextOf(size)) > 1 ? "listbox" : "combobox";
837
+ }
838
+ function collectAriaElements(node, source, acc) {
839
+ if (Array.isArray(node)) {
840
+ for (const child of node) collectAriaElements(child, source, acc);
841
+ return;
842
+ }
843
+ if (!node || typeof node !== "object") return;
844
+ if (node.type === "RegularElement" && Array.isArray(node.attributes)) {
845
+ const roleAttr = findAttr(node.attributes, "role");
846
+ const ariaAttrs = node.attributes.filter((a) => a?.type === "Attribute" && typeof a.name === "string" && a.name.toLowerCase().startsWith("aria-"));
847
+ if (roleAttr || ariaAttrs.length > 0) {
848
+ const tag = node.name.toLowerCase();
849
+ const inputType = tag === "input" ? attrText(node.attributes, "type") : void 0;
850
+ const hasList = tag === "input" && findAttr(node.attributes, "list") !== void 0;
851
+ const selectKind = tag === "select" ? selectNativeRole(node.attributes) : void 0;
852
+ const hasSpread = node.attributes.some((a) => a?.type === "SpreadAttribute");
853
+ acc.push({
854
+ tag,
855
+ line: lineOf(source, node.start),
856
+ ...roleAttr ? { role: classifyAttrValue(roleAttr.value) } : {},
857
+ ...inputType !== void 0 ? { inputType: inputType.toLowerCase() } : {},
858
+ ...hasList ? { hasList: true } : {},
859
+ ...selectKind ? { selectKind } : {},
860
+ ...hasSpread ? { hasSpread: true } : {},
861
+ aria: ariaAttrs.map((a) => ({
862
+ name: String(a.name).toLowerCase(),
863
+ line: lineOf(source, a.start ?? node.start),
864
+ ...classifyAttrValue(a.value)
865
+ }))
866
+ });
867
+ }
868
+ }
869
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectAriaElements(node[key], source, acc);
870
+ }
871
+ /** This element's `Attribute` nodes (directives/spreads excluded) as `isInteractiveElement`'s
872
+ * input shape. */
873
+ function elementAttrs(attributes) {
874
+ return attributes.filter((a) => a?.type === "Attribute" && typeof a.name === "string").map((a) => ({
875
+ name: String(a.name).toLowerCase(),
876
+ ...classifyAttrValue(a.value)
877
+ }));
878
+ }
879
+ /**
880
+ * Interactive elements nested inside another interactive container (a11y/interactive-nesting).
881
+ * Only `RegularElement` is checked, so `<svelte:element this="button">` is out of static reach
882
+ * — same convention as `collectAriaElements`. `stack` tracks the enclosing containers
883
+ * (`isInteractiveContainer`); any interactive element entering while it is non-empty records
884
+ * one fact at the descendant's line, keyed to the nearest enclosing container.
885
+ */
886
+ /** The literal role that made an element a container, for the finding's message. */
887
+ function containerRoleOf(attrs) {
888
+ const role = attrs.find((a) => a.name === "role")?.literal;
889
+ return role ? resolveRole(splitTokens(role)) : void 0;
890
+ }
891
+ function collectInteractiveNestings(node, source, acc, stack) {
892
+ if (Array.isArray(node)) {
893
+ for (const child of node) collectInteractiveNestings(child, source, acc, stack);
894
+ return;
895
+ }
896
+ if (!node || typeof node !== "object") return;
897
+ if (node.type === "SnippetBlock") {
898
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectInteractiveNestings(node[key], source, acc, []);
899
+ return;
900
+ }
901
+ let opened = false;
902
+ if (node.type === "RegularElement" && Array.isArray(node.attributes)) {
903
+ const attrs = elementAttrs(node.attributes);
904
+ if (stack.length > 0 && isInteractiveElement(node.name, attrs)) {
905
+ const container = stack[stack.length - 1];
906
+ acc.push({
907
+ containerTag: container.tag,
908
+ ...container.role ? { containerRole: container.role } : {},
909
+ descendantTag: node.name,
910
+ line: lineOf(source, node.start)
911
+ });
912
+ }
913
+ if (isInteractiveContainer(node.name, attrs)) {
914
+ const role = node.name === "button" || node.name === "a" ? void 0 : containerRoleOf(attrs);
915
+ stack.push({
916
+ tag: node.name,
917
+ ...role ? { role } : {}
918
+ });
919
+ opened = true;
920
+ }
921
+ }
922
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectInteractiveNestings(node[key], source, acc, stack);
923
+ if (opened) stack.pop();
924
+ }
925
+ /** Tags on which a `disabled` attribute actually removes focusability (it does nothing on `<a>`). */
926
+ const DISABLEABLE_TAGS = /* @__PURE__ */ new Set([
927
+ "button",
928
+ "input",
929
+ "select",
930
+ "textarea"
931
+ ]);
932
+ /**
933
+ * Keyboard-focusable elements hidden by a literal `aria-hidden="true"` (a11y/aria-hidden-focus).
934
+ * Same conventions as `collectInteractiveNestings`: only `RegularElement`, snippet bodies walk
935
+ * with a fresh stack (they render at their `{@render}` site), and an expression `aria-hidden`
936
+ * is unknowable — a toggled `aria-hidden={...}` never opens a container. Exempt as not
937
+ * focusable: a `tabindex` that is negative (the documented remediation) or an expression (it
938
+ * may resolve to -1), a `disabled` form control, and anything at or under an `inert` or
939
+ * `hidden` element — an inert subtree leaves the tab order and a hidden one does not render,
940
+ * so both carry down the walk as `unfocusable`.
941
+ */
942
+ function collectAriaHiddenFocusables(node, source, acc, stack, unfocusable = false) {
943
+ if (Array.isArray(node)) {
944
+ for (const child of node) collectAriaHiddenFocusables(child, source, acc, stack, unfocusable);
945
+ return;
946
+ }
947
+ if (!node || typeof node !== "object") return;
948
+ if (node.type === "SnippetBlock") {
949
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectAriaHiddenFocusables(node[key], source, acc, []);
950
+ return;
951
+ }
952
+ let opened = false;
953
+ if (node.type === "RegularElement" && Array.isArray(node.attributes)) {
954
+ const attrs = elementAttrs(node.attributes);
955
+ if (attrs.some((a) => a.name === "inert" || a.name === "hidden")) unfocusable = true;
956
+ const hiddenSelf = attrs.find((a) => a.name === "aria-hidden")?.literal?.trim().toLowerCase() === "true";
957
+ const tabindexAttr = attrs.find((a) => a.name === "tabindex");
958
+ const tabindex = literalTabindexValue(tabindexAttr?.literal);
959
+ const tabindexExempts = tabindexAttr !== void 0 && tabindexAttr.literal === void 0;
960
+ if (!unfocusable && !tabindexExempts && !(tabindex !== void 0 && tabindex < 0) && !(DISABLEABLE_TAGS.has(node.name) && attrs.some((a) => a.name === "disabled")) && isInteractiveElement(node.name, attrs) && (hiddenSelf || stack.length > 0)) acc.push({
961
+ tag: node.name,
962
+ ...hiddenSelf ? {} : { containerTag: stack[stack.length - 1] },
963
+ line: lineOf(source, node.start)
964
+ });
965
+ if (hiddenSelf) {
966
+ stack.push(node.name);
967
+ opened = true;
968
+ }
969
+ }
970
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectAriaHiddenFocusables(node[key], source, acc, stack, unfocusable);
971
+ if (opened) stack.pop();
972
+ }
973
+ /** `aria-label`/`aria-labelledby`/`title` present on this element: a literal non-empty value,
974
+ * or any expression (its rendered value is unknowable, but its *presence* is enough to name
975
+ * the element — a11y/accessible-name treats an expression name source as present). */
976
+ /** An attribute present with a non-blank literal, or with an expression whose value is unknowable
977
+ * and so must be assumed to carry one. */
978
+ function hasNamingValue(attributes, name) {
979
+ const attr = findAttr(attributes, name);
980
+ if (!attr) return false;
981
+ const v = classifyAttrValue(attr.value);
982
+ return "expression" in v || v.literal !== void 0 && v.literal.trim().length > 0;
983
+ }
984
+ function hasNamingAttr(attributes) {
985
+ return [
986
+ "aria-label",
987
+ "aria-labelledby",
988
+ "title"
989
+ ].some((name) => hasNamingValue(attributes, name));
990
+ }
991
+ /** A tag name with a hyphen is a custom element: it may be form-associated, and its shadow root may
992
+ * supply content, neither of which is visible here. Treated as unknowable by both a11y scanners. */
993
+ function isCustomElement(node) {
994
+ return node.type === "RegularElement" && typeof node.name === "string" && node.name.includes("-");
995
+ }
996
+ function scanAccessibleNameSubtree(node, skip) {
997
+ if (Array.isArray(node)) {
998
+ const acc = {
999
+ named: false,
1000
+ unknowable: false
1001
+ };
1002
+ for (const child of node) {
1003
+ const r = scanAccessibleNameSubtree(child, skip);
1004
+ acc.named ||= r.named;
1005
+ acc.unknowable ||= r.unknowable;
1006
+ }
1007
+ return acc;
1008
+ }
1009
+ if (!node || typeof node !== "object") return {
1010
+ named: false,
1011
+ unknowable: false
1012
+ };
1013
+ if (skip !== void 0 && node === skip) return {
1014
+ named: false,
1015
+ unknowable: false
1016
+ };
1017
+ if (node.type === "SnippetBlock") return {
1018
+ named: false,
1019
+ unknowable: false
1020
+ };
1021
+ if (node.type === "Text") return {
1022
+ named: String(node.data ?? "").trim().length > 0,
1023
+ unknowable: false
1024
+ };
1025
+ if (node.type === "ExpressionTag" || node.type === "RenderTag" || node.type === "HtmlTag" || node.type === "SlotElement" || node.type === "SvelteFragment" || COMPONENT_LIKE_TYPES.has(node.type) || isCustomElement(node)) return {
1026
+ named: false,
1027
+ unknowable: true
1028
+ };
1029
+ if (node.type === "RegularElement" && node.name === "img" && Array.isArray(node.attributes)) {
1030
+ if (hasNamingValue(node.attributes, "alt")) return {
1031
+ named: true,
1032
+ unknowable: false
1033
+ };
1034
+ }
1035
+ const acc = {
1036
+ named: false,
1037
+ unknowable: false
1038
+ };
1039
+ for (const key of CHILD_NODE_KEYS) if (key in node) {
1040
+ const r = scanAccessibleNameSubtree(node[key], skip);
1041
+ acc.named ||= r.named;
1042
+ acc.unknowable ||= r.unknowable;
1043
+ }
1044
+ return acc;
1045
+ }
1046
+ /** Which accessible-name target `node` is, or undefined if it isn't one (a11y/accessible-name).
1047
+ * `a` needs a literal `href` (an expression href is unknowable whether the anchor even renders
1048
+ * as a link, matching the `isInteractiveContainer` convention above); `input` needs a literal
1049
+ * `type="image"`. */
1050
+ function accessibleNameTarget(node) {
1051
+ if (node.name === "button") return "button";
1052
+ if (node.name === "a") return attrText(node.attributes, "href") !== void 0 ? "a" : void 0;
1053
+ if (node.name === "input") return attrText(node.attributes, "type")?.toLowerCase() === "image" ? "input" : void 0;
1054
+ }
1055
+ /** The first labelable descendant of a `<label>` — the only control an implicit association
1056
+ * reaches, so a second control inside the same label is not named by it. */
1057
+ function firstLabelableDescendant(node) {
1058
+ if (Array.isArray(node)) {
1059
+ for (const child of node) {
1060
+ const hit = firstLabelableDescendant(child);
1061
+ if (hit) return hit;
1062
+ }
1063
+ return;
1064
+ }
1065
+ if (!node || typeof node !== "object") return void 0;
1066
+ if (node.type === "SnippetBlock") return void 0;
1067
+ if (node.type === "RegularElement" && isLabelableDescendant(node)) return node;
1068
+ for (const key of CHILD_NODE_KEYS) if (key in node) {
1069
+ const hit = firstLabelableDescendant(node[key]);
1070
+ if (hit) return hit;
1071
+ }
1072
+ }
1073
+ /**
1074
+ * What each `<label>` in the file names: the literal `for` it points at, and the one control it
1075
+ * encapsulates. HTML-AAM gives `button` and `input type="image"` this step ahead of their own
1076
+ * subtree, so a label that contributes a name — or content this analysis cannot read — takes the
1077
+ * element out of `a11y/accessible-name`'s reach. A label that is itself provably empty does not:
1078
+ * the control stays unnamed and is still reported.
1079
+ */
1080
+ function collectLabelTargets(node, acc) {
1081
+ if (Array.isArray(node)) {
1082
+ for (const child of node) collectLabelTargets(child, acc);
1083
+ return;
1084
+ }
1085
+ if (!node || typeof node !== "object") return;
1086
+ if (node.type === "RegularElement" && node.name === "label" && Array.isArray(node.attributes)) {
1087
+ const wrapped = firstLabelableDescendant(node);
1088
+ const scan = scanAccessibleNameSubtree(node, wrapped);
1089
+ if (scan.named || scan.unknowable) {
1090
+ const forId = attrText(node.attributes, "for");
1091
+ if (forId !== void 0 && forId.trim().length > 0) acc.ids.add(forId.trim());
1092
+ if (wrapped) acc.nodes.add(wrapped);
1093
+ }
1094
+ }
1095
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectLabelTargets(node[key], acc);
1096
+ }
1097
+ /**
1098
+ * `button`/`a href`/`input type="image"` elements with no computable accessible name
1099
+ * (a11y/accessible-name). A spread attribute on the element itself makes its rendered
1100
+ * attributes unknowable, so the element is skipped entirely rather than risk a false positive.
1101
+ *
1102
+ * `labels` carries the two `<label>` name routes, which `<a>` does not have.
1103
+ */
1104
+ function collectUnnamedInteractive(node, source, acc, labels) {
1105
+ if (Array.isArray(node)) {
1106
+ for (const child of node) collectUnnamedInteractive(child, source, acc, labels);
1107
+ return;
1108
+ }
1109
+ if (!node || typeof node !== "object") return;
1110
+ if (node.type === "RegularElement" && Array.isArray(node.attributes)) {
1111
+ const target = accessibleNameTarget(node);
1112
+ const hasSpread = node.attributes.some((a) => a?.type === "SpreadAttribute");
1113
+ const id = attrText(node.attributes, "id");
1114
+ const namedByLabel = target !== "a" && (labels.nodes.has(node) || id !== void 0 && labels.ids.has(id.trim()));
1115
+ if (target && !hasSpread && !namedByLabel) if (target === "input") {
1116
+ if (!hasNamingAttr(node.attributes) && !hasNamingValue(node.attributes, "alt")) acc.push({
1117
+ tag: node.name,
1118
+ line: lineOf(source, node.start)
1119
+ });
1120
+ } else {
1121
+ const scan = scanAccessibleNameSubtree(node);
1122
+ if (!hasNamingAttr(node.attributes) && !scan.named && !scan.unknowable) acc.push({
1123
+ tag: node.name,
1124
+ line: lineOf(source, node.start)
1125
+ });
1126
+ }
1127
+ }
1128
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectUnnamedInteractive(node[key], source, acc, labels);
1129
+ }
1130
+ /** Whether `node` is a labelable element that associates a `<label>` by wrapping it
1131
+ * (a11y/label-has-control): `input` (unless its literal `type` is `hidden`), `select`,
1132
+ * `textarea`, `button`, `meter`, `output`, `progress`. */
1133
+ const LABELABLE_TAGS = /* @__PURE__ */ new Set([
1134
+ "input",
1135
+ "select",
1136
+ "textarea",
1137
+ "button",
1138
+ "meter",
1139
+ "output",
1140
+ "progress"
1141
+ ]);
1142
+ function isLabelableDescendant(node) {
1143
+ if (node.type !== "RegularElement" || !LABELABLE_TAGS.has(node.name)) return false;
1144
+ if (node.name !== "input") return true;
1145
+ return attrText(node.attributes ?? [], "type")?.toLowerCase() !== "hidden";
1146
+ }
1147
+ function scanLabelSubtree(node) {
1148
+ if (Array.isArray(node)) {
1149
+ const acc = {
1150
+ hasControl: false,
1151
+ unknowable: false
1152
+ };
1153
+ for (const child of node) {
1154
+ const r = scanLabelSubtree(child);
1155
+ acc.hasControl ||= r.hasControl;
1156
+ acc.unknowable ||= r.unknowable;
1157
+ }
1158
+ return acc;
1159
+ }
1160
+ if (!node || typeof node !== "object") return {
1161
+ hasControl: false,
1162
+ unknowable: false
1163
+ };
1164
+ if (node.type === "SnippetBlock") return {
1165
+ hasControl: false,
1166
+ unknowable: false
1167
+ };
1168
+ if (node.type === "ExpressionTag" || node.type === "RenderTag" || node.type === "HtmlTag" || node.type === "SlotElement" || node.type === "SvelteFragment" || COMPONENT_LIKE_TYPES.has(node.type) || isCustomElement(node)) return {
1169
+ hasControl: false,
1170
+ unknowable: true
1171
+ };
1172
+ if (isLabelableDescendant(node)) return {
1173
+ hasControl: true,
1174
+ unknowable: false
1175
+ };
1176
+ const acc = {
1177
+ hasControl: false,
1178
+ unknowable: false
1179
+ };
1180
+ for (const key of CHILD_NODE_KEYS) if (key in node) {
1181
+ const r = scanLabelSubtree(node[key]);
1182
+ acc.hasControl ||= r.hasControl;
1183
+ acc.unknowable ||= r.unknowable;
1184
+ }
1185
+ return acc;
1186
+ }
1187
+ /**
1188
+ * `<label>` elements with neither a `for` attribute (literal or expression — its presence is
1189
+ * enough) nor a wrapped labelable descendant (a11y/label-has-control). A spread attribute may
1190
+ * supply `for`, so it is treated the same as an explicit one.
1191
+ */
1192
+ function collectUnassociatedLabels(node, source, acc) {
1193
+ if (Array.isArray(node)) {
1194
+ for (const child of node) collectUnassociatedLabels(child, source, acc);
1195
+ return;
1196
+ }
1197
+ if (!node || typeof node !== "object") return;
1198
+ if (node.type === "RegularElement" && node.name === "label" && Array.isArray(node.attributes)) {
1199
+ const hasFor = findAttr(node.attributes, "for") !== void 0;
1200
+ const hasSpread = node.attributes.some((a) => a?.type === "SpreadAttribute");
1201
+ if (!hasFor && !hasSpread) {
1202
+ const scan = scanLabelSubtree(node);
1203
+ if (!scan.hasControl && !scan.unknowable) acc.push({ line: lineOf(source, node.start) });
1204
+ }
1205
+ }
1206
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectUnassociatedLabels(node[key], source, acc);
1207
+ }
1208
+ /** Requires trailing whitespace after the bullet char to avoid flagging `-webkit` prose or a
1209
+ * signed number like `-1` (a11y/use-list). */
1210
+ const BULLET_TEXT_RE = /^[•・·\-*]\s/;
1211
+ /** Ancestries whose text is code, keystrokes or user input rather than prose, so a leading dash
1212
+ * is a character in the content and not a bullet (a11y/use-list). */
1213
+ const VERBATIM_TEXT_TAGS = /* @__PURE__ */ new Set([
1214
+ "pre",
1215
+ "code",
1216
+ "kbd",
1217
+ "samp",
1218
+ "textarea"
1219
+ ]);
1220
+ /**
1221
+ * Text nodes whose trimmed content opens with a bullet character followed by whitespace,
1222
+ * outside any `li` ancestor — a plain-text bullet where a `<ul>`/`<ol>` would give assistive
1223
+ * technology a real list structure (a11y/use-list). A list is two or more items: a lone bullet
1224
+ * line (`<p>- note</p>`) is a dash, not a list, and WCAG H48 is about sequences. So bullet texts
1225
+ * count only in twos or more under one parent — either as sibling text nodes (`- one<br>- two`)
1226
+ * or as the opening text of sibling elements (`<p>• one</p><p>• two</p>`).
1227
+ */
1228
+ function collectBulletTexts(node, source, acc, inert, afterExpression = false, reported = /* @__PURE__ */ new WeakSet()) {
1229
+ if (Array.isArray(node)) {
1230
+ let prevWasExpression = afterExpression;
1231
+ let group = [];
1232
+ const flush = () => {
1233
+ if (group.length >= 2) for (const b of group) {
1234
+ if (reported.has(b.text)) continue;
1235
+ reported.add(b.text);
1236
+ acc.push({
1237
+ line: b.line,
1238
+ char: b.char
1239
+ });
1240
+ }
1241
+ group = [];
1242
+ };
1243
+ const bulletOf = (text, after) => {
1244
+ if (!text || typeof text !== "object" || text.type !== "Text" || after) return void 0;
1245
+ const trimmed = String(text.data ?? "").trim();
1246
+ return BULLET_TEXT_RE.test(trimmed) ? {
1247
+ line: lineOf(source, text.start),
1248
+ char: trimmed[0],
1249
+ text
1250
+ } : void 0;
1251
+ };
1252
+ for (const child of node) {
1253
+ if (!child || typeof child !== "object") continue;
1254
+ if (child.type === "Text") {
1255
+ const b = inert ? void 0 : bulletOf(child, prevWasExpression);
1256
+ if (b) group.push(b);
1257
+ else if (String(child.data ?? "").trim()) flush();
1258
+ } else if (child.type === "Comment" || child.type === "RegularElement" && child.name === "br") {} else {
1259
+ let item;
1260
+ if (!inert && child.type === "RegularElement" && child.name !== "li" && !VERBATIM_TEXT_TAGS.has(child.name)) item = bulletOf((child.fragment?.nodes ?? []).find((n) => !(n?.type === "Text" && !String(n.data ?? "").trim()) && n?.type !== "Comment"), prevWasExpression);
1261
+ if (item) group.push(item);
1262
+ else flush();
1263
+ collectBulletTexts(child, source, acc, inert, prevWasExpression, reported);
1264
+ }
1265
+ if (child.type !== "Comment") prevWasExpression = child.type === "ExpressionTag";
1266
+ }
1267
+ flush();
1268
+ return;
1269
+ }
1270
+ if (!node || typeof node !== "object") return;
1271
+ if (node.type === "Text") return;
1272
+ if (node.type === "SnippetBlock") return;
1273
+ const nowInert = inert || node.type === "RegularElement" && (node.name === "li" || VERBATIM_TEXT_TAGS.has(node.name));
1274
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectBulletTexts(node[key], source, acc, nowInert, false, reported);
1275
+ }
1276
+ /** Whether `<select attrs>` is subject to the placeholder-label-option requirement: `required`
1277
+ * present (literal — a dynamic `required` makes presence unknowable), no `multiple` (literal
1278
+ * or dynamic — either way this select is out of scope), and `size` absent or a literal ≤ 1
1279
+ * (a dynamic `size` is unknowable, so treated the same as out of scope). */
1280
+ function selectNeedsPlaceholder(attributes) {
1281
+ const requiredAttr = findAttr(attributes, "required");
1282
+ if (!requiredAttr || attrValueOf(requiredAttr) === "dynamic") return false;
1283
+ if (findAttr(attributes, "multiple")) return false;
1284
+ const sizeAttr = findAttr(attributes, "size");
1285
+ if (sizeAttr) {
1286
+ if (attrValueOf(sizeAttr) === "dynamic") return false;
1287
+ const size = Number(attrText(attributes, "size"));
1288
+ if (!(Number.isFinite(size) && size <= 1)) return false;
1289
+ }
1290
+ return true;
1291
+ }
1292
+ /** First child of a `<select>`'s fragment that isn't purely a whitespace `Text` node or a
1293
+ * `Comment` — what the browser treats as the element's first real content. */
1294
+ function firstSignificantChild(nodes) {
1295
+ for (const child of nodes ?? []) {
1296
+ if (!child) continue;
1297
+ if (child.type === "Comment") continue;
1298
+ if (child.type === "Text" && !String(child.data ?? "").trim()) continue;
1299
+ return child;
1300
+ }
1301
+ }
1302
+ /** Whether an `<option>` is a valid placeholder label option per the HTML spec: an empty
1303
+ * `value` attribute, or no `value` attribute and no text content. A dynamic `value` or a
1304
+ * dynamic text child makes the option's effective value unknowable, so it is treated the
1305
+ * same as a valid placeholder — proving a violation is what this rule requires, not guessing one. */
1306
+ function isPlaceholderOption(option) {
1307
+ const attributes = option.attributes ?? [];
1308
+ if (findAttr(attributes, "value")) {
1309
+ const literal = attrText(attributes, "value");
1310
+ return literal === void 0 || literal === "";
1311
+ }
1312
+ return textFromNodes(option.fragment?.nodes ?? []) === void 0;
1313
+ }
1314
+ /**
1315
+ * `<select required>` (no `multiple`, display size absent or ≤ 1) whose first `option` element
1316
+ * child is not a placeholder label option (a11y/placeholder-label-option). The first significant
1317
+ * child not being a literal `option` — an `{#each}` block, a Component, an `{#if}`, etc. — makes
1318
+ * the select's first option unknowable, so it is skipped rather than risk a false positive; a
1319
+ * `<select>` with no content at all has no placeholder to find, so it is flagged. A spread
1320
+ * attribute on the `<select>` itself (may supply `multiple`/`size`) or on the first `option`
1321
+ * (may supply `value`) makes the relevant check unknowable, so it is skipped too.
1322
+ */
1323
+ function collectSelectsMissingPlaceholder(node, source, acc) {
1324
+ if (Array.isArray(node)) {
1325
+ for (const child of node) collectSelectsMissingPlaceholder(child, source, acc);
1326
+ return;
1327
+ }
1328
+ if (!node || typeof node !== "object") return;
1329
+ if (node.type === "RegularElement" && node.name === "select" && Array.isArray(node.attributes)) {
1330
+ if (!node.attributes.some((a) => a?.type === "SpreadAttribute") && selectNeedsPlaceholder(node.attributes)) {
1331
+ const first = firstSignificantChild(node.fragment?.nodes);
1332
+ if (!first) acc.push({ line: lineOf(source, node.start) });
1333
+ else if (first.type === "RegularElement" && first.name === "option") {
1334
+ if (!(first.attributes ?? []).some((a) => a?.type === "SpreadAttribute") && !isPlaceholderOption(first)) acc.push({ line: lineOf(source, node.start) });
1335
+ }
1336
+ }
1337
+ }
1338
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectSelectsMissingPlaceholder(node[key], source, acc);
1339
+ }
1340
+ /** Machine-readable literal formats a `<time>` may use without a `datetime` attribute — a
1341
+ * permissive subset of the HTML spec's valid time-string grammar (year/month/date, time,
1342
+ * date-time, yearless date, duration). Anything else needs an explicit `datetime`. */
1343
+ /** The `<time>` content syntaxes the HTML spec permits without a `datetime` attribute. A year is
1344
+ * four **or more** digits, and a duration has two spellings: the `PnYnMnD` form and the
1345
+ * alternative `4h 18m 3s` form, which is what recipes and media lengths are written in. */
1346
+ const MACHINE_READABLE_TIME = [
1347
+ /^\d{4,}(-\d{2}){0,2}$/,
1348
+ /^\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/,
1349
+ /^\d{4,}-\d{2}-\d{2}[T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?$/,
1350
+ /^\d{2}-\d{2}$/,
1351
+ /^\d{4,}-W\d{2}$/,
1352
+ /^(Z|[+-]\d{2}:?\d{2})$/,
1353
+ /^P(?=\d|T)[\d.,TYWDHMS]*$/i,
1354
+ /^\d+(\.\d+)?\s*[wdhms](\s+\d+(\.\d+)?\s*[wdhms])*$/i
1355
+ ];
1356
+ /**
1357
+ * `<time>` elements with no `datetime` attribute whose content is plain literal text that isn't
1358
+ * itself machine-readable (a11y/require-datetime). Any non-`Text` child — an `ExpressionTag`, a
1359
+ * Component, a block — makes the rendered text unknowable, so the element is skipped rather than
1360
+ * risk a false positive. A spread attribute may itself supply `datetime`, so it is skipped too.
1361
+ */
1362
+ function collectTimesMissingDatetime(node, source, acc) {
1363
+ if (Array.isArray(node)) {
1364
+ for (const child of node) collectTimesMissingDatetime(child, source, acc);
1365
+ return;
1366
+ }
1367
+ if (!node || typeof node !== "object") return;
1368
+ if (node.type === "RegularElement" && node.name === "time" && findAttr(node.attributes ?? [], "datetime") === void 0 && !(node.attributes ?? []).some((a) => a?.type === "SpreadAttribute")) {
1369
+ const nodes = node.fragment?.nodes ?? [];
1370
+ if (nodes.length > 0 && nodes.every((n) => n?.type === "Text")) {
1371
+ const trimmed = nodes.map((n) => String(n.data ?? "")).join("").trim();
1372
+ if (trimmed.length > 0 && !MACHINE_READABLE_TIME.some((re) => re.test(trimmed))) acc.push({
1373
+ line: lineOf(source, node.start),
1374
+ text: trimmed
1375
+ });
1376
+ }
1377
+ }
1378
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectTimesMissingDatetime(node[key], source, acc);
1379
+ }
1380
+ /**
1381
+ * Fully-static text content of a node list: concatenated `Text` data, looked through static
1382
+ * regular elements (`<dt><code>HTTP</code></dt>` names "HTTP"), with comments contributing
1383
+ * nothing. `undefined` when anything dynamic appears anywhere below — an expression, a
1384
+ * component, a block, `{@html}`/`{@render}`, `<svelte:element>`, a `<slot>`, or a custom
1385
+ * element (its shadow root may supply content) — because the rendered text is then unknowable.
1386
+ */
1387
+ function staticTextOf(nodes) {
1388
+ let out = "";
1389
+ for (const n of nodes) {
1390
+ if (!n || typeof n !== "object") continue;
1391
+ if (n.type === "Comment") continue;
1392
+ if (n.type === "Text") {
1393
+ out += String(n.data ?? "");
1394
+ continue;
1395
+ }
1396
+ if (n.type === "RegularElement" && typeof n.name === "string" && !n.name.includes("-")) {
1397
+ const inner = staticTextOf(n.fragment?.nodes ?? []);
1398
+ if (inner === void 0) return void 0;
1399
+ out += inner;
1400
+ continue;
1401
+ }
1402
+ return;
1403
+ }
1404
+ return out;
1405
+ }
1406
+ /**
1407
+ * Duplicate `<dt>` names within one `<dl>` (a11y/no-duplicate-dt). Candidates are `<dt>`
1408
+ * elements that are direct children of the `<dl>`, or direct children of a direct `<div>`
1409
+ * child (the spec's div-wrapped name-value groups); a dt under a logic block or component is
1410
+ * not a candidate — its multiplicity or rendered content is unknowable, and unknowable names
1411
+ * can only ever add duplicates, never fabricate one between two static non-duplicates. A
1412
+ * candidate's name is its fully-static text content (`staticTextOf` above — static phrasing
1413
+ * markup like `<dt><code>HTTP</code></dt>` is looked through, comments contribute nothing),
1414
+ * trimmed and whitespace-collapsed; an empty name is not a candidate (two empty dts are a
1415
+ * missing-content defect, not a duplicate name).
1416
+ * Comparison is case-sensitive — the spec leaves the equality undefined, so only certain
1417
+ * duplicates are reported. Namespace is tracked like `collectElements`: a `<dl>` inside
1418
+ * `<svg>` never renders as an HTML definition list, so it is skipped; `<foreignObject>`
1419
+ * children return to HTML. Nested `<dl>`s get their own scope for free — the walk recurses
1420
+ * and a nested dl's dts are never the outer dl's direct children.
1421
+ */
1422
+ function collectDuplicateDts(node, source, acc, inSvg) {
1423
+ if (Array.isArray(node)) {
1424
+ for (const child of node) collectDuplicateDts(child, source, acc, inSvg);
1425
+ return;
1426
+ }
1427
+ if (!node || typeof node !== "object") return;
1428
+ let next = inSvg;
1429
+ if (node.type === "RegularElement" && typeof node.name === "string") {
1430
+ const tag = node.name.toLowerCase();
1431
+ if (tag === "svg") next = true;
1432
+ else if (tag === "foreignobject") next = false;
1433
+ if (!inSvg && tag === "dl") {
1434
+ const seen = /* @__PURE__ */ new Set();
1435
+ const judge = (dt) => {
1436
+ const raw = staticTextOf(dt.fragment?.nodes ?? []);
1437
+ if (raw === void 0) return;
1438
+ const name = raw.trim().replace(/\s+/g, " ");
1439
+ if (!name) return;
1440
+ if (seen.has(name)) acc.push({
1441
+ line: lineOf(source, dt.start),
1442
+ text: name
1443
+ });
1444
+ else seen.add(name);
1445
+ };
1446
+ for (const child of node.fragment?.nodes ?? []) {
1447
+ if (child?.type !== "RegularElement" || typeof child.name !== "string") continue;
1448
+ const childTag = child.name.toLowerCase();
1449
+ if (childTag === "dt") judge(child);
1450
+ else if (childTag === "div") {
1451
+ for (const inner of child.fragment?.nodes ?? []) if (inner?.type === "RegularElement" && typeof inner.name === "string" && inner.name.toLowerCase() === "dt") judge(inner);
1452
+ }
1453
+ }
1454
+ }
1455
+ }
1456
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectDuplicateDts(node[key], source, acc, next);
1457
+ }
1458
+ /**
1459
+ * `<video>` with a literal `autoplay` attribute and no `muted` in any form
1460
+ * (correctness/autoplay-muted). Presence is what autoplays, so any literal value counts
1461
+ * (`autoplay="false"` still autoplays); an expression value is unknowable and is skipped.
1462
+ * `muted` passes in every form — bare, `bind:muted`, a spread that could supply it, or
1463
+ * `muted={expr}`, whose value is deliberately not evaluated (issue #580 scopes the rule to
1464
+ * attribute presence). Only `RegularElement` is checked — `<svelte:element this="video">`
1465
+ * follows the same RegularElement-only convention as `collectCheckableBindValues`. Namespace
1466
+ * is tracked like `collectElements`: a `<video>` inside `<svg>` is an SVG-namespace element,
1467
+ * not an HTML video, so it is skipped; `<foreignObject>` children return to HTML.
1468
+ */
1469
+ function collectVideosAutoplayNoMuted(node, source, acc, inSvg) {
1470
+ if (Array.isArray(node)) {
1471
+ for (const child of node) collectVideosAutoplayNoMuted(child, source, acc, inSvg);
1472
+ return;
1473
+ }
1474
+ if (!node || typeof node !== "object") return;
1475
+ let next = inSvg;
1476
+ if (node.type === "RegularElement" && typeof node.name === "string") {
1477
+ const tag = node.name.toLowerCase();
1478
+ if (tag === "svg") next = true;
1479
+ else if (tag === "foreignobject") next = false;
1480
+ if (!inSvg && tag === "video" && Array.isArray(node.attributes)) {
1481
+ const literalAutoplay = attrText(node.attributes, "autoplay") !== void 0;
1482
+ const hasMuted = findAttr(node.attributes, "muted") !== void 0 || node.attributes.some((a) => a?.type === "BindDirective" && a.name === "muted" || a?.type === "SpreadAttribute");
1483
+ if (literalAutoplay && !hasMuted) acc.push({ line: lineOf(source, node.start) });
1484
+ }
1485
+ }
1486
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectVideosAutoplayNoMuted(node[key], source, acc, next);
1487
+ }
1488
+ /**
1489
+ * Root-relative `<a href="/…">` literals (correctness/base-path-navigation). Only
1490
+ * `RegularElement` anchors with a fully static href are considered, which is what makes the
1491
+ * correct forms self-excluding: `href="{base}/x"` and `href={resolve('/x')}` contain an
1492
+ * `ExpressionTag`, so `attrTextOf` returns undefined. `<svelte:element this="a">` is a
1493
+ * different node type and is out of static reach.
1494
+ */
1495
+ function collectHrefLinks(node, source, acc) {
1496
+ if (Array.isArray(node)) {
1497
+ for (const child of node) collectHrefLinks(child, source, acc);
1498
+ return;
1499
+ }
1500
+ if (!node || typeof node !== "object") return;
1501
+ if (node.type === "RegularElement" && node.name === "a" && Array.isArray(node.attributes)) {
1502
+ const attr = findAttr(node.attributes, "href");
1503
+ const value = attr ? attrTextOf(attr) : void 0;
1504
+ if (value !== void 0 && isRootRelativePath(value)) acc.push({
1505
+ kind: "href",
1506
+ path: value,
1507
+ line: lineOf(source, attr?.start ?? node.start)
1508
+ });
1509
+ }
1510
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectHrefLinks(node[key], source, acc);
1511
+ }
1512
+ const GOTO_NAMES = /* @__PURE__ */ new Set(["goto"]);
1513
+ /**
1514
+ * Root-relative `goto('/…')` calls (correctness/base-path-navigation). Only a plain string
1515
+ * literal argument counts, which self-excludes the correct forms — `goto(resolve('/x'))` is a
1516
+ * CallExpression and ``goto(`${base}/x`)`` is a TemplateLiteral. `roots` are the nodes to walk
1517
+ * (the instance program plus the template fragment, so inline handlers are covered); `locals`
1518
+ * comes from the same program's imports.
1519
+ */
1520
+ function collectGotoLinks(locals, roots, source, acc) {
1521
+ if (locals.size === 0) return;
1522
+ for (const root of roots) {
1523
+ if (!root) continue;
1524
+ walkEstree(root, (n) => {
1525
+ if (n.type !== "CallExpression" || n.callee?.type !== "Identifier" || !locals.has(n.callee.name)) return;
1526
+ const arg = n.arguments?.[0];
1527
+ if (arg?.type !== "Literal" || typeof arg.value !== "string" || !isRootRelativePath(arg.value)) return;
1528
+ acc.push({
1529
+ kind: "goto",
1530
+ path: arg.value,
1531
+ line: lineOf(source, n.start)
1532
+ });
1533
+ });
1534
+ }
1535
+ }
1536
+ /** Whether a CallExpression is a bare `$props()` call. */
1537
+ function isPropsCall(node) {
1538
+ return node?.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "$props";
1539
+ }
1540
+ /** Whether a CallExpression is a `$bindable(...)` call (a destructured prop's default value). */
1541
+ function isBindableCall(node) {
1542
+ return node?.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "$bindable";
1543
+ }
1544
+ /**
1545
+ * Local identifier names bound to a prop from `$props()`: plain and renamed destructured
1546
+ * names, and the `...rest` binding. `includeBindable` controls whether a prop initialized
1547
+ * with `$bindable(...)` is included — `false` excludes it (correctness/prop-mutation: mutating
1548
+ * a `$bindable` prop is the intended contract), `true` includes it (correctness/stale-prop-derivation:
1549
+ * a `$bindable` prop can still be derived-from-at-init just like any other prop). `let props
1550
+ * = $props()` (no destructuring) tracks `props` itself, since none of its fields can be
1551
+ * `$bindable` either. Returns an empty set when `$props()` appears more than once, or a
1552
+ * destructuring shape is ambiguous (nested pattern) — conservative, to avoid false positives
1553
+ * rather than chase every shape.
1554
+ */
1555
+ function collectPropNames(program, includeBindable) {
1556
+ const names = /* @__PURE__ */ new Set();
1557
+ let seen = 0;
1558
+ let ambiguous = false;
1559
+ walkEstree(program, (n) => {
1560
+ if (n.type !== "VariableDeclarator" || !n.init || !isPropsCall(unwrapTs(n.init))) return;
1561
+ seen++;
1562
+ if (n.id?.type === "Identifier") {
1563
+ names.add(n.id.name);
1564
+ return;
1565
+ }
1566
+ if (n.id?.type !== "ObjectPattern" || !Array.isArray(n.id.properties)) {
1567
+ ambiguous = true;
1568
+ return;
1569
+ }
1570
+ for (const p of n.id.properties) if (p?.type === "RestElement") addBoundNames(p.argument, names);
1571
+ else if (p?.type === "Property") {
1572
+ if (p.value?.type === "AssignmentPattern") {
1573
+ if ((includeBindable || !isBindableCall(p.value.right)) && p.value.left?.type === "Identifier") names.add(p.value.left.name);
1574
+ } else if (p.value?.type === "Identifier") names.add(p.value.name);
1575
+ }
1576
+ });
1577
+ return ambiguous || seen > 1 ? /* @__PURE__ */ new Set() : names;
1578
+ }
1579
+ /**
1580
+ * Local identifier names bound to a prop via legacy `export let foo` / `export let foo = default`
1581
+ * (correctness/stale-prop-derivation, correctness/prop-mutation: the same two bugs exist under
1582
+ * legacy reactivity, just with a different fix). Top-level only, plain identifiers only — a
1583
+ * component can't mix legacy `export let` props with runes-mode `$props()` in the same file
1584
+ * (a Svelte compile error), so this and `collectPropNames`'s result are never both non-empty
1585
+ * for the same component.
1586
+ */
1587
+ function collectLegacyPropNames(program) {
1588
+ const names = /* @__PURE__ */ new Set();
1589
+ for (const stmt of program.body ?? []) {
1590
+ if (stmt?.type !== "ExportNamedDeclaration" || stmt.declaration?.type !== "VariableDeclaration") continue;
1591
+ for (const d of stmt.declaration.declarations ?? []) if (d?.id?.type === "Identifier") names.add(d.id.name);
1592
+ }
1593
+ return names;
1594
+ }
1595
+ /** Mutating array/Set/Map methods — a call to one of these on a non-bindable prop mutates it (correctness/prop-mutation). */
1596
+ const MUTATING_METHODS = /* @__PURE__ */ new Set([
1597
+ "push",
1598
+ "pop",
1599
+ "shift",
1600
+ "unshift",
1601
+ "splice",
1602
+ "sort",
1603
+ "reverse",
1604
+ "copyWithin",
1605
+ "fill",
1606
+ "set",
1607
+ "add",
1608
+ "delete",
1609
+ "clear"
1610
+ ]);
1611
+ /**
1612
+ * Flag mutations of a non-`$bindable` prop (correctness/prop-mutation): a member-expression write
1613
+ * (`prop.x = …`, `prop.x += …`, `prop.x++`), `delete prop.x`, or a call to a mutating
1614
+ * method on the prop (`prop.push(...)`). Plain reassignment of the prop identifier
1615
+ * itself (`prop = 5`) is NOT flagged — Svelte's docs explicitly sanction temporary
1616
+ * reassignment for ephemeral state; only mutation is prohibited. Run over the instance
1617
+ * program AND the template fragment (inline handlers can mutate props in the template).
1618
+ * Scope-aware (issue #140): a local that shadows the prop's name is not flagged.
1619
+ */
1620
+ function collectPropMutations(root, propNames, source, acc) {
1621
+ if (propNames.size === 0) return;
1622
+ walkScoped(root, (n, scope) => {
1623
+ const flag = (r) => {
1624
+ if (r && propNames.has(r) && !scope.has(r)) acc.push({
1625
+ name: r,
1626
+ line: lineOf(source, n.start)
1627
+ });
1628
+ };
1629
+ if (n.type === "AssignmentExpression" && n.left?.type === "MemberExpression") flag(rootObjectName(n.left));
1630
+ else if (n.type === "UpdateExpression" && n.argument?.type === "MemberExpression") flag(rootObjectName(n.argument));
1631
+ else if (n.type === "UnaryExpression" && n.operator === "delete") flag(rootObjectName(n.argument));
1632
+ else if (n.type === "CallExpression" && n.callee?.type === "MemberExpression") {
1633
+ const method = n.callee.property?.type === "Identifier" ? n.callee.property.name : void 0;
1634
+ if (method && MUTATING_METHODS.has(method)) flag(rootObjectName(n.callee.object));
1635
+ }
1636
+ });
1637
+ }
1638
+ /** Named props destructured from `$props()`, or 0 when unknowable (architecture/prop-count). */
1639
+ function countProps(program) {
1640
+ let count = 0;
1641
+ let seen = 0;
1642
+ let uncountable = false;
1643
+ walkEstree(program, (n) => {
1644
+ if (n.type !== "VariableDeclarator" || !n.init || !isPropsCall(unwrapTs(n.init))) return;
1645
+ seen++;
1646
+ const props = n.id?.type === "ObjectPattern" ? n.id.properties : void 0;
1647
+ if (!Array.isArray(props)) {
1648
+ uncountable = true;
1649
+ return;
1650
+ }
1651
+ count = props.filter((p) => p?.type === "Property").length;
1652
+ });
1653
+ return uncountable || seen > 1 ? 0 : count;
1654
+ }
1655
+ /** Source line count, not over-counting a single trailing newline (architecture/component-size). */
1656
+ function countLines(source) {
1657
+ if (source.length === 0) return 0;
1658
+ return source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
1659
+ }
1660
+ /** Whether an import declaration contributes no runtime value binding — see `ComponentFacts.importSpans`. */
1661
+ function isTypeOnlyImport(n) {
1662
+ if (n.importKind === "type") return true;
1663
+ const specs = n.specifiers;
1664
+ return Array.isArray(specs) && specs.length > 0 && specs.every((s) => s?.importKind === "type");
1665
+ }
1666
+ /** Module specifiers of every `import`, each with its source line (see `ComponentFacts.importSpans`). */
1667
+ function collectImportSources(program, source, acc) {
1668
+ walkEstree(program, (n) => {
1669
+ if (n.type === "ImportDeclaration" && typeof n.source?.value === "string") acc.push({
1670
+ source: n.source.value,
1671
+ line: lineOf(source, n.start),
1672
+ ...isTypeOnlyImport(n) ? { type: true } : {}
1673
+ });
1674
+ });
1675
+ }
1676
+ /** A specifier is "bare" (a node_modules package) when it is not relative/absolute/alias-local. */
1677
+ function isBareSpecifier(s) {
1678
+ return !/^[./$#]/.test(s);
1679
+ }
1680
+ /** Value `import * as X from '<bare pkg>'` namespace imports (type-only excluded) — performance/namespace-import. */
1681
+ function collectNamespaceImports(program, source, acc) {
1682
+ walkEstree(program, (n) => {
1683
+ if (n.type !== "ImportDeclaration" || n.importKind === "type") return;
1684
+ const spec = n.source?.value;
1685
+ if (typeof spec !== "string" || !isBareSpecifier(spec)) return;
1686
+ if (Array.isArray(n.specifiers) && n.specifiers.some((s) => s?.type === "ImportNamespaceSpecifier")) acc.push({
1687
+ source: spec,
1688
+ line: lineOf(source, n.start)
1689
+ });
1690
+ });
1691
+ }
1692
+ const MD_LINK = /\[[^\]]*\]\(([^)\s]+)\)/g;
1693
+ const SCRIPT_OPEN = /<script(?:\s[^>]*)?>/;
1694
+ const SCRIPT_CLOSE = /<\/script\s*>/;
1695
+ const STYLE_OPEN = /<style(?:\s[^>]*)?>/;
1696
+ const STYLE_CLOSE = /<\/style\s*>/;
1697
+ /**
1698
+ * Markdown links inside comments. A plain text scan, like `collectSuppressions` — a link in a comment is
1699
+ * not an AST node. `//` counts as a comment opener only at the start of a line, which is what keeps the
1700
+ * scan off the `//` in `https://`; a trailing `// [x](y)` is therefore missed, and measurement found no
1701
+ * such link, so the cost is a case that does not occur rather than one this rule needs.
1702
+ *
1703
+ * `<script>`/`<style>` blocks are tracked separately (open on the tag, close on the closing tag) because
1704
+ * neither comment form applies uniformly inside them: `<!--`/`-->` are markup syntax, not script/style
1705
+ * syntax, so a `<!--` inside a script string literal (e.g. a URL comparison) must not open a markup
1706
+ * comment; and a line-leading `//` is only a comment in a script, never in markup or style, where it is
1707
+ * either content or a syntax error. The tags themselves are read from the markup *outside* any comment, so
1708
+ * a commented-out block does not open one. `wholeFileIsScript` covers `.svelte.ts`/`.svelte.js` runes
1709
+ * modules, which have no `<script>` tag at all — the entire file is script.
1710
+ */
1711
+ function collectCommentLinks(source, { wholeFileIsScript = false } = {}) {
1712
+ const out = [];
1713
+ let htmlOpen = false;
1714
+ let block = wholeFileIsScript ? "script" : void 0;
1715
+ source.split("\n").forEach((line, i) => {
1716
+ let text = "";
1717
+ if (block !== void 0) {
1718
+ if (block === "script" && /^\s*\/\//.test(line)) text = line.replace(/^\s*\/\//, "");
1719
+ if (!wholeFileIsScript && (block === "script" ? SCRIPT_CLOSE : STYLE_CLOSE).test(line)) block = void 0;
1720
+ } else {
1721
+ let plain = "";
1722
+ let rest = line;
1723
+ while (rest.length > 0) {
1724
+ if (htmlOpen) {
1725
+ const end = rest.indexOf("-->");
1726
+ if (end === -1) {
1727
+ text += rest;
1728
+ break;
1729
+ }
1730
+ text += rest.slice(0, end);
1731
+ htmlOpen = false;
1732
+ rest = rest.slice(end + 3);
1733
+ continue;
1734
+ }
1735
+ const start = rest.indexOf("<!--");
1736
+ if (start === -1) {
1737
+ plain += rest;
1738
+ break;
1739
+ }
1740
+ plain += rest.slice(0, start);
1741
+ htmlOpen = true;
1742
+ rest = rest.slice(start + 4);
1743
+ }
1744
+ const opened = SCRIPT_OPEN.test(plain) ? "script" : STYLE_OPEN.test(plain) ? "style" : void 0;
1745
+ if (opened !== void 0 && !(opened === "script" ? SCRIPT_CLOSE : STYLE_CLOSE).test(plain)) block = opened;
1746
+ }
1747
+ for (const m of text.matchAll(MD_LINK)) if (m[1] !== void 0) out.push({
1748
+ url: m[1],
1749
+ line: i + 1
1750
+ });
1751
+ });
1752
+ return out;
1753
+ }
1754
+ /**
1755
+ * Calls matching `matcher` that run when `root` itself is evaluated (correctness/orphan-effect, correctness/orphan-lifecycle).
1756
+ * `skipSubtree` exempts a call's children — correctness/orphan-effect uses it for `$effect.root(...)`
1757
+ * callbacks, which are a legal standalone reactive scope. `initialShadowed` seeds the
1758
+ * shadow set threaded through `walkEvalScope` (correctness/orphan-lifecycle uses it to seed a
1759
+ * constructor's own parameters before scanning its body).
1760
+ */
1761
+ function collectEvalScopeCalls(root, source, matcher, skipSubtree, initialShadowed) {
1762
+ const out = [];
1763
+ walkEvalScope(root, (n, shadowed) => {
1764
+ if (n.type !== "CallExpression") return void 0;
1765
+ if (skipSubtree?.(n)) return true;
1766
+ const name = matcher(n, shadowed);
1767
+ if (name) out.push({
1768
+ name,
1769
+ line: lineOf(source, n.start)
1770
+ });
1771
+ }, initialShadowed);
1772
+ return out;
1773
+ }
1774
+ /**
1775
+ * Matcher-parameterised orphan-call collector (correctness/orphan-effect, correctness/orphan-lifecycle): (1) matching calls that
1776
+ * run at module evaluation time, (2) a module-scope `new` (direct top-level statements
1777
+ * only, export-unwrapped) of a same-file top-level class whose constructor directly
1778
+ * makes a matching call. See `collectOrphanEffects`'s doc comment for why pattern 2 is
1779
+ * restricted to top-level `ClassDeclaration`s and top-level `new` statements.
1780
+ */
1781
+ function collectOrphanCalls(program, source, matcher, skipSubtree) {
1782
+ const out = collectEvalScopeCalls(program, source, matcher, skipSubtree).map((c) => ({
1783
+ ...c,
1784
+ kind: "top-level"
1785
+ }));
1786
+ const body = program.body ?? [];
1787
+ const matchingClasses = /* @__PURE__ */ new Map();
1788
+ for (const stmt of body) {
1789
+ const decl = unwrapExport(stmt);
1790
+ if (decl?.type !== "ClassDeclaration" || decl.id?.type !== "Identifier") continue;
1791
+ const ctor = (decl.body?.body ?? []).find((m) => m?.type === "MethodDefinition" && m.kind === "constructor" && m.value?.body);
1792
+ if (!ctor) continue;
1793
+ const ctorShadow = /* @__PURE__ */ new Set();
1794
+ for (const p of ctor.value.params ?? []) addBoundNames(p, ctorShadow);
1795
+ const calls = collectEvalScopeCalls(ctor.value.body, source, matcher, skipSubtree, ctorShadow);
1796
+ if (calls.length > 0) matchingClasses.set(decl.id.name, calls[0].name);
1797
+ }
1798
+ if (matchingClasses.size > 0) for (const stmt of body) {
1799
+ const decl = unwrapExport(stmt);
1800
+ if (!(decl?.type === "VariableDeclaration" || decl?.type === "ExpressionStatement" || stmt.type === "ExportDefaultDeclaration" && decl?.type !== "FunctionDeclaration" && decl?.type !== "ClassDeclaration")) continue;
1801
+ walkEvalScope(decl, (n) => {
1802
+ if (n.type === "NewExpression" && n.callee?.type === "Identifier" && matchingClasses.has(n.callee.name)) out.push({
1803
+ name: matchingClasses.get(n.callee.name),
1804
+ line: lineOf(source, n.start),
1805
+ kind: "constructor-instantiated",
1806
+ className: n.callee.name
1807
+ });
1808
+ });
1809
+ }
1810
+ return out.sort((a, b) => a.line - b.line);
1811
+ }
1812
+ /**
1813
+ * Orphan `$effect` facts for a module-context program (correctness/orphan-effect): (1) effects that run
1814
+ * at module evaluation time, (2) a module-scope `new` of a same-file class whose
1815
+ * constructor creates a bare effect. Conservative by construction — never crosses a
1816
+ * function boundary, so factory functions, IIFEs, and cross-file classes are not flagged.
1817
+ *
1818
+ * Pattern 2 (the class/`new` half) operates on DIRECT top-level statements only — it
1819
+ * never descends into blocks/if/for/try. A top-level `ClassDeclaration` name is a real
1820
+ * module-scope binding that an import can't legally share, so restricting collection to
1821
+ * `program.body` (unwrapping only `export`/`export default`) rules out two false-positive
1822
+ * vectors: a block-scoped class shadowing an imported name of the same name, and a class
1823
+ * expression's own name (`const A = class Store {…}`), which is only visible inside the
1824
+ * expression, never as a module-scope binding. This matches the design spec's own wording
1825
+ * for pattern 2: "flag top-level `new ClassName(...)` statements". Pattern 1 (top-level
1826
+ * `$effect`, including inside top-level blocks/if) is unaffected — see
1827
+ * `collectEvalScopeCalls` above. Generalised as `collectOrphanCalls` — correctness/orphan-lifecycle reuses
1828
+ * the same walk with a lifecycle-import matcher.
1829
+ */
1830
+ function collectOrphanEffects(program, source) {
1831
+ return collectOrphanCalls(program, source, (n) => isEffectCall(n) ? "$effect" : void 0, isEffectRootCall).map(({ line, kind, className }) => ({
1832
+ line,
1833
+ kind,
1834
+ ...className !== void 0 ? { className } : {}
1835
+ }));
1836
+ }
1837
+ /** Orphan lifecycle-call facts for a module-context program (correctness/orphan-lifecycle). */
1838
+ function collectOrphanLifecycleCalls(program, source) {
1839
+ const imports = collectSvelteLifecycleImports(program);
1840
+ if (imports.locals.size === 0 && imports.namespaces.size === 0) return [];
1841
+ return collectOrphanCalls(program, source, (n, shadowed) => {
1842
+ const m = matchLifecycleCall(n, imports);
1843
+ return m && !shadowed.has(m.local) ? m.canonical : void 0;
1844
+ });
1845
+ }
1846
+ /** A Svelte runes module file — the whole file is one module-scope program (correctness/orphan-effect). */
1847
+ const MODULE_FILE_RE = /\.svelte\.(ts|js)$/;
1848
+ /**
1849
+ * Module-scope reactive-state declarations in a runes module (security/shared-state-import): a top-level
1850
+ * `let|const x = $state(...)` / `$state.raw(...)` declaration, and a module-scope
1851
+ * `new` (in a top-level variable declaration) of a same-file top-level class with a
1852
+ * `$state` field initializer — recorded under the instance binding's name at the
1853
+ * declaration line. Direct top-level statements only (export-unwrapped), mirroring
1854
+ * correctness/orphan-effect's pattern-2 conservatism.
1855
+ */
1856
+ function collectModuleStateDecls(program, source) {
1857
+ const out = [];
1858
+ const body = program.body ?? [];
1859
+ const statefulClasses = /* @__PURE__ */ new Set();
1860
+ for (const stmt of body) {
1861
+ const decl = unwrapExport(stmt);
1862
+ if (decl?.type === "VariableDeclaration") {
1863
+ for (const d of decl.declarations ?? []) if (d?.id?.type === "Identifier" && d.init && isStateDeclaration(unwrapTs(d.init))) out.push({
1864
+ name: d.id.name,
1865
+ line: lineOf(source, d.start)
1866
+ });
1867
+ } else if (decl?.type === "ClassDeclaration" && decl.id?.type === "Identifier") {
1868
+ if ((decl.body?.body ?? []).some((m) => m?.type === "PropertyDefinition" && m.value && isStateDeclaration(unwrapTs(m.value)))) statefulClasses.add(decl.id.name);
1869
+ }
1870
+ }
1871
+ if (statefulClasses.size > 0) for (const stmt of body) {
1872
+ const decl = unwrapExport(stmt);
1873
+ if (decl?.type !== "VariableDeclaration") continue;
1874
+ for (const d of decl.declarations ?? []) if (d?.init?.type === "NewExpression" && d.init.callee?.type === "Identifier" && statefulClasses.has(d.init.callee.name)) out.push({
1875
+ name: d.id?.type === "Identifier" ? d.id.name : d.init.callee.name,
1876
+ line: lineOf(source, d.start)
1877
+ });
1878
+ }
1879
+ return out.sort((a, b) => a.line - b.line);
1880
+ }
1881
+ /**
1882
+ * Facts for a `.svelte.ts`/`.svelte.js` runes module (correctness/orphan-effect, correctness/orphan-lifecycle, correctness/server-browser-global,
1883
+ * performance/heavy-import, performance/namespace-import, architecture/private-scope-import, architecture/route-component-import).
1884
+ * The whole file runs at import time, so only `orphanEffects`, `orphanLifecycleCalls`, `browserGlobalRefs`,
1885
+ * `moduleStateDecls`, `imports`/`importSpans`/`namespaceImports`, and `suppressions` are populated —
1886
+ * `loc` stays 0 so architecture/component-size skips module files (there is no component to size), and
1887
+ * the rest of the component-only facts stay empty. Uses `parseModuleProgram` to get the ESTree program
1888
+ * from the wrapped source; the 1-line wrap prefix is subtracted from every reported line.
1889
+ */
1890
+ function parseModuleFacts(source, filename) {
1891
+ const { program, wrapped } = parseModuleProgram(source, filename);
1892
+ const shift = (line) => Math.max(0, line - 1);
1893
+ const orphanEffects = program ? collectOrphanEffects(program, wrapped).map((f) => ({
1894
+ ...f,
1895
+ line: shift(f.line)
1896
+ })) : [];
1897
+ const orphanLifecycleCalls = program ? collectOrphanLifecycleCalls(program, wrapped).map((f) => ({
1898
+ ...f,
1899
+ line: shift(f.line)
1900
+ })) : [];
1901
+ const browserGlobalRefs = program ? collectBrowserGlobalRefs(program, wrapped).map((r) => ({
1902
+ ...r,
1903
+ line: shift(r.line),
1904
+ context: "module"
1905
+ })) : [];
1906
+ const moduleStateDecls = program ? collectModuleStateDecls(program, wrapped).map((d) => ({
1907
+ ...d,
1908
+ line: shift(d.line)
1909
+ })) : [];
1910
+ const basePathLinks = [];
1911
+ if (program) {
1912
+ const locals = collectNamedImportAliases(program, "$app/navigation", GOTO_NAMES);
1913
+ const raw = [];
1914
+ collectGotoLinks(locals, [program], wrapped, raw);
1915
+ for (const l of raw) basePathLinks.push({
1916
+ ...l,
1917
+ line: shift(l.line)
1918
+ });
1919
+ basePathLinks.sort((a, b) => a.line - b.line);
1920
+ }
1921
+ const importSpans = [];
1922
+ const namespaceImports = [];
1923
+ if (program) {
1924
+ const rawImportSpans = [];
1925
+ collectImportSources(program, wrapped, rawImportSpans);
1926
+ for (const s of rawImportSpans) importSpans.push({
1927
+ ...s,
1928
+ line: shift(s.line)
1929
+ });
1930
+ const rawNamespaceImports = [];
1931
+ collectNamespaceImports(program, wrapped, rawNamespaceImports);
1932
+ for (const n of rawNamespaceImports) namespaceImports.push({
1933
+ ...n,
1934
+ line: shift(n.line)
1935
+ });
1936
+ }
1937
+ return {
1938
+ eachBlocks: [],
1939
+ effects: [],
1940
+ htmlTags: [],
1941
+ javascriptUrls: [],
1942
+ loc: 0,
1943
+ propCount: 0,
1944
+ imports: importSpans.map((s) => s.source),
1945
+ importSpans,
1946
+ namespaceImports,
1947
+ constableStates: [],
1948
+ mutatedProps: [],
1949
+ stalePropDerivations: [],
1950
+ rawableStates: [],
1951
+ nonreactiveBuiltinStates: [],
1952
+ checkableBindValues: [],
1953
+ basePathLinks,
1954
+ suppressions: collectSuppressions(source),
1955
+ commentLinks: collectCommentLinks(source, { wholeFileIsScript: true }),
1956
+ orphanEffects,
1957
+ orphanLifecycleCalls,
1958
+ browserGlobalRefs,
1959
+ moduleStateDecls
1960
+ };
1961
+ }
1962
+ /**
1963
+ * Parse one source file's facts (CLI/static + vite build mode): a `.svelte` component's
1964
+ * reactivity/correctness + security + architecture facts, or a `.svelte.ts`/`.svelte.js`
1965
+ * runes module's orphan-$effect facts (correctness/orphan-effect).
1966
+ */
1967
+ function parseComponentFacts(source, filename) {
1968
+ if (MODULE_FILE_RE.test(filename)) return parseModuleFacts(source, filename);
1969
+ const ast = parseSvelte(source, filename);
1970
+ const eachBlocks = [];
1971
+ collectEachBlocks(ast.fragment ?? ast, source, eachBlocks);
1972
+ const htmlTags = [];
1973
+ const javascriptUrls = [];
1974
+ collectSecurityFacts(ast.fragment ?? ast, source, htmlTags, javascriptUrls);
1975
+ const checkableBindValues = [];
1976
+ collectCheckableBindValues(ast.fragment ?? ast, source, checkableBindValues);
1977
+ const ariaElements = [];
1978
+ collectAriaElements(ast.fragment ?? ast, source, ariaElements);
1979
+ const elements = [];
1980
+ collectElements(ast.fragment ?? ast, source, elements, ast.options?.namespace === "svg");
1981
+ const interactiveNestings = [];
1982
+ collectInteractiveNestings(ast.fragment ?? ast, source, interactiveNestings, []);
1983
+ const ariaHiddenFocusables = [];
1984
+ collectAriaHiddenFocusables(ast.fragment ?? ast, source, ariaHiddenFocusables, []);
1985
+ const unnamedInteractive = [];
1986
+ const labelTargets = {
1987
+ ids: /* @__PURE__ */ new Set(),
1988
+ nodes: /* @__PURE__ */ new Set()
1989
+ };
1990
+ collectLabelTargets(ast.fragment ?? ast, labelTargets);
1991
+ collectUnnamedInteractive(ast.fragment ?? ast, source, unnamedInteractive, labelTargets);
1992
+ const unassociatedLabels = [];
1993
+ collectUnassociatedLabels(ast.fragment ?? ast, source, unassociatedLabels);
1994
+ const bulletTexts = [];
1995
+ collectBulletTexts(ast.fragment ?? ast, source, bulletTexts, false);
1996
+ const selectsMissingPlaceholder = [];
1997
+ collectSelectsMissingPlaceholder(ast.fragment ?? ast, source, selectsMissingPlaceholder);
1998
+ const timesMissingDatetime = [];
1999
+ collectTimesMissingDatetime(ast.fragment ?? ast, source, timesMissingDatetime);
2000
+ const videosAutoplayNoMuted = [];
2001
+ collectVideosAutoplayNoMuted(ast.fragment ?? ast, source, videosAutoplayNoMuted, ast.options?.namespace === "svg");
2002
+ const duplicateDts = [];
2003
+ collectDuplicateDts(ast.fragment ?? ast, source, duplicateDts, ast.options?.namespace === "svg");
2004
+ const basePathLinks = [];
2005
+ collectHrefLinks(ast.fragment ?? ast, source, basePathLinks);
2006
+ const gotoPrograms = [ast.module?.content, ast.instance?.content].filter(Boolean);
2007
+ const gotoLocals = /* @__PURE__ */ new Set();
2008
+ for (const p of gotoPrograms) for (const n of collectNamedImportAliases(p, "$app/navigation", GOTO_NAMES)) gotoLocals.add(n);
2009
+ collectGotoLinks(gotoLocals, [...gotoPrograms, ast.fragment], source, basePathLinks);
2010
+ basePathLinks.sort((a, b) => a.line - b.line);
2011
+ const loc = countLines(source);
2012
+ const suppressions = collectSuppressions(source);
2013
+ const moduleProgram = ast.module?.content;
2014
+ const importSpans = [];
2015
+ const namespaceImports = [];
2016
+ if (moduleProgram) {
2017
+ collectImportSources(moduleProgram, source, importSpans);
2018
+ collectNamespaceImports(moduleProgram, source, namespaceImports);
2019
+ }
2020
+ const orphanEffects = moduleProgram ? collectOrphanEffects(moduleProgram, source) : [];
2021
+ const orphanLifecycleCalls = moduleProgram ? collectOrphanLifecycleCalls(moduleProgram, source) : [];
2022
+ const browserGlobalRefs = [];
2023
+ if (moduleProgram) for (const r of collectBrowserGlobalRefs(moduleProgram, source)) browserGlobalRefs.push({
2024
+ ...r,
2025
+ context: "module"
2026
+ });
2027
+ const effects = [];
2028
+ const constableStates = [];
2029
+ const mutatedProps = [];
2030
+ const stalePropDerivations = [];
2031
+ const rawableStates = [];
2032
+ const nonreactiveBuiltinStates = [];
2033
+ let propCount = 0;
2034
+ const program = ast.instance?.content;
2035
+ if (program) {
2036
+ collectImportSources(program, source, importSpans);
2037
+ collectNamespaceImports(program, source, namespaceImports);
2038
+ propCount = countProps(program);
2039
+ const legacyPropNames = collectLegacyPropNames(program);
2040
+ const nonBindableProps = /* @__PURE__ */ new Set([...collectPropNames(program, false), ...legacyPropNames]);
2041
+ const rawMutations = [];
2042
+ collectPropMutations(program, nonBindableProps, source, rawMutations);
2043
+ if (ast.fragment) collectPropMutations(ast.fragment, nonBindableProps, source, rawMutations);
2044
+ for (const m of rawMutations) mutatedProps.push(legacyPropNames.has(m.name) ? {
2045
+ ...m,
2046
+ legacy: true
2047
+ } : m);
2048
+ const allPropNames = /* @__PURE__ */ new Set([...collectPropNames(program, true), ...legacyPropNames]);
2049
+ if (allPropNames.size > 0) {
2050
+ const candidates = collectStalePropCandidates(program, allPropNames, source);
2051
+ if (candidates.length > 0) {
2052
+ const candidateNames = new Set(candidates.map((c) => c.name));
2053
+ const disqualified = /* @__PURE__ */ new Set();
2054
+ collectStateWrites(program, candidateNames, disqualified);
2055
+ if (ast.fragment) {
2056
+ collectStateWrites(ast.fragment, candidateNames, disqualified);
2057
+ collectTemplateEscapes(ast.fragment, candidateNames, disqualified);
2058
+ }
2059
+ const referenced = /* @__PURE__ */ new Set();
2060
+ if (ast.fragment) collectFragmentRefs(ast.fragment, candidateNames, referenced);
2061
+ const isLegacy = legacyPropNames.size > 0;
2062
+ for (const c of candidates) if (!disqualified.has(c.name) && referenced.has(c.name)) stalePropDerivations.push(isLegacy ? {
2063
+ ...c,
2064
+ legacy: true
2065
+ } : c);
2066
+ }
2067
+ }
2068
+ const stateNames = /* @__PURE__ */ new Set();
2069
+ const reactiveNames = /* @__PURE__ */ new Set();
2070
+ collectImportedLocalNames(program, reactiveNames);
2071
+ collectNewExprLocalNames(program, reactiveNames);
2072
+ if (moduleProgram) {
2073
+ collectImportedLocalNames(moduleProgram, reactiveNames);
2074
+ collectNewExprLocalNames(moduleProgram, reactiveNames);
2075
+ }
2076
+ const stateDecls = [];
2077
+ walkEstree(program, (n) => {
2078
+ if (n.type !== "VariableDeclarator" || !n.init) return;
2079
+ const init = unwrapTs(n.init);
2080
+ if (isStateDeclaration(init) && n.id?.type === "Identifier") {
2081
+ stateNames.add(n.id.name);
2082
+ stateDecls.push({
2083
+ name: n.id.name,
2084
+ line: lineOf(source, n.start)
2085
+ });
2086
+ }
2087
+ if (isStateDeclaration(init) || isDerivedDeclaration(init) || isPropsCall(init)) addBoundNames(n.id, reactiveNames);
2088
+ });
2089
+ walkEstree(program, (n) => {
2090
+ if (n.type !== "CallExpression" || !isEffectCall(n)) return;
2091
+ const fn = n.arguments?.[0];
2092
+ const isFn = fn?.type === "ArrowFunctionExpression" || fn?.type === "FunctionExpression";
2093
+ effects.push({
2094
+ line: lineOf(source, n.start),
2095
+ assignsOnlyState: isFn ? bodyOnlyAssignsState(fn, stateNames) : false,
2096
+ mountOnly: isFn ? !bodyIsEmpty(fn) && !bodyReadsReactive(fn, reactiveNames) : false
2097
+ });
2098
+ });
2099
+ const writtenOrEscaped = /* @__PURE__ */ new Set();
2100
+ collectStateWrites(program, stateNames, writtenOrEscaped);
2101
+ if (ast.fragment) {
2102
+ collectStateWrites(ast.fragment, stateNames, writtenOrEscaped);
2103
+ collectTemplateEscapes(ast.fragment, stateNames, writtenOrEscaped);
2104
+ collectDirectiveEscapes(ast.fragment, stateNames, writtenOrEscaped);
2105
+ }
2106
+ for (const d of stateDecls) if (!writtenOrEscaped.has(d.name)) constableStates.push(d);
2107
+ const rawableCandidates = [];
2108
+ for (const stmt of program.body ?? []) {
2109
+ if (stmt?.type !== "VariableDeclaration") continue;
2110
+ for (const d of stmt.declarations ?? []) {
2111
+ if (d?.id?.type !== "Identifier" || !d.init) continue;
2112
+ const init = unwrapTs(d.init);
2113
+ if (!isPlainStateCall(init)) continue;
2114
+ const arg = unwrapTs(init.arguments?.[0]);
2115
+ if (arg?.type === "ObjectExpression" || arg?.type === "ArrayExpression") rawableCandidates.push({
2116
+ name: d.id.name,
2117
+ line: lineOf(source, d.start)
2118
+ });
2119
+ }
2120
+ }
2121
+ if (rawableCandidates.length > 0) {
2122
+ const candNames = new Set(rawableCandidates.map((c) => c.name));
2123
+ const union = /* @__PURE__ */ new Set();
2124
+ const kinds = /* @__PURE__ */ new Map();
2125
+ collectStateWrites(program, candNames, union, kinds);
2126
+ if (ast.fragment) {
2127
+ collectStateWrites(ast.fragment, candNames, union, kinds);
2128
+ collectTemplateEscapes(ast.fragment, candNames, union, kinds);
2129
+ }
2130
+ const aliasEscapes = /* @__PURE__ */ new Set();
2131
+ collectAliasRefs(program, candNames, aliasEscapes);
2132
+ const eachTaint = /* @__PURE__ */ new Set();
2133
+ if (ast.fragment) {
2134
+ collectFragmentAliasRefs(ast.fragment, candNames, aliasEscapes);
2135
+ collectDirectiveEscapes(ast.fragment, candNames, aliasEscapes);
2136
+ collectEachContextTaint(ast.fragment, candNames, eachTaint);
2137
+ }
2138
+ for (const c of rawableCandidates) {
2139
+ const k = kinds.get(c.name);
2140
+ const reassigned = k?.has("reassign") ?? false;
2141
+ const dirty = k !== void 0 && [...k].some((kind) => kind !== "reassign") || aliasEscapes.has(c.name) || eachTaint.has(c.name);
2142
+ if (reassigned && !dirty) rawableStates.push(c);
2143
+ }
2144
+ }
2145
+ const builtinCandidates = /* @__PURE__ */ new Map();
2146
+ for (const stmt of program.body ?? []) {
2147
+ if (stmt?.type !== "VariableDeclaration") continue;
2148
+ for (const d of stmt.declarations ?? []) {
2149
+ if (d?.id?.type !== "Identifier" || !d.init) continue;
2150
+ const init = unwrapTs(d.init);
2151
+ if (!isPlainStateCall(init)) continue;
2152
+ const arg = unwrapTs(init.arguments?.[0]);
2153
+ if (arg?.type === "NewExpression" && arg.callee?.type === "Identifier" && BUILTIN_STATE_TYPES.has(arg.callee.name)) builtinCandidates.set(d.id.name, {
2154
+ type: arg.callee.name,
2155
+ line: lineOf(source, d.start)
2156
+ });
2157
+ }
2158
+ }
2159
+ if (builtinCandidates.size > 0) {
2160
+ const types = new Map([...builtinCandidates].map(([n, meta]) => [n, meta.type]));
2161
+ const mutatedBuiltins = /* @__PURE__ */ new Set();
2162
+ const reassignedBuiltins = /* @__PURE__ */ new Set();
2163
+ collectBuiltinStateSignals(program, types, mutatedBuiltins, reassignedBuiltins);
2164
+ if (ast.fragment) collectBuiltinStateSignals(ast.fragment, types, mutatedBuiltins, reassignedBuiltins);
2165
+ for (const [name, meta] of builtinCandidates) if (mutatedBuiltins.has(name) && !reassignedBuiltins.has(name)) nonreactiveBuiltinStates.push({
2166
+ name,
2167
+ type: meta.type,
2168
+ line: meta.line
2169
+ });
2170
+ }
2171
+ let moduleExtra;
2172
+ if (moduleProgram) {
2173
+ const moduleBrowserImports = collectBrowserGuardImports(moduleProgram);
2174
+ moduleExtra = {
2175
+ guards: /* @__PURE__ */ new Set([...moduleBrowserImports, ...collectDerivedGuardBindings(moduleProgram, moduleBrowserImports)]),
2176
+ bound: collectProgramBindings(moduleProgram)
2177
+ };
2178
+ }
2179
+ for (const r of collectBrowserGlobalRefs(program, source, moduleExtra)) browserGlobalRefs.push({
2180
+ ...r,
2181
+ context: "instance"
2182
+ });
2183
+ }
2184
+ const imports = importSpans.map((s) => s.source);
2185
+ return {
2186
+ eachBlocks,
2187
+ effects,
2188
+ htmlTags,
2189
+ javascriptUrls,
2190
+ loc,
2191
+ propCount,
2192
+ imports,
2193
+ importSpans,
2194
+ namespaceImports,
2195
+ constableStates,
2196
+ mutatedProps,
2197
+ stalePropDerivations,
2198
+ rawableStates,
2199
+ nonreactiveBuiltinStates,
2200
+ checkableBindValues,
2201
+ basePathLinks,
2202
+ orphanEffects,
2203
+ orphanLifecycleCalls,
2204
+ browserGlobalRefs,
2205
+ moduleStateDecls: [],
2206
+ suppressions,
2207
+ commentLinks: collectCommentLinks(source),
2208
+ ariaElements,
2209
+ elements,
2210
+ interactiveNestings,
2211
+ ariaHiddenFocusables,
2212
+ unnamedInteractive,
2213
+ unassociatedLabels,
2214
+ bulletTexts,
2215
+ selectsMissingPlaceholder,
2216
+ timesMissingDatetime,
2217
+ videosAutoplayNoMuted,
2218
+ duplicateDts
2219
+ };
2220
+ }
2221
+ //#endregion
2
2222
  //#region src/inline-directives.ts
2223
+ /**
2224
+ * Enter every fact-carrying file into a directive index — components, Kit modules, and the Vite
2225
+ * config when its minify flag was read. The one home for the union both pipelines apply, so a new
2226
+ * fact family that carries suppressions is wired into inline directives in a single place.
2227
+ */
2228
+ function addFactsDirectives(index, facts) {
2229
+ for (const c of facts.components ?? []) index.set(c.file, c.suppressions ?? []);
2230
+ for (const m of facts.kitModules ?? []) index.set(m.file, m.suppressions ?? []);
2231
+ const vc = facts.viteMinifyDisabled;
2232
+ if (vc?.file) index.set(vc.file, vc.suppressions ?? []);
2233
+ }
3
2234
  /** The directive silencing `r`, if one sits on its line and names its rule (or names nothing). */
4
2235
  function directiveFor(index, r) {
5
2236
  const line = r.line;
@@ -21,6 +2252,7 @@ function directiveFor(index, r) {
21
2252
  * rule's `passLabel` when it declares one, else its `title`.
22
2253
  */
23
2254
  function applyInlineDirectives(results, index, rules, config) {
2255
+ if (index.size === 0) return [...results];
24
2256
  const labels = new Map(rules.map((r) => [r.id, r.passLabel ?? r.title]));
25
2257
  const kept = [];
26
2258
  /** rule+route → the first suppressed result, for the PASS this pair may need. */
@@ -73,6 +2305,11 @@ function unknownDirectiveIds(index, rules) {
73
2305
  //#endregion
74
2306
  //#region src/component.ts
75
2307
  /**
2308
+ * Component-body facts for the Correctness category — the source-analysis boundary
2309
+ * (mirrors images.ts / headings.ts). Collected by the static (CLI) provider only;
2310
+ * the rendered provider can't see reactivity, so correctness rules no-op there.
2311
+ */
2312
+ /**
76
2313
  * Warnings for files a collector could not read or parse: the file contributes empty facts, so any
77
2314
  * findings it would have produced are simply missing rather than reported as fixed. The two causes
78
2315
  * are reported separately — an unreadable file is an environment problem (permissions, a descriptor
@@ -93,8 +2330,6 @@ function skippedFileWarnings(facts) {
93
2330
  if (unparsed.length > 0) out.push(`skipped ${unparsed.length} file(s) that could not be parsed: ${list(unparsed)}`, "findings for these files are unavailable until they parse.");
94
2331
  return out;
95
2332
  }
96
- //#endregion
97
- //#region src/component-collect.ts
98
2333
  /**
99
2334
  * Fallback facts for a file that fails to read or parse (dev tooling must never
100
2335
  * throw). This is the single source of truth for the empty-facts shape — add new
@@ -659,19 +2894,6 @@ const VITE_CONFIG_FILES = [
659
2894
  ];
660
2895
  /** SvelteKit's config resolution order (`@sveltejs/kit` checks js before ts). */
661
2896
  const SVELTE_CONFIG_FILES = ["svelte.config.js", "svelte.config.ts"];
662
- //#endregion
663
- //#region src/runtime.ts
664
- /**
665
- * How many file reads may be in flight at once. Analysis reads every `.svelte` file in a project
666
- * in parallel, which on a large project opens more descriptors than the process is allowed: at
667
- * `ulimit -n 1024` — a common container default — a 1 681-route project raised `EMFILE`, and
668
- * because a failed read lands in the same `catch` as a malformed component, 682 files were dropped
669
- * and the run still reported a normal score. The cap is what keeps the analysis whole.
670
- *
671
- * 64 is chosen to sit well under the stock 256 on macOS while leaving descriptors for everything
672
- * else the process holds open. It is not a throughput knob: reads are a few percent of the work.
673
- */
674
- const READ_CONCURRENCY = 64;
675
2897
  /**
676
2898
  * `readFile` with at most `limit` reads in flight. A plain counter plus a queue of waiters —
677
2899
  * deliberately not a dependency, and pure enough to live in core.
@@ -727,6 +2949,26 @@ async function runRules(rules, ctx) {
727
2949
  failedRules
728
2950
  };
729
2951
  }
2952
+ /**
2953
+ * Run rules and apply the correction sequence every pipeline owes its results: configured
2954
+ * severities, overrides, inline directives, then the failed-rule weight correction — a failed
2955
+ * rule examined nothing, so its weight must not stay in the Health denominator, and every
2956
+ * downstream consumer must score against the returned `scoringConfig` so they agree.
2957
+ * A caller with no directive sources passes an empty index, which makes the directive pass
2958
+ * identity; the severity and override passes are config-governed and run regardless.
2959
+ */
2960
+ async function runAnalysis(rules, ctx, directives) {
2961
+ const { results: raw, examined, failedRules } = await runRules(rules, ctx);
2962
+ const results = applyInlineDirectives(applyOverrides(applyRuleSeverities(raw, ctx.config), ctx.config), directives, rules, ctx.config);
2963
+ const failedRuleIds = failedRules.map((f) => f.id);
2964
+ return {
2965
+ results,
2966
+ examined,
2967
+ failedRules,
2968
+ failedRuleIds,
2969
+ scoringConfig: withFailedRulesOff(ctx.config, failedRuleIds)
2970
+ };
2971
+ }
730
2972
  //#endregion
731
2973
  //#region src/reporter/palette.ts
732
2974
  /** Default: no decoration (identity) — output is byte-identical to plain text. */
@@ -891,7 +3133,7 @@ function formatAgentReport(results, config) {
891
3133
  lines.push("No issues to fix.", "");
892
3134
  return lines.join("\n").replace(/\n+$/, "\n");
893
3135
  }
894
- lines.push(`${failing.length} issue(s) to fix, ordered most-severe first. Fix critical issues first; warning and info items improve SEO but do not fail the default build. Apply each fix below, then re-run \`svelte-vitals\` (or the build) to confirm each rule passes. Run \`svelte-vitals explain <rule-id>\` for any rule's rationale and options (works offline).`, "");
3136
+ lines.push(`${failing.length} issue(s) to fix, ordered most-severe first. Fix critical issues first; warning and info items do not fail the default build. Apply each fix below, then re-run \`svelte-vitals\` (or the build) and check each finding's Accept line. Run \`svelte-vitals explain <rule-id>\` for any rule's rationale and options (works offline).`, "");
895
3137
  const groups = /* @__PURE__ */ new Map();
896
3138
  for (const r of failing) {
897
3139
  const key = r.location ?? r.route ?? "(project)";
@@ -967,14 +3209,6 @@ function formatSarifReport(results, config, meta) {
967
3209
  }
968
3210
  //#endregion
969
3211
  //#region src/reporter/app-shell.ts
970
- const BAND_COLOR = {
971
- good: "#2FA968",
972
- warn: "#E8A317",
973
- poor: "#E5484D"
974
- };
975
- function scoreBand(score) {
976
- return score >= 90 ? "good" : score >= 50 ? "warn" : "poor";
977
- }
978
3212
  function escapeHtml(s) {
979
3213
  return s.replace(/[&<>"']/g, (c) => c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : c === "\"" ? "&quot;" : "&#39;");
980
3214
  }
@@ -1733,4 +3967,4 @@ function formatHtmlReport(results, config, meta) {
1733
3967
  return buildHtmlDocument(buildJsonReport(results, config, meta), meta);
1734
3968
  }
1735
3969
  //#endregion
1736
- export { APP_SCRIPT, APP_STYLE, BAND_COLOR, CHILD_NODE_KEYS, IDREF_ATTRS, LANDMARK_ROLES, READ_CONCURRENCY, ROBOTS_SOURCE_PATHS, 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, allRules, applyInlineDirectives, applyOverrides, applyRuleSeverities, 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, emptyKitModuleFacts, escapeHtml, explainRule, findAttr, findKitAliasesInSvelteConfig, findKitPathsBaseInSvelteConfig, findKitPathsBaseInViteConfig, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, intOption, isMentionedAnywhere, isPenalized, isTopFragment, lineOf, linkRule, listOption, mapOption, noColorPalette, overrideMatches, parseComponentFacts, parseKitModuleFacts, parseSvelte, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceIframeLoading, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRepoLocalPath, resolveRole, resolveRuleOptions, resolveRunesModuleSpecifier, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, 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, settingOptions, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, validateRuleOptions, validateRuleSetting, valueFromNodes, withFailedRulesOff, withReadLimit };
3970
+ export { ANCESTRY_DEPENDENT_TAGS, APP_SCRIPT, ASIDE_DEMOTING_TAGS, CHILD_NODE_KEYS, IDREF_ATTRS, NAMING_ATTRS, ROBOTS_SOURCE_PATHS, 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, escapeHtml, explainRule, findAttr, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, isClassicScriptType, isPenalized, 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, withReadLimit };