@svelte-vitals/core 0.25.0 → 0.26.0

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +155 -26
  2. package/dist/index.js +1104 -8
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -219,6 +219,35 @@ interface EffectFact {
219
219
  /** True when this $effect has a NON-EMPTY body that reads no reactive value and makes no bare call — it never re-runs, so it should be onMount (CORRECT003). */
220
220
  mountOnly: boolean;
221
221
  }
222
+ /** A `$effect` guaranteed to run outside component initialisation — it throws `effect_orphan` at runtime (CORRECT006). */
223
+ interface OrphanEffectFact {
224
+ /** 1-based source line, or 0 if unknown. For 'constructor-instantiated', the module-scope `new` site. */
225
+ line: number;
226
+ /** 'top-level' = runs at module evaluation; 'constructor-instantiated' = module-scope `new` of a same-file class whose constructor creates a bare effect. */
227
+ kind: 'top-level' | 'constructor-instantiated';
228
+ /** Class name when kind is 'constructor-instantiated' (used in the finding message). */
229
+ className?: string;
230
+ }
231
+ /** A svelte lifecycle/context call guaranteed to run outside component initialisation — it throws `lifecycle_outside_component` at runtime (CORRECT007). */
232
+ interface OrphanLifecycleCallFact {
233
+ /** Canonical svelte export name (alias-resolved), e.g. 'onMount'. */
234
+ name: string;
235
+ /** 1-based source line, or 0 if unknown. For 'constructor-instantiated', the module-scope `new` site. */
236
+ line: number;
237
+ /** 'top-level' = runs at module evaluation; 'constructor-instantiated' = module-scope `new` of a same-file class whose constructor calls a tracked function. */
238
+ kind: 'top-level' | 'constructor-instantiated';
239
+ /** Class name when kind is 'constructor-instantiated' (used in the finding message). */
240
+ className?: string;
241
+ }
242
+ /** A browser-only global read in code that runs on the server — SSR crashes with "<name> is not defined" (CORRECT008/009). */
243
+ interface BrowserGlobalRefFact {
244
+ /** The global's name, e.g. 'window'. */
245
+ name: string;
246
+ /** 1-based source line, or 0 if unknown. */
247
+ line: number;
248
+ /** 'module' = module evaluation (script module / runes module — CORRECT008); 'instance' = component-init top level (runs on the server during SSR — CORRECT009). */
249
+ context: 'module' | 'instance';
250
+ }
222
251
  /** A flagged source position in a component (e.g. an `{@html}` tag or a `javascript:` URL). */
223
252
  interface SourceSpan {
224
253
  /** 1-based source line, or 0 if unknown. */
@@ -267,52 +296,123 @@ interface ComponentFacts {
267
296
  name: string;
268
297
  line: number;
269
298
  }[];
299
+ /** `$effect` calls guaranteed to run outside component initialisation — module scope in `.svelte.ts`/`.svelte.js` or `<script module>` (CORRECT006). */
300
+ orphanEffects: OrphanEffectFact[];
301
+ /** Svelte lifecycle/context calls guaranteed to run outside component initialisation — module scope in `.svelte.ts`/`.svelte.js` or `<script module>` (CORRECT007). */
302
+ orphanLifecycleCalls: OrphanLifecycleCallFact[];
303
+ /** Browser-global reads in server-executed positions of this file (CORRECT008/009). */
304
+ browserGlobalRefs: BrowserGlobalRefFact[];
305
+ /** Module-scope `$state` declarations in a `.svelte.ts`/`.svelte.js` runes module — on a server, one instance shared by every request (SEC005). Always empty for `.svelte` files. */
306
+ moduleStateDecls: {
307
+ name: string;
308
+ line: number;
309
+ }[];
270
310
  /** Inline `svelte-vitals-disable-next-line` directives found in this file's source — component-rule escape hatch (issue #92). Optional: absent is equivalent to no directives, so existing external constructors of `ComponentFacts` are unaffected. */
271
311
  suppressions?: SuppressionDirective[];
272
312
  }
273
313
 
274
- /** Parse a component's reactivity/correctness + security + architecture facts (CLI/static + vite build mode). */
275
- declare function parseComponentFacts(source: string, filename: string): {
276
- eachBlocks: EachBlockFact[];
277
- effects: EffectFact[];
278
- htmlTags: SourceSpan[];
279
- javascriptUrls: SourceSpan[];
280
- loc: number;
281
- propCount: number;
282
- imports: string[];
283
- importSpans: {
284
- source: string;
314
+ /** What the per-file parsers produce `ComponentFacts` minus `file`, with `suppressions` always present. */
315
+ type ParsedFacts = Omit<ComponentFacts, 'file' | 'suppressions'> & {
316
+ suppressions: SuppressionDirective[];
317
+ };
318
+ /**
319
+ * Parse one source file's facts (CLI/static + vite build mode): a `.svelte` component's
320
+ * reactivity/correctness + security + architecture facts, or a `.svelte.ts`/`.svelte.js`
321
+ * runes module's orphan-$effect facts (CORRECT006).
322
+ */
323
+ declare function parseComponentFacts(source: string, filename: string): ParsedFacts;
324
+
325
+ /**
326
+ * Fallback facts for a file that fails to read or parse (dev tooling must never
327
+ * throw). This is the single source of truth for the empty-facts shape — add new
328
+ * `ComponentFacts` fields HERE so TypeScript catches every call site that still
329
+ * needs updating.
330
+ */
331
+ declare function emptyComponentFacts(file: string): ComponentFacts;
332
+ /**
333
+ * Scan every `.svelte` component and `.svelte.ts`/`.svelte.js` runes module under `src/`
334
+ * for Correctness/Security/Architecture/Bundle-Performance facts. Independent of route
335
+ * resolution — covers `$lib` and non-route components too. A file that fails to read or
336
+ * parse contributes empty facts instead of aborting the whole scan (dev tooling must
337
+ * never throw).
338
+ */
339
+ declare function collectComponentFacts(rt: Runtime, cwd: string): Promise<ComponentFacts[]>;
340
+
341
+ /**
342
+ * Facts parsed from one SvelteKit route/hooks file for the SSR shared-state rules
343
+ * (SEC003–005). Collected by `collectKitModuleFacts` (static/CLI + vite build mode).
344
+ */
345
+ interface KitModuleFacts {
346
+ /** Repo-relative source file. */
347
+ file: string;
348
+ /** 'server' = runs only on the server (+*.server, +server, hooks.server); 'universal' = +page.ts/+layout.ts (still runs on the server during SSR). */
349
+ kind: 'server' | 'universal';
350
+ /** Module-scope let/var reassigned from inside a function (SEC004). */
351
+ moduleStateReassignments: {
352
+ name: string;
285
353
  line: number;
354
+ inHandler: boolean;
286
355
  }[];
287
- namespaceImports: {
356
+ /** Writes to an imported binding from inside an exported handler (SEC003). */
357
+ importedStateWrites: {
358
+ name: string;
359
+ line: number;
360
+ via: 'assignment' | 'set-call';
361
+ }[];
362
+ /** Writes to an imported binding outside handlers — top level or helper functions (SEC005's write flavour). */
363
+ importedStateWritesOutsideHandlers: {
364
+ name: string;
365
+ line: number;
366
+ }[];
367
+ /** Value imports whose specifier resolves to a repo-local `.svelte.ts`/`.svelte.js` runes module (SEC005). */
368
+ runesModuleImports: {
288
369
  source: string;
370
+ resolved: string;
371
+ names: string[];
289
372
  line: number;
290
373
  }[];
291
- constableStates: {
374
+ /** Svelte lifecycle/context calls that run outside component initialisation — top level, handler bodies, or the `init` hook (CORRECT007). */
375
+ lifecycleCalls: {
292
376
  name: string;
293
377
  line: number;
378
+ inHandler: boolean;
294
379
  }[];
295
- mutatedProps: {
380
+ /** Browser-global reads in server-executed positions — top level, handler bodies, the `init` hook (CORRECT008). Empty when the file itself exports `ssr = false`. */
381
+ browserGlobalRefs: {
296
382
  name: string;
297
383
  line: number;
384
+ inHandler: boolean;
298
385
  }[];
386
+ /** Inline `svelte-vitals-disable-next-line` directives in this file. */
299
387
  suppressions: SuppressionDirective[];
300
- };
388
+ }
301
389
 
302
390
  /**
303
- * Fallback facts for a file that fails to read or parse (dev tooling must never
304
- * throw). This is the single source of truth for the empty-facts shape — add new
305
- * `ComponentFacts` fields HERE so TypeScript catches every call site that still
306
- * needs updating.
391
+ * Resolve an import specifier to a repo-relative `.svelte.ts`/`.svelte.js` path, or
392
+ * undefined when it cannot be a runes module: `$lib/` maps to `src/lib/`, `./`/`../`
393
+ * resolve against the importing file's directory; bare packages, other aliases, and
394
+ * a relative specifier whose `..` segments escape the repo root are skipped. An
395
+ * extensionless `…/x.svelte` specifier canonicalises to `….svelte.ts` (SEC005 also
396
+ * tries the `.js` sibling when matching).
307
397
  */
308
- declare function emptyComponentFacts(file: string): ComponentFacts;
398
+ declare function resolveRunesModuleSpecifier(spec: string, importerFile: string): string | undefined;
309
399
  /**
310
- * Scan every `.svelte` component under `src/` for Correctness/Security/Architecture/
311
- * Bundle-Performance facts. Independent of route resolution covers `$lib` and
312
- * non-route components too. A file that fails to read or parse contributes empty
313
- * facts instead of aborting the whole scan (dev tooling must never throw).
400
+ * Parse one SvelteKit route/hooks file's SSR shared-state facts (SEC003–005). Uses
401
+ * the shared wrap parser (`parseModuleProgram`), so reported lines subtract the
402
+ * 1-line wrap prefix; suppressions are scanned on the unwrapped source.
314
403
  */
315
- declare function collectComponentFacts(rt: Runtime, cwd: string): Promise<ComponentFacts[]>;
404
+ declare function parseKitModuleFacts(source: string, filename: string): Omit<KitModuleFacts, 'file' | 'kind'>;
405
+
406
+ /** Fallback facts for a Kit file that fails to read or parse (dev tooling must never throw). */
407
+ declare function emptyKitModuleFacts(file: string, kind: KitModuleFacts['kind']): KitModuleFacts;
408
+ /**
409
+ * Scan SvelteKit route/hooks files for SSR shared-state facts (SEC003–005): route
410
+ * `+page`/`+layout` server and universal modules, `+server` endpoints, and
411
+ * `src/hooks.server`. `src/lib/server/**` is deliberately NOT scanned — legitimate
412
+ * module singletons (DB connections, clients) live there (design). A file that
413
+ * fails to read or parse contributes empty facts instead of aborting the scan.
414
+ */
415
+ declare function collectKitModuleFacts(rt: Runtime, cwd: string): Promise<KitModuleFacts[]>;
316
416
 
317
417
  type Node = any;
318
418
  /**
@@ -361,6 +461,8 @@ interface RuleContext {
361
461
  headings?: ResolvedHeadings[];
362
462
  /** Per-file component-body facts for Correctness rules (static/CLI mode only). */
363
463
  components?: ComponentFacts[];
464
+ /** Per-file SvelteKit route/hooks facts for the SSR shared-state rules (static/CLI + vite build mode only). */
465
+ kitModules?: KitModuleFacts[];
364
466
  project: Project;
365
467
  config: Config;
366
468
  }
@@ -518,9 +620,36 @@ declare const correct004UnmutatedState: Rule;
518
620
 
519
621
  declare const correct005PropMutation: Rule;
520
622
 
623
+ declare const correct006OrphanEffect: Rule;
624
+
625
+ /**
626
+ * CORRECT007 — svelte lifecycle/context calls guaranteed to run outside component
627
+ * initialisation: module scope in runes modules / `<script module>`, the constructor of
628
+ * a module-scope-instantiated class, and Kit load/handler/`init` bodies. A custom check
629
+ * because the facts live on BOTH the component channel and the Kit-module channel.
630
+ */
631
+ declare const correct007OrphanLifecycle: Rule;
632
+
633
+ /**
634
+ * CORRECT008 — browser globals read in server-executed MODULE code: module scope of
635
+ * runes modules / `<script module>`, and Kit route/hooks files (top level, handler
636
+ * bodies, the `init` hook). All of it runs on the server, where these globals do not
637
+ * exist — SSR crashes with a ReferenceError. Instance-script reads are CORRECT009's
638
+ * (warning) territory. A custom check because the facts live on both channels.
639
+ */
640
+ declare const correct008BrowserGlobals: Rule;
641
+
642
+ declare const correct009InstanceBrowserGlobals: Rule;
643
+
521
644
  declare const sec001Html: Rule;
522
645
  declare const sec002JavascriptUrl: Rule;
523
646
 
647
+ declare const sec003LoadStateWrite: Rule;
648
+
649
+ declare const sec004ServerModuleState: Rule;
650
+
651
+ declare const sec005SharedStateImport: Rule;
652
+
524
653
  declare const arch001ComponentSize: Rule;
525
654
  declare const arch002PropCount: Rule;
526
655
 
@@ -811,4 +940,4 @@ declare function selectRules(rules: Rule[], config: Config): Rule[];
811
940
  /** Apply per-rule severity overrides to results (design §6). */
812
941
  declare function applyRuleSeverities(results: Result[], config: Config): Result[];
813
942
 
814
- export { APP_SCRIPT, APP_STYLE, type AppSnapshot, BAND_COLOR, CHILD_NODE_KEYS, type Category, type Classification, type ComponentFacts, type Config, type ConsoleReportOptions, type Detection, type EachBlockFact, type EffectFact, type Fix, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, type ImageInfo, type JsonReport, type Palette, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type Result, type RouteBadge, type Rule, type RuleContext, type RuleInfo, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type SourceSpan, type Summary, type SuppressionDirective, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, arch001ComponentSize, arch002PropCount, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, computeHealth, computeScore, correct001EachKey, correct002EffectDerived, correct003EffectAsOnMount, correct004UnmutatedState, correct005PropMutation, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, emptyComponentFacts, escapeHtml, explainRule, findAttr, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, isPenalized, lineOf, linkRule, noColorPalette, parseComponentFacts, perf001ImageDimensions, perf002ImageLoading, perf003PreloadAs, perf004FontPreloadCrossorigin, perf005LcpImage, perf006ResponsiveImage, perf007RenderBlockingScript, perf008Preconnect, perf009HeavyImport, perf010NamespaceImport, renderAppShell, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, sec001Html, sec002JavascriptUrl, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, seo010Indexability, seo011TwitterCard, seo012OgDescription, seo013OgUrl, seo014Viewport, seo015SitemapInRobots, seo016JsonLdValidity, seo017DeprecatedType, seo018RelativeUrl, seo019DateFormat, seo020Placeholder, seo021RequiredProps, seo022TitleLength, seo023DescriptionLength, seo024Charset, seo025ImageAlt, seo026Hreflang, seo027Heading, seo028TitleUnique, seo029DescriptionUnique, seo030HeadingOrder, summarize, textFromNodes, valueFromNodes };
943
+ export { APP_SCRIPT, APP_STYLE, type AppSnapshot, BAND_COLOR, CHILD_NODE_KEYS, type Category, type Classification, type ComponentFacts, type Config, type ConsoleReportOptions, type Detection, type EachBlockFact, type EffectFact, type Fix, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, type ImageInfo, type JsonReport, type KitModuleFacts, type OrphanEffectFact, type Palette, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type Result, type RouteBadge, type Rule, type RuleContext, type RuleInfo, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type SourceSpan, type Summary, type SuppressionDirective, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, arch001ComponentSize, arch002PropCount, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, collectKitModuleFacts, computeHealth, computeScore, correct001EachKey, correct002EffectDerived, correct003EffectAsOnMount, correct004UnmutatedState, correct005PropMutation, correct006OrphanEffect, correct007OrphanLifecycle, correct008BrowserGlobals, correct009InstanceBrowserGlobals, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, emptyComponentFacts, emptyKitModuleFacts, escapeHtml, explainRule, findAttr, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, isPenalized, lineOf, linkRule, noColorPalette, parseComponentFacts, parseKitModuleFacts, perf001ImageDimensions, perf002ImageLoading, perf003PreloadAs, perf004FontPreloadCrossorigin, perf005LcpImage, perf006ResponsiveImage, perf007RenderBlockingScript, perf008Preconnect, perf009HeavyImport, perf010NamespaceImport, renderAppShell, resolveRunesModuleSpecifier, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, sec001Html, sec002JavascriptUrl, sec003LoadStateWrite, sec004ServerModuleState, sec005SharedStateImport, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, seo010Indexability, seo011TwitterCard, seo012OgDescription, seo013OgUrl, seo014Viewport, seo015SitemapInRobots, seo016JsonLdValidity, seo017DeprecatedType, seo018RelativeUrl, seo019DateFormat, seo020Placeholder, seo021RequiredProps, seo022TitleLength, seo023DescriptionLength, seo024Charset, seo025ImageAlt, seo026Hreflang, seo027Heading, seo028TitleUnique, seo029DescriptionUnique, seo030HeadingOrder, summarize, textFromNodes, valueFromNodes };