@svelte-vitals/core 0.45.0 → 0.47.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.
- package/dist/{chunk-BCJZO532.js → chunk-25TAKBFU.js} +923 -116
- package/dist/{index-DbUx4tlY.d.ts → index-Qrw_f9HP.d.ts} +486 -357
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +75 -6
- package/dist/internal.js +72 -1
- package/package.json +4 -2
|
@@ -1,7 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component-body facts for the Correctness category — the source-analysis boundary
|
|
3
|
+
* (mirrors images.ts / headings.ts). Collected by the static (CLI) provider only;
|
|
4
|
+
* the rendered provider can't see reactivity, so correctness rules no-op there.
|
|
5
|
+
*/
|
|
6
|
+
/** An `{#each}` block in a component template. */
|
|
7
|
+
interface EachBlockFact {
|
|
8
|
+
/** True when the block has a key, e.g. `{#each items as item (item.id)}`. */
|
|
9
|
+
hasKey: boolean;
|
|
10
|
+
/** 1-based source line, or 0 if unknown. */
|
|
11
|
+
line: number;
|
|
12
|
+
/** Set when the block's key expression is its index binding or a trivial coercion of it — `(i)`, `(String(i))`, `(Number(i))`, `` (`${i}`) ``, `(i.toString())`, `(i + '')` — correctness/each-index-key. */
|
|
13
|
+
indexKey?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** An `$effect(...)` / `$effect.pre(...)` call in a component's instance script. */
|
|
16
|
+
interface EffectFact {
|
|
17
|
+
/** 1-based source line, or 0 if unknown. */
|
|
18
|
+
line: number;
|
|
19
|
+
/** True when the effect body only assigns to `$state` variables (the "use $derived" smell). */
|
|
20
|
+
assignsOnlyState: boolean;
|
|
21
|
+
/** 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 (correctness/effect-as-onmount). */
|
|
22
|
+
mountOnly: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** A `$effect` guaranteed to run outside component initialisation — it throws `effect_orphan` at runtime (correctness/orphan-effect). */
|
|
25
|
+
interface OrphanEffectFact {
|
|
26
|
+
/** 1-based source line, or 0 if unknown. For 'constructor-instantiated', the module-scope `new` site. */
|
|
27
|
+
line: number;
|
|
28
|
+
/** 'top-level' = runs at module evaluation; 'constructor-instantiated' = module-scope `new` of a same-file class whose constructor creates a bare effect. */
|
|
29
|
+
kind: 'top-level' | 'constructor-instantiated';
|
|
30
|
+
/** Class name when kind is 'constructor-instantiated' (used in the finding message). */
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
/** A svelte lifecycle/context call guaranteed to run outside component initialisation — it throws `lifecycle_outside_component` at runtime (correctness/orphan-lifecycle). */
|
|
34
|
+
interface OrphanLifecycleCallFact {
|
|
35
|
+
/** Canonical svelte export name (alias-resolved), e.g. 'onMount'. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** 1-based source line, or 0 if unknown. For 'constructor-instantiated', the module-scope `new` site. */
|
|
38
|
+
line: number;
|
|
39
|
+
/** 'top-level' = runs at module evaluation; 'constructor-instantiated' = module-scope `new` of a same-file class whose constructor calls a tracked function. */
|
|
40
|
+
kind: 'top-level' | 'constructor-instantiated';
|
|
41
|
+
/** Class name when kind is 'constructor-instantiated' (used in the finding message). */
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
/** A browser-only global read in code that runs on the server — SSR crashes with "<name> is not defined" (correctness/server-browser-global, correctness/instance-browser-global). */
|
|
45
|
+
interface BrowserGlobalRefFact {
|
|
46
|
+
/** The global's name, e.g. 'window'. */
|
|
47
|
+
name: string;
|
|
48
|
+
/** 1-based source line, or 0 if unknown. */
|
|
49
|
+
line: number;
|
|
50
|
+
/** 'module' = module evaluation (script module / runes module — correctness/server-browser-global); 'instance' = component-init top level (runs on the server during SSR — correctness/instance-browser-global). */
|
|
51
|
+
context: 'module' | 'instance';
|
|
52
|
+
}
|
|
53
|
+
/** A flagged source position in a component (e.g. an `{@html}` tag or a `javascript:` URL). */
|
|
54
|
+
interface SourceSpan {
|
|
55
|
+
/** 1-based source line, or 0 if unknown. */
|
|
56
|
+
line: number;
|
|
57
|
+
}
|
|
58
|
+
/** An inline `svelte-vitals-disable-next-line` directive found in the component's source (issue #92). */
|
|
59
|
+
interface SuppressionDirective {
|
|
60
|
+
/** 1-based line the directive suppresses (the line immediately after the comment). */
|
|
61
|
+
line: number;
|
|
62
|
+
/** Rule ids suppressed on that line; undefined = suppress every rule on that line. */
|
|
63
|
+
ruleIds?: string[];
|
|
64
|
+
}
|
|
65
|
+
/** An `<input type="checkbox">` / `<input type="radio">` element carrying a `bind:value`
|
|
66
|
+
* directive — `bind:value` observes the DOM `value` property, which checkbox/radio
|
|
67
|
+
* interaction never changes, so the bound state silently never updates
|
|
68
|
+
* (correctness/checkable-bind-value). */
|
|
69
|
+
interface CheckableBindValueFact {
|
|
70
|
+
/** Which checkable input type was flagged — selects the message wording. */
|
|
71
|
+
kind: 'checkbox' | 'radio';
|
|
72
|
+
/** 1-based source line, or 0 if unknown. */
|
|
73
|
+
line: number;
|
|
74
|
+
}
|
|
75
|
+
/** A root-relative navigation literal — broken when the app is served under `kit.paths.base`
|
|
76
|
+
* (correctness/base-path-navigation). Shared by the component and Kit-module channels. */
|
|
77
|
+
interface BasePathLinkFact {
|
|
78
|
+
/** Which navigation surface it was written on — selects the message wording. */
|
|
79
|
+
kind: 'href' | 'goto' | 'redirect';
|
|
80
|
+
/** The literal path as written, e.g. '/about'. */
|
|
81
|
+
path: string;
|
|
82
|
+
/** 1-based source line, or 0 if unknown. */
|
|
83
|
+
line: number;
|
|
84
|
+
}
|
|
85
|
+
/** An interactive element (e.g. `<button>`) found nested inside another interactive
|
|
86
|
+
* container (e.g. `<a href>`) (a11y/interactive-nesting). */
|
|
87
|
+
interface InteractiveNestingFact {
|
|
88
|
+
containerTag: string;
|
|
89
|
+
/** The container's literal `role`, when that is what made it a container rather than its tag. */
|
|
90
|
+
containerRole?: string;
|
|
91
|
+
descendantTag: string;
|
|
92
|
+
/** 1-based source line of the descendant, or 0 if unknown. */
|
|
93
|
+
line: number;
|
|
94
|
+
}
|
|
95
|
+
/** A `button`/`a href`/`input type="image"` with no computable accessible name (a11y/accessible-name). */
|
|
96
|
+
interface UnnamedInteractiveFact {
|
|
97
|
+
tag: string;
|
|
98
|
+
/** 1-based source line, or 0 if unknown. */
|
|
99
|
+
line: number;
|
|
100
|
+
}
|
|
101
|
+
/** An element carrying a `role` and/or `aria-*` attribute(s) (a11y ARIA rules). */
|
|
102
|
+
interface AriaElementFact {
|
|
103
|
+
tag: string;
|
|
104
|
+
/** 1-based source line, or 0 if unknown. */
|
|
105
|
+
line: number;
|
|
106
|
+
/** literal role value; undefined = no role attr; { expression: true } = dynamic */
|
|
107
|
+
role?: {
|
|
108
|
+
literal?: string;
|
|
109
|
+
expression?: boolean;
|
|
110
|
+
};
|
|
111
|
+
/** every aria-* attribute on the element */
|
|
112
|
+
aria: {
|
|
113
|
+
name: string;
|
|
114
|
+
literal?: string;
|
|
115
|
+
expression?: boolean;
|
|
116
|
+
line: number;
|
|
117
|
+
}[];
|
|
118
|
+
/** literal `type` of an `<input>`, lowercased; undefined for non-inputs or a dynamic type */
|
|
119
|
+
inputType?: string;
|
|
120
|
+
/** an `<input>` carrying a `list` attribute — its implicit role is `combobox` and the host supplies `aria-expanded` */
|
|
121
|
+
hasList?: true;
|
|
122
|
+
/**
|
|
123
|
+
* A `<select>`'s native role: `combobox` with no `multiple` and no `size` above 1, `listbox`
|
|
124
|
+
* otherwise; absent for a non-select or when a dynamic `size` leaves it unknowable.
|
|
125
|
+
*/
|
|
126
|
+
selectKind?: 'combobox' | 'listbox';
|
|
127
|
+
/** Set when the element also carries a spread attribute — its full attribute set is
|
|
128
|
+
* unknowable, so required-prop presence checks must treat it as satisfied (a11y/required-aria-props). */
|
|
129
|
+
hasSpread?: true;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Every element in a component with its literal attribute names — the input for the rules that
|
|
133
|
+
* judge against the HTML spec data (a11y/deprecated-element, a11y/deprecated-attr, and the rest of
|
|
134
|
+
* that family). Tag and attribute names are lowercased, matching how HTML parses them.
|
|
135
|
+
*/
|
|
136
|
+
interface ElementFact {
|
|
137
|
+
tag: string;
|
|
138
|
+
/** 1-based source line, or 0 if unknown. */
|
|
139
|
+
line: number;
|
|
140
|
+
/**
|
|
141
|
+
* Literal attribute names on the element (directives, spreads and expression-only names excluded).
|
|
142
|
+
* The per-attribute line is not what the deprecation rules anchor to — they anchor at the start
|
|
143
|
+
* tag so a `disable-next-line` directive can reach a multi-line element — but a value-level rule
|
|
144
|
+
* (`invalid-attr`) may want it for its message.
|
|
145
|
+
*/
|
|
146
|
+
attrs: {
|
|
147
|
+
name: string;
|
|
148
|
+
line: number;
|
|
149
|
+
value?: string;
|
|
150
|
+
}[];
|
|
151
|
+
/**
|
|
152
|
+
* Inside an `<svg>` subtree, or in a component declaring `<svelte:options namespace="svg" />`.
|
|
153
|
+
* `<foreignObject>` returns to HTML. Names collide across the two namespaces (`a`, `script`,
|
|
154
|
+
* `style`, `title`), so HTML-only rules must skip these.
|
|
155
|
+
*/
|
|
156
|
+
inSvg?: true;
|
|
157
|
+
/**
|
|
158
|
+
* Index of the nearest literal ancestor element in the same array (push-before-children DFS
|
|
159
|
+
* keeps it sound), looking through `{#if}`/`{#each}`/`{#await}`/`{#key}`. Absent at template
|
|
160
|
+
* root and after every construct whose rendering position is not lexical — a component,
|
|
161
|
+
* `<svelte:element>`, `<slot>`, `{@render}`, `{@html}`, a custom element or unknown tag,
|
|
162
|
+
* a `{#snippet}` body root, `<svelte:head>` children — so `a11y/permitted-contents` never
|
|
163
|
+
* judges across one.
|
|
164
|
+
*/
|
|
165
|
+
parent?: number;
|
|
166
|
+
/** A spread attribute is present — every attribute test on this element is unknowable. */
|
|
167
|
+
hasSpread?: true;
|
|
168
|
+
/**
|
|
169
|
+
* A direct child the static walk cannot see through (component, `{@html}`, `{@render}`,
|
|
170
|
+
* `<slot />`, `<svelte:element>`, a custom element or unknown tag) — `:has(...)` over this
|
|
171
|
+
* element's subtree is unknowable.
|
|
172
|
+
*/
|
|
173
|
+
unknownContent?: true;
|
|
174
|
+
}
|
|
175
|
+
/** Reactivity/correctness + security + architecture facts parsed from one `.svelte` component. */
|
|
176
|
+
interface ComponentFacts {
|
|
177
|
+
/** Source file the component came from. */
|
|
178
|
+
file: string;
|
|
179
|
+
eachBlocks: EachBlockFact[];
|
|
180
|
+
effects: EffectFact[];
|
|
181
|
+
/** `{@html …}` occurrences — raw-HTML render surfaces (security/raw-html). */
|
|
182
|
+
htmlTags: SourceSpan[];
|
|
183
|
+
/** Element attributes with a literal `javascript:` URL (security/javascript-url). */
|
|
184
|
+
javascriptUrls: SourceSpan[];
|
|
185
|
+
/** Source line count of the component file (architecture/component-size). */
|
|
186
|
+
loc: number;
|
|
187
|
+
/** Named props destructured from `$props()`; 0 when unknowable (rest / non-destructured) (architecture/prop-count). */
|
|
188
|
+
propCount: number;
|
|
189
|
+
/** Module specifiers of every `import` in the instance + module scripts (performance/heavy-import). */
|
|
190
|
+
imports: string[];
|
|
191
|
+
/**
|
|
192
|
+
* Module specifiers of every `import`, each with its source line (performance/heavy-import,
|
|
193
|
+
* architecture/route-component-import). `type` marks a declaration that contributes **no runtime
|
|
194
|
+
* value binding** — either `import type …`, or one whose every specifier is inline-typed
|
|
195
|
+
* (`import { type A } from …`). A specifier-less side-effect import is not marked: it still loads
|
|
196
|
+
* the module. Optional, so existing external constructors of `ComponentFacts` are unaffected.
|
|
197
|
+
*/
|
|
198
|
+
importSpans: {
|
|
199
|
+
source: string;
|
|
200
|
+
line: number;
|
|
201
|
+
type?: true;
|
|
202
|
+
}[];
|
|
203
|
+
/** Value `import * as X from '<bare pkg>'` namespace imports (type-only excluded) — performance/namespace-import. */
|
|
204
|
+
namespaceImports: {
|
|
205
|
+
source: string;
|
|
206
|
+
line: number;
|
|
207
|
+
}[];
|
|
208
|
+
/** `$state` declarations never written or escaped anywhere in the component — candidates for const (correctness/unmutated-state). */
|
|
209
|
+
constableStates: {
|
|
210
|
+
name: string;
|
|
211
|
+
line: number;
|
|
212
|
+
}[];
|
|
213
|
+
/** Mutations of a non-`$bindable` prop from `$props()`, or a legacy `export let` prop — member writes, `delete`, or a mutating method call (correctness/prop-mutation). `legacy` distinguishes which mode the prop was declared in (absent/false: `$props()`), since the fix differs — optional so existing external constructors of `ComponentFacts` are unaffected. */
|
|
214
|
+
mutatedProps: {
|
|
215
|
+
name: string;
|
|
216
|
+
line: number;
|
|
217
|
+
legacy?: boolean;
|
|
218
|
+
}[];
|
|
219
|
+
/** Top-level const/let bindings computed from a $props() or legacy `export let` prop without $derived (or `$:`), never reassigned or escaped, and referenced (eagerly) in the template — frozen at init (correctness/stale-prop-derivation). `legacy` distinguishes which mode the prop was declared in, since the fix differs — optional so existing external constructors of `ComponentFacts` are unaffected. */
|
|
220
|
+
stalePropDerivations: {
|
|
221
|
+
name: string;
|
|
222
|
+
line: number;
|
|
223
|
+
legacy?: boolean;
|
|
224
|
+
}[];
|
|
225
|
+
/** Object/array-literal $state bindings reassigned at least once but never mutated, escaped, aliased, or item-edited — $state.raw candidates (performance/state-raw). */
|
|
226
|
+
rawableStates: {
|
|
227
|
+
name: string;
|
|
228
|
+
line: number;
|
|
229
|
+
}[];
|
|
230
|
+
/** Plain built-in instances (Map/Set/Date/URL/URLSearchParams) in $state whose type-specific mutations were observed inside functions, with no exempting reassignment — untracked by reactivity (correctness/nonreactive-builtin-state). */
|
|
231
|
+
nonreactiveBuiltinStates: {
|
|
232
|
+
name: string;
|
|
233
|
+
type: string;
|
|
234
|
+
line: number;
|
|
235
|
+
}[];
|
|
236
|
+
/** `<input type="checkbox">` / `<input type="radio">` elements bound with `bind:value`
|
|
237
|
+
* instead of `bind:checked`/`bind:group` (correctness/checkable-bind-value). */
|
|
238
|
+
checkableBindValues: CheckableBindValueFact[];
|
|
239
|
+
/** Root-relative `<a href>` and `goto()` literals in this component (correctness/base-path-navigation). */
|
|
240
|
+
basePathLinks: BasePathLinkFact[];
|
|
241
|
+
/** `$effect` calls guaranteed to run outside component initialisation — module scope in `.svelte.ts`/`.svelte.js` or `<script module>` (correctness/orphan-effect). */
|
|
242
|
+
orphanEffects: OrphanEffectFact[];
|
|
243
|
+
/** Svelte lifecycle/context calls guaranteed to run outside component initialisation — module scope in `.svelte.ts`/`.svelte.js` or `<script module>` (correctness/orphan-lifecycle). */
|
|
244
|
+
orphanLifecycleCalls: OrphanLifecycleCallFact[];
|
|
245
|
+
/** Browser-global reads in server-executed positions of this file (correctness/server-browser-global, correctness/instance-browser-global). */
|
|
246
|
+
browserGlobalRefs: BrowserGlobalRefFact[];
|
|
247
|
+
/** Module-scope `$state` declarations in a `.svelte.ts`/`.svelte.js` runes module — on a server, one instance shared by every request (security/shared-state-import). Always empty for `.svelte` files. */
|
|
248
|
+
moduleStateDecls: {
|
|
249
|
+
name: string;
|
|
250
|
+
line: number;
|
|
251
|
+
}[];
|
|
252
|
+
/** 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. */
|
|
253
|
+
suppressions?: SuppressionDirective[];
|
|
254
|
+
/** Markdown links `[label](url)` appearing inside a comment (architecture/doc-link-target). */
|
|
255
|
+
commentLinks: {
|
|
256
|
+
url: string;
|
|
257
|
+
line: number;
|
|
258
|
+
}[];
|
|
259
|
+
/** Elements carrying a role or any aria-* attribute (a11y ARIA rules). */
|
|
260
|
+
ariaElements?: AriaElementFact[];
|
|
261
|
+
/** Every element with its attribute names and SVG-namespace flag (the HTML spec-data rules). */
|
|
262
|
+
elements?: ElementFact[];
|
|
263
|
+
/** Interactive elements nested inside another interactive container (a11y/interactive-nesting). */
|
|
264
|
+
interactiveNestings?: InteractiveNestingFact[];
|
|
265
|
+
/** `button`/`a href`/`input type="image"` elements with no computable accessible name (a11y/accessible-name). */
|
|
266
|
+
unnamedInteractive?: UnnamedInteractiveFact[];
|
|
267
|
+
/** `<label>` elements with neither a `for` attribute nor a wrapped labelable descendant (a11y/label-has-control). */
|
|
268
|
+
unassociatedLabels?: {
|
|
269
|
+
line: number;
|
|
270
|
+
}[];
|
|
271
|
+
/** Text nodes whose trimmed content opens with a bullet character followed by whitespace, outside any `li` (a11y/use-list). */
|
|
272
|
+
bulletTexts?: {
|
|
273
|
+
line: number;
|
|
274
|
+
char: string;
|
|
275
|
+
}[];
|
|
276
|
+
/** `<select required>` (no `multiple`, display size absent or ≤ 1) whose first `option` element
|
|
277
|
+
* child is not a placeholder label option (a11y/placeholder-label-option). */
|
|
278
|
+
selectsMissingPlaceholder?: {
|
|
279
|
+
line: number;
|
|
280
|
+
}[];
|
|
281
|
+
/** `<time>` with no `datetime` attribute whose literal text content is not machine-readable (a11y/require-datetime). */
|
|
282
|
+
timesMissingDatetime?: {
|
|
283
|
+
line: number;
|
|
284
|
+
text: string;
|
|
285
|
+
}[];
|
|
286
|
+
/** Set when the file failed to read or parse and these facts are the empty fallback — the file was NOT analyzed. */
|
|
287
|
+
parseFailed?: true;
|
|
288
|
+
/** Set when the file could not be READ — an environment problem (permissions, a descriptor
|
|
289
|
+
* limit), not a malformed component. Reported separately so one does not masquerade as the other. */
|
|
290
|
+
readFailed?: true;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Warnings for files a collector could not read or parse: the file contributes empty facts, so any
|
|
294
|
+
* findings it would have produced are simply missing rather than reported as fixed. The two causes
|
|
295
|
+
* are reported separately — an unreadable file is an environment problem (permissions, a descriptor
|
|
296
|
+
* limit) and a malformed one is the author's, and sharing a message is how a descriptor limit once
|
|
297
|
+
* read as hundreds of broken components. Capped at 10 inline paths so one badly-broken directory
|
|
298
|
+
* cannot flood the terminal.
|
|
299
|
+
*/
|
|
300
|
+
declare function skippedFileWarnings(facts: readonly {
|
|
301
|
+
file: string;
|
|
302
|
+
parseFailed?: true;
|
|
303
|
+
readFailed?: true;
|
|
304
|
+
}[]): string[];
|
|
305
|
+
|
|
1
306
|
/**
|
|
2
307
|
* Core type definitions shared across modes. This module is pure: no I/O, no
|
|
3
308
|
* `node:` imports, no runtime-specific globals (design §8).
|
|
4
309
|
*/
|
|
310
|
+
|
|
5
311
|
type Severity = 'critical' | 'warning' | 'info';
|
|
6
312
|
/** Where a head tag is set, relative to the route being evaluated (design §4). */
|
|
7
313
|
type Presence = 'own' | 'inherited' | 'none';
|
|
@@ -55,11 +361,13 @@ interface Project {
|
|
|
55
361
|
* `file` is the config path relative to the analyzed root (posix, may start with `../`
|
|
56
362
|
* in monorepos); unset for inline programmatic configs. `line` is 1-based and set only
|
|
57
363
|
* when the literal `minify: false` was located in that file; unset when the value was
|
|
58
|
-
* resolved at build time (plugin/conditional config).
|
|
364
|
+
* resolved at build time (plugin/conditional config). `suppressions` carries the config file's
|
|
365
|
+
* own inline directives, so a line-anchored finding in it can be silenced like any other.
|
|
59
366
|
*/
|
|
60
367
|
viteMinifyDisabled?: {
|
|
61
368
|
file?: string;
|
|
62
369
|
line?: number;
|
|
370
|
+
suppressions?: SuppressionDirective[];
|
|
63
371
|
};
|
|
64
372
|
/**
|
|
65
373
|
* Set when the project configures a non-empty `kit.paths.base` — read from the `sveltekit()`
|
|
@@ -85,12 +393,13 @@ interface Project {
|
|
|
85
393
|
* silent then, like `viteMinifyDisabled`'s absent convention.
|
|
86
394
|
*/
|
|
87
395
|
appHtmlDoctype?: boolean;
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
396
|
+
/** Literal ids in src/app.html with the line each first appears on — shell content present on every rendered route. Absent when the file wasn't read. */
|
|
397
|
+
appHtmlIds?: {
|
|
398
|
+
id: string;
|
|
399
|
+
line: number;
|
|
400
|
+
}[];
|
|
401
|
+
/** Distinct lowercased tag names inside `app.html`'s `<body>` (a11y/required-element's presence set; static mode). */
|
|
402
|
+
appHtmlBodyTags?: string[];
|
|
94
403
|
}
|
|
95
404
|
declare const defaultProject: Project;
|
|
96
405
|
/** A concrete, agent-actionable remediation for a finding (design §10, issue #18). */
|
|
@@ -302,7 +611,7 @@ interface HeadTag {
|
|
|
302
611
|
presence: Exclude<Presence, 'none'>;
|
|
303
612
|
/** Whether the tag's value is static/dynamic/absent (design §4). */
|
|
304
613
|
value: Value;
|
|
305
|
-
/** Source file the tag came from (static mode
|
|
614
|
+
/** Source file the tag came from (static mode); unset on a rendered head. */
|
|
306
615
|
file?: string;
|
|
307
616
|
}
|
|
308
617
|
/** Resolved effective head for a single route (design §8). */
|
|
@@ -355,362 +664,140 @@ interface ResolvedImages {
|
|
|
355
664
|
/**
|
|
356
665
|
* A normalized page-body heading occurrence — the mode-independent boundary for
|
|
357
666
|
* the heading-hierarchy rule (mirrors images.ts). Both providers collect these
|
|
358
|
-
* so seo/single-h1 never needs to know which mode produced them.
|
|
359
|
-
*/
|
|
360
|
-
interface HeadingInfo {
|
|
361
|
-
/** Heading level 1–6 (the `n` in <hn>). */
|
|
362
|
-
level: number;
|
|
363
|
-
/** 1-based source line, or 0 if unknown (rendered mode does not track lines). */
|
|
364
|
-
line: number;
|
|
365
|
-
/** Source file the heading came from. */
|
|
366
|
-
file: string;
|
|
367
|
-
}
|
|
368
|
-
/** Resolved page-body headings for a single route (page + layout chain). */
|
|
369
|
-
interface ResolvedHeadings {
|
|
370
|
-
route: string;
|
|
371
|
-
headings: HeadingInfo[];
|
|
372
|
-
/**
|
|
373
|
-
* Headings found in child components rendered (transitively) by this route's
|
|
374
|
-
* chain files — source mode only; absent in rendered mode. Kept separate from
|
|
375
|
-
* `headings` because their position in document order is unknown: safe for
|
|
376
|
-
* counting (seo/single-h1), unusable for outline order (seo/heading-level-skip).
|
|
377
|
-
*/
|
|
378
|
-
componentHeadings?: HeadingInfo[];
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/** One step of a template branch address: which exclusive block, and which arm of it. */
|
|
382
|
-
interface BranchStep {
|
|
383
|
-
/** index of the {#if}/{#await} block among its file's blocks (document order) */
|
|
384
|
-
group: number;
|
|
385
|
-
/** branch index within the group (if: 0..n consequent→else; await: 0=pending,1=then,2=catch) */
|
|
386
|
-
branch: number;
|
|
387
|
-
}
|
|
388
|
-
/** Where a folded occurrence sits, for the finding location. */
|
|
389
|
-
interface A11yOccurrenceInfo {
|
|
390
|
-
file: string;
|
|
391
|
-
line: number;
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Route-scoped a11y facts, the mode-independent boundary for the landmark/id rules
|
|
395
|
-
* (mirrors headings.ts). Source mode composes the layout chain plus its resolved
|
|
396
|
-
* components; rendered mode reads the prerendered document.
|
|
397
|
-
*/
|
|
398
|
-
interface ResolvedA11y {
|
|
399
|
-
route: string;
|
|
400
|
-
/** representatives per landmark kind after the branch-aware fold ('main' | 'banner' | 'contentinfo' | 'complementary') */
|
|
401
|
-
landmarks: Record<string, A11yOccurrenceInfo[]>;
|
|
402
|
-
/** landmark occurrences nested inside another landmark after composition */
|
|
403
|
-
nestedLandmarks: {
|
|
404
|
-
kind: string;
|
|
405
|
-
within: string;
|
|
406
|
-
file: string;
|
|
407
|
-
line: number;
|
|
408
|
-
}[];
|
|
409
|
-
/** representatives per literal id */
|
|
410
|
-
ids: Record<string, A11yOccurrenceInfo[]>;
|
|
411
|
-
/** literal id references */
|
|
412
|
-
idRefs: {
|
|
413
|
-
id: string;
|
|
414
|
-
attr: string;
|
|
415
|
-
file: string;
|
|
416
|
-
line: number;
|
|
417
|
-
}[];
|
|
418
|
-
/** optimistic candidates: every literal id anywhere (all branches, each/snippet bodies, components, app.html) */
|
|
419
|
-
idCandidates: string[];
|
|
420
|
-
/** closed world holds: every component resolved, no depth truncation, no {@html}/spread, no dynamic id */
|
|
421
|
-
fullyResolved: boolean;
|
|
422
|
-
}
|
|
423
|
-
type Foldable = {
|
|
424
|
-
key: string;
|
|
425
|
-
path: BranchStep[];
|
|
426
|
-
repeatable: boolean;
|
|
427
|
-
};
|
|
428
|
-
/**
|
|
429
|
-
* Branch-aware occurrence fold (design "Control-flow semantics"): within a branch
|
|
430
|
-
* occurrences sum, across the arms of one exclusive block the arm with the most
|
|
431
|
-
* occurrences wins (tie → lowest branch index) and ITS occurrences are the group's
|
|
432
|
-
* representatives — so a caller's count is always `list.length`, with a location per
|
|
433
|
-
* representative. `{#each}`/`{#snippet}` occurrences render 0..N times and drop out.
|
|
434
|
-
* The max is per key: there is no scalar total to maximize.
|
|
435
|
-
*/
|
|
436
|
-
declare function foldOccurrences<T extends Foldable>(nodes: T[]): Map<string, T[]>;
|
|
437
|
-
/**
|
|
438
|
-
* Decode a fragment identifier the way navigation does before matching an element id
|
|
439
|
-
* (`href="#caf%C3%A9"` targets `id="café"`). Malformed escapes are kept verbatim —
|
|
440
|
-
* the browser would also fail to decode them, so the raw text is the comparable form.
|
|
441
|
-
*/
|
|
442
|
-
declare function decodeFragmentId(fragment: string): string;
|
|
443
|
-
/** Whitespace-split tokens of a (possibly undefined) literal attribute value. */
|
|
444
|
-
declare function splitTokens(value: string | undefined): string[];
|
|
445
|
-
/** Explicit `role` values that map to the landmark kinds the route rules inspect. */
|
|
446
|
-
declare const LANDMARK_ROLES: ReadonlySet<string>;
|
|
447
|
-
/** Attributes whose (whitespace-tokenized) values reference element ids. */
|
|
448
|
-
declare const IDREF_ATTRS: readonly string[];
|
|
449
|
-
/**
|
|
450
|
-
* Whether a decoded URL fragment is HTML's "top of the document" indicator: `#top` (ASCII
|
|
451
|
-
* case-insensitive) scrolls to the top when no element has that id, so it is never a missing
|
|
452
|
-
* reference. Compare AFTER percent-decoding — `#%74op` navigates identically to `#top`.
|
|
453
|
-
*/
|
|
454
|
-
declare function isTopFragment(id: string): boolean;
|
|
455
|
-
/**
|
|
456
|
-
* A fragment with its text directive removed. Everything from the first `:~:` on is user-agent
|
|
457
|
-
* instructions for finding text and names no element, while anything before it is still an
|
|
458
|
-
* ordinary element fragment — `#section:~:text=hi` targets `id="section"`, `#:~:text=hi` targets
|
|
459
|
-
* nothing. Returns an empty string when the fragment is a directive and nothing else.
|
|
460
|
-
*/
|
|
461
|
-
declare function stripTextDirective(fragment: string): string;
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Component-body facts for the Correctness category — the source-analysis boundary
|
|
465
|
-
* (mirrors images.ts / headings.ts). Collected by the static (CLI) provider only;
|
|
466
|
-
* the rendered provider can't see reactivity, so correctness rules no-op there.
|
|
467
|
-
*/
|
|
468
|
-
/** An `{#each}` block in a component template. */
|
|
469
|
-
interface EachBlockFact {
|
|
470
|
-
/** True when the block has a key, e.g. `{#each items as item (item.id)}`. */
|
|
471
|
-
hasKey: boolean;
|
|
472
|
-
/** 1-based source line, or 0 if unknown. */
|
|
473
|
-
line: number;
|
|
474
|
-
/** Set when the block's key expression is its index binding or a trivial coercion of it — `(i)`, `(String(i))`, `(Number(i))`, `` (`${i}`) ``, `(i.toString())`, `(i + '')` — correctness/each-index-key. */
|
|
475
|
-
indexKey?: boolean;
|
|
476
|
-
}
|
|
477
|
-
/** An `$effect(...)` / `$effect.pre(...)` call in a component's instance script. */
|
|
478
|
-
interface EffectFact {
|
|
479
|
-
/** 1-based source line, or 0 if unknown. */
|
|
480
|
-
line: number;
|
|
481
|
-
/** True when the effect body only assigns to `$state` variables (the "use $derived" smell). */
|
|
482
|
-
assignsOnlyState: boolean;
|
|
483
|
-
/** 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 (correctness/effect-as-onmount). */
|
|
484
|
-
mountOnly: boolean;
|
|
485
|
-
}
|
|
486
|
-
/** A `$effect` guaranteed to run outside component initialisation — it throws `effect_orphan` at runtime (correctness/orphan-effect). */
|
|
487
|
-
interface OrphanEffectFact {
|
|
488
|
-
/** 1-based source line, or 0 if unknown. For 'constructor-instantiated', the module-scope `new` site. */
|
|
489
|
-
line: number;
|
|
490
|
-
/** 'top-level' = runs at module evaluation; 'constructor-instantiated' = module-scope `new` of a same-file class whose constructor creates a bare effect. */
|
|
491
|
-
kind: 'top-level' | 'constructor-instantiated';
|
|
492
|
-
/** Class name when kind is 'constructor-instantiated' (used in the finding message). */
|
|
493
|
-
className?: string;
|
|
494
|
-
}
|
|
495
|
-
/** A svelte lifecycle/context call guaranteed to run outside component initialisation — it throws `lifecycle_outside_component` at runtime (correctness/orphan-lifecycle). */
|
|
496
|
-
interface OrphanLifecycleCallFact {
|
|
497
|
-
/** Canonical svelte export name (alias-resolved), e.g. 'onMount'. */
|
|
498
|
-
name: string;
|
|
499
|
-
/** 1-based source line, or 0 if unknown. For 'constructor-instantiated', the module-scope `new` site. */
|
|
500
|
-
line: number;
|
|
501
|
-
/** 'top-level' = runs at module evaluation; 'constructor-instantiated' = module-scope `new` of a same-file class whose constructor calls a tracked function. */
|
|
502
|
-
kind: 'top-level' | 'constructor-instantiated';
|
|
503
|
-
/** Class name when kind is 'constructor-instantiated' (used in the finding message). */
|
|
504
|
-
className?: string;
|
|
505
|
-
}
|
|
506
|
-
/** A browser-only global read in code that runs on the server — SSR crashes with "<name> is not defined" (correctness/server-browser-global, correctness/instance-browser-global). */
|
|
507
|
-
interface BrowserGlobalRefFact {
|
|
508
|
-
/** The global's name, e.g. 'window'. */
|
|
509
|
-
name: string;
|
|
510
|
-
/** 1-based source line, or 0 if unknown. */
|
|
511
|
-
line: number;
|
|
512
|
-
/** 'module' = module evaluation (script module / runes module — correctness/server-browser-global); 'instance' = component-init top level (runs on the server during SSR — correctness/instance-browser-global). */
|
|
513
|
-
context: 'module' | 'instance';
|
|
514
|
-
}
|
|
515
|
-
/** A flagged source position in a component (e.g. an `{@html}` tag or a `javascript:` URL). */
|
|
516
|
-
interface SourceSpan {
|
|
517
|
-
/** 1-based source line, or 0 if unknown. */
|
|
518
|
-
line: number;
|
|
519
|
-
}
|
|
520
|
-
/** An inline `svelte-vitals-disable-next-line` directive found in the component's source (issue #92). */
|
|
521
|
-
interface SuppressionDirective {
|
|
522
|
-
/** 1-based line the directive suppresses (the line immediately after the comment). */
|
|
523
|
-
line: number;
|
|
524
|
-
/** Rule ids suppressed on that line; undefined = suppress every rule on that line. */
|
|
525
|
-
ruleIds?: string[];
|
|
526
|
-
}
|
|
527
|
-
/** An `<input type="checkbox">` / `<input type="radio">` element carrying a `bind:value`
|
|
528
|
-
* directive — `bind:value` observes the DOM `value` property, which checkbox/radio
|
|
529
|
-
* interaction never changes, so the bound state silently never updates
|
|
530
|
-
* (correctness/checkable-bind-value). */
|
|
531
|
-
interface CheckableBindValueFact {
|
|
532
|
-
/** Which checkable input type was flagged — selects the message wording. */
|
|
533
|
-
kind: 'checkbox' | 'radio';
|
|
534
|
-
/** 1-based source line, or 0 if unknown. */
|
|
535
|
-
line: number;
|
|
536
|
-
}
|
|
537
|
-
/** A root-relative navigation literal — broken when the app is served under `kit.paths.base`
|
|
538
|
-
* (correctness/base-path-navigation). Shared by the component and Kit-module channels. */
|
|
539
|
-
interface BasePathLinkFact {
|
|
540
|
-
/** Which navigation surface it was written on — selects the message wording. */
|
|
541
|
-
kind: 'href' | 'goto' | 'redirect';
|
|
542
|
-
/** The literal path as written, e.g. '/about'. */
|
|
543
|
-
path: string;
|
|
544
|
-
/** 1-based source line, or 0 if unknown. */
|
|
545
|
-
line: number;
|
|
546
|
-
}
|
|
547
|
-
/** An interactive element (e.g. `<button>`) found nested inside another interactive
|
|
548
|
-
* container (e.g. `<a href>`) (a11y/interactive-nesting). */
|
|
549
|
-
interface InteractiveNestingFact {
|
|
550
|
-
containerTag: string;
|
|
551
|
-
/** The container's literal `role`, when that is what made it a container rather than its tag. */
|
|
552
|
-
containerRole?: string;
|
|
553
|
-
descendantTag: string;
|
|
554
|
-
/** 1-based source line of the descendant, or 0 if unknown. */
|
|
667
|
+
* so seo/single-h1 never needs to know which mode produced them.
|
|
668
|
+
*/
|
|
669
|
+
interface HeadingInfo {
|
|
670
|
+
/** Heading level 1–6 (the `n` in <hn>). */
|
|
671
|
+
level: number;
|
|
672
|
+
/** 1-based source line, or 0 if unknown (rendered mode does not track lines). */
|
|
555
673
|
line: number;
|
|
674
|
+
/** Source file the heading came from. */
|
|
675
|
+
file: string;
|
|
556
676
|
}
|
|
557
|
-
/**
|
|
558
|
-
interface
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
677
|
+
/** Resolved page-body headings for a single route (page + layout chain). */
|
|
678
|
+
interface ResolvedHeadings {
|
|
679
|
+
route: string;
|
|
680
|
+
headings: HeadingInfo[];
|
|
681
|
+
/**
|
|
682
|
+
* Headings found in child components rendered (transitively) by this route's
|
|
683
|
+
* chain files — source mode only; absent in rendered mode. Kept separate from
|
|
684
|
+
* `headings` because their position in document order is unknown: safe for
|
|
685
|
+
* counting (seo/single-h1), unusable for outline order (seo/heading-level-skip).
|
|
686
|
+
*/
|
|
687
|
+
componentHeadings?: HeadingInfo[];
|
|
562
688
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
/**
|
|
689
|
+
|
|
690
|
+
/** One step of a template branch address: which exclusive block, and which arm of it. */
|
|
691
|
+
interface BranchStep {
|
|
692
|
+
/** index of the {#if}/{#await} block among its file's blocks (document order) */
|
|
693
|
+
group: number;
|
|
694
|
+
/** branch index within the group (if: 0..n consequent→else; await: 0=pending,1=then,2=catch) */
|
|
695
|
+
branch: number;
|
|
696
|
+
}
|
|
697
|
+
/** Where a folded occurrence sits, for the finding location. */
|
|
698
|
+
interface A11yOccurrenceInfo {
|
|
699
|
+
file: string;
|
|
567
700
|
line: number;
|
|
568
|
-
/** literal role value; undefined = no role attr; { expression: true } = dynamic */
|
|
569
|
-
role?: {
|
|
570
|
-
literal?: string;
|
|
571
|
-
expression?: boolean;
|
|
572
|
-
};
|
|
573
|
-
/** every aria-* attribute on the element */
|
|
574
|
-
aria: {
|
|
575
|
-
name: string;
|
|
576
|
-
literal?: string;
|
|
577
|
-
expression?: boolean;
|
|
578
|
-
line: number;
|
|
579
|
-
}[];
|
|
580
|
-
/** literal `type` of an `<input>`, lowercased; undefined for non-inputs or a dynamic type */
|
|
581
|
-
inputType?: string;
|
|
582
|
-
/** Set when the element also carries a spread attribute — its full attribute set is
|
|
583
|
-
* unknowable, so required-prop presence checks must treat it as satisfied (a11y/required-aria-props). */
|
|
584
|
-
hasSpread?: true;
|
|
585
701
|
}
|
|
586
|
-
/**
|
|
587
|
-
interface
|
|
588
|
-
|
|
702
|
+
/** One reason a route's closed world failed to hold, with the first offending location. */
|
|
703
|
+
interface A11ySkipCause {
|
|
704
|
+
kind: 'component' | 'spread' | 'html' | 'dynamic-id';
|
|
589
705
|
file: string;
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
*/
|
|
609
|
-
importSpans: {
|
|
610
|
-
source: string;
|
|
611
|
-
line: number;
|
|
612
|
-
type?: true;
|
|
613
|
-
}[];
|
|
614
|
-
/** Value `import * as X from '<bare pkg>'` namespace imports (type-only excluded) — performance/namespace-import. */
|
|
615
|
-
namespaceImports: {
|
|
616
|
-
source: string;
|
|
617
|
-
line: number;
|
|
618
|
-
}[];
|
|
619
|
-
/** `$state` declarations never written or escaped anywhere in the component — candidates for const (correctness/unmutated-state). */
|
|
620
|
-
constableStates: {
|
|
621
|
-
name: string;
|
|
622
|
-
line: number;
|
|
623
|
-
}[];
|
|
624
|
-
/** Mutations of a non-`$bindable` prop from `$props()`, or a legacy `export let` prop — member writes, `delete`, or a mutating method call (correctness/prop-mutation). `legacy` distinguishes which mode the prop was declared in (absent/false: `$props()`), since the fix differs — optional so existing external constructors of `ComponentFacts` are unaffected. */
|
|
625
|
-
mutatedProps: {
|
|
626
|
-
name: string;
|
|
627
|
-
line: number;
|
|
628
|
-
legacy?: boolean;
|
|
629
|
-
}[];
|
|
630
|
-
/** Top-level const/let bindings computed from a $props() or legacy `export let` prop without $derived (or `$:`), never reassigned or escaped, and referenced (eagerly) in the template — frozen at init (correctness/stale-prop-derivation). `legacy` distinguishes which mode the prop was declared in, since the fix differs — optional so existing external constructors of `ComponentFacts` are unaffected. */
|
|
631
|
-
stalePropDerivations: {
|
|
632
|
-
name: string;
|
|
633
|
-
line: number;
|
|
634
|
-
legacy?: boolean;
|
|
635
|
-
}[];
|
|
636
|
-
/** Object/array-literal $state bindings reassigned at least once but never mutated, escaped, aliased, or item-edited — $state.raw candidates (performance/state-raw). */
|
|
637
|
-
rawableStates: {
|
|
638
|
-
name: string;
|
|
639
|
-
line: number;
|
|
640
|
-
}[];
|
|
641
|
-
/** Plain built-in instances (Map/Set/Date/URL/URLSearchParams) in $state whose type-specific mutations were observed inside functions, with no exempting reassignment — untracked by reactivity (correctness/nonreactive-builtin-state). */
|
|
642
|
-
nonreactiveBuiltinStates: {
|
|
643
|
-
name: string;
|
|
644
|
-
type: string;
|
|
645
|
-
line: number;
|
|
646
|
-
}[];
|
|
647
|
-
/** `<input type="checkbox">` / `<input type="radio">` elements bound with `bind:value`
|
|
648
|
-
* instead of `bind:checked`/`bind:group` (correctness/checkable-bind-value). */
|
|
649
|
-
checkableBindValues: CheckableBindValueFact[];
|
|
650
|
-
/** Root-relative `<a href>` and `goto()` literals in this component (correctness/base-path-navigation). */
|
|
651
|
-
basePathLinks: BasePathLinkFact[];
|
|
652
|
-
/** `$effect` calls guaranteed to run outside component initialisation — module scope in `.svelte.ts`/`.svelte.js` or `<script module>` (correctness/orphan-effect). */
|
|
653
|
-
orphanEffects: OrphanEffectFact[];
|
|
654
|
-
/** Svelte lifecycle/context calls guaranteed to run outside component initialisation — module scope in `.svelte.ts`/`.svelte.js` or `<script module>` (correctness/orphan-lifecycle). */
|
|
655
|
-
orphanLifecycleCalls: OrphanLifecycleCallFact[];
|
|
656
|
-
/** Browser-global reads in server-executed positions of this file (correctness/server-browser-global, correctness/instance-browser-global). */
|
|
657
|
-
browserGlobalRefs: BrowserGlobalRefFact[];
|
|
658
|
-
/** Module-scope `$state` declarations in a `.svelte.ts`/`.svelte.js` runes module — on a server, one instance shared by every request (security/shared-state-import). Always empty for `.svelte` files. */
|
|
659
|
-
moduleStateDecls: {
|
|
660
|
-
name: string;
|
|
661
|
-
line: number;
|
|
662
|
-
}[];
|
|
663
|
-
/** 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. */
|
|
664
|
-
suppressions?: SuppressionDirective[];
|
|
665
|
-
/** Markdown links `[label](url)` appearing inside a comment (architecture/doc-link-target). */
|
|
666
|
-
commentLinks: {
|
|
667
|
-
url: string;
|
|
668
|
-
line: number;
|
|
669
|
-
}[];
|
|
670
|
-
/** Elements carrying a role or any aria-* attribute (a11y ARIA rules). */
|
|
671
|
-
ariaElements?: AriaElementFact[];
|
|
672
|
-
/** Interactive elements nested inside another interactive container (a11y/interactive-nesting). */
|
|
673
|
-
interactiveNestings?: InteractiveNestingFact[];
|
|
674
|
-
/** `button`/`a href`/`input type="image"` elements with no computable accessible name (a11y/accessible-name). */
|
|
675
|
-
unnamedInteractive?: UnnamedInteractiveFact[];
|
|
676
|
-
/** `<label>` elements with neither a `for` attribute nor a wrapped labelable descendant (a11y/label-has-control). */
|
|
677
|
-
unassociatedLabels?: {
|
|
678
|
-
line: number;
|
|
679
|
-
}[];
|
|
680
|
-
/** Text nodes whose trimmed content opens with a bullet character followed by whitespace, outside any `li` (a11y/use-list). */
|
|
681
|
-
bulletTexts?: {
|
|
682
|
-
line: number;
|
|
683
|
-
char: string;
|
|
684
|
-
}[];
|
|
685
|
-
/** `<select required>` (no `multiple`, display size absent or ≤ 1) whose first `option` element
|
|
686
|
-
* child is not a placeholder label option (a11y/placeholder-label-option). */
|
|
687
|
-
selectsMissingPlaceholder?: {
|
|
706
|
+
line: number;
|
|
707
|
+
/** for kind 'component': the unresolvable component's name as written */
|
|
708
|
+
detail?: string;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Route-scoped a11y facts, the mode-independent boundary for the landmark/id rules
|
|
712
|
+
* (mirrors headings.ts). Source mode composes the layout chain plus its resolved
|
|
713
|
+
* components; rendered mode reads the prerendered document.
|
|
714
|
+
*/
|
|
715
|
+
interface ResolvedA11y {
|
|
716
|
+
route: string;
|
|
717
|
+
/** representatives per landmark kind after the branch-aware fold ('main' | 'banner' | 'contentinfo' | 'complementary') */
|
|
718
|
+
landmarks: Record<string, A11yOccurrenceInfo[]>;
|
|
719
|
+
/** landmark occurrences nested inside another landmark after composition */
|
|
720
|
+
nestedLandmarks: {
|
|
721
|
+
kind: string;
|
|
722
|
+
within: string;
|
|
723
|
+
file: string;
|
|
688
724
|
line: number;
|
|
689
725
|
}[];
|
|
690
|
-
/**
|
|
691
|
-
|
|
726
|
+
/** representatives per literal id */
|
|
727
|
+
ids: Record<string, A11yOccurrenceInfo[]>;
|
|
728
|
+
/** literal id references */
|
|
729
|
+
idRefs: {
|
|
730
|
+
id: string;
|
|
731
|
+
attr: string;
|
|
732
|
+
file: string;
|
|
692
733
|
line: number;
|
|
693
|
-
text: string;
|
|
694
734
|
}[];
|
|
695
|
-
/**
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
|
|
699
|
-
|
|
735
|
+
/** optimistic candidates: every literal id anywhere (all branches, each/snippet bodies, components, app.html) */
|
|
736
|
+
idCandidates: string[];
|
|
737
|
+
/** closed world holds: every component resolved, no depth truncation, no {@html}/spread, no dynamic id */
|
|
738
|
+
fullyResolved: boolean;
|
|
739
|
+
/** Why `fullyResolved` is false — deduped by (kind, file, detail), first occurrence's line kept. Present exactly when `fullyResolved` is false. */
|
|
740
|
+
unresolvedCauses?: A11ySkipCause[];
|
|
741
|
+
/**
|
|
742
|
+
* Distinct tag names in the route's body subtree — layout chain, page, every resolved component,
|
|
743
|
+
* and `app.html`'s `<body>` (static), or the prerendered `<body>` (rendered); optimistic across
|
|
744
|
+
* `{#if}` arms and `{#each}`/snippet bodies. Never `<svelte:head>` content, `<template>` children,
|
|
745
|
+
* or `<svelte:element>`. Absent where a provider does not collect it (a11y/required-element).
|
|
746
|
+
*/
|
|
747
|
+
elementTags?: string[];
|
|
748
|
+
/**
|
|
749
|
+
* The closed world for elements: every component descended into (an unresolved, depth-truncated,
|
|
750
|
+
* or — conservatively — cycle-cut one clears it), no `{@html}`, no `<svelte:element>`. Incomparable with `fullyResolved` — a spread or `id={expr}` clears that flag
|
|
751
|
+
* and not this one, since neither can hide an element; a `<svelte:element>` clears this and not
|
|
752
|
+
* that. "Missing" is only reportable when this holds; presence is sound regardless.
|
|
753
|
+
*/
|
|
754
|
+
elementsClosed?: boolean;
|
|
755
|
+
/** The file a route-level finding is anchored to: the page file (static) or the prerendered HTML path (rendered). */
|
|
756
|
+
file?: string;
|
|
700
757
|
}
|
|
758
|
+
type Foldable = {
|
|
759
|
+
key: string;
|
|
760
|
+
path: BranchStep[];
|
|
761
|
+
repeatable: boolean;
|
|
762
|
+
};
|
|
701
763
|
/**
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
764
|
+
* Branch-aware occurrence fold (design "Control-flow semantics"): within a branch
|
|
765
|
+
* occurrences sum, across the arms of one exclusive block the arm with the most
|
|
766
|
+
* occurrences wins (tie → lowest branch index) and ITS occurrences are the group's
|
|
767
|
+
* representatives — so a caller's count is always `list.length`, with a location per
|
|
768
|
+
* representative. `{#each}`/`{#snippet}` occurrences render 0..N times and drop out.
|
|
769
|
+
* The max is per key: there is no scalar total to maximize.
|
|
708
770
|
*/
|
|
709
|
-
declare function
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
771
|
+
declare function foldOccurrences<T extends Foldable>(nodes: T[]): Map<string, T[]>;
|
|
772
|
+
/**
|
|
773
|
+
* Decode a fragment identifier the way navigation does before matching an element id
|
|
774
|
+
* (`href="#caf%C3%A9"` targets `id="café"`). Malformed escapes are kept verbatim —
|
|
775
|
+
* the browser would also fail to decode them, so the raw text is the comparable form.
|
|
776
|
+
*/
|
|
777
|
+
declare function decodeFragmentId(fragment: string): string;
|
|
778
|
+
/** Whitespace-split tokens of a (possibly undefined) literal attribute value. */
|
|
779
|
+
declare function splitTokens(value: string | undefined): string[];
|
|
780
|
+
/** Explicit `role` values that map to the landmark kinds the route rules inspect. */
|
|
781
|
+
declare const LANDMARK_ROLES: ReadonlySet<string>;
|
|
782
|
+
/**
|
|
783
|
+
* Attributes whose (whitespace-tokenized) values reference element ids: the ARIA id-reference and
|
|
784
|
+
* id-reference-list properties, and HTML's own (`for`, `list`, `headers`, `form`, the popover and
|
|
785
|
+
* command targets). `href="#…"` is handled separately — its value is a URL, not a token list.
|
|
786
|
+
*/
|
|
787
|
+
declare const IDREF_ATTRS: readonly string[];
|
|
788
|
+
/**
|
|
789
|
+
* Whether a decoded URL fragment is HTML's "top of the document" indicator: `#top` (ASCII
|
|
790
|
+
* case-insensitive) scrolls to the top when no element has that id, so it is never a missing
|
|
791
|
+
* reference. Compare AFTER percent-decoding — `#%74op` navigates identically to `#top`.
|
|
792
|
+
*/
|
|
793
|
+
declare function isTopFragment(id: string): boolean;
|
|
794
|
+
/**
|
|
795
|
+
* A fragment with its text directive removed. Everything from the first `:~:` on is user-agent
|
|
796
|
+
* instructions for finding text and names no element, while anything before it is still an
|
|
797
|
+
* ordinary element fragment — `#section:~:text=hi` targets `id="section"`, `#:~:text=hi` targets
|
|
798
|
+
* nothing. Returns an empty string when the fragment is a directive and nothing else.
|
|
799
|
+
*/
|
|
800
|
+
declare function stripTextDirective(fragment: string): string;
|
|
714
801
|
|
|
715
802
|
/**
|
|
716
803
|
* Facts parsed from one SvelteKit route/hooks file for the SSR shared-state rules
|
|
@@ -798,7 +885,7 @@ interface KitModuleFacts {
|
|
|
798
885
|
declare function settingSeverity(setting: RuleSetting | undefined): Severity | 'off' | undefined;
|
|
799
886
|
/** The options a setting carries, or undefined for the string forms. */
|
|
800
887
|
declare function settingOptions(setting: RuleSetting | undefined): RuleOptions | undefined;
|
|
801
|
-
/** Drop rules disabled via config (design §6). */
|
|
888
|
+
/** Drop rules disabled via config (design §6), including a `defaultOff` rule with no entry. */
|
|
802
889
|
declare function selectRules(rules: Rule[], config: Config): Rule[];
|
|
803
890
|
/**
|
|
804
891
|
* `config` with `failedRuleIds` (from `runRules`' `failedRules`) forced `'off'`: a rule that threw
|
|
@@ -872,6 +959,16 @@ type RuleOptionSpec = {
|
|
|
872
959
|
} | {
|
|
873
960
|
kind: 'string-list';
|
|
874
961
|
default: readonly string[];
|
|
962
|
+
/**
|
|
963
|
+
* Grammar every entry must match, checked at config load. A declaration-driven rule reserves
|
|
964
|
+
* its grammar with this so a value the rule does not interpret today (`'input[type=file]'`
|
|
965
|
+
* for a tag-name list) is rejected rather than accepted-and-ignored — accepting it would make
|
|
966
|
+
* giving it meaning later a reinterpretation of a value the frozen schema already took.
|
|
967
|
+
*/
|
|
968
|
+
pattern?: {
|
|
969
|
+
regex: RegExp;
|
|
970
|
+
describe: string;
|
|
971
|
+
};
|
|
875
972
|
} | {
|
|
876
973
|
kind: 'string-map';
|
|
877
974
|
default: Readonly<Record<string, string>>;
|
|
@@ -990,9 +1087,9 @@ interface RuleContext {
|
|
|
990
1087
|
headings?: ResolvedHeadings[];
|
|
991
1088
|
/** Per-route composed landmark/id occurrences for the route-scoped a11y rules (absent in modes that don't collect them). */
|
|
992
1089
|
a11y?: ResolvedA11y[];
|
|
993
|
-
/** Per-file component-body facts for
|
|
1090
|
+
/** Per-file component-body facts for the component-scoped rules (absent in the dev handle's rendered pass). */
|
|
994
1091
|
components?: ComponentFacts[];
|
|
995
|
-
/** Per-file SvelteKit route/hooks facts for the
|
|
1092
|
+
/** Per-file SvelteKit route/hooks facts for the kit-module rules (absent in the dev handle's rendered pass). */
|
|
996
1093
|
kitModules?: KitModuleFacts[];
|
|
997
1094
|
/**
|
|
998
1095
|
* Every file under `src/`, as project-relative paths, for directory-shaped Architecture rules
|
|
@@ -1025,6 +1122,21 @@ interface Rule {
|
|
|
1025
1122
|
fix?: Fix;
|
|
1026
1123
|
/** Configurable options for this rule; absent means the rule takes none. */
|
|
1027
1124
|
options?: RuleOptionsSpec;
|
|
1125
|
+
/**
|
|
1126
|
+
* The message this rule puts on a PASS result. Declared so a PASS synthesised elsewhere — the
|
|
1127
|
+
* central inline-suppression pass, which turns a fully-suppressed rule+route into a pass — reads
|
|
1128
|
+
* the same as one the rule emitted itself. Rules built through `componentRule` and the a11y
|
|
1129
|
+
* route factory supply it; the rest fall back to `title`, which is a cosmetic difference visible
|
|
1130
|
+
* only in `--verbose`'s passed listing.
|
|
1131
|
+
*/
|
|
1132
|
+
passLabel?: string;
|
|
1133
|
+
/** Off unless config.rules names the rule explicitly — the opt-in class (design 2026-08-21). */
|
|
1134
|
+
defaultOff?: true;
|
|
1135
|
+
/**
|
|
1136
|
+
* The rule compares routes against each other (`seo/duplicate-title`), so it cannot be judged
|
|
1137
|
+
* from one route's rendered HTML — the dev dashboard's live layer leaves it to the static pass.
|
|
1138
|
+
*/
|
|
1139
|
+
crossRoute?: true;
|
|
1028
1140
|
/**
|
|
1029
1141
|
* Evaluate the resolved heads. A single rule may return one Result per route,
|
|
1030
1142
|
* so it always returns an array. Project-scoped rules return a single element.
|
|
@@ -1141,14 +1253,31 @@ interface JsonReport {
|
|
|
1141
1253
|
* nothing has an empty entry; a declaration that judged nothing has an entry of `0`.
|
|
1142
1254
|
*/
|
|
1143
1255
|
examined?: Record<string, Record<string, number>>;
|
|
1256
|
+
/**
|
|
1257
|
+
* Routes a closed-world rule skipped, keyed by rule id. Like `examined`, this describes the
|
|
1258
|
+
* analysis rather than the report: `--diff`, `--baseline` and suppressions do not narrow it.
|
|
1259
|
+
* `refs` is the route's literal id-reference count — a skipped route with `refs: 0` would
|
|
1260
|
+
* produce nothing even if unlocked. Only source-mode analysis populates it; absent when no
|
|
1261
|
+
* analyzed route was skipped.
|
|
1262
|
+
*/
|
|
1263
|
+
skipped?: Record<string, Array<{
|
|
1264
|
+
route: string;
|
|
1265
|
+
refs: number;
|
|
1266
|
+
causes: Array<{
|
|
1267
|
+
kind: string;
|
|
1268
|
+
file: string;
|
|
1269
|
+
line: number;
|
|
1270
|
+
detail?: string;
|
|
1271
|
+
}>;
|
|
1272
|
+
}>>;
|
|
1144
1273
|
}
|
|
1145
1274
|
/** Build the structured JSON report object (design §7). The shape the `json` reporter emits (issue #24). */
|
|
1146
1275
|
declare function buildJsonReport(results: Result[], config: Config, meta: {
|
|
1147
1276
|
version: string;
|
|
1148
|
-
}, ruleIds?: readonly string[], examined?: Record<string, Record<string, number
|
|
1277
|
+
}, ruleIds?: readonly string[], examined?: Record<string, Record<string, number>>, skipped?: JsonReport['skipped']): JsonReport;
|
|
1149
1278
|
/** Render results as the documented JSON report string (design §7). */
|
|
1150
1279
|
declare function formatJsonReport(results: Result[], config: Config, meta: {
|
|
1151
1280
|
version: string;
|
|
1152
|
-
}, ruleIds?: readonly string[], examined?: Record<string, Record<string, number
|
|
1281
|
+
}, ruleIds?: readonly string[], examined?: Record<string, Record<string, number>>, skipped?: JsonReport['skipped']): string;
|
|
1153
1282
|
|
|
1154
|
-
export {
|
|
1283
|
+
export { foldOccurrences as $, type A11yOccurrenceInfo as A, type BranchStep as B, type ComponentFacts as C, applyOverrides as D, type EachBlockFact as E, type Fix as F, applyRuleSeverities as G, type HeadTag as H, type ImageInfo as I, type JsonReport as J, type KitAlias as K, LANDMARK_ROLES as L, buildJsonReport as M, classify as N, type OrphanEffectFact as O, type Project as P, compileOverrides as Q, type Result as R, type SuppressionDirective as S, computeHealth as T, computeScore as U, type Value as V, decodeFragmentId as W, defaultConfig as X, defaultProject as Y, docsUrlFor as Z, effectiveSeverity as _, type Rule as a, formatFailedRuleWarning as a0, formatGithubReport as a1, formatJsonReport as a2, formatMarkdownReport as a3, hasFailureAtOrAbove as a4, intOption as a5, isMentionedAnywhere as a6, isPenalized as a7, isTopFragment as a8, listOption as a9, type TreatDynamicAs as aA, defineConfig as aB, mapOption as aa, overrideMatches as ab, resolveRuleOptions as ac, scoresByCategory as ad, selectRules as ae, settingOptions as af, settingSeverity as ag, shouldSkipRangeCheck as ah, skippedFileWarnings as ai, splitTokens as aj, stripTextDirective as ak, summarize as al, validateRuleOptions as am, validateRuleSetting as an, withFailedRulesOff as ao, withReadLimit as ap, CATEGORIES as aq, type Detection as ar, type Presence as as, type RuleEvidence as at, type RuleOptions as au, type RuleOverride as av, type RuleSetting as aw, type RuleSettingObject as ax, type ScoreModel as ay, type Summary as az, type Config as b, type Runtime as c, type KitModuleFacts as d, type RuleContext as e, type Category as f, type Severity as g, type RuleOptionSpec as h, type ResolvedHead as i, type A11ySkipCause as j, type Classification as k, type CompiledOverride as l, type EffectFact as m, type HeadProvider as n, type HeadingInfo as o, type HealthResult as p, IDREF_ATTRS as q, READ_CONCURRENCY as r, type ResolvedA11y as s, type ResolvedHeadings as t, type ResolvedImages as u, type RuleOptionsSpec as v, type Scope as w, type ScoreOptions as x, type ScoreResult as y, type SourceSpan as z };
|