@svelte-vitals/core 0.48.0 → 0.50.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/README.md CHANGED
@@ -3,23 +3,23 @@
3
3
  [![npm](https://img.shields.io/npm/v/@svelte-vitals/core)](https://www.npmjs.com/package/@svelte-vitals/core)
4
4
  [![MIT](https://img.shields.io/npm/l/@svelte-vitals/core)](https://opensource.org/licenses/MIT)
5
5
 
6
- Runtime-agnostic core for [svelte-vitals](https://github.com/oekazuma/svelte-vitals): shared types, the rule engine, scorer, and reporters, plus the full rule set across six categories SEO, Performance, Correctness, Security, Architecture, Accessibility.
6
+ Runtime-agnostic core for [svelte-vitals](https://github.com/oekazuma/svelte-vitals): shared types, the rule engine, scorer, and reporters, plus the full rule set across six categories: SEO, Performance, Correctness, Security, Architecture, Accessibility.
7
7
 
8
- This package is **mode-independent** and contains no I/O it operates on a normalized `ResolvedHead[]` intermediate representation, so the same rules run unchanged whether heads come from static source analysis (the `svelte-vitals` CLI) or from prerendered HTML (the `@svelte-vitals/vite` plugin). It has zero runtime dependencies and no `node:` imports.
8
+ This package is **mode-independent** and contains no I/O. It operates on a normalized `ResolvedHead[]` intermediate representation, so the same rules run unchanged whether heads come from static source analysis (the `svelte-vitals` CLI) or from prerendered HTML (the `@svelte-vitals/vite` plugin). It has zero runtime dependencies and no `node:` imports.
9
9
 
10
- > Most users don't depend on this directly install [`svelte-vitals`](https://www.npmjs.com/package/svelte-vitals) instead. This package is for building tools on top of the shared engine.
10
+ > Most users don't depend on this directly; install [`svelte-vitals`](https://www.npmjs.com/package/svelte-vitals) instead. This package is for building tools on top of the shared engine.
11
11
 
12
12
  > **ESM-only** (Node 24.16+). Ships ES modules only; `require()` is unsupported by design.
13
13
 
14
14
  ## Entry points
15
15
 
16
- | Entry | Contents | Stability |
17
- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
18
- | `@svelte-vitals/core` | `defineConfig` and the config types; the `JsonReport` types for reading a report; `summarize`, `hasFailureAtOrAbove`, `formatGithubReport`, `formatMarkdownReport` for rendering one and gating on it | Follows semver. For the renderers, that covers the function not the text it returns, which stays human-readable and may change. |
19
- | `@svelte-vitals/core/internal` | The engine, rules, fact collection, reporters, and scoring what the CLI and the Vite plugin share | **No guarantee. Anything here may change in any release, including a patch.** |
16
+ | Entry | Contents | Stability |
17
+ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `@svelte-vitals/core` | `defineConfig` and the config types; the `JsonReport` types for reading a report; `summarize`, `hasFailureAtOrAbove`, `formatGithubReport`, `formatMarkdownReport` for rendering one and gating on it | Follows semver. For the renderers, that covers the function, not the text it returns, which stays human-readable and may change. |
19
+ | `@svelte-vitals/core/internal` | The engine, rules, fact collection, reporters, and scoring, which is what the CLI and the Vite plugin share | **No guarantee. Anything here may change in any release, including a patch.** |
20
20
 
21
21
  Reach for `/internal` only if you accept pinning an exact version. If something there is what you
22
- actually need, open an issue the point of the split is that promoting a name into the stable
22
+ actually need, open an issue. The point of the split is that promoting a name into the stable
23
23
  entry is an additive change.
24
24
 
25
25
  ## License
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { Ct as RuleSetting, Dt as TreatDynamicAs, Et as Severity, Ot as Value, St as RuleOverride, at as formatGithubReport, bt as Result, dt as summarize, ft as CATEGORIES, gt as Fix, ht as Detection, it as formatMarkdownReport, jt as defineConfig, mt as Config, n as RuleEvidence, o as ScoreModel, pt as Category, st as Summary, t as JsonReport, ut as hasFailureAtOrAbove, vt as Presence, wt as RuleSettingObject, xt as RuleOptions } from "./json-B551DazD.js";
1
+ import { Ct as RuleSetting, Dt as TreatDynamicAs, Et as Severity, Ot as Value, St as RuleOverride, at as formatGithubReport, bt as Result, dt as summarize, ft as CATEGORIES, gt as Fix, ht as Detection, it as formatMarkdownReport, jt as defineConfig, mt as Config, n as RuleEvidence, o as ScoreModel, pt as Category, st as Summary, t as JsonReport, ut as hasFailureAtOrAbove, vt as Presence, wt as RuleSettingObject, xt as RuleOptions } from "./json-CcKHN-tx.js";
2
2
  export { CATEGORIES, type Category, type Config, type Detection, type Fix, type JsonReport, type Presence, type Result, type RuleEvidence, type RuleOptions, type RuleOverride, type RuleSetting, type RuleSettingObject, type ScoreModel, type Severity, type Summary, type TreatDynamicAs, type Value, defineConfig, formatGithubReport, formatMarkdownReport, hasFailureAtOrAbove, summarize };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { l as formatGithubReport, lr as defineConfig, nr as hasFailureAtOrAbove, or as CATEGORIES, rr as summarize, t as formatMarkdownReport } from "./markdown-BQwOMe_S.js";
1
+ import { cr as hasFailureAtOrAbove, fr as CATEGORIES, hr as defineConfig, l as formatGithubReport, lr as summarize, t as formatMarkdownReport } from "./markdown-CEYpv4SU.js";
2
2
  export { CATEGORIES, defineConfig, formatGithubReport, formatMarkdownReport, hasFailureAtOrAbove, summarize };
@@ -1,4 +1,4 @@
1
- import { $ as HeadTag, A as formatFailedRuleWarning, At as defaultProject, B as IDREF_ATTRS, C as shouldSkipRangeCheck, D as applyOverrides, E as CompiledOverride, Et as Severity, F as withFailedRulesOff, Ft as OrphanEffectFact, G as isTopFragment, H as ResolvedA11y, I as KitModuleFacts, It as SourceSpan, J as HeadingInfo, K as splitTokens, L as A11yOccurrenceInfo, Lt as SuppressionDirective, M as selectRules, Mt as ComponentFacts, N as settingOptions, Nt as EachBlockFact, O as applyRuleSeverities, Ot as Value, P as settingSeverity, Pt as EffectFact, Q as HeadProvider, R as A11ySkipCause, Rt as skippedFileWarnings, S as resolveRuleOptions, T as validateRuleSetting, Tt as Scope, U as decodeFragmentId, V as LANDMARK_ROLES, W as foldOccurrences, X as ImageInfo, Y as ResolvedHeadings, Z as ResolvedImages, _ as RuleOptionsSpec, _t as KitAlias, a as HealthResult, at as formatGithubReport, b as listOption, bt as Result, c as ScoreResult, ct as classify, d as scoresByCategory, dt as summarize, et as ResolvedHead, f as Rule, g as RuleOptionSpec, gt as Fix, h as isPenalized, i as formatJsonReport, it as formatMarkdownReport, j as overrideMatches, k as compileOverrides, kt as defaultConfig, l as computeHealth, lt as effectiveSeverity, m as docsUrlFor, mt as Config, nt as Runtime, ot as Classification, p as RuleContext, pt as Category, q as stripTextDirective, r as buildJsonReport, rt as withReadLimit, s as ScoreOptions, t as JsonReport, tt as READ_CONCURRENCY, u as computeScore, ut as hasFailureAtOrAbove, v as intOption, w as validateRuleOptions, x as mapOption, y as isMentionedAnywhere, yt as Project, z as BranchStep } from "./json-B551DazD.js";
1
+ import { $ as HeadTag, A as formatFailedRuleWarning, At as defaultProject, B as IDREF_ATTRS, C as shouldSkipRangeCheck, D as applyOverrides, E as CompiledOverride, Et as Severity, F as withFailedRulesOff, Ft as OrphanEffectFact, G as isTopFragment, H as ResolvedA11y, I as KitModuleFacts, It as SourceSpan, J as HeadingInfo, K as splitTokens, L as A11yOccurrenceInfo, Lt as SuppressionDirective, M as selectRules, Mt as ComponentFacts, N as settingOptions, Nt as EachBlockFact, O as applyRuleSeverities, Ot as Value, P as settingSeverity, Pt as EffectFact, Q as HeadProvider, R as A11ySkipCause, Rt as skippedFileWarnings, S as resolveRuleOptions, T as validateRuleSetting, Tt as Scope, U as decodeFragmentId, V as LANDMARK_ROLES, W as foldOccurrences, X as ImageInfo, Y as ResolvedHeadings, Z as ResolvedImages, _ as RuleOptionsSpec, _t as KitAlias, a as HealthResult, at as formatGithubReport, b as listOption, bt as Result, c as ScoreResult, ct as classify, d as scoresByCategory, dt as summarize, et as ResolvedHead, f as Rule, g as RuleOptionSpec, gt as Fix, h as isPenalized, i as formatJsonReport, it as formatMarkdownReport, j as overrideMatches, k as compileOverrides, kt as defaultConfig, l as computeHealth, lt as effectiveSeverity, m as docsUrlFor, mt as Config, nt as Runtime, ot as Classification, p as RuleContext, pt as Category, q as stripTextDirective, r as buildJsonReport, rt as withReadLimit, s as ScoreOptions, t as JsonReport, tt as READ_CONCURRENCY, u as computeScore, ut as hasFailureAtOrAbove, v as intOption, w as validateRuleOptions, x as mapOption, y as isMentionedAnywhere, yt as Project, z as BranchStep } from "./json-CcKHN-tx.js";
2
2
  import { AST } from "svelte/compiler";
3
3
  //#region src/component-parse.d.ts
4
4
  /**
@@ -753,6 +753,9 @@ declare const a11yInvalidAriaValue: Rule;
753
753
  //#region src/rules/a11y/interactive-nesting.d.ts
754
754
  declare const a11yInteractiveNesting: Rule;
755
755
  //#endregion
756
+ //#region src/rules/a11y/aria-hidden-focus.d.ts
757
+ declare const a11yAriaHiddenFocus: Rule;
758
+ //#endregion
756
759
  //#region src/rules/a11y/accessible-name.d.ts
757
760
  declare const a11yAccessibleName: Rule;
758
761
  //#endregion
@@ -780,6 +783,43 @@ declare const a11yRequireDatetime: Rule;
780
783
  */
781
784
  declare const a11yPositiveTabindex: Rule;
782
785
  //#endregion
786
+ //#region src/rules/a11y/no-accesskey.d.ts
787
+ /**
788
+ * a11y/no-accesskey — unlike most attribute rules, an expression-valued `accesskey` is reported
789
+ * too: the attribute's presence is the problem and its value never matters, so there is nothing
790
+ * unknowable about it. A spread-only carrier stays silent (the attribute never reaches `attrs`).
791
+ * SVG elements are judged along with HTML — `accesskey` is meaningless there, which makes its
792
+ * presence wrong in both namespaces.
793
+ */
794
+ declare const a11yNoAccesskey: Rule;
795
+ //#endregion
796
+ //#region src/rules/a11y/no-autofocus.d.ts
797
+ /**
798
+ * a11y/no-autofocus — only a literal `autofocus` counts (a bare boolean attribute parses to
799
+ * `value: ''`); an expression value could be `false` and passes as unknowable. The ancestor walk
800
+ * uses `ElementFact.parent`, which looks through logic blocks but breaks at every non-lexical
801
+ * construct (a component, `{#snippet}` body, `<svelte:element>`, custom element, `{@html}`,
802
+ * `{@render}`, `<slot>`) — past a break the dialog cannot be proven, so the element is reported;
803
+ * the docs page's Limitations section names these classes and the inline-suppression escape
804
+ * hatch. SVG elements are judged along with HTML: SVG2 honours `autofocus` with the same
805
+ * page-load focus semantics.
806
+ */
807
+ declare const a11yNoAutofocus: Rule;
808
+ //#endregion
809
+ //#region src/rules/a11y/no-duplicate-dt.d.ts
810
+ /**
811
+ * a11y/no-duplicate-dt — the candidate gathering (direct and div-wrapped `<dt>` children of a
812
+ * `<dl>`, fully-static names only, whitespace-normalized, case-sensitive) lives in the
813
+ * collector; see `collectDuplicateDts` in component-parse.ts for the exemption rationale.
814
+ */
815
+ declare const a11yNoDuplicateDt: Rule;
816
+ //#endregion
817
+ //#region src/rules/a11y/abbr-title.d.ts
818
+ declare const a11yAbbrTitle: Rule;
819
+ //#endregion
820
+ //#region src/rules/a11y/pattern-title.d.ts
821
+ declare const a11yPatternTitle: Rule;
822
+ //#endregion
783
823
  //#region src/rules/a11y/deprecated-element.d.ts
784
824
  declare const a11yDeprecatedElement: Rule;
785
825
  //#endregion
@@ -1013,7 +1053,11 @@ declare function formatSarifReport(results: Result[], config: Config, meta: {
1013
1053
  //#endregion
1014
1054
  //#region src/reporter/app-shell.d.ts
1015
1055
  type Band = 'good' | 'warn' | 'poor';
1016
- declare const BAND_COLOR: Record<Band, string>;
1056
+ declare const BAND_COLOR: {
1057
+ good: string;
1058
+ warn: string;
1059
+ poor: string;
1060
+ };
1017
1061
  declare function scoreBand(score: number): Band;
1018
1062
  declare function escapeHtml(s: string): string;
1019
1063
  /**
@@ -1075,4 +1119,4 @@ declare function formatHtmlReport(results: Result[], config: Config, meta: {
1075
1119
  coreVersion?: string;
1076
1120
  }): string;
1077
1121
  //#endregion
1078
- export { type A11yOccurrenceInfo, type A11ySkipCause, APP_SCRIPT, APP_STYLE, type AppSnapshot, BAND_COLOR, type BranchStep, CHILD_NODE_KEYS, type Classification, type CompiledOverride, type ComponentFacts, type ConsoleReportOptions, type DirectiveIndex, type EachBlockFact, type EffectFact, type FailedRule, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, IDREF_ATTRS, type ImageInfo, type KitAlias, type KitModuleFacts, LANDMARK_ROLES, type OrphanEffectFact, type Palette, type Project, READ_CONCURRENCY, ROBOTS_SOURCE_PATHS, type RawKitAliases, type ResolvedA11y, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type RouteBadge, type Rule, type RuleContext, RuleInfo, RuleOptionInfo, type RuleOptionSpec, type RuleOptionsSpec, type Runtime, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, type Scope, type ScoreOptions, type ScoreResult, type SourceSpan, type SuppressionDirective, VITE_CONFIG_FILES, type ViteKitConfigResult, a11yAccessibleName, a11yDeprecatedAria, a11yDeprecatedAttr, a11yDeprecatedElement, a11yDisallowedAriaProps, a11yDisallowedElement, a11yDoctype, a11yDuplicateLandmark, a11yIdDuplication, a11yInteractiveNesting, a11yInvalidAriaValue, a11yInvalidRole, a11yLabelHasControl, a11yNoMissingIdRef, a11yPermittedContents, a11yPlaceholderLabelOption, a11yPositiveTabindex, a11yRequireDatetime, a11yRequiredAriaProps, a11yRequiredElement, a11yTopLevelLandmark, a11yUnknownAriaAttribute, a11yUnverifiedIdRef, a11yUseList, allRules, applyInlineDirectives, applyOverrides, applyRuleSeverities, architectureComponentSize, architectureDirectoryNaming, architectureDocLinkTarget, architecturePrivateScopeImport, architecturePropCount, architectureReservedDirectoryNames, architectureReservedNamePlacement, architectureRouteComponentImport, architectureUnitEntryFile, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, collectSuppressions, compileOverrides, computeHealth, computeScore, correctnessAutoplayMuted, correctnessBasePathNavigation, correctnessCheckableBindValue, correctnessEachIndexKey, correctnessEachKey, correctnessEffectAsDerived, correctnessEffectAsOnMount, correctnessInstanceBrowserGlobal, correctnessNonreactiveBuiltinState, correctnessOrphanEffect, correctnessOrphanLifecycle, correctnessPropMutation, correctnessServerBrowserGlobal, correctnessStalePropDerivation, correctnessUnmutatedState, decodeFragmentId, defaultConfig, defaultProject, docsUrlFor, effectiveSeverity, emptyComponentFacts, emptyKitModuleFacts, escapeHtml, explainRule, findAttr, findKitAliasesInSvelteConfig, findKitPathsBaseInSvelteConfig, findKitPathsBaseInViteConfig, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, intOption, isMentionedAnywhere, isPenalized, isTopFragment, lineOf, linkRule, listOption, mapOption, noColorPalette, overrideMatches, parseComponentFacts, parseKitModuleFacts, parseSvelte, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceIframeLoading, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRepoLocalPath, resolveRuleOptions, resolveRunesModuleSpecifier, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, securityHandlerStateWrite, securityJavascriptUrl, securityRawHtml, securityServerModuleState, securitySharedStateImport, selectRules, seoCanonicalUrl, seoCharset, seoDescriptionLength, seoDescriptionPresence, seoDuplicateDescription, seoDuplicateTitle, seoHeadingLevelSkip, seoHreflang, seoHtmlLang, seoImageAlt, seoIndexability, seoJsonLd, seoJsonLdDateFormat, seoJsonLdDeprecatedType, seoJsonLdPlaceholder, seoJsonLdRelativeUrl, seoJsonLdRequiredProps, seoJsonLdValidity, seoOgDescription, seoOgImage, seoOgTitle, seoOgUrl, seoRobotsTxt, seoSingleH1, seoSitemapInRobots, seoSitemapXml, seoSsrDisabled, seoTitleLength, seoTitlePresence, seoTwitterCard, seoViewport, settingOptions, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, validateRuleOptions, validateRuleSetting, valueFromNodes, withFailedRulesOff, withReadLimit };
1122
+ export { type A11yOccurrenceInfo, type A11ySkipCause, APP_SCRIPT, APP_STYLE, type AppSnapshot, BAND_COLOR, type BranchStep, CHILD_NODE_KEYS, type Classification, type CompiledOverride, type ComponentFacts, type ConsoleReportOptions, type DirectiveIndex, type EachBlockFact, type EffectFact, type FailedRule, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, IDREF_ATTRS, type ImageInfo, type KitAlias, type KitModuleFacts, LANDMARK_ROLES, type OrphanEffectFact, type Palette, type Project, READ_CONCURRENCY, ROBOTS_SOURCE_PATHS, type RawKitAliases, type ResolvedA11y, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type RouteBadge, type Rule, type RuleContext, RuleInfo, RuleOptionInfo, type RuleOptionSpec, type RuleOptionsSpec, type Runtime, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, type Scope, type ScoreOptions, type ScoreResult, type SourceSpan, type SuppressionDirective, VITE_CONFIG_FILES, type ViteKitConfigResult, a11yAbbrTitle, a11yAccessibleName, a11yAriaHiddenFocus, a11yDeprecatedAria, a11yDeprecatedAttr, a11yDeprecatedElement, a11yDisallowedAriaProps, a11yDisallowedElement, a11yDoctype, a11yDuplicateLandmark, a11yIdDuplication, a11yInteractiveNesting, a11yInvalidAriaValue, a11yInvalidRole, a11yLabelHasControl, a11yNoAccesskey, a11yNoAutofocus, a11yNoDuplicateDt, a11yNoMissingIdRef, a11yPatternTitle, a11yPermittedContents, a11yPlaceholderLabelOption, a11yPositiveTabindex, a11yRequireDatetime, a11yRequiredAriaProps, a11yRequiredElement, a11yTopLevelLandmark, a11yUnknownAriaAttribute, a11yUnverifiedIdRef, a11yUseList, allRules, applyInlineDirectives, applyOverrides, applyRuleSeverities, architectureComponentSize, architectureDirectoryNaming, architectureDocLinkTarget, architecturePrivateScopeImport, architecturePropCount, architectureReservedDirectoryNames, architectureReservedNamePlacement, architectureRouteComponentImport, architectureUnitEntryFile, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, collectSuppressions, compileOverrides, computeHealth, computeScore, correctnessAutoplayMuted, correctnessBasePathNavigation, correctnessCheckableBindValue, correctnessEachIndexKey, correctnessEachKey, correctnessEffectAsDerived, correctnessEffectAsOnMount, correctnessInstanceBrowserGlobal, correctnessNonreactiveBuiltinState, correctnessOrphanEffect, correctnessOrphanLifecycle, correctnessPropMutation, correctnessServerBrowserGlobal, correctnessStalePropDerivation, correctnessUnmutatedState, decodeFragmentId, defaultConfig, defaultProject, docsUrlFor, effectiveSeverity, emptyComponentFacts, emptyKitModuleFacts, escapeHtml, explainRule, findAttr, findKitAliasesInSvelteConfig, findKitPathsBaseInSvelteConfig, findKitPathsBaseInViteConfig, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, intOption, isMentionedAnywhere, isPenalized, isTopFragment, lineOf, linkRule, listOption, mapOption, noColorPalette, overrideMatches, parseComponentFacts, parseKitModuleFacts, parseSvelte, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceIframeLoading, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRepoLocalPath, resolveRuleOptions, resolveRunesModuleSpecifier, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, securityHandlerStateWrite, securityJavascriptUrl, securityRawHtml, securityServerModuleState, securitySharedStateImport, selectRules, seoCanonicalUrl, seoCharset, seoDescriptionLength, seoDescriptionPresence, seoDuplicateDescription, seoDuplicateTitle, seoHeadingLevelSkip, seoHreflang, seoHtmlLang, seoImageAlt, seoIndexability, seoJsonLd, seoJsonLdDateFormat, seoJsonLdDeprecatedType, seoJsonLdPlaceholder, seoJsonLdRelativeUrl, seoJsonLdRequiredProps, seoJsonLdValidity, seoOgDescription, seoOgImage, seoOgTitle, seoOgUrl, seoRobotsTxt, seoSingleH1, seoSitemapInRobots, seoSitemapXml, seoSsrDisabled, seoTitleLength, seoTitlePresence, seoTwitterCard, seoViewport, settingOptions, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, validateRuleOptions, validateRuleSetting, valueFromNodes, withFailedRulesOff, withReadLimit };
package/dist/internal.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as architecturePrivateScopeImport, $n as seoTitlePresence, $t as seoSingleH1, A as a11yRequireDatetime, An as overrideMatches, At as securityServerModuleState, B as performanceIframeLoading, Bn as performanceResponsiveImage, Bt as correctnessCheckableBindValue, C as a11yPermittedContents, Cn as shouldSkipRangeCheck, Ct as lineOf, D as a11yDeprecatedAttr, Dn as applyRuleSeverities, Dt as architecturePropCount, E as a11yDisallowedAriaProps, En as applyOverrides, Et as valueFromNodes, F as a11yInteractiveNesting, Fn as performanceRenderBlockingScript, Ft as correctnessServerBrowserGlobal, G as performanceNamespaceImport, Gn as seoSitemapXml, Gt as correctnessEffectAsOnMount, H as performanceSequentialAwaits, Hn as performanceImageDimensions, Ht as correctnessStalePropDerivation, I as a11yInvalidAriaValue, In as performanceLcpImage, It as correctnessBasePathNavigation, J as architectureRouteComponentImport, Jn as seoOgTitle, Jt as correctnessEachKey, K as performanceHeavyImport, Kn as seoRobotsTxt, Kt as correctnessEffectAsDerived, L as a11yRequiredAriaProps, Ln as performanceFontPreloadCrossorigin, Lt as correctnessOrphanLifecycle, M as a11yUseList, Mn as settingOptions, Mt as securityJavascriptUrl, N as a11yLabelHasControl, Nn as settingSeverity, Nt as securityRawHtml, O as a11yDeprecatedElement, On as compileOverrides, Ot as architectureComponentSize, P as a11yAccessibleName, Pn as withFailedRulesOff, Pt as correctnessInstanceBrowserGlobal, Q as architectureUnitEntryFile, Qn as headTagRule, Qt as seoDuplicateTitle, R as a11yUnknownAriaAttribute, Rn as performancePreloadMissingAs, Rt as correctnessOrphanEffect, S as a11yRequiredElement, Sn as resolveRuleOptions, St as findAttr, T as a11yDeprecatedAria, Tn as validateRuleSetting, Tt as textFromNodes, U as performanceLoadWaterfall, Un as imageRule, Ut as correctnessPropMutation, V as performanceStateRaw, Vn as performanceImageLoadingHint, Vt as correctnessNonreactiveBuiltinState, W as performanceMinifyDisabled, Wn as seoHtmlLang, Wt as correctnessUnmutatedState, X as architectureReservedDirectoryNames, Xn as seoCanonicalUrl, Xt as seoHeadingLevelSkip, Y as architectureReservedNamePlacement, Yn as seoOgImage, Yt as seoSsrDisabled, Z as architectureDirectoryNaming, Zn as seoDescriptionPresence, Zt as seoDuplicateDescription, _ as a11yNoMissingIdRef, _n as performancePreconnect, _t as CHILD_NODE_KEYS, a as formatJsonReport, an as seoJsonLdRequiredProps, ar as isPenalized, at as collectNamedImportAliases, b as a11yDuplicateLandmark, bn as listOption, bt as attrValue, c as scoresByCategory, cn as seoJsonLdRelativeUrl, cr as defaultProject, ct as parseModuleProgram, d as messageText, dn as seoSitemapInRobots, dt as LANDMARK_ROLES, en as seoHreflang, er as classify, et as collectTopLevelBindings, f as ruleMetaById, fn as seoViewport, ft as decodeFragmentId, g as a11yUnverifiedIdRef, gn as seoIndexability, gt as stripTextDirective, h as explainRule, hn as seoTwitterCard, ht as splitTokens, i as buildJsonReport, in as seoTitleLength, ir as docsUrlFor, it as resolveRunesModuleSpecifier, j as a11yPlaceholderLabelOption, jn as selectRules, jt as securityHandlerStateWrite, k as a11yPositiveTabindex, kn as formatFailedRuleWarning, kt as securitySharedStateImport, l as formatGithubReport, ln as seoJsonLdDeprecatedType, lt as unwrapTs, m as allRules, mn as seoOgDescription, mt as isTopFragment, n as mdEscape, nn as seoCharset, nr as hasFailureAtOrAbove, nt as parseKitModuleFacts, o as computeHealth, on as seoJsonLdPlaceholder, ot as collectSuppressions, p as severityToSarifLevel, pn as seoOgUrl, pt as foldOccurrences, q as architectureDocLinkTarget, qn as seoJsonLd, qt as correctnessEachIndexKey, r as terminalSafe, rn as seoDescriptionLength, rr as summarize, rt as resolveRepoLocalPath, s as computeScore, sn as seoJsonLdDateFormat, sr as defaultConfig, st as parseComponentFacts, t as formatMarkdownReport, tn as seoImageAlt, tr as effectiveSeverity, tt as parseInMemoryExports, u as SEVERITY_RANK, un as seoJsonLdValidity, ut as IDREF_ATTRS, v as a11yIdDuplication, vn as intOption, vt as attrText, w as a11yDisallowedElement, wn as validateRuleOptions, wt as parseSvelte, x as a11yDoctype, xn as mapOption, xt as attrValueOf, y as a11yTopLevelLandmark, yn as isMentionedAnywhere, yt as attrTextOf, z as a11yInvalidRole, zn as linkRule, zt as correctnessAutoplayMuted } from "./markdown-BQwOMe_S.js";
1
+ import { $ as architectureDocLinkTarget, $n as seoJsonLd, $t as correctnessEachIndexKey, A as a11yAbbrTitle, An as validateRuleOptions, At as parseSvelte, B as a11yAriaHiddenFocus, Bn as withFailedRulesOff, Bt as correctnessInstanceBrowserGlobal, C as a11yPermittedContents, Cn as performancePreconnect, Ct as CHILD_NODE_KEYS, D as a11yDeprecatedAttr, Dn as mapOption, Dt as attrValueOf, E as a11yDisallowedAriaProps, En as listOption, Et as attrValue, F as a11yRequireDatetime, Fn as formatFailedRuleWarning, Ft as securitySharedStateImport, G as a11yInvalidRole, Gn as linkRule, Gt as correctnessAutoplayMuted, H as a11yInvalidAriaValue, Hn as performanceLcpImage, Ht as correctnessBasePathNavigation, I as a11yPlaceholderLabelOption, In as overrideMatches, It as securityServerModuleState, J as performanceSequentialAwaits, Jn as performanceImageDimensions, Jt as correctnessStalePropDerivation, K as performanceIframeLoading, Kn as performanceResponsiveImage, Kt as correctnessCheckableBindValue, L as a11yUseList, Ln as selectRules, Lt as securityHandlerStateWrite, M as a11yNoAutofocus, Mn as applyOverrides, Mt as valueFromNodes, N as a11yNoAccesskey, Nn as applyRuleSeverities, Nt as architecturePropCount, O as a11yDeprecatedElement, On as resolveRuleOptions, Ot as findAttr, P as a11yPositiveTabindex, Pn as compileOverrides, Pt as architectureComponentSize, Q as performanceHeavyImport, Qn as seoRobotsTxt, Qt as correctnessEffectAsDerived, R as a11yLabelHasControl, Rn as settingOptions, Rt as securityJavascriptUrl, S as a11yRequiredElement, Sn as seoIndexability, St as stripTextDirective, T as a11yDeprecatedAria, Tn as isMentionedAnywhere, Tt as attrTextOf, U as a11yRequiredAriaProps, Un as performanceFontPreloadCrossorigin, Ut as correctnessOrphanLifecycle, V as a11yInteractiveNesting, Vn as performanceRenderBlockingScript, Vt as correctnessServerBrowserGlobal, W as a11yUnknownAriaAttribute, Wn as performancePreloadMissingAs, Wt as correctnessOrphanEffect, X as performanceMinifyDisabled, Xn as seoHtmlLang, Xt as correctnessUnmutatedState, Y as performanceLoadWaterfall, Yn as imageRule, Yt as correctnessPropMutation, Z as performanceNamespaceImport, Zn as seoSitemapXml, Zt as correctnessEffectAsOnMount, _ as a11yNoMissingIdRef, _n as seoSitemapInRobots, _t as LANDMARK_ROLES, a as formatJsonReport, an as seoSingleH1, ar as seoTitlePresence, at as architecturePrivateScopeImport, b as a11yDuplicateLandmark, bn as seoOgDescription, bt as isTopFragment, c as scoresByCategory, cn as seoCharset, cr as hasFailureAtOrAbove, ct as parseKitModuleFacts, d as messageText, dn as seoJsonLdRequiredProps, dr as isPenalized, dt as collectNamedImportAliases, en as correctnessEachKey, er as seoOgTitle, et as architectureRouteComponentImport, f as ruleMetaById, fn as seoJsonLdPlaceholder, ft as collectSuppressions, g as a11yUnverifiedIdRef, gn as seoJsonLdValidity, gt as IDREF_ATTRS, h as explainRule, hn as seoJsonLdDeprecatedType, ht as unwrapTs, i as buildJsonReport, in as seoDuplicateTitle, ir as headTagRule, it as architectureUnitEntryFile, j as a11yNoDuplicateDt, jn as validateRuleSetting, jt as textFromNodes, k as a11yPatternTitle, kn as shouldSkipRangeCheck, kt as lineOf, l as formatGithubReport, ln as seoDescriptionLength, lr as summarize, lt as resolveRepoLocalPath, m as allRules, mn as seoJsonLdRelativeUrl, mr as defaultProject, mt as parseModuleProgram, n as mdEscape, nn as seoHeadingLevelSkip, nr as seoCanonicalUrl, nt as architectureReservedDirectoryNames, o as computeHealth, on as seoHreflang, or as classify, ot as collectTopLevelBindings, p as severityToSarifLevel, pn as seoJsonLdDateFormat, pr as defaultConfig, pt as parseComponentFacts, q as performanceStateRaw, qn as performanceImageLoadingHint, qt as correctnessNonreactiveBuiltinState, r as terminalSafe, rn as seoDuplicateDescription, rr as seoDescriptionPresence, rt as architectureDirectoryNaming, s as computeScore, sn as seoImageAlt, sr as effectiveSeverity, st as parseInMemoryExports, t as formatMarkdownReport, tn as seoSsrDisabled, tr as seoOgImage, tt as architectureReservedNamePlacement, u as SEVERITY_RANK, un as seoTitleLength, ur as docsUrlFor, ut as resolveRunesModuleSpecifier, v as a11yIdDuplication, vn as seoViewport, vt as decodeFragmentId, w as a11yDisallowedElement, wn as intOption, wt as attrText, x as a11yDoctype, xn as seoTwitterCard, xt as splitTokens, y as a11yTopLevelLandmark, yn as seoOgUrl, yt as foldOccurrences, z as a11yAccessibleName, zn as settingSeverity, zt as securityRawHtml } from "./markdown-CEYpv4SU.js";
2
2
  //#region src/inline-directives.ts
3
3
  /** The directive silencing `r`, if one sits on its line and names its rule (or names nothing). */
4
4
  function directiveFor(index, r) {
@@ -1707,4 +1707,4 @@ function formatHtmlReport(results, config, meta) {
1707
1707
  return buildHtmlDocument(buildJsonReport(results, config, meta), meta);
1708
1708
  }
1709
1709
  //#endregion
1710
- export { APP_SCRIPT, APP_STYLE, BAND_COLOR, CHILD_NODE_KEYS, IDREF_ATTRS, LANDMARK_ROLES, READ_CONCURRENCY, ROBOTS_SOURCE_PATHS, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, VITE_CONFIG_FILES, a11yAccessibleName, a11yDeprecatedAria, a11yDeprecatedAttr, a11yDeprecatedElement, a11yDisallowedAriaProps, a11yDisallowedElement, a11yDoctype, a11yDuplicateLandmark, a11yIdDuplication, a11yInteractiveNesting, a11yInvalidAriaValue, a11yInvalidRole, a11yLabelHasControl, a11yNoMissingIdRef, a11yPermittedContents, a11yPlaceholderLabelOption, a11yPositiveTabindex, a11yRequireDatetime, a11yRequiredAriaProps, a11yRequiredElement, a11yTopLevelLandmark, a11yUnknownAriaAttribute, a11yUnverifiedIdRef, a11yUseList, allRules, applyInlineDirectives, applyOverrides, applyRuleSeverities, architectureComponentSize, architectureDirectoryNaming, architectureDocLinkTarget, architecturePrivateScopeImport, architecturePropCount, architectureReservedDirectoryNames, architectureReservedNamePlacement, architectureRouteComponentImport, architectureUnitEntryFile, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, collectSuppressions, compileOverrides, computeHealth, computeScore, correctnessAutoplayMuted, correctnessBasePathNavigation, correctnessCheckableBindValue, correctnessEachIndexKey, correctnessEachKey, correctnessEffectAsDerived, correctnessEffectAsOnMount, correctnessInstanceBrowserGlobal, correctnessNonreactiveBuiltinState, correctnessOrphanEffect, correctnessOrphanLifecycle, correctnessPropMutation, correctnessServerBrowserGlobal, correctnessStalePropDerivation, correctnessUnmutatedState, decodeFragmentId, defaultConfig, defaultProject, docsUrlFor, effectiveSeverity, emptyComponentFacts, emptyKitModuleFacts, escapeHtml, explainRule, findAttr, findKitAliasesInSvelteConfig, findKitPathsBaseInSvelteConfig, findKitPathsBaseInViteConfig, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, intOption, isMentionedAnywhere, isPenalized, isTopFragment, lineOf, linkRule, listOption, mapOption, noColorPalette, overrideMatches, parseComponentFacts, parseKitModuleFacts, parseSvelte, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceIframeLoading, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRepoLocalPath, resolveRuleOptions, resolveRunesModuleSpecifier, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, securityHandlerStateWrite, securityJavascriptUrl, securityRawHtml, securityServerModuleState, securitySharedStateImport, selectRules, seoCanonicalUrl, seoCharset, seoDescriptionLength, seoDescriptionPresence, seoDuplicateDescription, seoDuplicateTitle, seoHeadingLevelSkip, seoHreflang, seoHtmlLang, seoImageAlt, seoIndexability, seoJsonLd, seoJsonLdDateFormat, seoJsonLdDeprecatedType, seoJsonLdPlaceholder, seoJsonLdRelativeUrl, seoJsonLdRequiredProps, seoJsonLdValidity, seoOgDescription, seoOgImage, seoOgTitle, seoOgUrl, seoRobotsTxt, seoSingleH1, seoSitemapInRobots, seoSitemapXml, seoSsrDisabled, seoTitleLength, seoTitlePresence, seoTwitterCard, seoViewport, settingOptions, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, validateRuleOptions, validateRuleSetting, valueFromNodes, withFailedRulesOff, withReadLimit };
1710
+ export { APP_SCRIPT, APP_STYLE, BAND_COLOR, CHILD_NODE_KEYS, IDREF_ATTRS, LANDMARK_ROLES, READ_CONCURRENCY, ROBOTS_SOURCE_PATHS, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, VITE_CONFIG_FILES, a11yAbbrTitle, a11yAccessibleName, a11yAriaHiddenFocus, a11yDeprecatedAria, a11yDeprecatedAttr, a11yDeprecatedElement, a11yDisallowedAriaProps, a11yDisallowedElement, a11yDoctype, a11yDuplicateLandmark, a11yIdDuplication, a11yInteractiveNesting, a11yInvalidAriaValue, a11yInvalidRole, a11yLabelHasControl, a11yNoAccesskey, a11yNoAutofocus, a11yNoDuplicateDt, a11yNoMissingIdRef, a11yPatternTitle, a11yPermittedContents, a11yPlaceholderLabelOption, a11yPositiveTabindex, a11yRequireDatetime, a11yRequiredAriaProps, a11yRequiredElement, a11yTopLevelLandmark, a11yUnknownAriaAttribute, a11yUnverifiedIdRef, a11yUseList, allRules, applyInlineDirectives, applyOverrides, applyRuleSeverities, architectureComponentSize, architectureDirectoryNaming, architectureDocLinkTarget, architecturePrivateScopeImport, architecturePropCount, architectureReservedDirectoryNames, architectureReservedNamePlacement, architectureRouteComponentImport, architectureUnitEntryFile, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, collectSuppressions, compileOverrides, computeHealth, computeScore, correctnessAutoplayMuted, correctnessBasePathNavigation, correctnessCheckableBindValue, correctnessEachIndexKey, correctnessEachKey, correctnessEffectAsDerived, correctnessEffectAsOnMount, correctnessInstanceBrowserGlobal, correctnessNonreactiveBuiltinState, correctnessOrphanEffect, correctnessOrphanLifecycle, correctnessPropMutation, correctnessServerBrowserGlobal, correctnessStalePropDerivation, correctnessUnmutatedState, decodeFragmentId, defaultConfig, defaultProject, docsUrlFor, effectiveSeverity, emptyComponentFacts, emptyKitModuleFacts, escapeHtml, explainRule, findAttr, findKitAliasesInSvelteConfig, findKitPathsBaseInSvelteConfig, findKitPathsBaseInViteConfig, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, intOption, isMentionedAnywhere, isPenalized, isTopFragment, lineOf, linkRule, listOption, mapOption, noColorPalette, overrideMatches, parseComponentFacts, parseKitModuleFacts, parseSvelte, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceIframeLoading, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRepoLocalPath, resolveRuleOptions, resolveRunesModuleSpecifier, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, securityHandlerStateWrite, securityJavascriptUrl, securityRawHtml, securityServerModuleState, securitySharedStateImport, selectRules, seoCanonicalUrl, seoCharset, seoDescriptionLength, seoDescriptionPresence, seoDuplicateDescription, seoDuplicateTitle, seoHeadingLevelSkip, seoHreflang, seoHtmlLang, seoImageAlt, seoIndexability, seoJsonLd, seoJsonLdDateFormat, seoJsonLdDeprecatedType, seoJsonLdPlaceholder, seoJsonLdRelativeUrl, seoJsonLdRequiredProps, seoJsonLdValidity, seoOgDescription, seoOgImage, seoOgTitle, seoOgUrl, seoRobotsTxt, seoSingleH1, seoSitemapInRobots, seoSitemapXml, seoSsrDisabled, seoTitleLength, seoTitlePresence, seoTwitterCard, seoViewport, settingOptions, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, validateRuleOptions, validateRuleSetting, valueFromNodes, withFailedRulesOff, withReadLimit };
@@ -93,6 +93,16 @@ interface InteractiveNestingFact {
93
93
  /** 1-based source line of the descendant, or 0 if unknown. */
94
94
  line: number;
95
95
  }
96
+ /** A keyboard-focusable element that a literal `aria-hidden="true"` hides from assistive
97
+ * technology — carried on the element itself or on an ancestor (a11y/aria-hidden-focus). */
98
+ interface AriaHiddenFocusFact {
99
+ /** The focusable element's tag. */
100
+ tag: string;
101
+ /** The nearest `aria-hidden="true"` ancestor's tag; absent when the element itself carries it. */
102
+ containerTag?: string;
103
+ /** 1-based source line of the focusable element, or 0 if unknown. */
104
+ line: number;
105
+ }
96
106
  /** A `button`/`a href`/`input type="image"` with no computable accessible name (a11y/accessible-name). */
97
107
  interface UnnamedInteractiveFact {
98
108
  tag: string;
@@ -263,6 +273,8 @@ interface ComponentFacts {
263
273
  elements?: ElementFact[];
264
274
  /** Interactive elements nested inside another interactive container (a11y/interactive-nesting). */
265
275
  interactiveNestings?: InteractiveNestingFact[];
276
+ /** Keyboard-focusable elements hidden by a literal `aria-hidden="true"` (a11y/aria-hidden-focus). */
277
+ ariaHiddenFocusables?: AriaHiddenFocusFact[];
266
278
  /** `button`/`a href`/`input type="image"` elements with no computable accessible name (a11y/accessible-name). */
267
279
  unnamedInteractive?: UnnamedInteractiveFact[];
268
280
  /** `<label>` elements with neither a `for` attribute nor a wrapped labelable descendant (a11y/label-has-control). */
@@ -288,6 +300,11 @@ interface ComponentFacts {
288
300
  videosAutoplayNoMuted?: {
289
301
  line: number;
290
302
  }[];
303
+ /** `<dt>` elements whose static name duplicates an earlier sibling `<dt>` in the same `<dl>` (a11y/no-duplicate-dt). */
304
+ duplicateDts?: {
305
+ line: number;
306
+ text: string;
307
+ }[];
291
308
  /** Set when the file failed to read or parse and these facts are the empty fallback — the file was NOT analyzed. */
292
309
  parseFailed?: true;
293
310
  /** Set when the file could not be READ — an environment problem (permissions, a descriptor
@@ -4344,7 +4344,8 @@ function collectElements(node, source, acc, inSvg, parent) {
4344
4344
  tag,
4345
4345
  line: lineOf(source, node.start),
4346
4346
  attrs: node.attributes.filter((a) => a?.type === "Attribute" && typeof a.name === "string").map((a) => {
4347
- const value = a.value === true ? "" : attrValueOf(a) === "static" ? attrTextOf(a) ?? void 0 : void 0;
4347
+ const cls = classifyAttrValue(a.value);
4348
+ const value = "expression" in cls ? void 0 : cls.literal;
4348
4349
  return {
4349
4350
  name: String(a.name).toLowerCase(),
4350
4351
  line: lineOf(source, a.start ?? node.start),
@@ -4463,6 +4464,54 @@ function collectInteractiveNestings(node, source, acc, stack) {
4463
4464
  for (const key of CHILD_NODE_KEYS) if (key in node) collectInteractiveNestings(node[key], source, acc, stack);
4464
4465
  if (opened) stack.pop();
4465
4466
  }
4467
+ /** Tags on which a `disabled` attribute actually removes focusability (it does nothing on `<a>`). */
4468
+ const DISABLEABLE_TAGS = /* @__PURE__ */ new Set([
4469
+ "button",
4470
+ "input",
4471
+ "select",
4472
+ "textarea"
4473
+ ]);
4474
+ /**
4475
+ * Keyboard-focusable elements hidden by a literal `aria-hidden="true"` (a11y/aria-hidden-focus).
4476
+ * Same conventions as `collectInteractiveNestings`: only `RegularElement`, snippet bodies walk
4477
+ * with a fresh stack (they render at their `{@render}` site), and an expression `aria-hidden`
4478
+ * is unknowable — a toggled `aria-hidden={...}` never opens a container. Exempt as not
4479
+ * focusable: a `tabindex` that is negative (the documented remediation) or an expression (it
4480
+ * may resolve to -1), a `disabled` form control, and anything at or under an `inert` or
4481
+ * `hidden` element — an inert subtree leaves the tab order and a hidden one does not render,
4482
+ * so both carry down the walk as `unfocusable`.
4483
+ */
4484
+ function collectAriaHiddenFocusables(node, source, acc, stack, unfocusable = false) {
4485
+ if (Array.isArray(node)) {
4486
+ for (const child of node) collectAriaHiddenFocusables(child, source, acc, stack, unfocusable);
4487
+ return;
4488
+ }
4489
+ if (!node || typeof node !== "object") return;
4490
+ if (node.type === "SnippetBlock") {
4491
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectAriaHiddenFocusables(node[key], source, acc, []);
4492
+ return;
4493
+ }
4494
+ let opened = false;
4495
+ if (node.type === "RegularElement" && Array.isArray(node.attributes)) {
4496
+ const attrs = elementAttrs(node.attributes);
4497
+ if (attrs.some((a) => a.name === "inert" || a.name === "hidden")) unfocusable = true;
4498
+ const hiddenSelf = attrs.find((a) => a.name === "aria-hidden")?.literal?.trim().toLowerCase() === "true";
4499
+ const tabindexAttr = attrs.find((a) => a.name === "tabindex");
4500
+ const tabindex = literalTabindexValue(tabindexAttr?.literal);
4501
+ const tabindexExempts = tabindexAttr !== void 0 && tabindexAttr.literal === void 0;
4502
+ if (!unfocusable && !tabindexExempts && !(tabindex !== void 0 && tabindex < 0) && !(DISABLEABLE_TAGS.has(node.name) && attrs.some((a) => a.name === "disabled")) && isInteractiveElement(node.name, attrs) && (hiddenSelf || stack.length > 0)) acc.push({
4503
+ tag: node.name,
4504
+ ...hiddenSelf ? {} : { containerTag: stack[stack.length - 1] },
4505
+ line: lineOf(source, node.start)
4506
+ });
4507
+ if (hiddenSelf) {
4508
+ stack.push(node.name);
4509
+ opened = true;
4510
+ }
4511
+ }
4512
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectAriaHiddenFocusables(node[key], source, acc, stack, unfocusable);
4513
+ if (opened) stack.pop();
4514
+ }
4466
4515
  /** `aria-label`/`aria-labelledby`/`title` present on this element: a literal non-empty value,
4467
4516
  * or any expression (its rendered value is unknowable, but its *presence* is enough to name
4468
4517
  * the element — a11y/accessible-name treats an expression name source as present). */
@@ -4486,11 +4535,6 @@ function hasNamingAttr(attributes) {
4486
4535
  function isCustomElement(node) {
4487
4536
  return node.type === "RegularElement" && typeof node.name === "string" && node.name.includes("-");
4488
4537
  }
4489
- /** Named/unknowable verdict for a candidate interactive element's descendant subtree
4490
- * (a11y/accessible-name). `named`: a non-whitespace text descendant, or a descendant `img`
4491
- * with a non-empty literal `alt`. `unknowable`: an expression-tag, `Component`, `{@render}`,
4492
- * or `{@html}` anywhere below — content the rule cannot statically resolve, so the element is
4493
- * skipped rather than risk a false positive. */
4494
4538
  function scanAccessibleNameSubtree(node, skip) {
4495
4539
  if (Array.isArray(node)) {
4496
4540
  const acc = {
@@ -4642,10 +4686,6 @@ function isLabelableDescendant(node) {
4642
4686
  if (node.name !== "input") return true;
4643
4687
  return attrText(node.attributes ?? [], "type")?.toLowerCase() !== "hidden";
4644
4688
  }
4645
- /** Wrapped-control/unknowable verdict for a `<label>`'s subtree (a11y/label-has-control).
4646
- * `hasControl`: a descendant labelable element. `unknowable`: an expression-tag, `Component`,
4647
- * `{@render}`, or `{@html}` anywhere below — content the rule cannot statically resolve, so
4648
- * the label is skipped rather than risk a false positive. */
4649
4689
  function scanLabelSubtree(node) {
4650
4690
  if (Array.isArray(node)) {
4651
4691
  const acc = {
@@ -4880,6 +4920,84 @@ function collectTimesMissingDatetime(node, source, acc) {
4880
4920
  for (const key of CHILD_NODE_KEYS) if (key in node) collectTimesMissingDatetime(node[key], source, acc);
4881
4921
  }
4882
4922
  /**
4923
+ * Fully-static text content of a node list: concatenated `Text` data, looked through static
4924
+ * regular elements (`<dt><code>HTTP</code></dt>` names "HTTP"), with comments contributing
4925
+ * nothing. `undefined` when anything dynamic appears anywhere below — an expression, a
4926
+ * component, a block, `{@html}`/`{@render}`, `<svelte:element>`, a `<slot>`, or a custom
4927
+ * element (its shadow root may supply content) — because the rendered text is then unknowable.
4928
+ */
4929
+ function staticTextOf(nodes) {
4930
+ let out = "";
4931
+ for (const n of nodes) {
4932
+ if (!n || typeof n !== "object") continue;
4933
+ if (n.type === "Comment") continue;
4934
+ if (n.type === "Text") {
4935
+ out += String(n.data ?? "");
4936
+ continue;
4937
+ }
4938
+ if (n.type === "RegularElement" && typeof n.name === "string" && !n.name.includes("-")) {
4939
+ const inner = staticTextOf(n.fragment?.nodes ?? []);
4940
+ if (inner === void 0) return void 0;
4941
+ out += inner;
4942
+ continue;
4943
+ }
4944
+ return;
4945
+ }
4946
+ return out;
4947
+ }
4948
+ /**
4949
+ * Duplicate `<dt>` names within one `<dl>` (a11y/no-duplicate-dt). Candidates are `<dt>`
4950
+ * elements that are direct children of the `<dl>`, or direct children of a direct `<div>`
4951
+ * child (the spec's div-wrapped name-value groups); a dt under a logic block or component is
4952
+ * not a candidate — its multiplicity or rendered content is unknowable, and unknowable names
4953
+ * can only ever add duplicates, never fabricate one between two static non-duplicates. A
4954
+ * candidate's name is its fully-static text content (`staticTextOf` above — static phrasing
4955
+ * markup like `<dt><code>HTTP</code></dt>` is looked through, comments contribute nothing),
4956
+ * trimmed and whitespace-collapsed; an empty name is not a candidate (two empty dts are a
4957
+ * missing-content defect, not a duplicate name).
4958
+ * Comparison is case-sensitive — the spec leaves the equality undefined, so only certain
4959
+ * duplicates are reported. Namespace is tracked like `collectElements`: a `<dl>` inside
4960
+ * `<svg>` never renders as an HTML definition list, so it is skipped; `<foreignObject>`
4961
+ * children return to HTML. Nested `<dl>`s get their own scope for free — the walk recurses
4962
+ * and a nested dl's dts are never the outer dl's direct children.
4963
+ */
4964
+ function collectDuplicateDts(node, source, acc, inSvg) {
4965
+ if (Array.isArray(node)) {
4966
+ for (const child of node) collectDuplicateDts(child, source, acc, inSvg);
4967
+ return;
4968
+ }
4969
+ if (!node || typeof node !== "object") return;
4970
+ let next = inSvg;
4971
+ if (node.type === "RegularElement" && typeof node.name === "string") {
4972
+ const tag = node.name.toLowerCase();
4973
+ if (tag === "svg") next = true;
4974
+ else if (tag === "foreignobject") next = false;
4975
+ if (!inSvg && tag === "dl") {
4976
+ const seen = /* @__PURE__ */ new Set();
4977
+ const judge = (dt) => {
4978
+ const raw = staticTextOf(dt.fragment?.nodes ?? []);
4979
+ if (raw === void 0) return;
4980
+ const name = raw.trim().replace(/\s+/g, " ");
4981
+ if (!name) return;
4982
+ if (seen.has(name)) acc.push({
4983
+ line: lineOf(source, dt.start),
4984
+ text: name
4985
+ });
4986
+ else seen.add(name);
4987
+ };
4988
+ for (const child of node.fragment?.nodes ?? []) {
4989
+ if (child?.type !== "RegularElement" || typeof child.name !== "string") continue;
4990
+ const childTag = child.name.toLowerCase();
4991
+ if (childTag === "dt") judge(child);
4992
+ else if (childTag === "div") {
4993
+ for (const inner of child.fragment?.nodes ?? []) if (inner?.type === "RegularElement" && typeof inner.name === "string" && inner.name.toLowerCase() === "dt") judge(inner);
4994
+ }
4995
+ }
4996
+ }
4997
+ }
4998
+ for (const key of CHILD_NODE_KEYS) if (key in node) collectDuplicateDts(node[key], source, acc, next);
4999
+ }
5000
+ /**
4883
5001
  * `<video>` with a literal `autoplay` attribute and no `muted` in any form
4884
5002
  * (correctness/autoplay-muted). Presence is what autoplays, so any literal value counts
4885
5003
  * (`autoplay="false"` still autoplays); an expression value is unknowable and is skipped.
@@ -5597,15 +5715,6 @@ function collectBrowserGlobalRefs(program, source, extra) {
5597
5715
  }
5598
5716
  /** A Svelte runes module file — the whole file is one module-scope program (correctness/orphan-effect). */
5599
5717
  const MODULE_FILE_RE = /\.svelte\.(ts|js)$/;
5600
- /**
5601
- * Parse a plain TS/JS module source by wrapping it in a `<script lang="ts">` tag —
5602
- * the Svelte script parser handles TS natively, so no extra parser dependency is
5603
- * needed. Literal "<\/script" occurrences are neutralised with a same-length
5604
- * placeholder first (string contents don't affect fact extraction; offsets are
5605
- * preserved). Returns the ESTree Program and the wrapped source — wrapped line
5606
- * numbers are +1 relative to the input, so callers subtract 1. Shared by the
5607
- * runes-module facts (correctness/orphan-effect) and the Kit-module facts (the security kit-module rules).
5608
- */
5609
5718
  function parseModuleProgram(source, filename) {
5610
5719
  const wrapped = `<script lang="ts">\n${source.replace(/<\/script/gi, "<_script")}\n<\/script>`;
5611
5720
  return {
@@ -5751,6 +5860,8 @@ function parseComponentFacts(source, filename) {
5751
5860
  collectElements(ast.fragment ?? ast, source, elements, ast.options?.namespace === "svg");
5752
5861
  const interactiveNestings = [];
5753
5862
  collectInteractiveNestings(ast.fragment ?? ast, source, interactiveNestings, []);
5863
+ const ariaHiddenFocusables = [];
5864
+ collectAriaHiddenFocusables(ast.fragment ?? ast, source, ariaHiddenFocusables, []);
5754
5865
  const unnamedInteractive = [];
5755
5866
  const labelTargets = {
5756
5867
  ids: /* @__PURE__ */ new Set(),
@@ -5768,6 +5879,8 @@ function parseComponentFacts(source, filename) {
5768
5879
  collectTimesMissingDatetime(ast.fragment ?? ast, source, timesMissingDatetime);
5769
5880
  const videosAutoplayNoMuted = [];
5770
5881
  collectVideosAutoplayNoMuted(ast.fragment ?? ast, source, videosAutoplayNoMuted, ast.options?.namespace === "svg");
5882
+ const duplicateDts = [];
5883
+ collectDuplicateDts(ast.fragment ?? ast, source, duplicateDts, ast.options?.namespace === "svg");
5771
5884
  const basePathLinks = [];
5772
5885
  collectHrefLinks(ast.fragment ?? ast, source, basePathLinks);
5773
5886
  const gotoPrograms = [ast.module?.content, ast.instance?.content].filter(Boolean);
@@ -5975,12 +6088,14 @@ function parseComponentFacts(source, filename) {
5975
6088
  ariaElements,
5976
6089
  elements,
5977
6090
  interactiveNestings,
6091
+ ariaHiddenFocusables,
5978
6092
  unnamedInteractive,
5979
6093
  unassociatedLabels,
5980
6094
  bulletTexts,
5981
6095
  selectsMissingPlaceholder,
5982
6096
  timesMissingDatetime,
5983
- videosAutoplayNoMuted
6097
+ videosAutoplayNoMuted,
6098
+ duplicateDts
5984
6099
  };
5985
6100
  }
5986
6101
  //#endregion
@@ -8429,19 +8544,70 @@ const a11yInteractiveNesting = componentRule({
8429
8544
  }))
8430
8545
  });
8431
8546
  //#endregion
8547
+ //#region src/rules/a11y/aria-hidden-focus.ts
8548
+ const a11yAriaHiddenFocus = componentRule({
8549
+ id: "a11y/aria-hidden-focus",
8550
+ title: "Focusable element hidden by aria-hidden",
8551
+ category: "a11y",
8552
+ label: "aria-hidden keeps focusables out",
8553
+ rationale: "An element inside `aria-hidden=\"true\"` stays keyboard-reachable while assistive technology announces nothing for it — a screen reader user lands on a control that does not exist for them. The author cannot see this defect: `aria-hidden` changes nothing visually, so only assistive-technology users ever encounter it.",
8554
+ recommendation: "Remove `aria-hidden`, or take the element out of the tab order too (`tabindex=\"-1\"`, `disabled`). To hide an inactive region such as a modal backdrop, prefer the `inert` attribute — it removes the subtree from both the accessibility tree and the tab order at once.",
8555
+ applies: (c) => (c.ariaHiddenFocusables ?? []).length > 0,
8556
+ bad: (c) => (c.ariaHiddenFocusables ?? []).map((f) => ({
8557
+ line: f.line,
8558
+ message: f.containerTag ? `<${f.tag}> inside <${f.containerTag} aria-hidden="true"> is still keyboard-focusable` : `<${f.tag} aria-hidden="true"> is still keyboard-focusable`
8559
+ }))
8560
+ });
8561
+ //#endregion
8432
8562
  //#region src/rules/a11y/accessible-name.ts
8563
+ const NAMING_ATTRS = /* @__PURE__ */ new Set([
8564
+ "title",
8565
+ "aria-label",
8566
+ "aria-labelledby"
8567
+ ]);
8568
+ /**
8569
+ * The iframe arm reads the generic elements channel rather than the collector's
8570
+ * `unnamedInteractive` facts: an iframe has no name-from-content step (its children are fallback
8571
+ * content, never rendered), so the subtree scan the other targets need does not apply — and the
8572
+ * channel already carries the SVG-namespace flag (SVG has no iframe; the element never renders)
8573
+ * and the spread guard. Hidden or presentational iframes are skipped — `aria-hidden="true"`,
8574
+ * `hidden`, `role="presentation"`/`"none"` — the tracking/analytics-frame class where a name
8575
+ * helps nobody; an expression value in any of the skip attributes (and `hidden` in any form,
8576
+ * bare included) resolves unknowable → silence, this rule's convention throughout. The skip set
8577
+ * is deliberate to this arm only: a hidden focusable button is a different defect.
8578
+ */
8579
+ function unnamedIframes(c) {
8580
+ return (c.elements ?? []).filter((e) => {
8581
+ if (e.tag !== "iframe" || e.inSvg || e.hasSpread) return false;
8582
+ if (e.attrs.some((a) => NAMING_ATTRS.has(a.name) && (a.value === void 0 || a.value.trim() !== ""))) return false;
8583
+ const attr = (name) => e.attrs.find((a) => a.name === name);
8584
+ const ariaHidden = attr("aria-hidden");
8585
+ if (ariaHidden && (ariaHidden.value === void 0 || ariaHidden.value.trim().toLowerCase() === "true")) return false;
8586
+ if (attr("hidden")) return false;
8587
+ const role = attr("role");
8588
+ if (role) {
8589
+ if (role.value === void 0) return false;
8590
+ const resolved = resolveRole(splitTokens(role.value.toLowerCase()));
8591
+ if (resolved === "presentation" || resolved === "none") return false;
8592
+ }
8593
+ return true;
8594
+ });
8595
+ }
8433
8596
  const a11yAccessibleName = componentRule({
8434
8597
  id: "a11y/accessible-name",
8435
8598
  title: "Interactive element has no accessible name",
8436
8599
  category: "a11y",
8437
8600
  label: "Accessible names",
8438
- rationale: "A button, link, or image button with no accessible name is announced by assistive technology as its bare role (\"button\", \"link\") with nothing to distinguish it from any other control on the page.",
8439
- recommendation: "Give the element visible text or an aria-label/aria-labelledby; a button or link whose only content is an icon image is named by that image's alt, and an <input type=\"image\"> by its own alt.",
8440
- applies: (c) => (c.unnamedInteractive ?? []).length > 0,
8441
- bad: (c) => (c.unnamedInteractive ?? []).map((f) => ({
8601
+ rationale: "A button, link, or image button with no accessible name is announced by assistive technology as its bare role (\"button\", \"link\") with nothing to distinguish it from any other control on the page; an iframe without one is announced as an unnamed frame, with no way to tell an embedded video from an ad slot before entering it.",
8602
+ recommendation: "Give the element visible text or an aria-label/aria-labelledby; a button or link whose only content is an icon image is named by that image's alt, an <input type=\"image\"> by its own alt, and an <iframe> by its title.",
8603
+ applies: (c) => (c.unnamedInteractive ?? []).length > 0 || unnamedIframes(c).length > 0,
8604
+ bad: (c) => [...(c.unnamedInteractive ?? []).map((f) => ({
8442
8605
  line: f.line,
8443
8606
  message: `<${f.tag}> has no accessible name`
8444
- }))
8607
+ })), ...unnamedIframes(c).map((e) => ({
8608
+ line: e.line,
8609
+ message: "<iframe> has no accessible name"
8610
+ }))]
8445
8611
  });
8446
8612
  //#endregion
8447
8613
  //#region src/rules/a11y/label-has-control.ts
@@ -8538,6 +8704,185 @@ const a11yPositiveTabindex = componentRule({
8538
8704
  })
8539
8705
  });
8540
8706
  //#endregion
8707
+ //#region src/rules/a11y/no-accesskey.ts
8708
+ /**
8709
+ * a11y/no-accesskey — unlike most attribute rules, an expression-valued `accesskey` is reported
8710
+ * too: the attribute's presence is the problem and its value never matters, so there is nothing
8711
+ * unknowable about it. A spread-only carrier stays silent (the attribute never reaches `attrs`).
8712
+ * SVG elements are judged along with HTML — `accesskey` is meaningless there, which makes its
8713
+ * presence wrong in both namespaces.
8714
+ */
8715
+ const a11yNoAccesskey = componentRule({
8716
+ id: "a11y/no-accesskey",
8717
+ title: "Accesskey attribute",
8718
+ category: "a11y",
8719
+ severity: "warning",
8720
+ label: "Accesskey attributes",
8721
+ recommendation: "Remove the accesskey attribute. Provide visible, focusable controls instead; if a real keyboard shortcut is needed, implement it with a key handler and document it in the page.",
8722
+ rationale: "The accesskey attribute assigns a page-level shortcut key, but the actual key combination varies by browser and OS, is undiscoverable to users, and routinely conflicts with screen reader and browser keyboard bindings. Long-standing accessibility guidance is to not use it.",
8723
+ fix: { description: "Remove the accesskey attribute." },
8724
+ applies: (c) => (c.elements ?? []).some((e) => e.attrs.some((a) => a.name === "accesskey")),
8725
+ bad: (c) => (c.elements ?? []).filter((e) => e.attrs.some((a) => a.name === "accesskey")).map((e) => ({
8726
+ line: e.line,
8727
+ message: `accesskey on <${e.tag}> — the shortcut key varies by browser and OS, is undiscoverable, and conflicts with assistive-technology bindings`
8728
+ }))
8729
+ });
8730
+ //#endregion
8731
+ //#region src/rules/a11y/no-autofocus.ts
8732
+ /**
8733
+ * `<dialog>` and popover containers run their focusing steps on show, not on page load, and both
8734
+ * honour an `autofocus` descendant — so autofocus there is the correct tool, not focus theft.
8735
+ * The popover check accepts the attribute in any form (bare, literal, expression): an expression
8736
+ * could resolve to a real popover value, and a generous carve-out trades a false negative for
8737
+ * never flagging the documented pattern (same trade as correctness/autoplay-muted's `muted`).
8738
+ * Neither container form counts in the SVG namespace — the focusing steps are defined for the
8739
+ * HTML `dialog` element and HTML elements with a `popover` attribute only; a `<dialog>` inside
8740
+ * `<foreignObject>` is back in HTML and still counts.
8741
+ */
8742
+ function inShowTimeContainer(elements, start) {
8743
+ let e = start;
8744
+ while (e !== void 0) {
8745
+ if (!e.inSvg && (e.tag === "dialog" || e.attrs.some((a) => a.name === "popover"))) return true;
8746
+ e = e.parent === void 0 ? void 0 : elements[e.parent];
8747
+ }
8748
+ return false;
8749
+ }
8750
+ /**
8751
+ * a11y/no-autofocus — only a literal `autofocus` counts (a bare boolean attribute parses to
8752
+ * `value: ''`); an expression value could be `false` and passes as unknowable. The ancestor walk
8753
+ * uses `ElementFact.parent`, which looks through logic blocks but breaks at every non-lexical
8754
+ * construct (a component, `{#snippet}` body, `<svelte:element>`, custom element, `{@html}`,
8755
+ * `{@render}`, `<slot>`) — past a break the dialog cannot be proven, so the element is reported;
8756
+ * the docs page's Limitations section names these classes and the inline-suppression escape
8757
+ * hatch. SVG elements are judged along with HTML: SVG2 honours `autofocus` with the same
8758
+ * page-load focus semantics.
8759
+ */
8760
+ const a11yNoAutofocus = componentRule({
8761
+ id: "a11y/no-autofocus",
8762
+ title: "Autofocus outside a dialog",
8763
+ category: "a11y",
8764
+ severity: "warning",
8765
+ label: "Autofocus placement",
8766
+ recommendation: "Remove the autofocus attribute and let focus start at the top of the page, or move the control into the <dialog> or popover it belongs to; focus moved in response to a user action belongs in an event handler.",
8767
+ rationale: "autofocus moves focus on page load without the user asking, skipping everything before the target — screen reader users lose the page context they were building, and keyboard users are dropped mid-page. Inside a <dialog> or a popover it is the correct tool (their focusing steps run on show, not on load); anywhere else it is almost always a usability bug.",
8768
+ fix: { description: "Remove the autofocus attribute, or move the element inside the <dialog>/popover container it is meant to focus." },
8769
+ applies: (c) => literalAutofocusElements(c).length > 0,
8770
+ bad: (c) => {
8771
+ const elements = c.elements ?? [];
8772
+ return literalAutofocusElements(c).filter((e) => !inShowTimeContainer(elements, e)).map((e) => ({
8773
+ line: e.line,
8774
+ message: `autofocus on <${e.tag}> steals focus when the page loads — keyboard and screen reader users lose their place`
8775
+ }));
8776
+ }
8777
+ });
8778
+ function literalAutofocusElements(c) {
8779
+ return (c.elements ?? []).filter((e) => e.attrs.some((a) => a.name === "autofocus" && a.value !== void 0));
8780
+ }
8781
+ //#endregion
8782
+ //#region src/rules/a11y/no-duplicate-dt.ts
8783
+ /**
8784
+ * a11y/no-duplicate-dt — the candidate gathering (direct and div-wrapped `<dt>` children of a
8785
+ * `<dl>`, fully-static names only, whitespace-normalized, case-sensitive) lives in the
8786
+ * collector; see `collectDuplicateDts` in component-parse.ts for the exemption rationale.
8787
+ */
8788
+ const a11yNoDuplicateDt = componentRule({
8789
+ id: "a11y/no-duplicate-dt",
8790
+ title: "Duplicate <dt> names in a <dl>",
8791
+ category: "a11y",
8792
+ severity: "info",
8793
+ label: "Description-list names",
8794
+ rationale: "Within a single dl element, there should not be more than one dt element for each name — the HTML spec calls this out explicitly, and a duplicated term usually means a copy-paste error where two descriptions were meant to share one dt.",
8795
+ recommendation: "Merge the descriptions under a single <dt> — one dt may be followed by several <dd> — or rename the term if the entries are genuinely different.",
8796
+ fix: { description: "Merge the duplicated terms into one <dt> followed by both <dd> descriptions, or rename one of them." },
8797
+ applies: (c) => (c.duplicateDts ?? []).length > 0,
8798
+ bad: (c) => (c.duplicateDts ?? []).map((d) => ({
8799
+ line: d.line,
8800
+ message: `Duplicate <dt> "${d.text}" in the same <dl>`
8801
+ }))
8802
+ });
8803
+ //#endregion
8804
+ //#region src/rules/a11y/abbr-title.ts
8805
+ /**
8806
+ * a11y/abbr-title — a best-practice nudge, not a conformance check: the spec makes `title` on
8807
+ * `<abbr>` optional, and an expansion given in the surrounding prose is correct markup this
8808
+ * rule cannot see (the known false-positive class; the docs page names the inline-suppression
8809
+ * escape hatch for it). A blank literal `title=""` gives no expansion and is reported; an
8810
+ * expression value is unknowable and passes, the same predicate as a11y/accessible-name's
8811
+ * iframe check. SVG is skipped — `<abbr>` is not an SVG element.
8812
+ */
8813
+ function untitledAbbrs(c) {
8814
+ return (c.elements ?? []).filter((e) => e.tag === "abbr" && !e.inSvg && !e.hasSpread && !e.attrs.some((a) => a.name === "title" && (a.value === void 0 || a.value.trim() !== "")));
8815
+ }
8816
+ const a11yAbbrTitle = componentRule({
8817
+ id: "a11y/abbr-title",
8818
+ title: "Abbreviation without an expansion",
8819
+ category: "a11y",
8820
+ severity: "info",
8821
+ label: "Abbreviation expansions",
8822
+ rationale: "<abbr> marks an abbreviation, but without a title giving the expansion the element adds no information — visual users get no tooltip and assistive technology has nothing to expand. The spec keeps title optional (an expansion in the surrounding text is also fine), so this is a nudge toward the common case, not a conformance requirement.",
8823
+ recommendation: "Add title with the expansion, or spell the term out in full at first use instead of using <abbr>. If the expansion is already given in the surrounding text, the markup is fine as written — silence the finding instead.",
8824
+ fix: {
8825
+ description: "Add a title attribute with the expansion, or spell the term out at first use.",
8826
+ snippet: "<abbr title=\"HyperText Markup Language\">HTML</abbr>",
8827
+ lang: "svelte"
8828
+ },
8829
+ applies: (c) => untitledAbbrs(c).length > 0,
8830
+ bad: (c) => untitledAbbrs(c).map((e) => ({
8831
+ line: e.line,
8832
+ message: "<abbr> without a title gives readers no expansion of the abbreviation"
8833
+ }))
8834
+ });
8835
+ //#endregion
8836
+ //#region src/rules/a11y/pattern-title.ts
8837
+ /** The input types the `pattern` attribute applies to, per the spec's applies-to table. */
8838
+ const PATTERN_TYPES = /* @__PURE__ */ new Set([
8839
+ "text",
8840
+ "search",
8841
+ "url",
8842
+ "tel",
8843
+ "email",
8844
+ "password"
8845
+ ]);
8846
+ /**
8847
+ * a11y/pattern-title — only judged where `pattern` is effective: no `type` (defaults to Text)
8848
+ * or a literal type in the applies-to set. A literal type outside it (`type="number"`) makes
8849
+ * pattern inert, so requiring a title there would be wrong; an expression type or pattern is
8850
+ * unknowable and passes. An unknown type keyword (`type="txet"`) falls back to the Text state
8851
+ * where pattern does apply — skipping it is a deliberate, conservative false negative (docs
8852
+ * Limitations). A blank literal `title=""` describes nothing and is reported; an expression
8853
+ * title counts as present, the a11y/accessible-name predicate. SVG-namespace inputs never
8854
+ * render as form controls and are skipped.
8855
+ */
8856
+ function patternInputsMissingTitle(c) {
8857
+ return (c.elements ?? []).filter((e) => {
8858
+ if (e.tag !== "input" || e.inSvg || e.hasSpread) return false;
8859
+ const pattern = e.attrs.find((a) => a.name === "pattern");
8860
+ if (pattern === void 0 || pattern.value === void 0) return false;
8861
+ const type = e.attrs.find((a) => a.name === "type");
8862
+ if (type !== void 0 && (type.value === void 0 || !PATTERN_TYPES.has(type.value.trim().toLowerCase()))) return false;
8863
+ return !e.attrs.some((a) => a.name === "title" && (a.value === void 0 || a.value.trim() !== ""));
8864
+ });
8865
+ }
8866
+ const a11yPatternTitle = componentRule({
8867
+ id: "a11y/pattern-title",
8868
+ title: "Pattern input without a format description",
8869
+ category: "a11y",
8870
+ severity: "info",
8871
+ label: "Pattern descriptions",
8872
+ rationale: "When an <input> has a pattern attribute, the spec says authors should include a title describing the expected format — browsers surface it in the validation error, and without it a failed submit tells the user only that the value is wrong, not what right looks like.",
8873
+ recommendation: "Add a title describing the expected format in plain words (e.g. \"Letters, a dash, then digits\"), and mirror it in visible help text — title alone is unavailable to many users.",
8874
+ fix: {
8875
+ description: "Add a title attribute describing the expected format.",
8876
+ snippet: "<input pattern=\"[A-Za-z]+-[0-9]+\" title=\"Letters, a dash, then digits\" />",
8877
+ lang: "svelte"
8878
+ },
8879
+ applies: (c) => patternInputsMissingTitle(c).length > 0,
8880
+ bad: (c) => patternInputsMissingTitle(c).map((e) => ({
8881
+ line: e.line,
8882
+ message: "<input pattern> without a title — a failed match tells the user nothing about the expected format"
8883
+ }))
8884
+ });
8885
+ //#endregion
8541
8886
  //#region src/html-spec/index.ts
8542
8887
  /** The HTML element's spec row; `undefined` for SVG (`svg:*`) and unknown names. */
8543
8888
  function htmlElement(tag) {
@@ -8609,13 +8954,6 @@ const a11yDeprecatedAttr = componentRule({
8609
8954
  });
8610
8955
  //#endregion
8611
8956
  //#region src/rules/a11y/role-candidates.ts
8612
- /**
8613
- * Element-level ARIA facts that override the dataset's, with the reason each is data judgment
8614
- * rather than data (design 2026-08-19-aria-role-table-rules, "the spec wins over the dataset").
8615
- * `<hgroup>` is `generic` + naming-prohibited in the dataset; html-aria (TR and editor's draft) and
8616
- * axe give it `role=group` with no prohibition. Replacing the whole fact closes every arm at once.
8617
- * `<address>` is already `group` in the dataset and only its naming flag is wrong.
8618
- */
8619
8957
  const ELEMENT_FACT_OVERRIDES = {
8620
8958
  hgroup: { implicitRole: "group" },
8621
8959
  address: { implicitRole: "group" }
@@ -9560,12 +9898,18 @@ const allRules = [
9560
9898
  a11yRequiredAriaProps,
9561
9899
  a11yInvalidAriaValue,
9562
9900
  a11yInteractiveNesting,
9901
+ a11yAriaHiddenFocus,
9563
9902
  a11yAccessibleName,
9564
9903
  a11yLabelHasControl,
9565
9904
  a11yUseList,
9566
9905
  a11yPlaceholderLabelOption,
9567
9906
  a11yRequireDatetime,
9568
9907
  a11yPositiveTabindex,
9908
+ a11yNoAccesskey,
9909
+ a11yNoAutofocus,
9910
+ a11yNoDuplicateDt,
9911
+ a11yAbbrTitle,
9912
+ a11yPatternTitle,
9569
9913
  a11yDeprecatedElement,
9570
9914
  a11yDeprecatedAttr,
9571
9915
  a11yDisallowedAriaProps,
@@ -10027,4 +10371,4 @@ function formatMarkdownReport(results, config, meta) {
10027
10371
  return lines.join("\n");
10028
10372
  }
10029
10373
  //#endregion
10030
- export { architecturePrivateScopeImport as $, seoTitlePresence as $n, seoSingleH1 as $t, a11yRequireDatetime as A, overrideMatches as An, securityServerModuleState as At, performanceIframeLoading as B, performanceResponsiveImage as Bn, correctnessCheckableBindValue as Bt, a11yPermittedContents as C, shouldSkipRangeCheck as Cn, lineOf as Ct, a11yDeprecatedAttr as D, applyRuleSeverities as Dn, architecturePropCount as Dt, a11yDisallowedAriaProps as E, applyOverrides as En, valueFromNodes as Et, a11yInteractiveNesting as F, performanceRenderBlockingScript as Fn, correctnessServerBrowserGlobal as Ft, performanceNamespaceImport as G, seoSitemapXml as Gn, correctnessEffectAsOnMount as Gt, performanceSequentialAwaits as H, performanceImageDimensions as Hn, correctnessStalePropDerivation as Ht, a11yInvalidAriaValue as I, performanceLcpImage as In, correctnessBasePathNavigation as It, architectureRouteComponentImport as J, seoOgTitle as Jn, correctnessEachKey as Jt, performanceHeavyImport as K, seoRobotsTxt as Kn, correctnessEffectAsDerived as Kt, a11yRequiredAriaProps as L, performanceFontPreloadCrossorigin as Ln, correctnessOrphanLifecycle as Lt, a11yUseList as M, settingOptions as Mn, securityJavascriptUrl as Mt, a11yLabelHasControl as N, settingSeverity as Nn, securityRawHtml as Nt, a11yDeprecatedElement as O, compileOverrides as On, architectureComponentSize as Ot, a11yAccessibleName as P, withFailedRulesOff as Pn, correctnessInstanceBrowserGlobal as Pt, architectureUnitEntryFile as Q, headTagRule as Qn, seoDuplicateTitle as Qt, a11yUnknownAriaAttribute as R, performancePreloadMissingAs as Rn, correctnessOrphanEffect as Rt, a11yRequiredElement as S, resolveRuleOptions as Sn, findAttr as St, a11yDeprecatedAria as T, validateRuleSetting as Tn, textFromNodes as Tt, performanceLoadWaterfall as U, imageRule as Un, correctnessPropMutation as Ut, performanceStateRaw as V, performanceImageLoadingHint as Vn, correctnessNonreactiveBuiltinState as Vt, performanceMinifyDisabled as W, seoHtmlLang as Wn, correctnessUnmutatedState as Wt, architectureReservedDirectoryNames as X, seoCanonicalUrl as Xn, seoHeadingLevelSkip as Xt, architectureReservedNamePlacement as Y, seoOgImage as Yn, seoSsrDisabled as Yt, architectureDirectoryNaming as Z, seoDescriptionPresence as Zn, seoDuplicateDescription as Zt, a11yNoMissingIdRef as _, performancePreconnect as _n, CHILD_NODE_KEYS as _t, formatJsonReport as a, seoJsonLdRequiredProps as an, isPenalized as ar, collectNamedImportAliases as at, a11yDuplicateLandmark as b, listOption as bn, attrValue as bt, scoresByCategory as c, seoJsonLdRelativeUrl as cn, defaultProject as cr, parseModuleProgram as ct, messageText as d, seoSitemapInRobots as dn, LANDMARK_ROLES as dt, seoHreflang as en, classify as er, collectTopLevelBindings as et, ruleMetaById as f, seoViewport as fn, decodeFragmentId as ft, a11yUnverifiedIdRef as g, seoIndexability as gn, stripTextDirective as gt, explainRule as h, seoTwitterCard as hn, splitTokens as ht, buildJsonReport as i, seoTitleLength as in, docsUrlFor as ir, resolveRunesModuleSpecifier as it, a11yPlaceholderLabelOption as j, selectRules as jn, securityHandlerStateWrite as jt, a11yPositiveTabindex as k, formatFailedRuleWarning as kn, securitySharedStateImport as kt, formatGithubReport as l, seoJsonLdDeprecatedType as ln, defineConfig as lr, unwrapTs as lt, allRules as m, seoOgDescription as mn, isTopFragment as mt, mdEscape as n, seoCharset as nn, hasFailureAtOrAbove as nr, parseKitModuleFacts as nt, computeHealth as o, seoJsonLdPlaceholder as on, CATEGORIES as or, collectSuppressions as ot, severityToSarifLevel as p, seoOgUrl as pn, foldOccurrences as pt, architectureDocLinkTarget as q, seoJsonLd as qn, correctnessEachIndexKey as qt, terminalSafe as r, seoDescriptionLength as rn, summarize as rr, resolveRepoLocalPath as rt, computeScore as s, seoJsonLdDateFormat as sn, defaultConfig as sr, parseComponentFacts as st, formatMarkdownReport as t, seoImageAlt as tn, effectiveSeverity as tr, parseInMemoryExports as tt, SEVERITY_RANK as u, seoJsonLdValidity as un, IDREF_ATTRS as ut, a11yIdDuplication as v, intOption as vn, attrText as vt, a11yDisallowedElement as w, validateRuleOptions as wn, parseSvelte as wt, a11yDoctype as x, mapOption as xn, attrValueOf as xt, a11yTopLevelLandmark as y, isMentionedAnywhere as yn, attrTextOf as yt, a11yInvalidRole as z, linkRule as zn, correctnessAutoplayMuted as zt };
10374
+ export { architectureDocLinkTarget as $, seoJsonLd as $n, correctnessEachIndexKey as $t, a11yAbbrTitle as A, validateRuleOptions as An, parseSvelte as At, a11yAriaHiddenFocus as B, withFailedRulesOff as Bn, correctnessInstanceBrowserGlobal as Bt, a11yPermittedContents as C, performancePreconnect as Cn, CHILD_NODE_KEYS as Ct, a11yDeprecatedAttr as D, mapOption as Dn, attrValueOf as Dt, a11yDisallowedAriaProps as E, listOption as En, attrValue as Et, a11yRequireDatetime as F, formatFailedRuleWarning as Fn, securitySharedStateImport as Ft, a11yInvalidRole as G, linkRule as Gn, correctnessAutoplayMuted as Gt, a11yInvalidAriaValue as H, performanceLcpImage as Hn, correctnessBasePathNavigation as Ht, a11yPlaceholderLabelOption as I, overrideMatches as In, securityServerModuleState as It, performanceSequentialAwaits as J, performanceImageDimensions as Jn, correctnessStalePropDerivation as Jt, performanceIframeLoading as K, performanceResponsiveImage as Kn, correctnessCheckableBindValue as Kt, a11yUseList as L, selectRules as Ln, securityHandlerStateWrite as Lt, a11yNoAutofocus as M, applyOverrides as Mn, valueFromNodes as Mt, a11yNoAccesskey as N, applyRuleSeverities as Nn, architecturePropCount as Nt, a11yDeprecatedElement as O, resolveRuleOptions as On, findAttr as Ot, a11yPositiveTabindex as P, compileOverrides as Pn, architectureComponentSize as Pt, performanceHeavyImport as Q, seoRobotsTxt as Qn, correctnessEffectAsDerived as Qt, a11yLabelHasControl as R, settingOptions as Rn, securityJavascriptUrl as Rt, a11yRequiredElement as S, seoIndexability as Sn, stripTextDirective as St, a11yDeprecatedAria as T, isMentionedAnywhere as Tn, attrTextOf as Tt, a11yRequiredAriaProps as U, performanceFontPreloadCrossorigin as Un, correctnessOrphanLifecycle as Ut, a11yInteractiveNesting as V, performanceRenderBlockingScript as Vn, correctnessServerBrowserGlobal as Vt, a11yUnknownAriaAttribute as W, performancePreloadMissingAs as Wn, correctnessOrphanEffect as Wt, performanceMinifyDisabled as X, seoHtmlLang as Xn, correctnessUnmutatedState as Xt, performanceLoadWaterfall as Y, imageRule as Yn, correctnessPropMutation as Yt, performanceNamespaceImport as Z, seoSitemapXml as Zn, correctnessEffectAsOnMount as Zt, a11yNoMissingIdRef as _, seoSitemapInRobots as _n, LANDMARK_ROLES as _t, formatJsonReport as a, seoSingleH1 as an, seoTitlePresence as ar, architecturePrivateScopeImport as at, a11yDuplicateLandmark as b, seoOgDescription as bn, isTopFragment as bt, scoresByCategory as c, seoCharset as cn, hasFailureAtOrAbove as cr, parseKitModuleFacts as ct, messageText as d, seoJsonLdRequiredProps as dn, isPenalized as dr, collectNamedImportAliases as dt, correctnessEachKey as en, seoOgTitle as er, architectureRouteComponentImport as et, ruleMetaById as f, seoJsonLdPlaceholder as fn, CATEGORIES as fr, collectSuppressions as ft, a11yUnverifiedIdRef as g, seoJsonLdValidity as gn, IDREF_ATTRS as gt, explainRule as h, seoJsonLdDeprecatedType as hn, defineConfig as hr, unwrapTs as ht, buildJsonReport as i, seoDuplicateTitle as in, headTagRule as ir, architectureUnitEntryFile as it, a11yNoDuplicateDt as j, validateRuleSetting as jn, textFromNodes as jt, a11yPatternTitle as k, shouldSkipRangeCheck as kn, lineOf as kt, formatGithubReport as l, seoDescriptionLength as ln, summarize as lr, resolveRepoLocalPath as lt, allRules as m, seoJsonLdRelativeUrl as mn, defaultProject as mr, parseModuleProgram as mt, mdEscape as n, seoHeadingLevelSkip as nn, seoCanonicalUrl as nr, architectureReservedDirectoryNames as nt, computeHealth as o, seoHreflang as on, classify as or, collectTopLevelBindings as ot, severityToSarifLevel as p, seoJsonLdDateFormat as pn, defaultConfig as pr, parseComponentFacts as pt, performanceStateRaw as q, performanceImageLoadingHint as qn, correctnessNonreactiveBuiltinState as qt, terminalSafe as r, seoDuplicateDescription as rn, seoDescriptionPresence as rr, architectureDirectoryNaming as rt, computeScore as s, seoImageAlt as sn, effectiveSeverity as sr, parseInMemoryExports as st, formatMarkdownReport as t, seoSsrDisabled as tn, seoOgImage as tr, architectureReservedNamePlacement as tt, SEVERITY_RANK as u, seoTitleLength as un, docsUrlFor as ur, resolveRunesModuleSpecifier as ut, a11yIdDuplication as v, seoViewport as vn, decodeFragmentId as vt, a11yDisallowedElement as w, intOption as wn, attrText as wt, a11yDoctype as x, seoTwitterCard as xn, splitTokens as xt, a11yTopLevelLandmark as y, seoOgUrl as yn, foldOccurrences as yt, a11yAccessibleName as z, settingSeverity as zn, securityRawHtml as zt };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/core",
3
- "version": "0.48.0",
3
+ "version": "0.50.0",
4
4
  "description": "Shared, runtime-agnostic core for svelte-vitals (types, rule engine, scorer, reporter).",
5
5
  "type": "module",
6
6
  "license": "MIT",