@brandlab-digital/conformance 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +26 -0
  2. package/dist/current-release.js +5 -5
  3. package/dist/current-release.js.map +1 -1
  4. package/dist/public-copy.d.ts +7 -0
  5. package/dist/public-copy.d.ts.map +1 -1
  6. package/dist/public-copy.js +8 -3
  7. package/dist/public-copy.js.map +1 -1
  8. package/dist/ready-cli.d.ts +38 -0
  9. package/dist/ready-cli.d.ts.map +1 -0
  10. package/dist/ready-cli.js +327 -0
  11. package/dist/ready-cli.js.map +1 -0
  12. package/dist/site-system/checks.d.ts +389 -0
  13. package/dist/site-system/checks.d.ts.map +1 -0
  14. package/dist/site-system/checks.js +96 -0
  15. package/dist/site-system/checks.js.map +1 -0
  16. package/dist/site-system/config.d.ts +40 -0
  17. package/dist/site-system/config.d.ts.map +1 -0
  18. package/dist/site-system/config.js +214 -0
  19. package/dist/site-system/config.js.map +1 -0
  20. package/dist/site-system/index.d.ts +14 -0
  21. package/dist/site-system/index.d.ts.map +1 -0
  22. package/dist/site-system/index.js +14 -0
  23. package/dist/site-system/index.js.map +1 -0
  24. package/dist/site-system/journeys/access.d.ts +7 -0
  25. package/dist/site-system/journeys/access.d.ts.map +1 -0
  26. package/dist/site-system/journeys/access.js +263 -0
  27. package/dist/site-system/journeys/access.js.map +1 -0
  28. package/dist/site-system/journeys/browser.d.ts +79 -0
  29. package/dist/site-system/journeys/browser.d.ts.map +1 -0
  30. package/dist/site-system/journeys/browser.js +218 -0
  31. package/dist/site-system/journeys/browser.js.map +1 -0
  32. package/dist/site-system/journeys/conversion.d.ts +3 -0
  33. package/dist/site-system/journeys/conversion.d.ts.map +1 -0
  34. package/dist/site-system/journeys/conversion.js +152 -0
  35. package/dist/site-system/journeys/conversion.js.map +1 -0
  36. package/dist/site-system/journeys/menu.d.ts +11 -0
  37. package/dist/site-system/journeys/menu.d.ts.map +1 -0
  38. package/dist/site-system/journeys/menu.js +395 -0
  39. package/dist/site-system/journeys/menu.js.map +1 -0
  40. package/dist/site-system/journeys/page-helpers.d.ts +146 -0
  41. package/dist/site-system/journeys/page-helpers.d.ts.map +1 -0
  42. package/dist/site-system/journeys/page-helpers.js +426 -0
  43. package/dist/site-system/journeys/page-helpers.js.map +1 -0
  44. package/dist/site-system/journeys/plan.d.ts +82 -0
  45. package/dist/site-system/journeys/plan.d.ts.map +1 -0
  46. package/dist/site-system/journeys/plan.js +112 -0
  47. package/dist/site-system/journeys/plan.js.map +1 -0
  48. package/dist/site-system/journeys/shell.d.ts +24 -0
  49. package/dist/site-system/journeys/shell.d.ts.map +1 -0
  50. package/dist/site-system/journeys/shell.js +269 -0
  51. package/dist/site-system/journeys/shell.js.map +1 -0
  52. package/dist/site-system/playwright-report.d.ts +22 -0
  53. package/dist/site-system/playwright-report.d.ts.map +1 -0
  54. package/dist/site-system/playwright-report.js +131 -0
  55. package/dist/site-system/playwright-report.js.map +1 -0
  56. package/dist/site-system/records.d.ts +30 -0
  57. package/dist/site-system/records.d.ts.map +1 -0
  58. package/dist/site-system/records.js +98 -0
  59. package/dist/site-system/records.js.map +1 -0
  60. package/dist/site-system/report.d.ts +116 -0
  61. package/dist/site-system/report.d.ts.map +1 -0
  62. package/dist/site-system/report.js +366 -0
  63. package/dist/site-system/report.js.map +1 -0
  64. package/dist/site-system/rule-modules.d.ts +8 -0
  65. package/dist/site-system/rule-modules.d.ts.map +1 -0
  66. package/dist/site-system/rule-modules.js +12 -0
  67. package/dist/site-system/rule-modules.js.map +1 -0
  68. package/dist/site-system/rules/design/d4-distinctness.d.ts +36 -0
  69. package/dist/site-system/rules/design/d4-distinctness.d.ts.map +1 -0
  70. package/dist/site-system/rules/design/d4-distinctness.js +207 -0
  71. package/dist/site-system/rules/design/d4-distinctness.js.map +1 -0
  72. package/dist/site-system/rules/design/index.d.ts +17 -0
  73. package/dist/site-system/rules/design/index.d.ts.map +1 -0
  74. package/dist/site-system/rules/design/index.js +7 -0
  75. package/dist/site-system/rules/design/index.js.map +1 -0
  76. package/dist/site-system/rules/enquiry/c2-quote-form.d.ts +31 -0
  77. package/dist/site-system/rules/enquiry/c2-quote-form.d.ts.map +1 -0
  78. package/dist/site-system/rules/enquiry/c2-quote-form.js +211 -0
  79. package/dist/site-system/rules/enquiry/c2-quote-form.js.map +1 -0
  80. package/dist/site-system/rules/enquiry/common.d.ts +37 -0
  81. package/dist/site-system/rules/enquiry/common.d.ts.map +1 -0
  82. package/dist/site-system/rules/enquiry/common.js +59 -0
  83. package/dist/site-system/rules/enquiry/common.js.map +1 -0
  84. package/dist/site-system/rules/enquiry/e1-recorded-titles.d.ts +8 -0
  85. package/dist/site-system/rules/enquiry/e1-recorded-titles.d.ts.map +1 -0
  86. package/dist/site-system/rules/enquiry/e1-recorded-titles.js +59 -0
  87. package/dist/site-system/rules/enquiry/e1-recorded-titles.js.map +1 -0
  88. package/dist/site-system/rules/enquiry/e9-cost-guides.d.ts +22 -0
  89. package/dist/site-system/rules/enquiry/e9-cost-guides.d.ts.map +1 -0
  90. package/dist/site-system/rules/enquiry/e9-cost-guides.js +55 -0
  91. package/dist/site-system/rules/enquiry/e9-cost-guides.js.map +1 -0
  92. package/dist/site-system/rules/enquiry/html.d.ts +53 -0
  93. package/dist/site-system/rules/enquiry/html.d.ts.map +1 -0
  94. package/dist/site-system/rules/enquiry/html.js +294 -0
  95. package/dist/site-system/rules/enquiry/html.js.map +1 -0
  96. package/dist/site-system/rules/enquiry/index.d.ts +17 -0
  97. package/dist/site-system/rules/enquiry/index.d.ts.map +1 -0
  98. package/dist/site-system/rules/enquiry/index.js +26 -0
  99. package/dist/site-system/rules/enquiry/index.js.map +1 -0
  100. package/dist/site-system/rules/enquiry/k8-sections.d.ts +17 -0
  101. package/dist/site-system/rules/enquiry/k8-sections.d.ts.map +1 -0
  102. package/dist/site-system/rules/enquiry/k8-sections.js +76 -0
  103. package/dist/site-system/rules/enquiry/k8-sections.js.map +1 -0
  104. package/dist/site-system/rules/enquiry/literal.d.ts +29 -0
  105. package/dist/site-system/rules/enquiry/literal.d.ts.map +1 -0
  106. package/dist/site-system/rules/enquiry/literal.js +189 -0
  107. package/dist/site-system/rules/enquiry/literal.js.map +1 -0
  108. package/dist/site-system/rules/enquiry/t11-licence-classes.d.ts +73 -0
  109. package/dist/site-system/rules/enquiry/t11-licence-classes.d.ts.map +1 -0
  110. package/dist/site-system/rules/enquiry/t11-licence-classes.js +275 -0
  111. package/dist/site-system/rules/enquiry/t11-licence-classes.js.map +1 -0
  112. package/dist/site-system/rules/enquiry/t5-reviews.d.ts +23 -0
  113. package/dist/site-system/rules/enquiry/t5-reviews.d.ts.map +1 -0
  114. package/dist/site-system/rules/enquiry/t5-reviews.js +91 -0
  115. package/dist/site-system/rules/enquiry/t5-reviews.js.map +1 -0
  116. package/dist/site-system/rules/shell/banned-wording.d.ts +24 -0
  117. package/dist/site-system/rules/shell/banned-wording.d.ts.map +1 -0
  118. package/dist/site-system/rules/shell/banned-wording.js +212 -0
  119. package/dist/site-system/rules/shell/banned-wording.js.map +1 -0
  120. package/dist/site-system/rules/shell/budgets.d.ts +43 -0
  121. package/dist/site-system/rules/shell/budgets.d.ts.map +1 -0
  122. package/dist/site-system/rules/shell/budgets.js +297 -0
  123. package/dist/site-system/rules/shell/budgets.js.map +1 -0
  124. package/dist/site-system/rules/shell/cta-labels.d.ts +17 -0
  125. package/dist/site-system/rules/shell/cta-labels.d.ts.map +1 -0
  126. package/dist/site-system/rules/shell/cta-labels.js +86 -0
  127. package/dist/site-system/rules/shell/cta-labels.js.map +1 -0
  128. package/dist/site-system/rules/shell/facts.d.ts +33 -0
  129. package/dist/site-system/rules/shell/facts.d.ts.map +1 -0
  130. package/dist/site-system/rules/shell/facts.js +217 -0
  131. package/dist/site-system/rules/shell/facts.js.map +1 -0
  132. package/dist/site-system/rules/shell/headings.d.ts +5 -0
  133. package/dist/site-system/rules/shell/headings.d.ts.map +1 -0
  134. package/dist/site-system/rules/shell/headings.js +23 -0
  135. package/dist/site-system/rules/shell/headings.js.map +1 -0
  136. package/dist/site-system/rules/shell/hero-location.d.ts +4 -0
  137. package/dist/site-system/rules/shell/hero-location.d.ts.map +1 -0
  138. package/dist/site-system/rules/shell/hero-location.js +45 -0
  139. package/dist/site-system/rules/shell/hero-location.js.map +1 -0
  140. package/dist/site-system/rules/shell/html.d.ts +53 -0
  141. package/dist/site-system/rules/shell/html.d.ts.map +1 -0
  142. package/dist/site-system/rules/shell/html.js +341 -0
  143. package/dist/site-system/rules/shell/html.js.map +1 -0
  144. package/dist/site-system/rules/shell/index.d.ts +18 -0
  145. package/dist/site-system/rules/shell/index.d.ts.map +1 -0
  146. package/dist/site-system/rules/shell/index.js +65 -0
  147. package/dist/site-system/rules/shell/index.js.map +1 -0
  148. package/dist/site-system/rules/shell/licence.d.ts +4 -0
  149. package/dist/site-system/rules/shell/licence.d.ts.map +1 -0
  150. package/dist/site-system/rules/shell/licence.js +60 -0
  151. package/dist/site-system/rules/shell/licence.js.map +1 -0
  152. package/dist/site-system/rules/shell/location-wording.d.ts +13 -0
  153. package/dist/site-system/rules/shell/location-wording.d.ts.map +1 -0
  154. package/dist/site-system/rules/shell/location-wording.js +15 -0
  155. package/dist/site-system/rules/shell/location-wording.js.map +1 -0
  156. package/dist/site-system/rules/shell/navigation.d.ts +5 -0
  157. package/dist/site-system/rules/shell/navigation.d.ts.map +1 -0
  158. package/dist/site-system/rules/shell/navigation.js +272 -0
  159. package/dist/site-system/rules/shell/navigation.js.map +1 -0
  160. package/dist/site-system/rules/shell/overlays.d.ts +4 -0
  161. package/dist/site-system/rules/shell/overlays.d.ts.map +1 -0
  162. package/dist/site-system/rules/shell/overlays.js +93 -0
  163. package/dist/site-system/rules/shell/overlays.js.map +1 -0
  164. package/dist/site-system/rules/shell/page.d.ts +51 -0
  165. package/dist/site-system/rules/shell/page.d.ts.map +1 -0
  166. package/dist/site-system/rules/shell/page.js +256 -0
  167. package/dist/site-system/rules/shell/page.js.map +1 -0
  168. package/dist/site-system/rules/shell/phone.d.ts +13 -0
  169. package/dist/site-system/rules/shell/phone.d.ts.map +1 -0
  170. package/dist/site-system/rules/shell/phone.js +99 -0
  171. package/dist/site-system/rules/shell/phone.js.map +1 -0
  172. package/dist/site-system/rules/shell/proof-numbers.d.ts +7 -0
  173. package/dist/site-system/rules/shell/proof-numbers.d.ts.map +1 -0
  174. package/dist/site-system/rules/shell/proof-numbers.js +81 -0
  175. package/dist/site-system/rules/shell/proof-numbers.js.map +1 -0
  176. package/dist/site-system/rules/shell/types.d.ts +80 -0
  177. package/dist/site-system/rules/shell/types.d.ts.map +1 -0
  178. package/dist/site-system/rules/shell/types.js +17 -0
  179. package/dist/site-system/rules/shell/types.js.map +1 -0
  180. package/dist/site-system/runners.d.ts +37 -0
  181. package/dist/site-system/runners.d.ts.map +1 -0
  182. package/dist/site-system/runners.js +48 -0
  183. package/dist/site-system/runners.js.map +1 -0
  184. package/dist/site-system/samples.d.ts +43 -0
  185. package/dist/site-system/samples.d.ts.map +1 -0
  186. package/dist/site-system/samples.js +99 -0
  187. package/dist/site-system/samples.js.map +1 -0
  188. package/dist/site-system/steps.d.ts +66 -0
  189. package/dist/site-system/steps.d.ts.map +1 -0
  190. package/dist/site-system/steps.js +45 -0
  191. package/dist/site-system/steps.js.map +1 -0
  192. package/dist/site-system/streamed-html.d.ts +9 -0
  193. package/dist/site-system/streamed-html.d.ts.map +1 -0
  194. package/dist/site-system/streamed-html.js +43 -0
  195. package/dist/site-system/streamed-html.js.map +1 -0
  196. package/dist/site-system-e2e.d.ts +6 -0
  197. package/dist/site-system-e2e.d.ts.map +1 -0
  198. package/dist/site-system-e2e.js +54 -0
  199. package/dist/site-system-e2e.js.map +1 -0
  200. package/package.json +22 -5
@@ -0,0 +1,53 @@
1
+ /**
2
+ * A small tolerant HTML reader for the rendered rules. The rules read server HTML that frameworks emit, so the reader
3
+ * handles what that output holds (void and raw-text elements, comments, entities, `<svg>` self-closing tags, the odd
4
+ * unclosed `<p>` or `<li>`) and never throws. It builds a tree with parent links and offers the few queries the rules
5
+ * need; it does not run scripts or apply CSS.
6
+ */
7
+ export interface HtmlElement {
8
+ readonly kind: "element";
9
+ /** Lower-case tag name; the root is "#document". */
10
+ readonly tag: string;
11
+ /** Lower-case attribute names to decoded values; a bare attribute is "". */
12
+ readonly attrs: Readonly<Record<string, string>>;
13
+ readonly children: HtmlNode[];
14
+ parent: HtmlElement | null;
15
+ /** Offset of the start tag in the source. */
16
+ readonly start: number;
17
+ }
18
+ export interface HtmlText {
19
+ readonly kind: "text";
20
+ /** Decoded text; script and style text stays raw. */
21
+ readonly text: string;
22
+ parent: HtmlElement | null;
23
+ }
24
+ export type HtmlNode = HtmlElement | HtmlText;
25
+ export interface HtmlDocument {
26
+ readonly root: HtmlElement;
27
+ /** The first element carrying each id. */
28
+ readonly byId: ReadonlyMap<string, HtmlElement>;
29
+ }
30
+ /** Decode named and numeric character references; unknown references stay as written. */
31
+ export declare function decodeEntities(text: string): string;
32
+ /** Read HTML into a tree. Never throws; malformed input gives a best-effort tree. */
33
+ export declare function parseHtml(html: string): HtmlDocument;
34
+ /** Every descendant element in document order. */
35
+ export declare function descendants(root: HtmlElement): Generator<HtmlElement>;
36
+ export declare function findAll(root: HtmlElement, predicate: (element: HtmlElement) => boolean): HtmlElement[];
37
+ export declare function findFirst(root: HtmlElement, predicate: (element: HtmlElement) => boolean): HtmlElement | null;
38
+ /** The element itself, then each ancestor up to (not including) the root. */
39
+ export declare function selfAndAncestors(element: HtmlElement): Generator<HtmlElement>;
40
+ export declare function closest(element: HtmlElement, predicate: (element: HtmlElement) => boolean): HtmlElement | null;
41
+ /** Whether `element` is `container` or inside it. */
42
+ export declare function isWithin(element: HtmlElement, container: HtmlElement): boolean;
43
+ export declare function classTokens(element: HtmlElement): string[];
44
+ export declare function collapseWhitespace(text: string): string;
45
+ /** Tags whose content is never visible text. */
46
+ export declare const NON_TEXT_TAGS: ReadonlySet<string>;
47
+ /** Elements that start a new line of text: a block boundary separates the words either side of it. */
48
+ export declare const BLOCK_TAGS: ReadonlySet<string>;
49
+ /** The text of an element as a browser would show it, whitespace collapsed and blocks separated by a space. */
50
+ export declare function textOf(element: HtmlElement, skip?: ReadonlySet<string>): string;
51
+ /** Whether an element shows anything: text, a link, an image, a form control or a video. */
52
+ export declare function hasContent(element: HtmlElement): boolean;
53
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/html.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,oDAAoD;IACpD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC;IAC9B,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACjD;AAsBD,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQnD;AAoED,qFAAqF;AACrF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAwHpD;AAID,kDAAkD;AAClD,wBAAiB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC,CActE;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,GAAG,WAAW,EAAE,CAItG;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,GAAG,WAAW,GAAG,IAAI,CAG7G;AAED,6EAA6E;AAC7E,wBAAiB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC,CAE9E;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,GAAG,WAAW,GAAG,IAAI,CAG9G;AAED,qDAAqD;AACrD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,GAAG,OAAO,CAG9E;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,EAAE,CAG1D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,gDAAgD;AAChD,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAA2F,CAAC;AAE1I,sGAAsG;AACtG,eAAO,MAAM,UAAU,EAAE,WAAW,CAAC,MAAM,CAKzC,CAAC;AAEH,+GAA+G;AAC/G,wBAAgB,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,GAAE,WAAW,CAAC,MAAM,CAAiB,GAAG,MAAM,CAiB9F;AAED,4FAA4F;AAC5F,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAGxD"}
@@ -0,0 +1,341 @@
1
+ /**
2
+ * A small tolerant HTML reader for the rendered rules. The rules read server HTML that frameworks emit, so the reader
3
+ * handles what that output holds (void and raw-text elements, comments, entities, `<svg>` self-closing tags, the odd
4
+ * unclosed `<p>` or `<li>`) and never throws. It builds a tree with parent links and offers the few queries the rules
5
+ * need; it does not run scripts or apply CSS.
6
+ */
7
+ const VOID_TAGS = new Set([
8
+ "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr",
9
+ ]);
10
+ const RAW_TEXT_TAGS = new Set(["script", "style", "textarea", "title"]);
11
+ /** Start tags that end an open paragraph. */
12
+ const PARAGRAPH_CLOSERS = new Set([
13
+ "address", "article", "aside", "blockquote", "details", "div", "dl", "fieldset", "figcaption", "figure", "footer",
14
+ "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "main", "menu", "nav", "ol", "p", "pre", "section",
15
+ "table", "ul",
16
+ ]);
17
+ const NAMED_ENTITIES = {
18
+ amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " ", ndash: "–", mdash: "—",
19
+ lsquo: "‘", rsquo: "’", ldquo: "“", rdquo: "”", hellip: "…", middot: "·",
20
+ copy: "©", reg: "®", trade: "™", deg: "°", pound: "£", euro: "€", cent: "¢",
21
+ times: "×", bull: "•", laquo: "«", raquo: "»", rarr: "→", larr: "←",
22
+ frac12: "½", frac14: "¼", frac34: "¾", shy: "­", ensp: " ", emsp: " ",
23
+ thinsp: " ", plusmn: "±", sup2: "²", sup3: "³", eacute: "é", egrave: "è",
24
+ };
25
+ /** Decode named and numeric character references; unknown references stay as written. */
26
+ export function decodeEntities(text) {
27
+ if (!text.includes("&"))
28
+ return text;
29
+ return text.replace(/&(?:#(\d{1,7})|#[xX]([0-9a-fA-F]{1,6})|([A-Za-z][A-Za-z0-9]{1,9}));/g, (whole, dec, hex, name) => {
30
+ if (name !== undefined)
31
+ return NAMED_ENTITIES[name] ?? NAMED_ENTITIES[name.toLowerCase()] ?? whole;
32
+ const code = dec !== undefined ? Number.parseInt(dec, 10) : Number.parseInt(hex, 16);
33
+ if (!Number.isFinite(code) || code <= 0 || code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff))
34
+ return whole;
35
+ return String.fromCodePoint(code);
36
+ });
37
+ }
38
+ function isSpace(code) {
39
+ return code === 32 || code === 9 || code === 10 || code === 12 || code === 13;
40
+ }
41
+ function createElement(tag, attrs, parent, start) {
42
+ return { kind: "element", tag, attrs, children: [], parent, start };
43
+ }
44
+ function readAttributes(html, from) {
45
+ const attrs = Object.create(null);
46
+ const length = html.length;
47
+ let index = from;
48
+ let selfClosing = false;
49
+ while (index < length) {
50
+ while (index < length && isSpace(html.charCodeAt(index)))
51
+ index += 1;
52
+ if (index >= length)
53
+ break;
54
+ const char = html[index];
55
+ if (char === ">") {
56
+ index += 1;
57
+ break;
58
+ }
59
+ if (char === "/") {
60
+ if (html[index + 1] === ">") {
61
+ selfClosing = true;
62
+ index += 2;
63
+ break;
64
+ }
65
+ index += 1;
66
+ continue;
67
+ }
68
+ const nameStart = index;
69
+ while (index < length) {
70
+ const code = html.charCodeAt(index);
71
+ if (isSpace(code) || code === 34 || code === 39 || code === 60 || code === 62 || code === 47 || code === 61)
72
+ break;
73
+ index += 1;
74
+ }
75
+ if (index === nameStart) {
76
+ index += 1;
77
+ continue;
78
+ }
79
+ const name = html.slice(nameStart, index).toLowerCase();
80
+ while (index < length && isSpace(html.charCodeAt(index)))
81
+ index += 1;
82
+ let value = "";
83
+ if (html[index] === "=") {
84
+ index += 1;
85
+ while (index < length && isSpace(html.charCodeAt(index)))
86
+ index += 1;
87
+ const quote = html[index];
88
+ if (quote === '"' || quote === "'") {
89
+ const close = html.indexOf(quote, index + 1);
90
+ const end = close === -1 ? length : close;
91
+ value = html.slice(index + 1, end);
92
+ index = close === -1 ? length : close + 1;
93
+ }
94
+ else {
95
+ const valueStart = index;
96
+ while (index < length && !isSpace(html.charCodeAt(index)) && html[index] !== ">")
97
+ index += 1;
98
+ value = html.slice(valueStart, index);
99
+ }
100
+ }
101
+ if (!(name in attrs))
102
+ attrs[name] = decodeEntities(value);
103
+ }
104
+ return { attrs, end: index, selfClosing };
105
+ }
106
+ const START_TAG = /<([A-Za-z][A-Za-z0-9:_.-]*)/y;
107
+ const END_TAG = /<\/([A-Za-z][A-Za-z0-9:_.-]*)[^>]*>/y;
108
+ /** Read HTML into a tree. Never throws; malformed input gives a best-effort tree. */
109
+ export function parseHtml(html) {
110
+ const root = createElement("#document", Object.create(null), null, 0);
111
+ const byId = new Map();
112
+ const length = html.length;
113
+ let current = root;
114
+ let index = 0;
115
+ const appendText = (text, raw) => {
116
+ if (text === "")
117
+ return;
118
+ current.children.push({ kind: "text", text: raw ? text : decodeEntities(text), parent: current });
119
+ };
120
+ const closeOpen = (names, boundaries) => {
121
+ for (let element = current; element && element !== root; element = element.parent) {
122
+ if (names.includes(element.tag)) {
123
+ current = element.parent ?? root;
124
+ return;
125
+ }
126
+ if (boundaries.includes(element.tag))
127
+ return;
128
+ }
129
+ };
130
+ const closeImplied = (tag) => {
131
+ switch (tag) {
132
+ case "li":
133
+ closeOpen(["li"], ["ul", "ol", "menu"]);
134
+ break;
135
+ case "dt":
136
+ case "dd":
137
+ closeOpen(["dt", "dd"], ["dl"]);
138
+ break;
139
+ case "td":
140
+ case "th":
141
+ closeOpen(["td", "th"], ["tr", "table"]);
142
+ break;
143
+ case "tr":
144
+ closeOpen(["td", "th"], ["tr", "table"]);
145
+ closeOpen(["tr"], ["table", "thead", "tbody", "tfoot"]);
146
+ break;
147
+ case "thead":
148
+ case "tbody":
149
+ case "tfoot":
150
+ closeOpen(["thead", "tbody", "tfoot", "tr", "td", "th"], ["table"]);
151
+ break;
152
+ case "option":
153
+ closeOpen(["option"], ["select", "datalist", "optgroup"]);
154
+ break;
155
+ case "optgroup":
156
+ closeOpen(["option", "optgroup"], ["select"]);
157
+ break;
158
+ default: break;
159
+ }
160
+ if (current.tag === "p" && PARAGRAPH_CLOSERS.has(tag))
161
+ current = current.parent ?? root;
162
+ };
163
+ while (index < length) {
164
+ const next = html.indexOf("<", index);
165
+ if (next === -1) {
166
+ appendText(html.slice(index), false);
167
+ break;
168
+ }
169
+ if (next > index)
170
+ appendText(html.slice(index, next), false);
171
+ index = next;
172
+ if (html.startsWith("<!--", index)) {
173
+ const close = html.indexOf("-->", index + 4);
174
+ index = close === -1 ? length : close + 3;
175
+ continue;
176
+ }
177
+ if (html.startsWith("<![CDATA[", index)) {
178
+ const close = html.indexOf("]]>", index + 9);
179
+ appendText(html.slice(index + 9, close === -1 ? length : close), true);
180
+ index = close === -1 ? length : close + 3;
181
+ continue;
182
+ }
183
+ const second = html[index + 1];
184
+ if (second === "!" || second === "?") {
185
+ const close = html.indexOf(">", index);
186
+ index = close === -1 ? length : close + 1;
187
+ continue;
188
+ }
189
+ if (second === "/") {
190
+ END_TAG.lastIndex = index;
191
+ const match = END_TAG.exec(html);
192
+ if (!match) {
193
+ appendText("<", false);
194
+ index += 1;
195
+ continue;
196
+ }
197
+ const tag = match[1].toLowerCase();
198
+ for (let element = current; element && element !== root; element = element.parent) {
199
+ if (element.tag === tag) {
200
+ current = element.parent ?? root;
201
+ break;
202
+ }
203
+ }
204
+ index += match[0].length;
205
+ continue;
206
+ }
207
+ START_TAG.lastIndex = index;
208
+ const start = START_TAG.exec(html);
209
+ if (!start) {
210
+ appendText("<", false);
211
+ index += 1;
212
+ continue;
213
+ }
214
+ const tag = start[1].toLowerCase();
215
+ const { attrs, end, selfClosing } = readAttributes(html, index + start[0].length);
216
+ closeImplied(tag);
217
+ const element = createElement(tag, attrs, current, index);
218
+ current.children.push(element);
219
+ const id = attrs.id;
220
+ if (id && !byId.has(id))
221
+ byId.set(id, element);
222
+ index = end;
223
+ if (VOID_TAGS.has(tag) || selfClosing)
224
+ continue;
225
+ if (RAW_TEXT_TAGS.has(tag)) {
226
+ const closing = new RegExp(`</${tag}(?=[\\s/>])`, "gi");
227
+ closing.lastIndex = index;
228
+ const found = closing.exec(html);
229
+ const rawEnd = found ? found.index : length;
230
+ const raw = html.slice(index, rawEnd);
231
+ if (raw !== "")
232
+ element.children.push({ kind: "text", text: tag === "script" || tag === "style" ? raw : decodeEntities(raw), parent: element });
233
+ if (found) {
234
+ const close = html.indexOf(">", found.index);
235
+ index = close === -1 ? length : close + 1;
236
+ }
237
+ else {
238
+ index = length;
239
+ }
240
+ continue;
241
+ }
242
+ current = element;
243
+ }
244
+ return { root, byId };
245
+ }
246
+ // --- queries -----------------------------------------------------------------
247
+ /** Every descendant element in document order. */
248
+ export function* descendants(root) {
249
+ const stack = [];
250
+ const push = (parent) => {
251
+ for (let position = parent.children.length - 1; position >= 0; position -= 1) {
252
+ const child = parent.children[position];
253
+ if (child.kind === "element")
254
+ stack.push(child);
255
+ }
256
+ };
257
+ push(root);
258
+ while (stack.length > 0) {
259
+ const element = stack.pop();
260
+ yield element;
261
+ push(element);
262
+ }
263
+ }
264
+ export function findAll(root, predicate) {
265
+ const found = [];
266
+ for (const element of descendants(root))
267
+ if (predicate(element))
268
+ found.push(element);
269
+ return found;
270
+ }
271
+ export function findFirst(root, predicate) {
272
+ for (const element of descendants(root))
273
+ if (predicate(element))
274
+ return element;
275
+ return null;
276
+ }
277
+ /** The element itself, then each ancestor up to (not including) the root. */
278
+ export function* selfAndAncestors(element) {
279
+ for (let current = element; current && current.tag !== "#document"; current = current.parent)
280
+ yield current;
281
+ }
282
+ export function closest(element, predicate) {
283
+ for (const candidate of selfAndAncestors(element))
284
+ if (predicate(candidate))
285
+ return candidate;
286
+ return null;
287
+ }
288
+ /** Whether `element` is `container` or inside it. */
289
+ export function isWithin(element, container) {
290
+ for (const candidate of selfAndAncestors(element))
291
+ if (candidate === container)
292
+ return true;
293
+ return false;
294
+ }
295
+ export function classTokens(element) {
296
+ const value = element.attrs.class;
297
+ return value ? value.split(/\s+/).filter(Boolean) : [];
298
+ }
299
+ export function collapseWhitespace(text) {
300
+ return text.replace(/[\s ]+/g, " ").trim();
301
+ }
302
+ /** Tags whose content is never visible text. */
303
+ export const NON_TEXT_TAGS = new Set(["script", "style", "template", "svg", "noscript", "head", "iframe", "object"]);
304
+ /** Elements that start a new line of text: a block boundary separates the words either side of it. */
305
+ export const BLOCK_TAGS = new Set([
306
+ "address", "article", "aside", "blockquote", "body", "br", "button", "caption", "dd", "details", "dialog", "div",
307
+ "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header",
308
+ "hr", "html", "label", "legend", "li", "main", "nav", "ol", "option", "p", "pre", "section", "select", "summary",
309
+ "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "tr", "ul",
310
+ ]);
311
+ /** The text of an element as a browser would show it, whitespace collapsed and blocks separated by a space. */
312
+ export function textOf(element, skip = NON_TEXT_TAGS) {
313
+ let text = "";
314
+ const visit = (node, depth) => {
315
+ if (depth > 1500)
316
+ return;
317
+ for (const child of node.children) {
318
+ if (child.kind === "text") {
319
+ text += child.text;
320
+ }
321
+ else if (!skip.has(child.tag)) {
322
+ const block = BLOCK_TAGS.has(child.tag);
323
+ if (block)
324
+ text += " ";
325
+ visit(child, depth + 1);
326
+ if (block)
327
+ text += " ";
328
+ }
329
+ }
330
+ };
331
+ if (!skip.has(element.tag))
332
+ visit(element, 0);
333
+ return collapseWhitespace(text);
334
+ }
335
+ /** Whether an element shows anything: text, a link, an image, a form control or a video. */
336
+ export function hasContent(element) {
337
+ if (textOf(element) !== "")
338
+ return true;
339
+ return findFirst(element, (child) => ["a", "img", "picture", "video", "audio", "input", "select", "textarea", "iframe", "table"].includes(child.tag)) !== null;
340
+ }
341
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/html.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A small tolerant HTML reader for the rendered rules. The rules read server HTML that frameworks emit, so the reader\n * handles what that output holds (void and raw-text elements, comments, entities, `<svg>` self-closing tags, the odd\n * unclosed `<p>` or `<li>`) and never throws. It builds a tree with parent links and offers the few queries the rules\n * need; it does not run scripts or apply CSS.\n */\n\nexport interface HtmlElement {\n readonly kind: \"element\";\n /** Lower-case tag name; the root is \"#document\". */\n readonly tag: string;\n /** Lower-case attribute names to decoded values; a bare attribute is \"\". */\n readonly attrs: Readonly<Record<string, string>>;\n readonly children: HtmlNode[];\n parent: HtmlElement | null;\n /** Offset of the start tag in the source. */\n readonly start: number;\n}\n\nexport interface HtmlText {\n readonly kind: \"text\";\n /** Decoded text; script and style text stays raw. */\n readonly text: string;\n parent: HtmlElement | null;\n}\n\nexport type HtmlNode = HtmlElement | HtmlText;\n\nexport interface HtmlDocument {\n readonly root: HtmlElement;\n /** The first element carrying each id. */\n readonly byId: ReadonlyMap<string, HtmlElement>;\n}\n\nconst VOID_TAGS = new Set([\n \"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\", \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\",\n]);\nconst RAW_TEXT_TAGS = new Set([\"script\", \"style\", \"textarea\", \"title\"]);\n/** Start tags that end an open paragraph. */\nconst PARAGRAPH_CLOSERS = new Set([\n \"address\", \"article\", \"aside\", \"blockquote\", \"details\", \"div\", \"dl\", \"fieldset\", \"figcaption\", \"figure\", \"footer\",\n \"form\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"header\", \"hr\", \"main\", \"menu\", \"nav\", \"ol\", \"p\", \"pre\", \"section\",\n \"table\", \"ul\",\n]);\n\nconst NAMED_ENTITIES: Readonly<Record<string, string>> = {\n amp: \"&\", lt: \"<\", gt: \">\", quot: '\"', apos: \"'\", nbsp: \" \", ndash: \"–\", mdash: \"—\",\n lsquo: \"‘\", rsquo: \"’\", ldquo: \"“\", rdquo: \"”\", hellip: \"…\", middot: \"·\",\n copy: \"©\", reg: \"®\", trade: \"™\", deg: \"°\", pound: \"£\", euro: \"€\", cent: \"¢\",\n times: \"×\", bull: \"•\", laquo: \"«\", raquo: \"»\", rarr: \"→\", larr: \"←\",\n frac12: \"½\", frac14: \"¼\", frac34: \"¾\", shy: \"­\", ensp: \" \", emsp: \" \",\n thinsp: \" \", plusmn: \"±\", sup2: \"²\", sup3: \"³\", eacute: \"é\", egrave: \"è\",\n};\n\n/** Decode named and numeric character references; unknown references stay as written. */\nexport function decodeEntities(text: string): string {\n if (!text.includes(\"&\")) return text;\n return text.replace(/&(?:#(\\d{1,7})|#[xX]([0-9a-fA-F]{1,6})|([A-Za-z][A-Za-z0-9]{1,9}));/g, (whole, dec?: string, hex?: string, name?: string) => {\n if (name !== undefined) return NAMED_ENTITIES[name] ?? NAMED_ENTITIES[name.toLowerCase()] ?? whole;\n const code = dec !== undefined ? Number.parseInt(dec, 10) : Number.parseInt(hex!, 16);\n if (!Number.isFinite(code) || code <= 0 || code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) return whole;\n return String.fromCodePoint(code);\n });\n}\n\nfunction isSpace(code: number): boolean {\n return code === 32 || code === 9 || code === 10 || code === 12 || code === 13;\n}\n\nfunction createElement(tag: string, attrs: Record<string, string>, parent: HtmlElement | null, start: number): HtmlElement {\n return { kind: \"element\", tag, attrs, children: [], parent, start };\n}\n\nfunction readAttributes(html: string, from: number): { attrs: Record<string, string>; end: number; selfClosing: boolean } {\n const attrs: Record<string, string> = Object.create(null) as Record<string, string>;\n const length = html.length;\n let index = from;\n let selfClosing = false;\n while (index < length) {\n while (index < length && isSpace(html.charCodeAt(index))) index += 1;\n if (index >= length) break;\n const char = html[index]!;\n if (char === \">\") {\n index += 1;\n break;\n }\n if (char === \"/\") {\n if (html[index + 1] === \">\") {\n selfClosing = true;\n index += 2;\n break;\n }\n index += 1;\n continue;\n }\n const nameStart = index;\n while (index < length) {\n const code = html.charCodeAt(index);\n if (isSpace(code) || code === 34 || code === 39 || code === 60 || code === 62 || code === 47 || code === 61) break;\n index += 1;\n }\n if (index === nameStart) {\n index += 1;\n continue;\n }\n const name = html.slice(nameStart, index).toLowerCase();\n while (index < length && isSpace(html.charCodeAt(index))) index += 1;\n let value = \"\";\n if (html[index] === \"=\") {\n index += 1;\n while (index < length && isSpace(html.charCodeAt(index))) index += 1;\n const quote = html[index];\n if (quote === '\"' || quote === \"'\") {\n const close = html.indexOf(quote, index + 1);\n const end = close === -1 ? length : close;\n value = html.slice(index + 1, end);\n index = close === -1 ? length : close + 1;\n } else {\n const valueStart = index;\n while (index < length && !isSpace(html.charCodeAt(index)) && html[index] !== \">\") index += 1;\n value = html.slice(valueStart, index);\n }\n }\n if (!(name in attrs)) attrs[name] = decodeEntities(value);\n }\n return { attrs, end: index, selfClosing };\n}\n\nconst START_TAG = /<([A-Za-z][A-Za-z0-9:_.-]*)/y;\nconst END_TAG = /<\\/([A-Za-z][A-Za-z0-9:_.-]*)[^>]*>/y;\n\n/** Read HTML into a tree. Never throws; malformed input gives a best-effort tree. */\nexport function parseHtml(html: string): HtmlDocument {\n const root = createElement(\"#document\", Object.create(null) as Record<string, string>, null, 0);\n const byId = new Map<string, HtmlElement>();\n const length = html.length;\n let current = root;\n let index = 0;\n\n const appendText = (text: string, raw: boolean) => {\n if (text === \"\") return;\n current.children.push({ kind: \"text\", text: raw ? text : decodeEntities(text), parent: current });\n };\n const closeOpen = (names: readonly string[], boundaries: readonly string[]) => {\n for (let element: HtmlElement | null = current; element && element !== root; element = element.parent) {\n if (names.includes(element.tag)) {\n current = element.parent ?? root;\n return;\n }\n if (boundaries.includes(element.tag)) return;\n }\n };\n const closeImplied = (tag: string) => {\n switch (tag) {\n case \"li\": closeOpen([\"li\"], [\"ul\", \"ol\", \"menu\"]); break;\n case \"dt\":\n case \"dd\": closeOpen([\"dt\", \"dd\"], [\"dl\"]); break;\n case \"td\":\n case \"th\": closeOpen([\"td\", \"th\"], [\"tr\", \"table\"]); break;\n case \"tr\":\n closeOpen([\"td\", \"th\"], [\"tr\", \"table\"]);\n closeOpen([\"tr\"], [\"table\", \"thead\", \"tbody\", \"tfoot\"]);\n break;\n case \"thead\":\n case \"tbody\":\n case \"tfoot\": closeOpen([\"thead\", \"tbody\", \"tfoot\", \"tr\", \"td\", \"th\"], [\"table\"]); break;\n case \"option\": closeOpen([\"option\"], [\"select\", \"datalist\", \"optgroup\"]); break;\n case \"optgroup\": closeOpen([\"option\", \"optgroup\"], [\"select\"]); break;\n default: break;\n }\n if (current.tag === \"p\" && PARAGRAPH_CLOSERS.has(tag)) current = current.parent ?? root;\n };\n\n while (index < length) {\n const next = html.indexOf(\"<\", index);\n if (next === -1) {\n appendText(html.slice(index), false);\n break;\n }\n if (next > index) appendText(html.slice(index, next), false);\n index = next;\n\n if (html.startsWith(\"<!--\", index)) {\n const close = html.indexOf(\"-->\", index + 4);\n index = close === -1 ? length : close + 3;\n continue;\n }\n if (html.startsWith(\"<![CDATA[\", index)) {\n const close = html.indexOf(\"]]>\", index + 9);\n appendText(html.slice(index + 9, close === -1 ? length : close), true);\n index = close === -1 ? length : close + 3;\n continue;\n }\n const second = html[index + 1];\n if (second === \"!\" || second === \"?\") {\n const close = html.indexOf(\">\", index);\n index = close === -1 ? length : close + 1;\n continue;\n }\n if (second === \"/\") {\n END_TAG.lastIndex = index;\n const match = END_TAG.exec(html);\n if (!match) {\n appendText(\"<\", false);\n index += 1;\n continue;\n }\n const tag = match[1]!.toLowerCase();\n for (let element: HtmlElement | null = current; element && element !== root; element = element.parent) {\n if (element.tag === tag) {\n current = element.parent ?? root;\n break;\n }\n }\n index += match[0].length;\n continue;\n }\n\n START_TAG.lastIndex = index;\n const start = START_TAG.exec(html);\n if (!start) {\n appendText(\"<\", false);\n index += 1;\n continue;\n }\n const tag = start[1]!.toLowerCase();\n const { attrs, end, selfClosing } = readAttributes(html, index + start[0].length);\n closeImplied(tag);\n const element = createElement(tag, attrs, current, index);\n current.children.push(element);\n const id = attrs.id;\n if (id && !byId.has(id)) byId.set(id, element);\n index = end;\n if (VOID_TAGS.has(tag) || selfClosing) continue;\n if (RAW_TEXT_TAGS.has(tag)) {\n const closing = new RegExp(`</${tag}(?=[\\\\s/>])`, \"gi\");\n closing.lastIndex = index;\n const found = closing.exec(html);\n const rawEnd = found ? found.index : length;\n const raw = html.slice(index, rawEnd);\n if (raw !== \"\") element.children.push({ kind: \"text\", text: tag === \"script\" || tag === \"style\" ? raw : decodeEntities(raw), parent: element });\n if (found) {\n const close = html.indexOf(\">\", found.index);\n index = close === -1 ? length : close + 1;\n } else {\n index = length;\n }\n continue;\n }\n current = element;\n }\n return { root, byId };\n}\n\n// --- queries -----------------------------------------------------------------\n\n/** Every descendant element in document order. */\nexport function* descendants(root: HtmlElement): Generator<HtmlElement> {\n const stack: HtmlElement[] = [];\n const push = (parent: HtmlElement) => {\n for (let position = parent.children.length - 1; position >= 0; position -= 1) {\n const child = parent.children[position]!;\n if (child.kind === \"element\") stack.push(child);\n }\n };\n push(root);\n while (stack.length > 0) {\n const element = stack.pop()!;\n yield element;\n push(element);\n }\n}\n\nexport function findAll(root: HtmlElement, predicate: (element: HtmlElement) => boolean): HtmlElement[] {\n const found: HtmlElement[] = [];\n for (const element of descendants(root)) if (predicate(element)) found.push(element);\n return found;\n}\n\nexport function findFirst(root: HtmlElement, predicate: (element: HtmlElement) => boolean): HtmlElement | null {\n for (const element of descendants(root)) if (predicate(element)) return element;\n return null;\n}\n\n/** The element itself, then each ancestor up to (not including) the root. */\nexport function* selfAndAncestors(element: HtmlElement): Generator<HtmlElement> {\n for (let current: HtmlElement | null = element; current && current.tag !== \"#document\"; current = current.parent) yield current;\n}\n\nexport function closest(element: HtmlElement, predicate: (element: HtmlElement) => boolean): HtmlElement | null {\n for (const candidate of selfAndAncestors(element)) if (predicate(candidate)) return candidate;\n return null;\n}\n\n/** Whether `element` is `container` or inside it. */\nexport function isWithin(element: HtmlElement, container: HtmlElement): boolean {\n for (const candidate of selfAndAncestors(element)) if (candidate === container) return true;\n return false;\n}\n\nexport function classTokens(element: HtmlElement): string[] {\n const value = element.attrs.class;\n return value ? value.split(/\\s+/).filter(Boolean) : [];\n}\n\nexport function collapseWhitespace(text: string): string {\n return text.replace(/[\\s ]+/g, \" \").trim();\n}\n\n/** Tags whose content is never visible text. */\nexport const NON_TEXT_TAGS: ReadonlySet<string> = new Set([\"script\", \"style\", \"template\", \"svg\", \"noscript\", \"head\", \"iframe\", \"object\"]);\n\n/** Elements that start a new line of text: a block boundary separates the words either side of it. */\nexport const BLOCK_TAGS: ReadonlySet<string> = new Set([\n \"address\", \"article\", \"aside\", \"blockquote\", \"body\", \"br\", \"button\", \"caption\", \"dd\", \"details\", \"dialog\", \"div\",\n \"dl\", \"dt\", \"fieldset\", \"figcaption\", \"figure\", \"footer\", \"form\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"header\",\n \"hr\", \"html\", \"label\", \"legend\", \"li\", \"main\", \"nav\", \"ol\", \"option\", \"p\", \"pre\", \"section\", \"select\", \"summary\",\n \"table\", \"tbody\", \"td\", \"textarea\", \"tfoot\", \"th\", \"thead\", \"tr\", \"ul\",\n]);\n\n/** The text of an element as a browser would show it, whitespace collapsed and blocks separated by a space. */\nexport function textOf(element: HtmlElement, skip: ReadonlySet<string> = NON_TEXT_TAGS): string {\n let text = \"\";\n const visit = (node: HtmlElement, depth: number) => {\n if (depth > 1500) return;\n for (const child of node.children) {\n if (child.kind === \"text\") {\n text += child.text;\n } else if (!skip.has(child.tag)) {\n const block = BLOCK_TAGS.has(child.tag);\n if (block) text += \" \";\n visit(child, depth + 1);\n if (block) text += \" \";\n }\n }\n };\n if (!skip.has(element.tag)) visit(element, 0);\n return collapseWhitespace(text);\n}\n\n/** Whether an element shows anything: text, a link, an image, a form control or a video. */\nexport function hasContent(element: HtmlElement): boolean {\n if (textOf(element) !== \"\") return true;\n return findFirst(element, (child) => [\"a\", \"img\", \"picture\", \"video\", \"audio\", \"input\", \"select\", \"textarea\", \"iframe\", \"table\"].includes(child.tag)) !== null;\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import type { SiteSystemFinding } from "../../checks.js";
2
+ import { type ShellRenderedInput, type ShellRuleOptions } from "./types.js";
3
+ export type { ShellAssetReader, ShellFinding, ShellRenderedInput, ShellRenderedRunner, ShellRuleOptions, ShellProjectFacts } from "./types.js";
4
+ export { loadShellProjectFacts } from "./facts.js";
5
+ /** The shell-tier rendered rules this module runs, in the order their findings are returned. */
6
+ export declare const SHELL_RENDERED_CHECK_IDS: readonly ["N2", "K7", "C5", "C9", "C10", "E2", "E8", "T3", "T4", "B1", "L10-hero"];
7
+ /**
8
+ * Run the shell-tier rendered rules over the sampled pages' server HTML: N2, K7, C5, C9, C10, E2, E8, T3, T4, B1 and
9
+ * L10-hero. A check returns a fail per problem found (with the page path), otherwise one pass, or not-applicable with
10
+ * the reason when it genuinely does not apply. When an input it needs exists but cannot be read (T3's licences,
11
+ * C10's business phone) it returns no finding, so the gate shows it as not run instead of passing it. The business
12
+ * facts (phone, licences) are read from the project's `src/data` unless `options.facts` supplies them. The decisions
13
+ * the project records (quote labels, free quotes, third-party hosts, extra phone numbers) come from the gate's parsed
14
+ * `siteSystem` settings (`input.siteSystem`, else brandlab.config.ts); `options` overrides them for a test. Recorded
15
+ * claims have no config home yet and come from `options` only.
16
+ */
17
+ export declare function runShellRenderedRules(input: ShellRenderedInput, options?: ShellRuleOptions): Promise<readonly SiteSystemFinding[]>;
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAa5E,OAAO,EAA8D,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAExI,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/I,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,gGAAgG;AAChG,eAAO,MAAM,wBAAwB,oFAA4H,CAAC;AAElK;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAsCvC"}
@@ -0,0 +1,65 @@
1
+ import { checkBannedWording } from "./banned-wording.js";
2
+ import { checkLabBudgets } from "./budgets.js";
3
+ import { checkCtaLabels } from "./cta-labels.js";
4
+ import { loadShellProjectFacts, loadSiteSystemSettings, siteSystemRecordsFrom } from "./facts.js";
5
+ import { checkSingleH1 } from "./headings.js";
6
+ import { checkHeroLocationCopy } from "./hero-location.js";
7
+ import { checkLicenceLine } from "./licence.js";
8
+ import { checkNavigationCrawlability } from "./navigation.js";
9
+ import { checkNoInterstitial } from "./overlays.js";
10
+ import { prepareShellPage } from "./page.js";
11
+ import { checkTelLinks } from "./phone.js";
12
+ import { checkProofNumbers } from "./proof-numbers.js";
13
+ import { notApplicableFinding } from "./types.js";
14
+ export { loadShellProjectFacts } from "./facts.js";
15
+ /** The shell-tier rendered rules this module runs, in the order their findings are returned. */
16
+ export const SHELL_RENDERED_CHECK_IDS = ["N2", "K7", "C5", "C9", "C10", "E2", "E8", "T3", "T4", "B1", "L10-hero"];
17
+ /**
18
+ * Run the shell-tier rendered rules over the sampled pages' server HTML: N2, K7, C5, C9, C10, E2, E8, T3, T4, B1 and
19
+ * L10-hero. A check returns a fail per problem found (with the page path), otherwise one pass, or not-applicable with
20
+ * the reason when it genuinely does not apply. When an input it needs exists but cannot be read (T3's licences,
21
+ * C10's business phone) it returns no finding, so the gate shows it as not run instead of passing it. The business
22
+ * facts (phone, licences) are read from the project's `src/data` unless `options.facts` supplies them. The decisions
23
+ * the project records (quote labels, free quotes, third-party hosts, extra phone numbers) come from the gate's parsed
24
+ * `siteSystem` settings (`input.siteSystem`, else brandlab.config.ts); `options` overrides them for a test. Recorded
25
+ * claims have no config home yet and come from `options` only.
26
+ */
27
+ export async function runShellRenderedRules(input, options = {}) {
28
+ if (input.pages.length === 0) {
29
+ return SHELL_RENDERED_CHECK_IDS.map((checkId) => notApplicableFinding(checkId, "No pages were sampled, so there is nothing to check."));
30
+ }
31
+ const pages = input.pages.map(prepareShellPage);
32
+ const loaded = options.facts === undefined ? loadShellProjectFacts(input.projectRoot) : { facts: options.facts, reason: options.facts ? "" : "no business facts were supplied" };
33
+ // The project records its decisions under `siteSystem` in brandlab.config.ts; `options` can override them in a test.
34
+ const records = siteSystemRecordsFrom(input.siteSystem !== undefined ? input.siteSystem : loadSiteSystemSettings(input.projectRoot));
35
+ const labels = options.labels ?? records.labels;
36
+ const freeQuotes = options.freeQuotes ?? records.freeQuotes;
37
+ const thirdParties = options.recordedThirdParties ?? records.thirdParties;
38
+ const phones = options.recordedPhones ?? records.phones;
39
+ const findings = [
40
+ ...checkNavigationCrawlability(pages),
41
+ ...checkCtaLabels(pages, {
42
+ ...(labels ? { labels } : {}),
43
+ ...(freeQuotes !== undefined ? { freeQuotes } : {}),
44
+ }),
45
+ ...checkNoInterstitial(pages),
46
+ ...checkTelLinks(pages, loaded.facts, loaded.reason, phones),
47
+ ...checkSingleH1(pages),
48
+ ...checkBannedWording(pages, loaded.facts, options.recordedClaims),
49
+ ...checkLicenceLine(pages, loaded.facts, loaded.reason, options.licenceExemptPaths, options.asOf),
50
+ ...checkProofNumbers(pages),
51
+ ...(await checkLabBudgets(pages, {
52
+ baseUrl: input.baseUrl,
53
+ projectRoot: input.projectRoot,
54
+ ...(thirdParties ? { recordedThirdParties: thirdParties } : {}),
55
+ ...(options.readAsset ? { readAsset: options.readAsset } : {}),
56
+ })),
57
+ ...checkHeroLocationCopy(pages),
58
+ ];
59
+ const order = new Map(SHELL_RENDERED_CHECK_IDS.map((id, position) => [id, position]));
60
+ return findings
61
+ .map((finding, position) => ({ finding, position }))
62
+ .sort((left, right) => (order.get(left.finding.checkId) ?? 0) - (order.get(right.finding.checkId) ?? 0) || left.position - right.position)
63
+ .map(({ finding }) => finding);
64
+ }
65
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SiteSystemCheckId, SiteSystemFinding } from \"../../checks.js\";\nimport { checkBannedWording } from \"./banned-wording.js\";\nimport { checkLabBudgets } from \"./budgets.js\";\nimport { checkCtaLabels } from \"./cta-labels.js\";\nimport { loadShellProjectFacts, loadSiteSystemSettings, siteSystemRecordsFrom } from \"./facts.js\";\nimport { checkSingleH1 } from \"./headings.js\";\nimport { checkHeroLocationCopy } from \"./hero-location.js\";\nimport { checkLicenceLine } from \"./licence.js\";\nimport { checkNavigationCrawlability } from \"./navigation.js\";\nimport { checkNoInterstitial } from \"./overlays.js\";\nimport { prepareShellPage } from \"./page.js\";\nimport { checkTelLinks } from \"./phone.js\";\nimport { checkProofNumbers } from \"./proof-numbers.js\";\nimport { notApplicableFinding, type ShellCheckId, type ShellFinding, type ShellRenderedInput, type ShellRuleOptions } from \"./types.js\";\n\nexport type { ShellAssetReader, ShellFinding, ShellRenderedInput, ShellRenderedRunner, ShellRuleOptions, ShellProjectFacts } from \"./types.js\";\nexport { loadShellProjectFacts } from \"./facts.js\";\n\n/** The shell-tier rendered rules this module runs, in the order their findings are returned. */\nexport const SHELL_RENDERED_CHECK_IDS = [\"N2\", \"K7\", \"C5\", \"C9\", \"C10\", \"E2\", \"E8\", \"T3\", \"T4\", \"B1\", \"L10-hero\"] as const satisfies readonly SiteSystemCheckId[];\n\n/**\n * Run the shell-tier rendered rules over the sampled pages' server HTML: N2, K7, C5, C9, C10, E2, E8, T3, T4, B1 and\n * L10-hero. A check returns a fail per problem found (with the page path), otherwise one pass, or not-applicable with\n * the reason when it genuinely does not apply. When an input it needs exists but cannot be read (T3's licences,\n * C10's business phone) it returns no finding, so the gate shows it as not run instead of passing it. The business\n * facts (phone, licences) are read from the project's `src/data` unless `options.facts` supplies them. The decisions\n * the project records (quote labels, free quotes, third-party hosts, extra phone numbers) come from the gate's parsed\n * `siteSystem` settings (`input.siteSystem`, else brandlab.config.ts); `options` overrides them for a test. Recorded\n * claims have no config home yet and come from `options` only.\n */\nexport async function runShellRenderedRules(\n input: ShellRenderedInput,\n options: ShellRuleOptions = {},\n): Promise<readonly SiteSystemFinding[]> {\n if (input.pages.length === 0) {\n return SHELL_RENDERED_CHECK_IDS.map((checkId) => notApplicableFinding(checkId, \"No pages were sampled, so there is nothing to check.\"));\n }\n const pages = input.pages.map(prepareShellPage);\n const loaded = options.facts === undefined ? loadShellProjectFacts(input.projectRoot) : { facts: options.facts, reason: options.facts ? \"\" : \"no business facts were supplied\" };\n // The project records its decisions under `siteSystem` in brandlab.config.ts; `options` can override them in a test.\n const records = siteSystemRecordsFrom(input.siteSystem !== undefined ? input.siteSystem : loadSiteSystemSettings(input.projectRoot));\n const labels = options.labels ?? records.labels;\n const freeQuotes = options.freeQuotes ?? records.freeQuotes;\n const thirdParties = options.recordedThirdParties ?? records.thirdParties;\n const phones = options.recordedPhones ?? records.phones;\n\n const findings: ShellFinding[] = [\n ...checkNavigationCrawlability(pages),\n ...checkCtaLabels(pages, {\n ...(labels ? { labels } : {}),\n ...(freeQuotes !== undefined ? { freeQuotes } : {}),\n }),\n ...checkNoInterstitial(pages),\n ...checkTelLinks(pages, loaded.facts, loaded.reason, phones),\n ...checkSingleH1(pages),\n ...checkBannedWording(pages, loaded.facts, options.recordedClaims),\n ...checkLicenceLine(pages, loaded.facts, loaded.reason, options.licenceExemptPaths, options.asOf),\n ...checkProofNumbers(pages),\n ...(await checkLabBudgets(pages, {\n baseUrl: input.baseUrl,\n projectRoot: input.projectRoot,\n ...(thirdParties ? { recordedThirdParties: thirdParties } : {}),\n ...(options.readAsset ? { readAsset: options.readAsset } : {}),\n })),\n ...checkHeroLocationCopy(pages),\n ];\n const order = new Map<ShellCheckId, number>(SHELL_RENDERED_CHECK_IDS.map((id, position) => [id, position]));\n return findings\n .map((finding, position) => ({ finding, position }))\n .sort((left, right) => (order.get(left.finding.checkId as ShellCheckId) ?? 0) - (order.get(right.finding.checkId as ShellCheckId) ?? 0) || left.position - right.position)\n .map(({ finding }) => finding);\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { ShellPage } from "./page.js";
2
+ import { type ShellFinding, type ShellProjectFacts } from "./types.js";
3
+ export declare function checkLicenceLine(pages: readonly ShellPage[], facts: ShellProjectFacts | null, _factsReason: string, exemptPaths?: readonly string[], asOf?: Date): ShellFinding[];
4
+ //# sourceMappingURL=licence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"licence.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/licence.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAkD,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAmBvH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,KAAK,EAAE,iBAAiB,GAAG,IAAI,EAC/B,YAAY,EAAE,MAAM,EACpB,WAAW,GAAE,SAAS,MAAM,EAAO,EACnC,IAAI,GAAE,IAAiB,GACtB,YAAY,EAAE,CAqChB"}