@svelte-vitals/core 0.55.2 → 0.55.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -152,6 +152,168 @@ const seoTitlePresence = {
152
152
  }
153
153
  };
154
154
  //#endregion
155
+ //#region src/kit-module.ts
156
+ /** The root layout — its page options cover the whole app. */
157
+ const ROOT_LAYOUT_RE = /^src\/routes\/\+layout(\.server)?\.(ts|js)$/;
158
+ /** Files whose `ssr` export is a page option — it has no effect in `+server` endpoints or hooks files. */
159
+ const PAGE_OPTION_FILE_RE = /\+(page|layout)(\.server)?\.(ts|js)$/;
160
+ const appSsrCache = /* @__PURE__ */ new WeakMap();
161
+ /**
162
+ * Whether no page is ever server-rendered: the hash router, or a root layout exporting `ssr = false`
163
+ * that no page-option file might turn back on. Universal loads and components then run only in the
164
+ * browser; server files (`+page.server`, `+server`, hooks) still run on the server.
165
+ */
166
+ function appSsrDisabled(scope) {
167
+ if (scope.project?.kitHashRouter) return true;
168
+ const kitModules = scope.kitModules;
169
+ if (!kitModules) return false;
170
+ let hit = appSsrCache.get(kitModules);
171
+ if (hit === void 0) {
172
+ hit = kitModules.some((m) => m.ssrDisabled && ROOT_LAYOUT_RE.test(m.file)) && !kitModules.some((m) => PAGE_OPTION_FILE_RE.test(m.file) && (m.ssrEnabled || m.parseFailed));
173
+ appSsrCache.set(kitModules, hit);
174
+ }
175
+ return hit;
176
+ }
177
+ const ROUTES_DIR$1 = "src/routes";
178
+ /** A `+page`/`+layout` file: its directory, kind, and the `@segment` layout reset (on `.svelte` names). */
179
+ const ROUTE_FILE_RE = /^(src\/routes(?:\/.+)?)\/\+(page|layout)(?:@([^/]*))?(?:\.server)?\.(?:svelte|ts|js)$/;
180
+ const parentDir = (dir) => dir === ROUTES_DIR$1 ? void 0 : dir.slice(0, dir.lastIndexOf("/"));
181
+ /** The nearest ancestor-or-self directory a `@segment` reset names (`''` is the root). */
182
+ function resetTarget(dir, segment) {
183
+ for (let d = dir; d !== void 0; d = parentDir(d)) if (segment === "" ? d === ROUTES_DIR$1 : d.slice(d.lastIndexOf("/") + 1) === segment) return d;
184
+ }
185
+ const routeOptionCache = {
186
+ ssr: /* @__PURE__ */ new WeakMap(),
187
+ csr: /* @__PURE__ */ new WeakMap()
188
+ };
189
+ const optionOn = (m, option) => m.parseFailed || (option === "ssr" ? m.ssrEnabled : m.csrEnabled);
190
+ const optionOff = (m, option) => (option === "ssr" ? m.ssrDisabled : m.csrDisabled) !== void 0;
191
+ /**
192
+ * Per route directory, whether `ssr` or `csr` is off for its page (`page:<dir>`) or layout
193
+ * (`layout:<dir>`). A page takes the nearest export of the option along its own files and then its
194
+ * layout chain, following `+page@x`/`+layout@x` resets as SvelteKit does; an export other than
195
+ * `false` (or `dev`), or an unparsed option file, counts as on. A layout is off only when every page
196
+ * using it is — and the root layout also needs its own opt-out, since it wraps the 404 page.
197
+ */
198
+ function routeOptionOff(scope, which) {
199
+ const kitModules = scope.kitModules ?? [];
200
+ let hit = routeOptionCache[which].get(scope);
201
+ if (hit) return hit;
202
+ const own = {
203
+ universal: /* @__PURE__ */ new Map(),
204
+ server: /* @__PURE__ */ new Map()
205
+ };
206
+ for (const m of kitModules) {
207
+ const match = ROUTE_FILE_RE.exec(m.file);
208
+ if (!match) continue;
209
+ const key = `${match[2]}:${match[1]}`;
210
+ if (optionOn(m, which)) own[m.kind].set(key, "on");
211
+ else if (optionOff(m, which)) own[m.kind].set(key, "off");
212
+ }
213
+ const option = new Map([...own.server, ...own.universal]);
214
+ hit = /* @__PURE__ */ new Map();
215
+ for (const [dir, chain] of pageChains(scope)) {
216
+ const off = [`page:${dir}`, ...chain.map((l) => `layout:${l}`)].map((k) => option.get(k)).find(Boolean) === "off";
217
+ hit.set(`page:${dir}`, off);
218
+ for (const l of chain) hit.set(`layout:${l}`, (hit.get(`layout:${l}`) ?? true) && off);
219
+ }
220
+ if (option.get(`layout:${ROUTES_DIR$1}`) !== "off") hit.delete(`layout:${ROUTES_DIR$1}`);
221
+ routeOptionCache[which].set(scope, hit);
222
+ return hit;
223
+ }
224
+ const chainCache = /* @__PURE__ */ new WeakMap();
225
+ /** Per page directory, the layout directories that wrap it, nearest first, following `+page@x`/`+layout@x` resets as SvelteKit does. */
226
+ function pageChains(scope) {
227
+ let hit = chainCache.get(scope);
228
+ if (hit) return hit;
229
+ const pages = /* @__PURE__ */ new Map();
230
+ const layouts = /* @__PURE__ */ new Map();
231
+ const files = [
232
+ ...scope.sourceFiles ?? [],
233
+ ...(scope.components ?? []).map((c) => c.file),
234
+ ...(scope.kitModules ?? []).map((m) => m.file)
235
+ ];
236
+ for (const file of files) {
237
+ const match = ROUTE_FILE_RE.exec(file);
238
+ if (!match) continue;
239
+ const map = match[2] === "page" ? pages : layouts;
240
+ if (match[3] !== void 0 || !map.has(match[1])) map.set(match[1], match[3]);
241
+ }
242
+ hit = /* @__PURE__ */ new Map();
243
+ for (const [dir, reset] of pages) {
244
+ const chain = [];
245
+ for (let d = reset === void 0 ? dir : resetTarget(dir, reset); d !== void 0;) {
246
+ const layoutReset = layouts.get(d);
247
+ if (layouts.has(d)) chain.push(d);
248
+ d = layoutReset === void 0 ? parentDir(d) : resetTarget(parentDir(d), layoutReset);
249
+ }
250
+ hit.set(dir, chain);
251
+ }
252
+ chainCache.set(scope, hit);
253
+ return hit;
254
+ }
255
+ /** Whether a `+page` file's route sends requests without a session away: its own load or a wrapping layout's does (`loadGated`). */
256
+ function routeGated(file, scope) {
257
+ const match = ROUTE_FILE_RE.exec(file);
258
+ if (!match || match[2] !== "page") return false;
259
+ const gated = /* @__PURE__ */ new Set();
260
+ for (const m of scope.kitModules ?? []) {
261
+ const g = m.loadGated && m.kind === "server" ? ROUTE_FILE_RE.exec(m.file) : null;
262
+ if (g) gated.add(`${g[2]}:${g[1]}`);
263
+ }
264
+ if (gated.size === 0) return false;
265
+ const chain = pageChains(scope).get(match[1]) ?? [];
266
+ return gated.has(`page:${match[1]}`) || chain.some((l) => gated.has(`layout:${l}`));
267
+ }
268
+ /**
269
+ * Whether a `+page` file's route never renders because a layout above it redirects or errors in its
270
+ * `load` on every call (`loadNeverRenders`). Not the root layout: one that sent every request away
271
+ * would leave the app nothing to serve, so that reading is more likely wrong than the app, and acting
272
+ * on it would silence every route.
273
+ */
274
+ function layoutNeverRenders(file, scope) {
275
+ const match = ROUTE_FILE_RE.exec(file);
276
+ if (!match || match[2] !== "page") return false;
277
+ const dead = /* @__PURE__ */ new Set();
278
+ for (const m of scope.kitModules ?? []) {
279
+ const layout = m.loadNeverRenders ? ROUTE_FILE_RE.exec(m.file) : null;
280
+ if (layout?.[2] === "layout" && layout[1] !== ROUTES_DIR$1) dead.add(layout[1]);
281
+ }
282
+ return dead.size > 0 && (pageChains(scope).get(match[1]) ?? []).some((dir) => dead.has(dir));
283
+ }
284
+ /**
285
+ * Whether a `+page` file's route never renders because its own `load` redirects or errors on every
286
+ * request, a build-time `if (building)` aside (`loadNeverRendersOutsideBuild`), and nothing on its chain
287
+ * exports `prerender`, so the build never renders it either.
288
+ */
289
+ function pageRendersOnlyWhenBuilding(file, scope) {
290
+ const match = ROUTE_FILE_RE.exec(file);
291
+ if (!match || match[2] !== "page") return false;
292
+ const at = (m) => ROUTE_FILE_RE.exec(m.file);
293
+ const modules = scope.kitModules ?? [];
294
+ if (!modules.some((m) => m.loadNeverRendersOutsideBuild && at(m)?.[2] === "page" && at(m)?.[1] === match[1])) return false;
295
+ const layouts = /* @__PURE__ */ new Set([match[1], ...pageChains(scope).get(match[1]) ?? []]);
296
+ return !modules.some((m) => {
297
+ const where = m.prerenderExported ? at(m) : null;
298
+ return !!where && (where[2] === "page" ? where[1] === match[1] : layouts.has(where[1]));
299
+ });
300
+ }
301
+ /** Whether a `+page`/`+layout` file (module or component) belongs to a route that is never server-rendered. */
302
+ function routeNeverSsr(file, scope) {
303
+ const match = ROUTE_FILE_RE.exec(file);
304
+ return !!match && (appSsrDisabled(scope) || routeOptionOff(scope, "ssr").get(`${match[2]}:${match[1]}`) === true);
305
+ }
306
+ /** Whether a universal `+page`/`+layout` module never runs in the browser: every page it serves has `csr` off, its own or inherited. */
307
+ function universalNeverCsr(m, scope) {
308
+ if (m.kind !== "universal") return false;
309
+ const match = ROUTE_FILE_RE.exec(m.file);
310
+ return !!match && routeOptionOff(scope, "csr").get(`${match[2]}:${match[1]}`) === true;
311
+ }
312
+ /** Whether a universal `+page`/`+layout` module never runs on the server — its own `ssr = false`, an app-wide one, or one its route inherits. */
313
+ function universalNeverSsr(m, scope) {
314
+ return m.kind === "universal" && (m.ssrDisabled !== void 0 || appSsrDisabled(scope) || routeNeverSsr(m.file, scope));
315
+ }
316
+ //#endregion
155
317
  //#region src/rules/seo/head-tag-rule.ts
156
318
  /** Open Graph keys are read from `property=`; `name="og:*"` is the common slip. */
157
319
  function ogMisspelled(key) {
@@ -179,6 +341,7 @@ function detect(head, match) {
179
341
  value: "absent"
180
342
  };
181
343
  }
344
+ const GATED_NOTE = "Reported as info: this route's load turns away visitors without a session (a redirect or a 401/403), so search engines never index it.";
182
345
  /** Build a route-scope rule asserting the presence of a single head tag (design §11). */
183
346
  function headTagRule(opts) {
184
347
  const docsUrl = docsUrlFor(opts.id);
@@ -195,15 +358,16 @@ function headTagRule(opts) {
195
358
  const detection = detect(head, opts.match);
196
359
  const misspelled = detection.presence === "none" && opts.misspelled && head.tags.some(opts.misspelled.match) ? opts.misspelled : void 0;
197
360
  const message = misspelled ? misspelled.label : detection.presence === "none" ? `Missing ${opts.label}` : detection.value === "absent" ? `Empty ${opts.label}` : opts.label;
361
+ const gated = opts.infoWhenGated && routeGated(head.file, ctx);
198
362
  return {
199
363
  id: opts.id,
200
364
  category: "seo",
201
- severity: opts.severity,
365
+ severity: gated ? "info" : opts.severity,
202
366
  detection,
203
367
  route: head.route,
204
368
  location: head.file,
205
369
  message,
206
- recommendation: misspelled?.recommendation ?? opts.recommendation,
370
+ recommendation: misspelled?.recommendation ?? (gated ? `${opts.recommendation} ${GATED_NOTE}` : opts.recommendation),
207
371
  docsUrl,
208
372
  ...misspelled ? { fix: { ...misspelled.fix } } : opts.fix ? { fix: { ...opts.fix } } : {}
209
373
  };
@@ -217,6 +381,7 @@ const seoDescriptionPresence = headTagRule({
217
381
  id: "seo/description-presence",
218
382
  title: "Description presence",
219
383
  severity: "warning",
384
+ infoWhenGated: true,
220
385
  match: (t) => t.kind === "meta" && t.name === "description",
221
386
  label: "<meta name=\"description\">",
222
387
  recommendation: "Add a <meta name=\"description\"> in <svelte:head>, or set the description on your meta component.",
@@ -228,12 +393,50 @@ const seoDescriptionPresence = headTagRule({
228
393
  }
229
394
  });
230
395
  //#endregion
396
+ //#region src/head.ts
397
+ /** Whether a `<link>`'s `rel` holds `token`: `rel` is a set of space-separated keywords (`dns-prefetch preconnect`). */
398
+ function hasRel(tag, token) {
399
+ return tag.rel?.split(/\s+/).includes(token) ?? false;
400
+ }
401
+ const JS_MIME_TYPES = /* @__PURE__ */ new Set([
402
+ "application/ecmascript",
403
+ "application/javascript",
404
+ "application/x-ecmascript",
405
+ "application/x-javascript",
406
+ "text/ecmascript",
407
+ "text/javascript",
408
+ "text/javascript1.0",
409
+ "text/javascript1.1",
410
+ "text/javascript1.2",
411
+ "text/javascript1.3",
412
+ "text/javascript1.4",
413
+ "text/javascript1.5",
414
+ "text/jscript",
415
+ "text/livescript",
416
+ "text/x-ecmascript",
417
+ "text/x-javascript"
418
+ ]);
419
+ /** HTML strips only ASCII whitespace from a `type` before matching — `trim()` would also strip
420
+ * U+00A0 etc. and misread a data block as a classic script. */
421
+ const ASCII_WHITESPACE_EDGES = /^[\t\n\f\r ]+|[\t\n\f\r ]+$/g;
422
+ /**
423
+ * Whether a `type` attribute (undefined = absent) makes a `<script>` a classic,
424
+ * render-blocking-capable script. Per the HTML spec, absent and the literal empty string are
425
+ * classic; any other value is ASCII-whitespace-stripped and must match a JavaScript MIME type —
426
+ * so a whitespace-only `type` is a data block, not a classic script.
427
+ */
428
+ function isClassicScriptType(type) {
429
+ if (type === void 0 || type === "") return true;
430
+ return JS_MIME_TYPES.has(type.replace(ASCII_WHITESPACE_EDGES, "").toLowerCase());
431
+ }
432
+ //#endregion
231
433
  //#region src/rules/seo/canonical-url.ts
232
434
  const seoCanonicalUrl = headTagRule({
233
435
  id: "seo/canonical-url",
234
436
  title: "Canonical URL",
235
437
  severity: "warning",
236
- match: (t) => t.kind === "link" && t.rel === "canonical",
438
+ infoWhenGated: true,
439
+ match: (t) => t.kind === "link" && hasRel(t, "canonical"),
237
440
  label: "<link rel=\"canonical\">",
238
441
  recommendation: "Add <link rel=\"canonical\"> in <svelte:head>, or set the canonical prop on your meta component.",
239
442
  rationale: "A canonical URL tells search engines which URL is authoritative, preventing duplicate-content dilution across query-string variants of the same page.",
@@ -557,7 +760,7 @@ const performancePreloadMissingAs = linkRule({
557
760
  snippet: "<link rel=\"preload\" href=\"/app.css\" as=\"style\" />",
558
761
  lang: "html"
559
762
  },
560
- relevant: (t) => t.rel === "preload",
763
+ relevant: (t) => hasRel(t, "preload"),
561
764
  ok: (t) => t.hasAs === true
562
765
  });
563
766
  //#endregion
@@ -574,7 +777,7 @@ const performanceFontPreloadCrossorigin = linkRule({
574
777
  snippet: "<link rel=\"preload\" href=\"/inter.woff2\" as=\"font\" type=\"font/woff2\" crossorigin />",
575
778
  lang: "html"
576
779
  },
577
- relevant: (t) => t.rel === "preload" && t.as === "font",
780
+ relevant: (t) => hasRel(t, "preload") && t.as === "font",
578
781
  ok: (t) => t.hasCrossorigin === true
579
782
  });
580
783
  //#endregion
@@ -663,7 +866,8 @@ const performanceRenderBlockingScript = {
663
866
  async check(ctx) {
664
867
  const out = [];
665
868
  for (const head of ctx.heads) {
666
- const scripts = head.tags.filter((t) => t.kind === "script");
869
+ const clientHead = routeNeverSsr(head.file, ctx);
870
+ const scripts = head.tags.filter((t) => t.kind === "script" && !(clientHead && t.file !== "src/app.html"));
667
871
  if (scripts.length === 0) continue;
668
872
  const blocking = scripts.filter((t) => t.blocking);
669
873
  if (blocking.length > 0) for (const tag of blocking) out.push({
@@ -1175,7 +1379,7 @@ const performancePreconnect = {
1175
1379
  if (tag.kind !== "link" && tag.kind !== "script" || typeof tag.href !== "string") continue;
1176
1380
  const host = hostOf(tag.href);
1177
1381
  if (!host) continue;
1178
- if (tag.kind === "link" && (tag.rel === "preconnect" || tag.rel === "dns-prefetch")) {
1382
+ if (tag.kind === "link" && (hasRel(tag, "preconnect") || hasRel(tag, "dns-prefetch"))) {
1179
1383
  covered.add(host);
1180
1384
  continue;
1181
1385
  }
@@ -2060,7 +2264,7 @@ const seoHreflang = {
2060
2264
  async check(ctx) {
2061
2265
  const out = [];
2062
2266
  for (const head of ctx.heads) {
2063
- const alternates = head.tags.filter((t) => t.kind === "link" && t.rel === "alternate" && typeof t.hreflang === "string");
2267
+ const alternates = head.tags.filter((t) => t.kind === "link" && hasRel(t, "alternate") && typeof t.hreflang === "string");
2064
2268
  if (alternates.length === 0) continue;
2065
2269
  const values = alternates.map((t) => t.hreflang);
2066
2270
  const badTag = alternates.find((t) => !isValidHreflang(t.hreflang));
@@ -2342,7 +2546,7 @@ const multipleRecommendation = "A single, clear <h1> is the conventional signal
2342
2546
  * `<svelte:boundary>` contribute only their largest arm. Separate blocks still sum, since both may be true.
2343
2547
  */
2344
2548
  function renderableH1s(headings) {
2345
- const nodes = headings.filter((h) => h.level === 1).map((heading) => ({
2549
+ const nodes = headings.filter((h) => h.level === 1 && !h.aria).map((heading) => ({
2346
2550
  key: "h1",
2347
2551
  path: heading.path ?? [],
2348
2552
  repeatable: false,
@@ -2371,7 +2575,7 @@ const seoSingleH1 = {
2371
2575
  severity: "warning",
2372
2576
  recommendation: missingRecommendation
2373
2577
  };
2374
- const first = route.headings[0];
2578
+ const first = route.headings.find((h) => !h.aria);
2375
2579
  if (first) where = {
2376
2580
  location: first.file,
2377
2581
  ...first.line > 0 ? { line: first.line } : {}
@@ -2496,6 +2700,12 @@ const seoDuplicateDescription = uniquenessRule({
2496
2700
  });
2497
2701
  //#endregion
2498
2702
  //#region src/headings.ts
2703
+ /** Sort comparator for `order`: element by element, a prefix first; headings without one keep their place. */
2704
+ function documentOrder(a, b) {
2705
+ if (!a.order || !b.order) return 0;
2706
+ for (let i = 0; i < Math.min(a.order.length, b.order.length); i++) if (a.order[i] !== b.order[i]) return a.order[i] - b.order[i];
2707
+ return a.order.length - b.order.length;
2708
+ }
2499
2709
  /** Two headings can never render together: different arms of one block. */
2500
2710
  function exclusiveHeadings(a, b) {
2501
2711
  if (!a.path || !b.path) return false;
@@ -2504,6 +2714,8 @@ function exclusiveHeadings(a, b) {
2504
2714
  //#endregion
2505
2715
  //#region src/rules/seo/heading-level-skip.ts
2506
2716
  const docsUrl$5 = docsUrlFor("seo/heading-level-skip");
2717
+ /** Every arm `p` sits in that can be skipped, `h` sits in too: whenever `h` renders, so does `p` (or one like it). */
2718
+ const rendersWith = (p, h) => (p.path ?? []).every((s) => s.always || (h.path ?? []).some((t) => t.group === s.group && t.branch === s.branch));
2507
2719
  const recommendation$5 = "Increase heading levels one step at a time (do not jump, e.g. from <h2> straight to <h4>).";
2508
2720
  /**
2509
2721
  * seo/heading-level-skip — Skipped heading level. Walking a route's body headings in
@@ -2522,13 +2734,33 @@ const seoHeadingLevelSkip = {
2522
2734
  const out = [];
2523
2735
  for (const route of ctx.headings ?? []) {
2524
2736
  if (route.headings.length === 0) continue;
2737
+ const placed = (route.componentHeadings ?? []).filter((h) => h.order);
2738
+ const own = new Set(route.headings);
2739
+ const headings = [...route.headings, ...placed].sort(documentOrder);
2525
2740
  let skip;
2526
- for (let i = 1; i < route.headings.length && !skip; i++) {
2527
- const h = route.headings[i];
2528
- const prev = route.headings.slice(0, i).reverse().find((p) => !exclusiveHeadings(p, h));
2529
- if (prev && h.level > prev.level + 1) skip = {
2741
+ for (let i = 1; i < headings.length && !skip; i++) {
2742
+ const h = headings[i];
2743
+ if (!own.has(h)) continue;
2744
+ const before = headings.slice(0, i).filter((p) => !exclusiveHeadings(p, h) && (own.has(p) || rendersWith(p, h)));
2745
+ const prev = before.at(-1);
2746
+ if (!prev) continue;
2747
+ const levels = [prev.level];
2748
+ const arms = /* @__PURE__ */ new Set();
2749
+ const looped = prev.path?.some((s) => s.repeat && !h.path?.some((t) => t.group === s.group));
2750
+ for (let j = looped ? -1 : before.length - 2; j >= 0; j--) {
2751
+ const q = before[j];
2752
+ const step = q.path?.find((s) => prev.path?.some((t) => t.group === s.group && t.branch !== s.branch));
2753
+ const arm = step && `${step.group}:${step.branch}`;
2754
+ if (arm && !arms.has(arm)) {
2755
+ arms.add(arm);
2756
+ levels.push(q.level);
2757
+ }
2758
+ }
2759
+ const known = levels.filter((level) => level > 0);
2760
+ const lowest = Math.min(...known);
2761
+ if (known.length === levels.length && h.level > lowest + 1) skip = {
2530
2762
  level: h.level,
2531
- prev: prev.level,
2763
+ prev: lowest,
2532
2764
  line: h.line,
2533
2765
  file: h.file
2534
2766
  };
@@ -2661,93 +2893,6 @@ function kitModuleRule(opts) {
2661
2893
  });
2662
2894
  }
2663
2895
  //#endregion
2664
- //#region src/kit-module.ts
2665
- /** The root layout — its page options cover the whole app. */
2666
- const ROOT_LAYOUT_RE = /^src\/routes\/\+layout(\.server)?\.(ts|js)$/;
2667
- /** Files whose `ssr` export is a page option — it has no effect in `+server` endpoints or hooks files. */
2668
- const PAGE_OPTION_FILE_RE = /\+(page|layout)(\.server)?\.(ts|js)$/;
2669
- const appSsrCache = /* @__PURE__ */ new WeakMap();
2670
- /**
2671
- * Whether no page is ever server-rendered: the root layout exports `ssr = false` and no page-option
2672
- * file might turn it back on. Universal loads and components then run only in the browser; server
2673
- * files (`+page.server`, `+server`, hooks) still run on the server.
2674
- */
2675
- function appSsrDisabled(kitModules) {
2676
- if (!kitModules) return false;
2677
- let hit = appSsrCache.get(kitModules);
2678
- if (hit === void 0) {
2679
- hit = kitModules.some((m) => m.ssrDisabled && ROOT_LAYOUT_RE.test(m.file)) && !kitModules.some((m) => PAGE_OPTION_FILE_RE.test(m.file) && (m.ssrEnabled || m.parseFailed));
2680
- appSsrCache.set(kitModules, hit);
2681
- }
2682
- return hit;
2683
- }
2684
- const ROUTES_DIR$1 = "src/routes";
2685
- /** A `+page`/`+layout` file: its directory, kind, and the `@segment` layout reset (on `.svelte` names). */
2686
- const ROUTE_FILE_RE = /^(src\/routes(?:\/.+)?)\/\+(page|layout)(?:@([^/]*))?(?:\.server)?\.(?:svelte|ts|js)$/;
2687
- const parentDir = (dir) => dir === ROUTES_DIR$1 ? void 0 : dir.slice(0, dir.lastIndexOf("/"));
2688
- /** The nearest ancestor-or-self directory a `@segment` reset names (`''` is the root). */
2689
- function resetTarget(dir, segment) {
2690
- for (let d = dir; d !== void 0; d = parentDir(d)) if (segment === "" ? d === ROUTES_DIR$1 : d.slice(d.lastIndexOf("/") + 1) === segment) return d;
2691
- }
2692
- const routeSsrCache = /* @__PURE__ */ new WeakMap();
2693
- /**
2694
- * Per route directory, whether its page (`page:<dir>`) or layout (`layout:<dir>`) is never
2695
- * server-rendered. A page takes the nearest `ssr` export along its own files and then its layout
2696
- * chain, following `+page@x`/`+layout@x` resets as SvelteKit does; an export other than a literal
2697
- * `false`, or an unparsed option file, counts as SSR on. A layout is off only when every page using
2698
- * it is — and the root layout also needs its own `ssr = false`, since it wraps the 404 page.
2699
- */
2700
- function routeSsrOff(scope) {
2701
- const kitModules = scope.kitModules ?? [];
2702
- let hit = routeSsrCache.get(scope);
2703
- if (hit) return hit;
2704
- const pages = /* @__PURE__ */ new Map();
2705
- const layouts = /* @__PURE__ */ new Map();
2706
- const files = [
2707
- ...scope.sourceFiles ?? [],
2708
- ...(scope.components ?? []).map((c) => c.file),
2709
- ...kitModules.map((m) => m.file)
2710
- ];
2711
- for (const file of files) {
2712
- const match = ROUTE_FILE_RE.exec(file);
2713
- if (!match) continue;
2714
- const map = match[2] === "page" ? pages : layouts;
2715
- if (match[3] !== void 0 || !map.has(match[1])) map.set(match[1], match[3]);
2716
- }
2717
- const option = /* @__PURE__ */ new Map();
2718
- for (const m of kitModules) {
2719
- const match = ROUTE_FILE_RE.exec(m.file);
2720
- if (!match) continue;
2721
- const key = `${match[2]}:${match[1]}`;
2722
- if (m.ssrEnabled || m.parseFailed) option.set(key, "on");
2723
- else if (m.ssrDisabled && !option.has(key)) option.set(key, "off");
2724
- }
2725
- hit = /* @__PURE__ */ new Map();
2726
- for (const [dir, reset] of pages) {
2727
- const chain = [];
2728
- for (let d = reset === void 0 ? dir : resetTarget(dir, reset); d !== void 0;) {
2729
- const layoutReset = layouts.get(d);
2730
- if (layouts.has(d)) chain.push(d);
2731
- d = layoutReset === void 0 ? parentDir(d) : resetTarget(parentDir(d), layoutReset);
2732
- }
2733
- const off = [`page:${dir}`, ...chain.map((l) => `layout:${l}`)].map((k) => option.get(k)).find(Boolean) === "off";
2734
- hit.set(`page:${dir}`, off);
2735
- for (const l of chain) hit.set(`layout:${l}`, (hit.get(`layout:${l}`) ?? true) && off);
2736
- }
2737
- if (option.get(`layout:${ROUTES_DIR$1}`) !== "off") hit.delete(`layout:${ROUTES_DIR$1}`);
2738
- routeSsrCache.set(scope, hit);
2739
- return hit;
2740
- }
2741
- /** Whether a `+page`/`+layout` file (module or component) belongs to a route that is never server-rendered. */
2742
- function routeNeverSsr(file, scope) {
2743
- const match = ROUTE_FILE_RE.exec(file);
2744
- return !!match && routeSsrOff(scope).get(`${match[2]}:${match[1]}`) === true;
2745
- }
2746
- /** Whether a universal `+page`/`+layout` module never runs on the server — its own `ssr = false`, an app-wide one, or one its route inherits. */
2747
- function universalNeverSsr(m, scope) {
2748
- return m.kind === "universal" && (m.ssrDisabled !== void 0 || appSsrDisabled(scope.kitModules) || routeNeverSsr(m.file, scope));
2749
- }
2750
- //#endregion
2751
2896
  //#region src/rules/seo/ssr-disabled.ts
2752
2897
  const seoSsrDisabled = kitModuleRule({
2753
2898
  id: "seo/ssr-disabled",
@@ -3244,7 +3389,7 @@ const correctnessInstanceBrowserGlobal = componentRule({
3244
3389
  label: "Server-safe component init",
3245
3390
  recommendation: "Move browser-only code into onMount or $effect (they never run on the server), or guard it with browser from $app/environment (or a typeof check).",
3246
3391
  rationale: "A component instance script runs on the server on every SSR render, where window/document/localStorage do not exist. Warning, not critical: a component rendered only behind a parent {#if browser} (or a client-only dynamic import) is a legitimate pattern that static analysis cannot prove cross-file.",
3247
- applies: (c, _o, ctx) => (c.browserGlobalRefs ?? []).some((r) => r.context === "instance") && !appSsrDisabled(ctx.kitModules) && !routeNeverSsr(c.file, ctx),
3392
+ applies: (c, _o, ctx) => (c.browserGlobalRefs ?? []).some((r) => r.context === "instance") && !appSsrDisabled(ctx) && !routeNeverSsr(c.file, ctx),
3248
3393
  bad: (c) => (c.browserGlobalRefs ?? []).filter((r) => r.context === "instance").map((r) => ({
3249
3394
  line: r.line,
3250
3395
  message: `${r.name} is accessed during component initialisation — during SSR this runs on the server, where ${r.name} is not defined`
@@ -3432,7 +3577,8 @@ const PREPROCESSED_STYLE_RE = /(<style\b[^>]*\slang\s*=\s*['"]?[^'"\s>]+['"]?[^>
3432
3577
  * the text scan's imprecision cannot reach it. A file that does not parse is already a hard failure,
3433
3578
  * which the retry can only improve on.
3434
3579
  */
3435
- function parseSvelte(source, filename) {
3580
+ function parseSvelte(raw, filename) {
3581
+ const source = raw.charCodeAt(0) === 65279 ? ` ${raw.slice(1)}` : raw;
3436
3582
  try {
3437
3583
  return parse(source, {
3438
3584
  modern: true,
@@ -3498,12 +3644,25 @@ function attrValue(attributes, name) {
3498
3644
  const attr = findAttr(attributes, name);
3499
3645
  return attr ? attrValueOf(attr) : "absent";
3500
3646
  }
3647
+ const lineStarts = /* @__PURE__ */ new Map();
3501
3648
  function lineOf(source, offset) {
3502
3649
  if (typeof offset !== "number" || offset < 0) return 0;
3503
- let line = 1;
3650
+ let starts = lineStarts.get(source);
3651
+ if (!starts) {
3652
+ starts = [0];
3653
+ for (let i = source.indexOf("\n"); i !== -1; i = source.indexOf("\n", i + 1)) starts.push(i + 1);
3654
+ if (lineStarts.size >= 8) lineStarts.delete(lineStarts.keys().next().value);
3655
+ lineStarts.set(source, starts);
3656
+ }
3504
3657
  const end = Math.min(offset, source.length);
3505
- for (let i = 0; i < end; i++) if (source[i] === "\n") line++;
3506
- return line;
3658
+ let lo = 0;
3659
+ let hi = starts.length;
3660
+ while (lo < hi) {
3661
+ const mid = lo + hi >> 1;
3662
+ if (starts[mid] <= end) lo = mid + 1;
3663
+ else hi = mid;
3664
+ }
3665
+ return lo;
3507
3666
  }
3508
3667
  /**
3509
3668
  * An element's attribute by name, matched **case-insensitively**: HTML attribute names are, so
@@ -3536,7 +3695,7 @@ function attrTextOf(attr) {
3536
3695
  //#region src/module-ast.ts
3537
3696
  function unwrapTs(expr) {
3538
3697
  let cur = expr;
3539
- while (cur !== void 0 && (cur.type === "TSSatisfiesExpression" || cur.type === "TSAsExpression" || cur.type === "TSNonNullExpression")) cur = cur.expression;
3698
+ while (cur != null && (cur.type === "TSSatisfiesExpression" || cur.type === "TSAsExpression" || cur.type === "TSNonNullExpression")) cur = cur.expression;
3540
3699
  return cur;
3541
3700
  }
3542
3701
  /** AST metadata keys every walker skips — never traversed as child nodes. Shared with the Kit-module parser (the security kit-module rules). */
@@ -3589,8 +3748,8 @@ function addBoundNames(id, acc) {
3589
3748
  }
3590
3749
  /** The base identifier name of a (possibly nested) member expression or identifier, else undefined. Shared with the Kit-module parser (the security kit-module rules). */
3591
3750
  function rootObjectName(node) {
3592
- let cur = node;
3593
- while (cur?.type === "MemberExpression") cur = cur.object;
3751
+ let cur = unwrapTs(node);
3752
+ while (cur?.type === "MemberExpression") cur = unwrapTs(cur.object);
3594
3753
  return cur?.type === "Identifier" ? cur.name : void 0;
3595
3754
  }
3596
3755
  /**
@@ -3842,6 +4001,11 @@ function collectProgramBindings(program) {
3842
4001
  for (const s of stmt.specifiers ?? []) if (s?.local?.type === "Identifier") bound.add(s.local.name);
3843
4002
  continue;
3844
4003
  }
4004
+ if (stmt?.type === "LabeledStatement" && stmt.label?.name === "$") {
4005
+ const e = stmt.body?.type === "ExpressionStatement" ? stmt.body.expression : void 0;
4006
+ if (e?.type === "AssignmentExpression" && e.operator === "=") addBoundNames(e.left, bound);
4007
+ continue;
4008
+ }
3845
4009
  const decl = unwrapExport(stmt);
3846
4010
  if (decl?.type === "VariableDeclaration") for (const d of decl.declarations ?? []) addBoundNames(d?.id, bound);
3847
4011
  else if ((decl?.type === "FunctionDeclaration" || decl?.type === "ClassDeclaration") && decl.id?.type === "Identifier") bound.add(decl.id.name);
@@ -3886,6 +4050,13 @@ function isBrowserGuardTest(test, guardBindings) {
3886
4050
  });
3887
4051
  return guarded;
3888
4052
  }
4053
+ /** The tracked global `G` of a `typeof G === 'undefined'` test: the test is false only where `G` is defined. */
4054
+ function missingGlobalOf(n) {
4055
+ if (n?.type !== "BinaryExpression" || n.operator !== "===" && n.operator !== "==") return void 0;
4056
+ const sides = [n.left, n.right];
4057
+ if (!sides.some((s) => s?.type === "Literal" && s.value === "undefined")) return void 0;
4058
+ return sides.find((s) => s?.type === "UnaryExpression" && s.operator === "typeof" && s.argument?.type === "Identifier" && BROWSER_GLOBALS.has(s.argument.name))?.argument.name;
4059
+ }
3889
4060
  /**
3890
4061
  * Names of derived guard bindings (one level, no fixpoint chasing): a top-level
3891
4062
  * (export-unwrapped) `const`/`let` declarator whose init passes `isBrowserGuardTest` —
@@ -3905,6 +4076,95 @@ function collectDerivedGuardBindings(program, guards) {
3905
4076
  }
3906
4077
  return derived;
3907
4078
  }
4079
+ const DOM_ONLY_GLOBALS = /* @__PURE__ */ new Set(["window", "document"]);
4080
+ /**
4081
+ * Unlike `isBrowserGuardTest` this keeps the polarity and each outcome apart, because a write the
4082
+ * guard leaves reachable on the server must still be reported: `!browser && url.search` being falsy
4083
+ * proves nothing.
4084
+ */
4085
+ function guardImplies(test, guards, shadowed) {
4086
+ if (test?.type === "Identifier") return !shadowed.has(test.name) && guards.get(test.name) || {};
4087
+ if (test?.type === "UnaryExpression" && test.operator === "!") {
4088
+ const inner = guardImplies(test.argument, guards, shadowed);
4089
+ return {
4090
+ truthy: inner.falsy,
4091
+ falsy: inner.truthy
4092
+ };
4093
+ }
4094
+ if (test?.type === "LogicalExpression" && (test.operator === "&&" || test.operator === "||")) {
4095
+ const a = guardImplies(test.left, guards, shadowed);
4096
+ const b = guardImplies(test.right, guards, shadowed);
4097
+ const agree = (x, y) => x === y ? x : void 0;
4098
+ return test.operator === "&&" ? {
4099
+ truthy: a.truthy ?? b.truthy,
4100
+ falsy: agree(a.falsy, b.falsy)
4101
+ } : {
4102
+ truthy: agree(a.truthy, b.truthy),
4103
+ falsy: a.falsy ?? b.falsy
4104
+ };
4105
+ }
4106
+ if (test?.type === "BinaryExpression" && [
4107
+ "===",
4108
+ "!==",
4109
+ "==",
4110
+ "!="
4111
+ ].includes(test.operator)) {
4112
+ const sides = [test.left, test.right];
4113
+ const hasTypeofGlobal = sides.some((s) => s?.type === "UnaryExpression" && s.operator === "typeof" && s.argument?.type === "Identifier" && DOM_ONLY_GLOBALS.has(s.argument.name) && !shadowed.has(s.argument.name));
4114
+ const type = sides.find((s) => s?.type === "Literal" && typeof s.value === "string")?.value;
4115
+ if (!hasTypeofGlobal || type === void 0) return {};
4116
+ const equal = test.operator === "===" || test.operator === "==";
4117
+ if (type === "undefined") return equal ? {
4118
+ truthy: "server",
4119
+ falsy: "browser"
4120
+ } : {
4121
+ truthy: "browser",
4122
+ falsy: "server"
4123
+ };
4124
+ return equal ? { truthy: "browser" } : { falsy: "browser" };
4125
+ }
4126
+ return {};
4127
+ }
4128
+ /**
4129
+ * Source ranges only the browser reaches: the arm of an `if`/ternary its browser guard selects, the
4130
+ * right side of `browser && x` or `!browser || x`, and the statements after a terminating early return
4131
+ * the server always takes (`if (!browser) return;`) in their block. A top-level `const` derived from a
4132
+ * guard (`const serverOnly = !browser`) carries its own polarity.
4133
+ */
4134
+ function browserOnlyRanges(program, envGuards) {
4135
+ const guards = new Map([...envGuards].map((name) => [name, {
4136
+ truthy: "browser",
4137
+ falsy: "server"
4138
+ }]));
4139
+ const bound = collectProgramBindings(program);
4140
+ for (const name of envGuards) bound.delete(name);
4141
+ for (const stmt of program.body ?? []) {
4142
+ const decl = unwrapExport(stmt);
4143
+ if (decl?.type !== "VariableDeclaration" || decl.kind !== "const") continue;
4144
+ for (const d of decl.declarations ?? []) {
4145
+ const implied = d?.id?.type === "Identifier" && d.init ? guardImplies(d.init, guards, bound) : {};
4146
+ if (implied.truthy || implied.falsy) guards.set(d.id.name, implied);
4147
+ }
4148
+ }
4149
+ for (const name of guards.keys()) bound.delete(name);
4150
+ const out = [];
4151
+ const push = (n) => n && out.push([n.start, n.end]);
4152
+ walkScoped(program, (n, shadowed) => {
4153
+ const implies = (test) => guardImplies(test, guards, shadowed);
4154
+ if (n.type === "IfStatement" || n.type === "ConditionalExpression") {
4155
+ const implied = implies(n.test);
4156
+ if (implied.truthy === "browser") push(n.consequent);
4157
+ if (implied.falsy === "browser") push(n.alternate);
4158
+ } else if (n.type === "LogicalExpression" && (n.operator === "&&" || n.operator === "||")) {
4159
+ if (implies(n.left)[n.operator === "&&" ? "truthy" : "falsy"] === "browser") push(n.right);
4160
+ } else if (n.type === "BlockStatement" || n.type === "Program") {
4161
+ const body = n.body ?? [];
4162
+ const at = body.findIndex((stmt) => stmt?.type === "IfStatement" && implies(stmt.test).falsy === "browser" && guardTerminates(stmt.consequent));
4163
+ if (at >= 0 && at < body.length - 1) out.push([body[at + 1].start, body[body.length - 1].end]);
4164
+ }
4165
+ }, bound);
4166
+ return out;
4167
+ }
3908
4168
  /**
3909
4169
  * Browser-global reads in code that executes when `program` (or a passed function body)
3910
4170
  * is evaluated (correctness/server-browser-global, correctness/instance-browser-global). Position-aware — only read positions match: never a
@@ -3940,6 +4200,12 @@ function collectBrowserGlobalRefs(program, source, extra) {
3940
4200
  if (EVAL_SCOPE_BOUNDARIES.has(n.type)) return;
3941
4201
  if ((n.type === "IfStatement" || n.type === "ConditionalExpression") && isBrowserGuardTest(n.test, guards)) return;
3942
4202
  if (n.type === "LogicalExpression" && (n.operator === "&&" ? isBrowserGuardTest(n.left, guards) : n.left?.type === "UnaryExpression" && n.left.operator === "!" && isBrowserGuardTest(n.left.argument, guards))) return;
4203
+ const missing = n.type === "LogicalExpression" && n.operator === "||" ? missingGlobalOf(n.left) : void 0;
4204
+ if (missing && !bound.has(missing) && !shadowed.has(missing)) {
4205
+ visit(n.left, shadowed);
4206
+ if (!DOM_ONLY_GLOBALS.has(missing)) visit(n.right, /* @__PURE__ */ new Set([...shadowed, missing]));
4207
+ return;
4208
+ }
3943
4209
  const introduced = scopeIntroducedNames(n);
3944
4210
  const scope = introduced.size > 0 ? /* @__PURE__ */ new Set([...shadowed, ...introduced]) : shadowed;
3945
4211
  switch (n.type) {
@@ -4034,6 +4300,11 @@ const HANDLER_NAMES = /* @__PURE__ */ new Set([
4034
4300
  "OPTIONS",
4035
4301
  "fallback"
4036
4302
  ]);
4303
+ /** Whether `arg` can be a store updater: a function literal, or a function this module declares. */
4304
+ function isUpdaterArgument(arg, localFunctions) {
4305
+ const e = arg ? unwrapTs(arg) : void 0;
4306
+ return isFunctionNode(e) || e?.type === "Identifier" && localFunctions.has(e.name);
4307
+ }
4037
4308
  function isFunctionNode(n) {
4038
4309
  return n?.type === "FunctionDeclaration" || n?.type === "FunctionExpression" || n?.type === "ArrowFunctionExpression";
4039
4310
  }
@@ -4135,7 +4406,7 @@ function collectStartupFunctions(program) {
4135
4406
  }
4136
4407
  /**
4137
4408
  * The `export const ssr = false` / `export const csr = false` opt-out, when
4138
- * present: inline form (`satisfies`/`as` unwrapped) or same-file alias export
4409
+ * present (`= dev` too): inline form (`satisfies`/`as` unwrapped) or same-file alias export
4139
4410
  * (`const ssr = false; export { ssr };`). Returns the declaration's line in the
4140
4411
  * WRAPPED source (the caller applies the −1 shift). An `ssr = false` file never
4141
4412
  * runs on the server — correctness/server-browser-global skips its browser-global scan, and seo/ssr-disabled
@@ -4143,10 +4414,27 @@ function collectStartupFunctions(program) {
4143
4414
  * performance/load-waterfall exempts it (see `csrDisabled` on `KitModuleFacts`).
4144
4415
  */
4145
4416
  function findFalseOptOut(program, source, name) {
4417
+ const dev = collectNamedImportAliases(program, "$app/environment", DEV_NAMES);
4146
4418
  let hit;
4147
4419
  forEachNamedExport(program, (exported, value, anchor) => {
4148
- if (exported !== name || value?.type !== "Literal" || value.value !== false) return void 0;
4149
- hit = { line: lineOf(source, anchor.start) };
4420
+ const literal = value?.type === "Literal" && value.value === false;
4421
+ if (exported !== name || !(literal || value?.type === "Identifier" && dev.has(value.name))) return void 0;
4422
+ hit = {
4423
+ line: lineOf(source, anchor.start),
4424
+ literal
4425
+ };
4426
+ return true;
4427
+ });
4428
+ return hit;
4429
+ }
4430
+ const DEV_NAMES = /* @__PURE__ */ new Set(["dev"]);
4431
+ const BUILDING_NAMES = /* @__PURE__ */ new Set(["building"]);
4432
+ /** Whether the module exports `name` as the literal `true` (inline or same-file alias export). */
4433
+ function exportsTrue(program, name) {
4434
+ let hit = false;
4435
+ forEachNamedExport(program, (exported, value) => {
4436
+ if (exported !== name) return void 0;
4437
+ hit = value?.type === "Literal" && value.value === true;
4150
4438
  return true;
4151
4439
  });
4152
4440
  return hit;
@@ -4197,8 +4485,18 @@ const MUTATING_METHODS = /* @__PURE__ */ new Set([
4197
4485
  "add",
4198
4486
  "set"
4199
4487
  ]);
4488
+ const ITERATING_METHODS = /* @__PURE__ */ new Set([
4489
+ "forEach",
4490
+ "map",
4491
+ "flatMap",
4492
+ "filter",
4493
+ "some",
4494
+ "every",
4495
+ "reduce"
4496
+ ]);
4200
4497
  const REDIRECT_NAMES = /* @__PURE__ */ new Set(["redirect"]);
4201
4498
  const EXIT_NAMES = /* @__PURE__ */ new Set(["redirect", "error"]);
4499
+ const ERROR_NAMES = /* @__PURE__ */ new Set(["error"]);
4202
4500
  /**
4203
4501
  * Root-relative `redirect(status, '/…')` targets (correctness/base-path-navigation). Argument 1
4204
4502
  * is the location (argument 0 is the status). Only a plain string literal counts, which
@@ -4218,6 +4516,116 @@ function collectRedirectCalls(node, locals, out = []) {
4218
4516
  }
4219
4517
  return out;
4220
4518
  }
4519
+ /**
4520
+ * The string literals a `transformPageChunk` option replaces in the page HTML (`html.replace(TOKEN, …)`,
4521
+ * `.replaceAll`), wherever the file passes one to `resolve`, a same-file `const` holding the string
4522
+ * included. A token the hook builds at run time is not read.
4523
+ */
4524
+ function pageChunkTokens(program) {
4525
+ const consts = /* @__PURE__ */ new Map();
4526
+ for (const stmt of program.body ?? []) {
4527
+ const decl = stmt?.type === "ExportNamedDeclaration" ? stmt.declaration : stmt;
4528
+ if (decl?.type !== "VariableDeclaration" || decl.kind !== "const") continue;
4529
+ for (const d of decl.declarations ?? []) {
4530
+ const init = unwrapTs(d.init);
4531
+ if (d.id?.type === "Identifier" && init?.type === "Literal" && typeof init.value === "string") consts.set(d.id.name, init.value);
4532
+ }
4533
+ }
4534
+ const out = /* @__PURE__ */ new Set();
4535
+ const visit = (n, inChunk) => {
4536
+ if (Array.isArray(n)) return n.forEach((c) => visit(c, inChunk));
4537
+ if (!n || typeof n !== "object" || typeof n.type !== "string") return;
4538
+ const chunk = inChunk || n.type === "Property" && !n.computed && n.key?.type === "Identifier" && n.key.name === "transformPageChunk";
4539
+ if (chunk && n.type === "CallExpression" && n.callee?.type === "MemberExpression" && !n.callee.computed) {
4540
+ const method = n.callee.property?.name;
4541
+ const arg = unwrapTs(n.arguments?.[0]);
4542
+ const token = arg?.type === "Literal" && typeof arg.value === "string" ? arg.value : arg?.type === "Identifier" ? consts.get(arg.name) : void 0;
4543
+ if ((method === "replace" || method === "replaceAll") && token) out.add(token);
4544
+ }
4545
+ for (const key of Object.keys(n)) if (!WALK_IGNORED_KEYS.has(key)) visit(n[key], chunk);
4546
+ };
4547
+ visit(program, false);
4548
+ return [...out];
4549
+ }
4550
+ /** Whether `node` calls `name` outside nested functions. */
4551
+ function callsOutsideFunctions(node, name) {
4552
+ if (Array.isArray(node)) return node.some((n) => callsOutsideFunctions(n, name));
4553
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return false;
4554
+ if (node.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === name) return true;
4555
+ return Object.keys(node).some((key) => !WALK_IGNORED_KEYS.has(key) && callsOutsideFunctions(node[key], name));
4556
+ }
4557
+ /**
4558
+ * The request paths the exported `handle` answers on every request, before calling `resolve`: an `if` on
4559
+ * the path alone (`pathname === '/x'`, `pathname.startsWith('/x')`, or an `||` of those, with `pathname`
4560
+ * read from the `event` parameter, directly or through a `const`) whose body returns or throws, among the
4561
+ * statements before the first one that calls `resolve`. A test that also reads anything else, a `sequence(…)`
4562
+ * handle and a guard in another function are not read.
4563
+ */
4564
+ function handleAnsweredPaths(program, exits) {
4565
+ let handle;
4566
+ forEachNamedExport(program, (name, value) => {
4567
+ if (name === "handle" && isFunctionNode(value)) handle = value;
4568
+ return handle !== void 0;
4569
+ });
4570
+ const param = handle?.params?.[0];
4571
+ if (handle?.body?.type !== "BlockStatement" || param?.type !== "ObjectPattern") return [];
4572
+ const local = (key) => {
4573
+ const p = param.properties?.find((q) => q.type === "Property" && q.key?.type === "Identifier" && q.key.name === key);
4574
+ return p?.value?.type === "Identifier" ? p.value.name : void 0;
4575
+ };
4576
+ const event = local("event");
4577
+ const resolve = local("resolve");
4578
+ if (!event || !resolve) return [];
4579
+ const isUrl = (n) => n?.type === "MemberExpression" && !n.computed && n.property?.name === "url" && n.object?.type === "Identifier" && n.object.name === event;
4580
+ const aliases = /* @__PURE__ */ new Set();
4581
+ const isPath = (n) => {
4582
+ const e = unwrapTs(n);
4583
+ if (e?.type === "Identifier") return aliases.has(e.name);
4584
+ return e?.type === "MemberExpression" && !e.computed && e.property?.name === "pathname" && isUrl(e.object);
4585
+ };
4586
+ const literal = (n) => n?.type === "Literal" && typeof n.value === "string" ? n.value : void 0;
4587
+ const conds = (t) => {
4588
+ if (t?.type === "LogicalExpression" && t.operator === "||") {
4589
+ const l = conds(t.left);
4590
+ const r = conds(t.right);
4591
+ return l && r ? [...l, ...r] : void 0;
4592
+ }
4593
+ if (t?.type === "BinaryExpression" && (t.operator === "===" || t.operator === "==")) {
4594
+ const v = isPath(t.left) ? literal(t.right) : isPath(t.right) ? literal(t.left) : void 0;
4595
+ return v === void 0 ? void 0 : [{
4596
+ op: "eq",
4597
+ v
4598
+ }];
4599
+ }
4600
+ const callee = t?.type === "CallExpression" ? t.callee : void 0;
4601
+ const v = literal(t?.arguments?.[0]);
4602
+ return callee?.type === "MemberExpression" && callee.property?.name === "startsWith" && isPath(callee.object) && v !== void 0 ? [{
4603
+ op: "starts",
4604
+ v
4605
+ }] : void 0;
4606
+ };
4607
+ const exitsAlways = (s) => {
4608
+ if (s?.type === "BlockStatement") return s.body.length > 0 && exitsAlways(s.body[s.body.length - 1]);
4609
+ if (s?.type === "ReturnStatement" || s?.type === "ThrowStatement") return true;
4610
+ const call = s?.type === "ExpressionStatement" ? unwrapTs(s.expression) : void 0;
4611
+ return call?.type === "CallExpression" && call.callee?.type === "Identifier" && exits.has(call.callee.name);
4612
+ };
4613
+ const out = [];
4614
+ for (const stmt of handle.body.body) {
4615
+ if (callsOutsideFunctions(stmt, resolve)) break;
4616
+ if (stmt.type === "VariableDeclaration" && stmt.kind === "const") for (const d of stmt.declarations ?? []) {
4617
+ if (d.id?.type === "Identifier" && isPath(d.init)) aliases.add(d.id.name);
4618
+ if (d.id?.type === "ObjectPattern" && isUrl(unwrapTs(d.init))) {
4619
+ const p = d.id.properties?.find((q) => q.type === "Property" && q.key?.name === "pathname");
4620
+ if (p?.value?.type === "Identifier") aliases.add(p.value.name);
4621
+ }
4622
+ }
4623
+ if (stmt.type !== "IfStatement" || !exitsAlways(stmt.consequent)) continue;
4624
+ const c = conds(stmt.test);
4625
+ if (c) out.push(...c);
4626
+ }
4627
+ return out;
4628
+ }
4221
4629
  /** Whether `node` holds a `return` outside nested functions. */
4222
4630
  function containsReturn(node) {
4223
4631
  if (Array.isArray(node)) return node.some(containsReturn);
@@ -4232,9 +4640,16 @@ function containsReturn(node) {
4232
4640
  * of the same file that itself always redirects. A redirect under an `if` without an `else`, in a
4233
4641
  * loop, or in a function declared inside `load` does not count.
4234
4642
  */
4235
- function loadNeverRenders(program, locals) {
4643
+ function loadNeverRenders(program, locals, building = /* @__PURE__ */ new Set()) {
4236
4644
  const load = locals.size > 0 ? findLoadFunction(program) : void 0;
4237
- if (!load?.body) return false;
4645
+ return !!load?.body && alwaysExits(program, load, locals, building);
4646
+ }
4647
+ /**
4648
+ * Whether `fn`, a function of `program`, redirects or errors on every call (see `loadNeverRenders`).
4649
+ * `building` holds the names `building` from `$app/environment` is imported as: an `if` on one of them
4650
+ * is read with the test false, as it is on every request.
4651
+ */
4652
+ function alwaysExits(program, fn, locals, building = /* @__PURE__ */ new Set()) {
4238
4653
  const topLevel = collectTopLevelBindings(program);
4239
4654
  const fnRedirects = (fn, seen, observed) => {
4240
4655
  const shadow = /* @__PURE__ */ new Set();
@@ -4276,15 +4691,183 @@ function loadNeverRenders(program, locals) {
4276
4691
  }
4277
4692
  return false;
4278
4693
  };
4694
+ const plainReturn = (node, shadow, frame) => {
4695
+ if (Array.isArray(node)) return node.some((n) => plainReturn(n, shadow, frame));
4696
+ if (!node || typeof node !== "object" || typeof node.type !== "string" || isFunctionNode(node)) return false;
4697
+ if (node.type === "ReturnStatement") return !isRedirect(node.argument, shadow, frame, frame.observed);
4698
+ const scope = node.type === "BlockStatement" ? within(node.body, shadow) : shadow;
4699
+ return Object.keys(node).some((key) => !WALK_IGNORED_KEYS.has(key) && plainReturn(node[key], scope, frame));
4700
+ };
4279
4701
  const always = (body, outer, rethrow, frame) => {
4280
4702
  const shadow = within(body, outer);
4281
- for (const stmt of body ?? []) {
4703
+ for (let stmt of body ?? []) {
4704
+ const test = stmt?.type === "IfStatement" ? unwrapTs(stmt.test) : void 0;
4705
+ if (test?.type === "Identifier" && building.has(test.name) && !shadow.has(test.name)) {
4706
+ if (!stmt.alternate) continue;
4707
+ stmt = stmt.alternate;
4708
+ }
4282
4709
  if (redirects(stmt, shadow, rethrow, frame)) return true;
4283
- if (containsReturn(stmt)) return false;
4710
+ if (plainReturn(stmt, shadow, frame)) return false;
4284
4711
  }
4285
4712
  return false;
4286
4713
  };
4287
- return fnRedirects(load, /* @__PURE__ */ new Set(), true);
4714
+ return fnRedirects(fn, /* @__PURE__ */ new Set(), true);
4715
+ }
4716
+ /**
4717
+ * The binding a load's leading flag guard reads: a top-level `if (!FLAG) <exit>` (or `!NS.FLAG`) before
4718
+ * any `return`, where `FLAG` is imported from a repo-local module. When that export is the literal
4719
+ * `false`, every request takes the exit; `collectKitModuleFacts` reads the module to decide.
4720
+ */
4721
+ function leadingFlagGuard(program, exits, imported) {
4722
+ const load = exits.size > 0 ? findLoadFunction(program) : void 0;
4723
+ if (load?.body?.type !== "BlockStatement") return void 0;
4724
+ const local = /* @__PURE__ */ new Set();
4725
+ for (const param of load.params ?? []) addBoundNames(param, local);
4726
+ for (const stmt of load.body.body) if (stmt?.type === "VariableDeclaration") for (const d of stmt.declarations ?? []) addBoundNames(d?.id, local);
4727
+ else if ((stmt?.type === "FunctionDeclaration" || stmt?.type === "ClassDeclaration") && stmt.id) local.add(stmt.id.name);
4728
+ const exitCall = (e) => {
4729
+ const x = unwrapTs(e);
4730
+ return x?.type === "CallExpression" && x.callee?.type === "Identifier" && exits.has(x.callee.name) && !local.has(x.callee.name);
4731
+ };
4732
+ const exitsFirst = (stmt) => {
4733
+ if (stmt?.type === "BlockStatement") {
4734
+ for (const inner of stmt.body) {
4735
+ if (exitsFirst(inner)) return true;
4736
+ if (containsReturn(inner)) return false;
4737
+ }
4738
+ return false;
4739
+ }
4740
+ return stmt?.type === "ExpressionStatement" && exitCall(stmt.expression) || (stmt?.type === "ThrowStatement" || stmt?.type === "ReturnStatement") && exitCall(stmt.argument);
4741
+ };
4742
+ for (const stmt of load.body.body) {
4743
+ const test = stmt?.type === "IfStatement" && exitsFirst(stmt.consequent) ? unwrapTs(stmt.test) : void 0;
4744
+ if (test?.type === "UnaryExpression" && test.operator === "!") {
4745
+ const arg = unwrapTs(test.argument);
4746
+ const root = arg?.type === "MemberExpression" && !arg.computed ? unwrapTs(arg.object) : arg;
4747
+ const member = arg !== root && arg.property?.type === "Identifier" ? arg.property.name : void 0;
4748
+ const from = root?.type === "Identifier" && !local.has(root.name) && (arg === root || member) ? imported(root.name) : void 0;
4749
+ if (from) return {
4750
+ ...from,
4751
+ ...member ? { member } : {}
4752
+ };
4753
+ }
4754
+ if (containsReturn(stmt)) return void 0;
4755
+ }
4756
+ }
4757
+ /** The imported function `export const load = factory(…)` calls, for `collectKitModuleFacts` to read. */
4758
+ function loadFactory(program, imported) {
4759
+ let call;
4760
+ forEachNamedExport(program, (name, value) => {
4761
+ if (name !== "load") return void 0;
4762
+ call = unwrapTs(value);
4763
+ return true;
4764
+ });
4765
+ return call?.type === "CallExpression" && call.callee?.type === "Identifier" ? imported(call.callee.name) : void 0;
4766
+ }
4767
+ /**
4768
+ * The exported functions of a module that return a function which redirects or errors on every call
4769
+ * (`createRedirect = (to) => () => redirect(307, to)`), read like a `load` that does.
4770
+ */
4771
+ function parseExitFactoryExports(source, filename) {
4772
+ const { program } = parseModuleProgram(source, filename);
4773
+ const exits = collectNamedImportAliases(program, "@sveltejs/kit", EXIT_NAMES);
4774
+ const names = /* @__PURE__ */ new Set();
4775
+ if (exits.size === 0) return names;
4776
+ forEachNamedExport(program, (name, value) => {
4777
+ if (!isFunctionNode(value)) return void 0;
4778
+ const first = (value.body?.type === "BlockStatement" ? value.body.body : [{
4779
+ type: "ReturnStatement",
4780
+ argument: value.body
4781
+ }]).find((stmt) => containsReturn(stmt));
4782
+ const returned = first?.type === "ReturnStatement" ? first.argument : void 0;
4783
+ if (isFunctionNode(returned) && alwaysExits(program, returned, exits)) names.add(name);
4784
+ });
4785
+ return names;
4786
+ }
4787
+ /** The exports of a module that are the literal `false`, as `NAME` or `NAME.member` (an object literal's own property). */
4788
+ function parseFalseExports(source, filename) {
4789
+ return collectFalseExports(parseModuleProgram(source, filename).program);
4790
+ }
4791
+ /** `parseFalseExports` over a parsed module. */
4792
+ function collectFalseExports(program) {
4793
+ const names = /* @__PURE__ */ new Set();
4794
+ for (const stmt of program?.body ?? []) {
4795
+ if (stmt?.type !== "ExportNamedDeclaration" || stmt.declaration?.type !== "VariableDeclaration") continue;
4796
+ if (stmt.declaration.kind !== "const") continue;
4797
+ for (const d of stmt.declaration.declarations ?? []) {
4798
+ const init = unwrapTs(d?.init);
4799
+ if (d?.id?.type !== "Identifier") continue;
4800
+ if (init?.type === "Literal" && init.value === false) names.add(d.id.name);
4801
+ if (init?.type === "ObjectExpression") {
4802
+ const members = /* @__PURE__ */ new Map();
4803
+ for (const p of init.properties ?? []) {
4804
+ const v = unwrapTs(p?.value);
4805
+ if (p?.type !== "Property" || p.computed || p.key?.type !== "Identifier") members.clear();
4806
+ else members.set(p.key.name, v?.type === "Literal" && v.value === false);
4807
+ }
4808
+ for (const [key, isFalse] of members) if (isFalse) names.add(`${d.id.name}.${key}`);
4809
+ }
4810
+ }
4811
+ }
4812
+ return names;
4813
+ }
4814
+ /**
4815
+ * Whether the exported `load` sends a request without a session away before rendering: a top-level
4816
+ * `if` whose test is only negated reads of the request's `locals` (`!locals.user`,
4817
+ * `!(await locals.auth())`, or a `const` taken from one earlier in the body) and whose branch
4818
+ * redirects or errors 401/403. A test that also reads anything else — the URL, params — gates only
4819
+ * some requests and does not count; neither does a gate after a `return`.
4820
+ */
4821
+ function loadGatesOnLocals(program) {
4822
+ const redirects = collectNamedImportAliases(program, "@sveltejs/kit", REDIRECT_NAMES);
4823
+ const errors = collectNamedImportAliases(program, "@sveltejs/kit", ERROR_NAMES);
4824
+ const load = redirects.size + errors.size > 0 ? findLoadFunction(program) : void 0;
4825
+ if (load?.body?.type !== "BlockStatement") return false;
4826
+ const param = load.params?.[0];
4827
+ const event = param?.type === "Identifier" ? param.name : void 0;
4828
+ const derived = /* @__PURE__ */ new Set();
4829
+ if (param?.type === "ObjectPattern") {
4830
+ for (const p of param.properties ?? []) if (p?.type === "Property" && (p.key?.name ?? p.key?.value) === "locals") addBoundNames(p.value, derived);
4831
+ }
4832
+ const fromLocals = (expr) => {
4833
+ let cur = unwrapTs(expr);
4834
+ let prev;
4835
+ for (;;) if (cur?.type === "AwaitExpression" || cur?.type === "ChainExpression") cur = unwrapTs(cur.argument ?? cur.expression);
4836
+ else if (cur?.type === "CallExpression" && (cur.arguments ?? []).length === 0) cur = unwrapTs(cur.callee);
4837
+ else if (cur?.type === "MemberExpression" && !cur.computed) [prev, cur] = [cur, unwrapTs(cur.object)];
4838
+ else break;
4839
+ if (cur?.type !== "Identifier") return false;
4840
+ return derived.has(cur.name) || cur.name === event && prev?.computed === false && prev.property?.name === "locals";
4841
+ };
4842
+ const negated = (test) => {
4843
+ const t = unwrapTs(test);
4844
+ if (t?.type === "LogicalExpression") return t.operator === "||" && negated(t.left) && negated(t.right);
4845
+ return t?.type === "UnaryExpression" && t.operator === "!" && fromLocals(t.argument);
4846
+ };
4847
+ const exitCall = (expr) => {
4848
+ const e = unwrapTs(expr);
4849
+ if (e?.type !== "CallExpression" || e.callee?.type !== "Identifier") return false;
4850
+ if (redirects.has(e.callee.name)) return true;
4851
+ if (!errors.has(e.callee.name)) return false;
4852
+ const status = e.arguments?.[0];
4853
+ return status?.type === "Literal" && (status.value === 401 || status.value === 403);
4854
+ };
4855
+ const exitsNow = (stmt) => {
4856
+ if (stmt?.type !== "BlockStatement") return stmt?.type === "ExpressionStatement" && exitCall(stmt.expression) || (stmt?.type === "ThrowStatement" || stmt?.type === "ReturnStatement") && exitCall(stmt.argument);
4857
+ for (const inner of stmt.body) {
4858
+ if (exitsNow(inner)) return true;
4859
+ if (containsReturn(inner)) return false;
4860
+ }
4861
+ return false;
4862
+ };
4863
+ for (const stmt of load.body.body) {
4864
+ if (stmt?.type === "IfStatement" && negated(stmt.test) && exitsNow(stmt.consequent)) return true;
4865
+ if (containsReturn(stmt)) return false;
4866
+ if (stmt?.type === "VariableDeclaration" && stmt.kind === "const") {
4867
+ for (const d of stmt.declarations ?? []) if (d?.init && fromLocals(d.init)) addBoundNames(d.id, derived);
4868
+ }
4869
+ }
4870
+ return false;
4288
4871
  }
4289
4872
  /**
4290
4873
  * Whether `await`'s argument is a `parent()` / `<x>.parent()` call (Kit's parent-load
@@ -4306,6 +4889,7 @@ const BODY_METHODS = /* @__PURE__ */ new Set([
4306
4889
  "formData",
4307
4890
  "bytes"
4308
4891
  ]);
4892
+ const PROMISE_COMBINATORS = /* @__PURE__ */ new Set(["all", "allSettled"]);
4309
4893
  /**
4310
4894
  * Whether `await`'s argument is a response-body read (`res.json()`, `res.text()`, …):
4311
4895
  * parsing an already-received body costs no extra round trip, so it is not a
@@ -4314,9 +4898,12 @@ const BODY_METHODS = /* @__PURE__ */ new Set([
4314
4898
  */
4315
4899
  function isBodyParseCall(arg) {
4316
4900
  const e = unwrapTs(arg);
4317
- if (e?.type !== "CallExpression" || e.arguments?.length) return false;
4901
+ if (e?.type !== "CallExpression") return false;
4318
4902
  const callee = e.callee;
4319
- return callee?.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier" && BODY_METHODS.has(callee.property.name);
4903
+ if (callee?.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier") return false;
4904
+ if (BODY_METHODS.has(callee.property.name)) return !e.arguments?.length;
4905
+ const list = e.arguments?.length === 1 ? unwrapTs(e.arguments[0]) : void 0;
4906
+ return callee.object.type === "Identifier" && callee.object.name === "Promise" && PROMISE_COMBINATORS.has(callee.property.name) && list?.type === "ArrayExpression" && list.elements.length > 0 && list.elements.every((el) => el && isBodyParseCall(el));
4320
4907
  }
4321
4908
  const WORK_STARTING = /* @__PURE__ */ new Set([
4322
4909
  "CallExpression",
@@ -4413,6 +5000,19 @@ function collectLoadWaterfalls(program, wrapped) {
4413
5000
  };
4414
5001
  const line = (start) => Math.max(0, lineOf(wrapped, start) - 1);
4415
5002
  const tainted = /* @__PURE__ */ new Set();
5003
+ const ownBindings = /* @__PURE__ */ new Set();
5004
+ const collectOwn = (body) => {
5005
+ for (const stmt of body ?? []) {
5006
+ if (stmt?.type === "TryStatement") collectOwn(stmt.block?.body);
5007
+ if (stmt?.type !== "VariableDeclaration") continue;
5008
+ for (const d of stmt.declarations ?? []) {
5009
+ let init = unwrapTs(d?.init);
5010
+ if (init?.type === "AwaitExpression") init = unwrapTs(init.argument);
5011
+ if (init?.type === "CallExpression" || init?.type === "NewExpression") addBoundNames(d?.id, ownBindings);
5012
+ }
5013
+ }
5014
+ };
5015
+ collectOwn(load.body.body);
4416
5016
  const syncTainted = /* @__PURE__ */ new Set();
4417
5017
  let sawAwaitSite = false;
4418
5018
  const taintTarget = (target, rhs) => {
@@ -4435,6 +5035,26 @@ function collectLoadWaterfalls(program, wrapped) {
4435
5035
  const root = rootObjectName(callee);
4436
5036
  if (root) tainted.add(root);
4437
5037
  };
5038
+ const taintLoopFill = (call) => {
5039
+ const callee = unwrapTs(call?.callee);
5040
+ if (call?.type !== "CallExpression" || callee?.type !== "MemberExpression" || callee.computed) return;
5041
+ if (!ITERATING_METHODS.has(callee.property?.name) || !refsTainted(callee.object, tainted)) return;
5042
+ const fill = (node) => {
5043
+ if (Array.isArray(node)) return node.forEach(fill);
5044
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
5045
+ if (isFunctionNode(node)) return;
5046
+ const c = node.type === "CallExpression" ? unwrapTs(node.callee) : void 0;
5047
+ if (c?.type === "MemberExpression" && !c.computed && MUTATING_METHODS.has(c.property?.name)) {
5048
+ const root = rootObjectName(c);
5049
+ if (root) tainted.add(root);
5050
+ }
5051
+ if (c?.type === "MemberExpression" && !c.computed && ITERATING_METHODS.has(c.property?.name)) {
5052
+ for (const arg of node.arguments ?? []) if (isFunctionNode(arg)) fill(arg.body);
5053
+ }
5054
+ for (const key of Object.keys(node)) if (!WALK_IGNORED_KEYS.has(key)) fill(node[key]);
5055
+ };
5056
+ for (const arg of call.arguments ?? []) if (isFunctionNode(arg)) fill(arg.body);
5057
+ };
4438
5058
  const taintOnly = (node) => {
4439
5059
  if (Array.isArray(node)) {
4440
5060
  for (const child of node) taintOnly(child);
@@ -4443,13 +5063,18 @@ function collectLoadWaterfalls(program, wrapped) {
4443
5063
  if (!node || typeof node !== "object" || typeof node.type !== "string") return;
4444
5064
  if (isFunctionNode(node)) return;
4445
5065
  if (node.type === "AssignmentExpression") taintTarget(node.left, node.right);
4446
- else if (node.type === "CallExpression") taintMutation(node);
4447
- else if ((node.type === "ForOfStatement" || node.type === "ForInStatement") && refsTainted(node.right, tainted)) {
5066
+ else if (node.type === "CallExpression") {
5067
+ taintMutation(node);
5068
+ taintLoopFill(node);
5069
+ } else if ((node.type === "ForOfStatement" || node.type === "ForInStatement") && refsTainted(node.right, tainted)) {
4448
5070
  const names = /* @__PURE__ */ new Set();
4449
5071
  addBoundNames(node.left?.type === "VariableDeclaration" ? node.left.declarations?.[0]?.id : node.left, names);
4450
5072
  for (const name of names) tainted.add(name);
4451
5073
  } else if (node.type === "VariableDeclaration") {
4452
- for (const d of node.declarations ?? []) if (d?.id && d.init) taintTarget(d.id, d.init);
5074
+ for (const d of node.declarations ?? []) if (d?.id && d.init) {
5075
+ taintTarget(d.id, d.init);
5076
+ taintLoopFill(unwrapTs(d.init));
5077
+ }
4453
5078
  }
4454
5079
  for (const key of Object.keys(node)) {
4455
5080
  if (WALK_IGNORED_KEYS.has(key)) continue;
@@ -4483,11 +5108,21 @@ function collectLoadWaterfalls(program, wrapped) {
4483
5108
  sawAwaitSite = true;
4484
5109
  }
4485
5110
  if (stmt.type === "VariableDeclaration") {
4486
- for (const d of stmt.declarations ?? []) if (d?.id && d.init) taintTarget(d.id, d.init);
5111
+ for (const d of stmt.declarations ?? []) if (d?.id && d.init) {
5112
+ taintTarget(d.id, d.init);
5113
+ taintLoopFill(unwrapTs(d.init));
5114
+ }
4487
5115
  } else if (stmt.type === "ExpressionStatement") {
4488
5116
  const expr = unwrapTs(stmt.expression);
4489
5117
  if (expr?.type === "AssignmentExpression") taintTarget(expr.left, expr.right);
4490
- else taintMutation(expr);
5118
+ else {
5119
+ taintMutation(expr);
5120
+ taintLoopFill(expr);
5121
+ }
5122
+ const call = expr?.type === "AwaitExpression" ? unwrapTs(expr.argument) : void 0;
5123
+ const callee = call?.type === "CallExpression" ? unwrapTs(call.callee) : void 0;
5124
+ const owner = callee?.type === "MemberExpression" && !callee.computed ? unwrapTs(callee.object) : void 0;
5125
+ if (owner?.type === "Identifier" && ownBindings.has(owner.name)) tainted.add(owner.name);
4491
5126
  }
4492
5127
  } else taintOnly(stmt);
4493
5128
  }
@@ -4584,7 +5219,7 @@ function aliasMatches(entry, spec) {
4584
5219
  */
4585
5220
  function resolveRepoLocalPath(spec, importerFile, aliases = DEFAULT_KIT_ALIASES) {
4586
5221
  let path;
4587
- if (spec.startsWith("./") || spec.startsWith("../")) path = `${importerFile.split("/").slice(0, -1).join("/")}/${spec}`;
5222
+ if (spec === "." || spec === ".." || spec.startsWith("./") || spec.startsWith("../")) path = `${importerFile.split("/").slice(0, -1).join("/")}/${spec}`;
4588
5223
  else {
4589
5224
  const entry = aliases.find((a) => aliasMatches(a, spec));
4590
5225
  if (entry?.replacement == null) return void 0;
@@ -4650,13 +5285,9 @@ function libServerRoot(aliases) {
4650
5285
  * risks a false positive in this default-on security rule, and staying silent only costs a
4651
5286
  * missed finding.
4652
5287
  */
4653
- /** The resolved repo path when `spec` lands on, or under, the `$lib` server root; else undefined. */
4654
- function serverRootRelativePath(spec, importerFile, aliases) {
4655
- const serverRoot = libServerRoot(aliases);
4656
- if (serverRoot === void 0) return void 0;
4657
- const path = resolveRepoLocalPath(spec, importerFile, aliases);
4658
- if (path === void 0) return void 0;
4659
- return path === serverRoot || path.startsWith(`${serverRoot}/`) ? path : void 0;
5288
+ /** The resolved repo path of a repo-local `spec`, or undefined when it is a package or the `$lib` root is opaque. */
5289
+ function localModulePath(spec, importerFile, aliases) {
5290
+ return libServerRoot(aliases) === void 0 ? void 0 : resolveRepoLocalPath(spec, importerFile, aliases);
4660
5291
  }
4661
5292
  function isLocalStateSpecifier(spec, importerFile, aliases) {
4662
5293
  const serverRoot = libServerRoot(aliases);
@@ -4672,23 +5303,60 @@ const IN_MEMORY_CTORS = /* @__PURE__ */ new Set([
4672
5303
  "WeakMap",
4673
5304
  "WeakSet"
4674
5305
  ]);
5306
+ const isContainerCtor = (n) => n?.type === "NewExpression" && n.callee?.type === "Identifier" && IN_MEMORY_CTORS.has(n.callee.name);
4675
5307
  /**
4676
5308
  * Whether an initializer positively identifies an in-memory container. An object literal with a
4677
5309
  * spread is a composite of values this parse cannot see — typically a facade over clients and
4678
5310
  * imported modules (`{ ...prismaModels, ...handlers }`) — so it counts only when one of its own
4679
- * properties is itself a container.
5311
+ * properties is itself a container. One whose properties are all functions (`{ get, set }` of the
5312
+ * module's own functions) holds no data: `.set()` on it calls its own method.
4680
5313
  */
4681
- function isInMemoryInit(init) {
5314
+ function isInMemoryInit(init, fns = /* @__PURE__ */ new Set()) {
4682
5315
  if (!init) return false;
4683
5316
  if (init.type === "ArrayExpression") return true;
4684
5317
  if (init.type === "ObjectExpression") {
4685
5318
  const props = init.properties ?? [];
5319
+ const isFn = (p) => p?.type === "Property" && (p.method || p.value?.type === "FunctionExpression" || p.value?.type === "ArrowFunctionExpression" || p.value?.type === "Identifier" && fns.has(p.value.name));
5320
+ if (props.length > 0 && props.every(isFn)) return false;
4686
5321
  if (!props.some((p) => p?.type === "SpreadElement")) return true;
4687
- return props.some((p) => p?.type === "Property" && isInMemoryInit(p.value));
5322
+ return props.some((p) => p?.type === "Property" && isInMemoryInit(p.value, fns));
4688
5323
  }
4689
5324
  return init.type === "NewExpression" && init.callee?.type === "Identifier" && IN_MEMORY_CTORS.has(init.callee.name);
4690
5325
  }
4691
5326
  /**
5327
+ * The exported bindings of a module that are a persistence or API client: `new C()` of a class imported
5328
+ * from an installed package, or of a class this module declares that neither extends another class nor
5329
+ * keeps a `Map`/`Set`/`WeakMap`/`WeakSet` field; or a call into an installed package (`drizzle(…)`,
5330
+ * `createClient(…)`). A write through one of them reaches a database or service, not module state.
5331
+ */
5332
+ function parseClientExports(source, filename, aliases) {
5333
+ const names = /* @__PURE__ */ new Set();
5334
+ const { program } = parseModuleProgram(source, filename);
5335
+ const fromPackage = /* @__PURE__ */ new Set();
5336
+ for (const stmt of program?.body ?? []) {
5337
+ const src = stmt?.type === "ImportDeclaration" ? String(stmt.source?.value ?? "") : "";
5338
+ if (!(!src || /^[./$~#]|^@\//.test(src) || resolveRepoLocalPath(src, filename, aliases) !== void 0) && !/^svelte|store/i.test(src)) for (const sp of stmt.specifiers ?? []) fromPackage.add(sp.local.name);
5339
+ }
5340
+ const plainClasses = /* @__PURE__ */ new Set();
5341
+ for (const stmt of program?.body ?? []) {
5342
+ const decl = unwrapExport(stmt);
5343
+ if (decl?.type !== "ClassDeclaration" || !decl.id || decl.superClass) continue;
5344
+ if (!(decl.body?.body ?? []).some((m) => isContainerCtor(m?.value) || m?.kind === "constructor" && m.value?.body?.body?.some((st) => st?.type === "ExpressionStatement" && isContainerCtor(st.expression?.right)))) plainClasses.add(decl.id.name);
5345
+ }
5346
+ for (const stmt of program?.body ?? []) {
5347
+ if (stmt?.type !== "ExportNamedDeclaration" || stmt.declaration?.type !== "VariableDeclaration") continue;
5348
+ for (const d of stmt.declaration.declarations ?? []) {
5349
+ let init = unwrapTs(d?.init);
5350
+ if (init?.type === "AwaitExpression") init = unwrapTs(init.argument);
5351
+ if (d?.id?.type !== "Identifier") continue;
5352
+ const ctor = init?.type === "NewExpression" && init.callee?.type === "Identifier" ? init.callee.name : void 0;
5353
+ if (ctor !== void 0 && (fromPackage.has(ctor) || plainClasses.has(ctor))) names.add(d.id.name);
5354
+ if (init?.type === "CallExpression" && fromPackage.has(rootObjectName(init.callee) ?? "")) names.add(d.id.name);
5355
+ }
5356
+ }
5357
+ return names;
5358
+ }
5359
+ /**
4692
5360
  * The exported bindings of a module that are initialized to an in-memory container — an object
4693
5361
  * or array literal (a spread literal only as `isInMemoryInit` allows), or `new Map`/`Set`/`WeakMap`/`WeakSet`. Used to arbitrate a `.set()` call on
4694
5362
  * an import from under the `$lib` server root, where the call shape alone cannot separate a
@@ -4702,11 +5370,16 @@ function isInMemoryInit(init) {
4702
5370
  function parseInMemoryExports(source, filename) {
4703
5371
  const names = /* @__PURE__ */ new Set();
4704
5372
  const { program } = parseModuleProgram(source, filename);
5373
+ const fns = /* @__PURE__ */ new Set();
5374
+ for (const stmt of program?.body ?? []) {
5375
+ const decl = unwrapExport(stmt);
5376
+ if (decl?.type === "FunctionDeclaration" && decl.id) fns.add(decl.id.name);
5377
+ }
4705
5378
  for (const stmt of program?.body ?? []) {
4706
5379
  if (stmt?.type !== "ExportNamedDeclaration" || !stmt.declaration) continue;
4707
5380
  const decl = stmt.declaration;
4708
5381
  if (decl.type !== "VariableDeclaration") continue;
4709
- for (const d of decl.declarations ?? []) if (d?.id?.type === "Identifier" && isInMemoryInit(d.init)) names.add(d.id.name);
5382
+ for (const d of decl.declarations ?? []) if (d?.id?.type === "Identifier" && isInMemoryInit(d.init, fns)) names.add(d.id.name);
4710
5383
  }
4711
5384
  return names;
4712
5385
  }
@@ -4722,6 +5395,7 @@ function parseKitModuleFacts(source, filename, aliases) {
4722
5395
  const importedStateWrites = [];
4723
5396
  const importedStateWritesOutsideHandlers = [];
4724
5397
  const pendingServerStoreWrites = [];
5398
+ const storeWriteTargets = [];
4725
5399
  const runesModuleImports = [];
4726
5400
  const lifecycleCalls = [];
4727
5401
  const browserGlobalRefs = [];
@@ -4740,6 +5414,11 @@ function parseKitModuleFacts(source, filename, aliases) {
4740
5414
  const importedSpecifiers = /* @__PURE__ */ new Map();
4741
5415
  const importedNames = /* @__PURE__ */ new Map();
4742
5416
  const namespaceImports = /* @__PURE__ */ new Set();
5417
+ const localFunctions = /* @__PURE__ */ new Set();
5418
+ walkEstree(program, (n) => {
5419
+ if (n.type === "FunctionDeclaration" && n.id?.name) localFunctions.add(n.id.name);
5420
+ if (n.type === "VariableDeclarator" && n.id?.type === "Identifier" && n.init && isFunctionNode(unwrapTs(n.init))) localFunctions.add(n.id.name);
5421
+ });
4743
5422
  for (const stmt of program.body ?? []) {
4744
5423
  if (stmt?.type !== "ImportDeclaration" || stmt.importKind === "type") continue;
4745
5424
  const spec = typeof stmt.source?.value === "string" ? stmt.source.value : "";
@@ -4771,7 +5450,7 @@ function parseKitModuleFacts(source, filename, aliases) {
4771
5450
  const ssrOptOut = findFalseOptOut(program, wrapped, "ssr");
4772
5451
  const csrOptOut = findFalseOptOut(program, wrapped, "csr");
4773
5452
  const waterfalls = collectLoadWaterfalls(program, wrapped);
4774
- if (!ssrOptOut) {
5453
+ if (!ssrOptOut?.literal) {
4775
5454
  const shiftLine = (l) => Math.max(0, l - 1);
4776
5455
  const browserImports = collectBrowserGuardImports(program);
4777
5456
  const guards = /* @__PURE__ */ new Set([...browserImports, ...collectDerivedGuardBindings(program, browserImports)]);
@@ -4803,10 +5482,12 @@ function parseKitModuleFacts(source, filename, aliases) {
4803
5482
  scanFn(fn, false);
4804
5483
  }
4805
5484
  }
5485
+ const browserOnly = browserOnlyRanges(program, collectBrowserGuardImports(program));
5486
+ const reachedOnlyByBrowser = (at) => browserOnly.some(([from, to]) => at >= from && at < to);
4806
5487
  walkKit(program, handlerFns, startupFns, (n, shadowed, inFunction, inHandler, inStartup) => {
4807
5488
  if (inFunction && !inStartup) {
4808
5489
  const flagLet = (name) => {
4809
- if (name && !shadowed.has(name) && moduleLets.has(name)) moduleStateReassignments.push({
5490
+ if (name && !shadowed.has(name) && moduleLets.has(name) && !reachedOnlyByBrowser(n.start)) moduleStateReassignments.push({
4810
5491
  name,
4811
5492
  line: line(n.start),
4812
5493
  inHandler
@@ -4847,23 +5528,25 @@ function parseKitModuleFacts(source, filename, aliases) {
4847
5528
  } else if (n.type === "CallExpression" && n.callee?.type === "MemberExpression") {
4848
5529
  const method = n.callee.property?.type === "Identifier" ? n.callee.property.name : void 0;
4849
5530
  const nsFunction = n.callee.object?.type === "Identifier" && namespaceImports.has(n.callee.object.name);
4850
- if ((method === "set" || method === "update") && !nsFunction) {
5531
+ const updater = method !== "update" || isUpdaterArgument(n.arguments?.[0], localFunctions);
5532
+ if ((method === "set" || method === "update") && !nsFunction && updater) {
4851
5533
  const r = importedRoot(n.callee.object);
4852
5534
  const spec = r ? importedSpecifiers.get(r) : void 0;
4853
5535
  if (r && spec !== void 0) {
4854
- if (isLocalStateSpecifier(spec, filename, aliases)) write = {
5536
+ const resolved = localModulePath(spec, filename, aliases);
5537
+ const target = {
4855
5538
  name: r,
4856
- via: "set-call"
5539
+ imported: importedNames.get(r) ?? r,
5540
+ resolved,
5541
+ line: line(n.start)
4857
5542
  };
4858
- else {
4859
- const resolved = serverRootRelativePath(spec, filename, aliases);
4860
- if (resolved !== void 0 && inHandler) pendingServerStoreWrites.push({
5543
+ if (isLocalStateSpecifier(spec, filename, aliases)) {
5544
+ write = {
4861
5545
  name: r,
4862
- imported: importedNames.get(r) ?? r,
4863
- resolved,
4864
- line: line(n.start)
4865
- });
4866
- }
5546
+ via: "set-call"
5547
+ };
5548
+ if (inHandler) storeWriteTargets.push(target);
5549
+ } else if (resolved !== void 0 && inHandler) pendingServerStoreWrites.push(target);
4867
5550
  }
4868
5551
  }
4869
5552
  } else if (n.type === "AssignmentExpression" && (n.left?.type === "ObjectPattern" || n.left?.type === "ArrayPattern")) {
@@ -4884,6 +5567,7 @@ function parseKitModuleFacts(source, filename, aliases) {
4884
5567
  };
4885
5568
  scanPatternTargets(n.left);
4886
5569
  }
5570
+ if (write && reachedOnlyByBrowser(n.start)) write = void 0;
4887
5571
  if (write) {
4888
5572
  if (inHandler) importedStateWrites.push({
4889
5573
  ...write,
@@ -4915,19 +5599,51 @@ function parseKitModuleFacts(source, filename, aliases) {
4915
5599
  line: line(call.start)
4916
5600
  });
4917
5601
  }
5602
+ const exitLocals = collectNamedImportAliases(program, "@sveltejs/kit", EXIT_NAMES);
5603
+ const neverRenders = loadNeverRenders(program, exitLocals);
5604
+ const building = collectNamedImportAliases(program, "$app/environment", BUILDING_NAMES);
5605
+ const hooks = /(^|\/)hooks\.server\.(ts|js)$/.test(filename);
5606
+ const answered = hooks ? handleAnsweredPaths(program, exitLocals) : [];
5607
+ const chunkTokens = hooks ? pageChunkTokens(program) : [];
5608
+ const flagGuard = leadingFlagGuard(program, exitLocals, (local) => {
5609
+ const spec = namespaceImports.has(local) ? void 0 : importedSpecifiers.get(local);
5610
+ const resolved = spec === void 0 ? void 0 : localModulePath(spec, filename, aliases);
5611
+ return resolved === void 0 ? void 0 : {
5612
+ resolved,
5613
+ name: importedNames.get(local) ?? local
5614
+ };
5615
+ });
5616
+ const factory = loadFactory(program, (local) => {
5617
+ const spec = namespaceImports.has(local) ? void 0 : importedSpecifiers.get(local);
5618
+ const resolved = spec === void 0 ? void 0 : localModulePath(spec, filename, aliases);
5619
+ return resolved === void 0 ? void 0 : {
5620
+ resolved,
5621
+ name: importedNames.get(local) ?? local
5622
+ };
5623
+ });
4918
5624
  return {
4919
5625
  moduleStateReassignments: byLine(moduleStateReassignments),
4920
5626
  importedStateWrites: byLine(importedStateWrites),
4921
5627
  importedStateWritesOutsideHandlers: byLine(importedStateWritesOutsideHandlers),
4922
5628
  pendingServerStoreWrites: byLine(pendingServerStoreWrites),
5629
+ ...storeWriteTargets.length > 0 ? { storeWriteTargets } : {},
4923
5630
  runesModuleImports: byLine(runesModuleImports),
4924
5631
  lifecycleCalls: byLine(lifecycleCalls),
4925
5632
  browserGlobalRefs: byLine(browserGlobalRefs),
4926
5633
  basePathLinks: byLine(basePathLinks),
4927
5634
  ...ssrOptOut ? { ssrDisabled: { line: Math.max(0, ssrOptOut.line - 1) } } : {},
4928
5635
  ...!ssrOptOut && exportsName(program, "ssr") ? { ssrEnabled: true } : {},
5636
+ ...!ssrOptOut && exportsTrue(program, "ssr") ? { ssrTrue: true } : {},
4929
5637
  ...csrOptOut ? { csrDisabled: { line: Math.max(0, csrOptOut.line - 1) } } : {},
4930
- ...loadNeverRenders(program, collectNamedImportAliases(program, "@sveltejs/kit", EXIT_NAMES)) ? { loadNeverRenders: true } : {},
5638
+ ...!csrOptOut && exportsName(program, "csr") ? { csrEnabled: true } : {},
5639
+ ...neverRenders ? { loadNeverRenders: true } : {},
5640
+ ...!neverRenders && building.size > 0 && loadNeverRenders(program, exitLocals, building) ? { loadNeverRendersOutsideBuild: true } : {},
5641
+ ...exportsName(program, "prerender") ? { prerenderExported: true } : {},
5642
+ ...answered.length > 0 ? { answeredPaths: answered } : {},
5643
+ ...chunkTokens.length > 0 ? { pageChunkTokens: chunkTokens } : {},
5644
+ ...flagGuard ? { pendingFlagGuard: flagGuard } : {},
5645
+ ...factory ? { pendingLoadFactory: factory } : {},
5646
+ ...loadGatesOnLocals(program) ? { loadGated: true } : {},
4931
5647
  ...waterfalls.dependentLines.length > 0 || waterfalls.independentLines.length > 0 ? { loadWaterfalls: waterfalls } : {},
4932
5648
  suppressions
4933
5649
  };
@@ -6399,8 +7115,8 @@ const MESSAGE$1 = "Sequential dependent awaits in a universal load create a clie
6399
7115
  /**
6400
7116
  * performance/load-waterfall — dependent await chains in universal loads. Server loads are exempt:
6401
7117
  * a dependent chain cannot be parallelized, and on the server there is no better
6402
- * placement to suggest. csr = false files are exempt too — without a client
6403
- * runtime the universal load only runs during SSR.
7118
+ * placement to suggest. Loads on a route with `csr = false` (its own or a layout's) are exempt
7119
+ * too — without a client runtime the universal load only runs during SSR.
6404
7120
  */
6405
7121
  const performanceLoadWaterfall = kitModuleRule({
6406
7122
  id: "performance/load-waterfall",
@@ -6415,7 +7131,7 @@ const performanceLoadWaterfall = kitModuleRule({
6415
7131
  snippet: "// +page.server.ts — same chain, server-side hops\nexport async function load({ fetch }) {\n const user = await fetch(`/api/user`).then((r) => r.json());\n const posts = await fetch(`/api/posts/${user.id}`).then((r) => r.json());\n return { user, posts };\n}",
6416
7132
  lang: "ts"
6417
7133
  },
6418
- applies: (m) => m.kind === "universal" && m.csrDisabled === void 0 && (m.loadWaterfalls?.dependentLines.length ?? 0) > 0,
7134
+ applies: (m, ctx) => m.kind === "universal" && (m.loadWaterfalls?.dependentLines.length ?? 0) > 0 && !universalNeverCsr(m, ctx),
6419
7135
  bad: (m) => m.loadWaterfalls.dependentLines.map((line) => ({
6420
7136
  line,
6421
7137
  message: MESSAGE$1
@@ -7088,7 +7804,7 @@ const a11yPatternTitle = componentRule({
7088
7804
  //#endregion
7089
7805
  //#region src/html-spec/generated.ts
7090
7806
  /*!
7091
- * HTML spec data projected from @markuplint/html-spec@5.0.0 — https://github.com/markuplint/markuplint
7807
+ * HTML spec data projected from @markuplint/html-spec@5.0.1 — https://github.com/markuplint/markuplint
7092
7808
  *
7093
7809
  * MIT License
7094
7810
  *
@@ -8481,8 +9197,29 @@ function buildJsonReport(results, config, meta, ruleIds, examined, skipped) {
8481
9197
  };
8482
9198
  }
8483
9199
  /** Render results as the documented JSON report string (design §7). */
8484
- function formatJsonReport(results, config, meta, ruleIds, examined, skipped) {
8485
- return JSON.stringify(buildJsonReport(results, config, meta, ruleIds, examined, skipped), null, 2);
9200
+ function formatJsonReport(...args) {
9201
+ return [...formatJsonReportChunks(...args)].join("");
9202
+ }
9203
+ /**
9204
+ * `formatJsonReport`'s text in pieces, one route at a time: a large prerendered site's report is
9205
+ * longer than the longest string V8 can hold.
9206
+ */
9207
+ function* formatJsonReportChunks(results, config, meta, ruleIds, examined, skipped) {
9208
+ const report = buildJsonReport(results, config, meta, ruleIds, examined, skipped);
9209
+ const marker = JSON.stringify("\0routes");
9210
+ const text = JSON.stringify({
9211
+ ...report,
9212
+ routes: "\0routes"
9213
+ }, null, 2);
9214
+ const at = text.indexOf(marker);
9215
+ yield text.slice(0, at);
9216
+ if (report.routes.length === 0) yield "[]";
9217
+ else {
9218
+ yield "[\n";
9219
+ for (const [i, route] of report.routes.entries()) yield `${i > 0 ? ",\n" : ""} ${JSON.stringify(route, null, 2).replaceAll("\n", "\n ")}`;
9220
+ yield "\n ]";
9221
+ }
9222
+ yield text.slice(at + marker.length);
8486
9223
  }
8487
9224
  //#endregion
8488
9225
  //#region src/reporter/sanitize.ts
@@ -8628,4 +9365,4 @@ function formatMarkdownReport(results, config, meta) {
8628
9365
  return lines.join("\n");
8629
9366
  }
8630
9367
  //#endregion
8631
- export { performanceMinifyDisabled as $, applyRuleSeverities as $n, correctnessOrphanLifecycle as $t, a11yAbbrTitle as A, seoImageAlt as An, defaultProject as Ar, walkEstree as At, a11yUseList as B, seoSitemapInRobots as Bn, parseSvelte as Bt, a11yDisallowedElement as C, foldOccurrences as Cn, effectiveSeverity as Cr, collectSvelteLifecycleImports as Ct, a11yDeprecatedElement as D, stripTextDirective as Dn, isPenalized as Dr, scopeIntroducedNames as Dt, a11yDeprecatedAttr as E, splitTokens as En, docsUrlFor as Er, rootObjectName as Et, isInteractiveContainer as F, seoJsonLdPlaceholder as Fn, attrTextOf as Ft, a11yInvalidAriaValue as G, seoIndexability as Gn, securitySharedStateImport as Gt, a11yAccessibleName as H, seoOgUrl as Hn, valueFromNodes as Ht, isInteractiveElement as I, seoJsonLdDateFormat as In, attrValue as It, a11yInvalidRole as J, resolveRuleOptions as Jn, securityJavascriptUrl as Jt, a11yRequiredAriaProps as K, performancePreconnect as Kn, securityServerModuleState as Kt, literalTabindexValue as L, seoJsonLdRelativeUrl as Ln, attrValueOf as Lt, a11yNoAutofocus as M, seoDescriptionLength as Mn, walkScoped as Mt, a11yNoAccesskey as N, seoTitleLength as Nn, CHILD_NODE_KEYS as Nt, HTML_SPEC as O, resolveRole as On, CATEGORIES as Or, unwrapExport as Ot, a11yPositiveTabindex as P, seoJsonLdRequiredProps as Pn, attrText as Pt, performanceLoadWaterfall as Q, applyOverrides as Qn, correctnessBasePathNavigation as Qt, a11yRequireDatetime as R, seoJsonLdDeprecatedType as Rn, findAttr as Rt, a11yPermittedContents as S, decodeFragmentId as Sn, classify as Sr, collectSuppressions as St, a11yDisallowedAriaProps as T, resolveLandmark as Tn, summarize as Tr, parseModuleProgram as Tt, a11yAriaHiddenFocus as U, seoOgDescription as Un, architecturePropCount as Ut, a11yLabelHasControl as V, seoViewport as Vn, textFromNodes as Vt, a11yInteractiveNesting as W, seoTwitterCard as Wn, architectureComponentSize as Wt, performanceStateRaw as X, validateRuleSetting as Xn, correctnessInstanceBrowserGlobal as Xt, performanceIframeLoading as Y, shouldSkipRangeCheck as Yn, securityRawHtml as Yt, performanceSequentialAwaits as Z, SEARCH_RESULT_RULES as Zn, correctnessServerBrowserGlobal as Zt, a11yIdDuplication as _, ANCESTRY_DEPENDENT_TAGS as _n, seoOgTitle as _r, collectBrowserGlobalRefs as _t, formatJsonReport as a, correctnessPropMutation as an, withIndexableOff as ar, architectureReservedDirectoryNames as at, a11yDoctype as b, NAMING_ATTRS$1 as bn, seoDescriptionPresence as br, collectNamedImportAliases as bt, formatGithubReport as c, correctnessEffectAsDerived as cn, performanceFontPreloadCrossorigin as cr, architecturePrivateScopeImport as ct, ruleMetaById as d, seoSsrDisabled as dn, performanceImageLoadingHint as dr, parseInMemoryExports as dt, correctnessOrphanEffect as en, compileOverrides as er, performanceNamespaceImport as et, severityToSarifLevel as f, routeNeverSsr as fn, performanceImageDimensions as fr, parseKitModuleFacts as ft, a11yNoMissingIdRef as g, seoSingleH1 as gn, seoJsonLd as gr, addBoundNames as gt, a11yUnverifiedIdRef as h, seoDuplicateTitle as hn, seoRobotsTxt as hr, WALK_IGNORED_KEYS as ht, buildJsonReport as i, correctnessStalePropDerivation as in, withFailedRulesOff as ir, architectureReservedNamePlacement as it, a11yNoDuplicateDt as j, seoCharset as jn, defineConfig as jr, walkEvalScope as jt, a11yPatternTitle as k, seoHreflang as kn, defaultConfig as kr, unwrapTs as kt, SEVERITY_RANK as l, correctnessEachIndexKey as ln, performancePreloadMissingAs as lr, DEFAULT_KIT_ALIASES as lt, explainRule as m, seoDuplicateDescription as mn, seoSitemapXml as mr, isRootRelativePath as mt, mdEscape as n, correctnessCheckableBindValue as nn, selectRules as nr, architectureDocLinkTarget as nt, computeHealth as o, correctnessUnmutatedState as on, performanceRenderBlockingScript as or, architectureDirectoryNaming as ot, allRules as p, seoHeadingLevelSkip as pn, seoHtmlLang as pr, resolveRepoLocalPath as pt, a11yUnknownAriaAttribute as q, isPlainObject as qn, securityHandlerStateWrite as qt, terminalSafe as r, correctnessNonreactiveBuiltinState as rn, settingSeverity as rr, architectureRouteComponentImport as rt, computeScore as s, correctnessEffectAsOnMount as sn, performanceLcpImage as sr, architectureUnitEntryFile as st, formatMarkdownReport as t, correctnessAutoplayMuted as tn, formatFailedRuleWarning as tr, performanceHeavyImport as tt, messageText as u, correctnessEachKey as un, performanceResponsiveImage as ur, collectTopLevelBindings as ut, a11yTopLevelLandmark as v, ASIDE_DEMOTING_TAGS as vn, seoOgImage as vr, collectBrowserGuardImports as vt, a11yDeprecatedAria as w, isTopFragment as wn, hasFailureAtOrAbove as wr, matchLifecycleCall as wt, a11yRequiredElement as x, SECTIONING_TAGS as xn, seoTitlePresence as xr, collectProgramBindings as xt, a11yDuplicateLandmark as y, IDREF_ATTRS as yn, seoCanonicalUrl as yr, collectDerivedGuardBindings as yt, a11yPlaceholderLabelOption as z, seoJsonLdValidity as zn, lineOf as zt };
9368
+ export { performanceLoadWaterfall as $, shouldSkipRangeCheck as $n, securityJavascriptUrl as $t, a11yPatternTitle as A, splitTokens as An, seoTitlePresence as Ar, parseModuleProgram as At, a11yPlaceholderLabelOption as B, seoJsonLdDateFormat as Bn, defineConfig as Br, attrTextOf as Bt, a11yPermittedContents as C, IDREF_ATTRS as Cn, seoCanonicalUrl as Cr, collectBrowserGuardImports as Ct, a11yDeprecatedAttr as D, foldOccurrences as Dn, layoutNeverRenders as Dr, collectSuppressions as Dt, a11yDisallowedAriaProps as E, decodeFragmentId as En, seoDescriptionPresence as Er, collectProgramBindings as Et, a11yPositiveTabindex as F, seoCharset as Fn, docsUrlFor as Fr, walkEstree as Ft, a11yInteractiveNesting as G, seoViewport as Gn, parseSvelte as Gt, a11yLabelHasControl as H, seoJsonLdDeprecatedType as Hn, attrValueOf as Ht, isInteractiveContainer as I, seoDescriptionLength as In, isPenalized as Ir, walkEvalScope as It, a11yUnknownAriaAttribute as J, seoTwitterCard as Jn, architecturePropCount as Jt, a11yInvalidAriaValue as K, seoOgUrl as Kn, textFromNodes as Kt, isInteractiveElement as L, seoTitleLength as Ln, CATEGORIES as Lr, walkScoped as Lt, a11yNoDuplicateDt as M, resolveRole as Mn, effectiveSeverity as Mr, scopeIntroducedNames as Mt, a11yNoAutofocus as N, seoHreflang as Nn, hasFailureAtOrAbove as Nr, unwrapExport as Nt, a11yDeprecatedElement as O, isTopFragment as On, pageRendersOnlyWhenBuilding as Or, collectSvelteLifecycleImports as Ot, a11yNoAccesskey as P, seoImageAlt as Pn, summarize as Pr, unwrapTs as Pt, performanceSequentialAwaits as Q, resolveRuleOptions as Qn, securityHandlerStateWrite as Qt, literalTabindexValue as R, seoJsonLdRequiredProps as Rn, defaultConfig as Rr, CHILD_NODE_KEYS as Rt, a11yRequiredElement as S, ASIDE_DEMOTING_TAGS as Sn, seoOgImage as Sr, collectBrowserGlobalRefs as St, a11yDeprecatedAria as T, SECTIONING_TAGS as Tn, isClassicScriptType as Tr, collectNamedImportAliases as Tt, a11yAccessibleName as U, seoJsonLdValidity as Un, findAttr as Ut, a11yUseList as V, seoJsonLdRelativeUrl as Vn, attrValue as Vt, a11yAriaHiddenFocus as W, seoSitemapInRobots as Wn, lineOf as Wt, performanceIframeLoading as X, performancePreconnect as Xn, securitySharedStateImport as Xt, a11yInvalidRole as Y, seoIndexability as Yn, architectureComponentSize as Yt, performanceStateRaw as Z, isPlainObject as Zn, securityServerModuleState as Zt, a11yNoMissingIdRef as _, seoHeadingLevelSkip as _n, seoHtmlLang as _r, parseKitModuleFacts as _t, formatJsonReport as a, correctnessOrphanEffect as an, formatFailedRuleWarning as ar, architectureReservedNamePlacement as at, a11yDuplicateLandmark as b, seoSingleH1 as bn, seoJsonLd as br, WALK_IGNORED_KEYS as bt, computeScore as c, correctnessNonreactiveBuiltinState as cn, withFailedRulesOff as cr, architectureUnitEntryFile as ct, messageText as d, correctnessUnmutatedState as dn, performanceLcpImage as dr, collectFalseExports as dt, securityRawHtml as en, validateRuleSetting as er, performanceMinifyDisabled as et, ruleMetaById as f, correctnessEffectAsOnMount as fn, performanceFontPreloadCrossorigin as fr, collectTopLevelBindings as ft, a11yUnverifiedIdRef as g, seoSsrDisabled as gn, performanceImageDimensions as gr, parseInMemoryExports as gt, explainRule as h, correctnessEachKey as hn, performanceImageLoadingHint as hr, parseFalseExports as ht, buildJsonReport as i, correctnessOrphanLifecycle as in, compileOverrides as ir, architectureRouteComponentImport as it, a11yAbbrTitle as j, stripTextDirective as jn, classify as jr, rootObjectName as jt, HTML_SPEC as k, resolveLandmark as kn, routeNeverSsr as kr, matchLifecycleCall as kt, formatGithubReport as l, correctnessStalePropDerivation as ln, withIndexableOff as lr, architecturePrivateScopeImport as lt, allRules as m, correctnessEachIndexKey as mn, performanceResponsiveImage as mr, parseExitFactoryExports as mt, mdEscape as n, correctnessServerBrowserGlobal as nn, applyOverrides as nr, performanceHeavyImport as nt, formatJsonReportChunks as o, correctnessAutoplayMuted as on, selectRules as or, architectureReservedDirectoryNames as ot, severityToSarifLevel as p, correctnessEffectAsDerived as pn, performancePreloadMissingAs as pr, parseClientExports as pt, a11yRequiredAriaProps as q, seoOgDescription as qn, valueFromNodes as qt, terminalSafe as r, correctnessBasePathNavigation as rn, applyRuleSeverities as rr, architectureDocLinkTarget as rt, computeHealth as s, correctnessCheckableBindValue as sn, settingSeverity as sr, architectureDirectoryNaming as st, formatMarkdownReport as t, correctnessInstanceBrowserGlobal as tn, SEARCH_RESULT_RULES as tr, performanceNamespaceImport as tt, SEVERITY_RANK as u, correctnessPropMutation as un, performanceRenderBlockingScript as ur, DEFAULT_KIT_ALIASES as ut, a11yIdDuplication as v, seoDuplicateDescription as vn, seoSitemapXml as vr, resolveRepoLocalPath as vt, a11yDisallowedElement as w, NAMING_ATTRS$1 as wn, hasRel as wr, collectDerivedGuardBindings as wt, a11yDoctype as x, ANCESTRY_DEPENDENT_TAGS as xn, seoOgTitle as xr, addBoundNames as xt, a11yTopLevelLandmark as y, seoDuplicateTitle as yn, seoRobotsTxt as yr, isRootRelativePath as yt, a11yRequireDatetime as z, seoJsonLdPlaceholder as zn, defaultProject as zr, attrText as zt };