html-validate 8.8.0 → 8.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/core.js CHANGED
@@ -601,6 +601,18 @@ const patternProperties = {
601
601
  description: "Script-supporting elements are elements which can be inserted where othersise not permitted to assist in templating",
602
602
  type: "boolean"
603
603
  },
604
+ focusable: {
605
+ title: "Mark this element as focusable",
606
+ description: "This element may contain an associated label element.",
607
+ anyOf: [
608
+ {
609
+ type: "boolean"
610
+ },
611
+ {
612
+ "function": true
613
+ }
614
+ ]
615
+ },
604
616
  form: {
605
617
  title: "Mark element as a submittable form element",
606
618
  type: "boolean"
@@ -975,6 +987,7 @@ const MetaCopyableProperty = [
975
987
  "embedded",
976
988
  "interactive",
977
989
  "transparent",
990
+ "focusable",
978
991
  "form",
979
992
  "formAssociated",
980
993
  "labelable",
@@ -1047,6 +1060,7 @@ function migrateElement(src) {
1047
1060
  },
1048
1061
  attributes: migrateAttributes(src),
1049
1062
  textContent: src.textContent,
1063
+ focusable: src.focusable ?? false,
1050
1064
  implicitRole: src.implicitRole ?? (() => null)
1051
1065
  };
1052
1066
  delete result.deprecatedAttributes;
@@ -1302,6 +1316,9 @@ function expandProperties(node, entry) {
1302
1316
  setMetaProperty(entry, key, evaluateProperty(node, property));
1303
1317
  }
1304
1318
  }
1319
+ if (typeof entry.focusable === "function") {
1320
+ setMetaProperty(entry, "focusable", entry.focusable(node._adapter));
1321
+ }
1305
1322
  }
1306
1323
  function expandRegexValue(value) {
1307
1324
  if (value instanceof RegExp) {
@@ -1516,10 +1533,10 @@ class Context {
1516
1533
  constructor(source) {
1517
1534
  this.state = State.INITIAL;
1518
1535
  this.string = source.data;
1519
- this.filename = source.filename ?? "";
1520
- this.offset = source.offset ?? 0;
1521
- this.line = source.line ?? 1;
1522
- this.column = source.column ?? 1;
1536
+ this.filename = source.filename;
1537
+ this.offset = source.offset;
1538
+ this.line = source.line;
1539
+ this.column = source.column;
1523
1540
  this.contentModel = 1 /* TEXT */;
1524
1541
  }
1525
1542
  getTruncatedLine(n = 13) {
@@ -1552,6 +1569,16 @@ class Context {
1552
1569
  }
1553
1570
  }
1554
1571
 
1572
+ function normalizeSource(source) {
1573
+ return {
1574
+ filename: "",
1575
+ offset: 0,
1576
+ line: 1,
1577
+ column: 1,
1578
+ ...source
1579
+ };
1580
+ }
1581
+
1555
1582
  var NodeType = /* @__PURE__ */ ((NodeType2) => {
1556
1583
  NodeType2[NodeType2["ELEMENT_NODE"] = 1] = "ELEMENT_NODE";
1557
1584
  NodeType2[NodeType2["TEXT_NODE"] = 3] = "TEXT_NODE";
@@ -3145,8 +3172,21 @@ function isKeywordIgnored(options, keyword, matcher = (list, it) => list.include
3145
3172
  const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
3146
3173
  const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
3147
3174
  const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
3175
+ const STYLE_HIDDEN_CACHE = Symbol(isStyleHidden.name);
3148
3176
  function inAccessibilityTree(node) {
3149
- return !isAriaHidden(node) && !isPresentation(node);
3177
+ if (isAriaHidden(node)) {
3178
+ return false;
3179
+ }
3180
+ if (isPresentation(node)) {
3181
+ return false;
3182
+ }
3183
+ if (isHTMLHidden(node)) {
3184
+ return false;
3185
+ }
3186
+ if (isStyleHidden(node)) {
3187
+ return false;
3188
+ }
3189
+ return true;
3150
3190
  }
3151
3191
  function isAriaHiddenImpl(node) {
3152
3192
  const isHidden = (node2) => {
@@ -3184,22 +3224,41 @@ function isHTMLHidden(node, details) {
3184
3224
  const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
3185
3225
  return details ? result : result.byParent || result.bySelf;
3186
3226
  }
3227
+ function isStyleHiddenImpl(node) {
3228
+ const isHidden = (node2) => {
3229
+ const style = node2.getAttribute("style");
3230
+ const { display, visibility } = parseCssDeclaration(style == null ? void 0 : style.value);
3231
+ return display === "none" || visibility === "hidden";
3232
+ };
3233
+ const byParent = node.parent ? isStyleHidden(node.parent) : false;
3234
+ const bySelf = isHidden(node);
3235
+ return byParent || bySelf;
3236
+ }
3237
+ function isStyleHidden(node) {
3238
+ const cached = node.cacheGet(STYLE_HIDDEN_CACHE);
3239
+ if (cached) {
3240
+ return cached;
3241
+ }
3242
+ return node.cacheSet(STYLE_HIDDEN_CACHE, isStyleHiddenImpl(node));
3243
+ }
3187
3244
  function isPresentation(node) {
3188
3245
  if (node.cacheExists(ROLE_PRESENTATION_CACHE)) {
3189
3246
  return Boolean(node.cacheGet(ROLE_PRESENTATION_CACHE));
3190
3247
  }
3191
- let cur = node;
3192
- do {
3193
- const role = cur.getAttribute("role");
3194
- if (role && role.value === "presentation") {
3195
- return cur.cacheSet(ROLE_PRESENTATION_CACHE, true);
3196
- }
3197
- if (!cur.parent) {
3198
- break;
3199
- }
3200
- cur = cur.parent;
3201
- } while (!cur.isRootElement());
3202
- return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
3248
+ const meta = node.meta;
3249
+ if (meta && meta.interactive) {
3250
+ return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
3251
+ }
3252
+ const tabindex = node.getAttribute("tabindex");
3253
+ if (tabindex) {
3254
+ return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
3255
+ }
3256
+ const role = node.getAttribute("role");
3257
+ if (role && (role.value === "presentation" || role.value === "none")) {
3258
+ return node.cacheSet(ROLE_PRESENTATION_CACHE, true);
3259
+ } else {
3260
+ return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
3261
+ }
3203
3262
  }
3204
3263
 
3205
3264
  const cachePrefix = classifyNodeText.name;
@@ -6187,6 +6246,58 @@ class HeadingLevel extends Rule {
6187
6246
  }
6188
6247
  }
6189
6248
 
6249
+ class HiddenFocusable extends Rule {
6250
+ documentation(context) {
6251
+ const byParent = context === "parent" ? " In this case it is being hidden by an ancestor with `aria-hidden.`" : "";
6252
+ return {
6253
+ description: [
6254
+ `\`aria-hidden\` cannot be used on focusable elements.${byParent}`,
6255
+ "",
6256
+ "When focusable elements are hidden with `aria-hidden` they are still reachable using conventional means such as a mouse or keyboard but won't be exposed to assistive technology (AT).",
6257
+ "This is often confusing for users of AT such as screenreaders.",
6258
+ "",
6259
+ "To fix this either:",
6260
+ " - Remove `aria-hidden`.",
6261
+ " - Remove the element from the DOM instead.",
6262
+ " - Use the `hidden` attribute or similar means to hide the element."
6263
+ ].join("\n"),
6264
+ url: "https://html-validate.org/rules/hidden-focusable.html"
6265
+ };
6266
+ }
6267
+ setup() {
6268
+ const focusable = this.getTagsWithProperty("focusable");
6269
+ const selector = ["[tabindex]", ...focusable].join(",");
6270
+ this.on("dom:ready", (event) => {
6271
+ const { document } = event;
6272
+ for (const element of document.querySelectorAll(selector)) {
6273
+ if (isHTMLHidden(element) || isStyleHidden(element)) {
6274
+ continue;
6275
+ }
6276
+ if (isAriaHidden(element)) {
6277
+ this.validateElement(element);
6278
+ }
6279
+ }
6280
+ });
6281
+ }
6282
+ validateElement(element) {
6283
+ const { meta } = element;
6284
+ const tabindex = element.getAttribute("tabindex");
6285
+ if (meta && !meta.focusable && !tabindex) {
6286
+ return;
6287
+ }
6288
+ const attribute = element.getAttribute("aria-hidden");
6289
+ const message = attribute ? `aria-hidden cannot be used on focusable elements` : `aria-hidden cannot be used on focusable elements (hidden by ancestor element)`;
6290
+ const location = attribute ? attribute.keyLocation : element.location;
6291
+ const context = attribute ? "self" : "parent";
6292
+ this.report({
6293
+ node: element,
6294
+ message,
6295
+ location,
6296
+ context
6297
+ });
6298
+ }
6299
+ }
6300
+
6190
6301
  const defaults$g = {
6191
6302
  pattern: "kebabcase"
6192
6303
  };
@@ -6372,7 +6483,7 @@ function isHidden(node, context) {
6372
6483
  if (reference && reference.isSameNode(node)) {
6373
6484
  return false;
6374
6485
  } else {
6375
- return isHTMLHidden(node) || !inAccessibilityTree(node);
6486
+ return !inAccessibilityTree(node);
6376
6487
  }
6377
6488
  }
6378
6489
  function hasImgAltText(node, context) {
@@ -7949,7 +8060,7 @@ class RequireSri extends Rule {
7949
8060
  }
7950
8061
  documentation() {
7951
8062
  return {
7952
- description: `Subresource Integrity (SRI) \`integrity\` attribute is required to prevent manipulation from Content Delivery Networks or other third-party hosting.`,
8063
+ description: `Subresource Integrity (SRI) \`integrity\` attribute is required to prevent tampering or manipulation from Content Delivery Networks (CDN), rouge proxies, malicious entities, etc.`,
7953
8064
  url: "https://html-validate.org/rules/require-sri.html"
7954
8065
  };
7955
8066
  }
@@ -8362,6 +8473,107 @@ class TextContent extends Rule {
8362
8473
  }
8363
8474
  }
8364
8475
 
8476
+ function getTextFromReference(document, id) {
8477
+ if (!id || id instanceof DynamicValue) {
8478
+ return id;
8479
+ }
8480
+ const selector = `#${id}`;
8481
+ const ref = document.querySelector(selector);
8482
+ if (ref) {
8483
+ return ref.textContent;
8484
+ } else {
8485
+ return selector;
8486
+ }
8487
+ }
8488
+ function getTextEntryFromElement(document, node) {
8489
+ const ariaLabel = node.getAttribute("aria-label");
8490
+ if (ariaLabel) {
8491
+ return {
8492
+ node,
8493
+ text: ariaLabel.value,
8494
+ location: ariaLabel.keyLocation
8495
+ };
8496
+ }
8497
+ const ariaLabelledby = node.getAttribute("aria-labelledby");
8498
+ if (ariaLabelledby) {
8499
+ const text = getTextFromReference(document, ariaLabelledby.value);
8500
+ return {
8501
+ node,
8502
+ text,
8503
+ location: ariaLabelledby.keyLocation
8504
+ };
8505
+ }
8506
+ return {
8507
+ node,
8508
+ text: null,
8509
+ location: node.location
8510
+ };
8511
+ }
8512
+ class UniqueLandmark extends Rule {
8513
+ documentation() {
8514
+ return {
8515
+ description: [
8516
+ "When the same type of landmark is present more than once in the same document each must be uniquely identifiable with a non-empty and unique name.",
8517
+ "For instance, if the document has two `<nav>` elements each of them need an accessible name to be distinguished from each other.",
8518
+ "",
8519
+ "The following elements / roles are considered landmarks:",
8520
+ "",
8521
+ ' - `aside` or `[role="complementary"]`',
8522
+ ' - `footer` or `[role="contentinfo"]`',
8523
+ ' - `form` or `[role="form"]`',
8524
+ ' - `header` or `[role="banner"]`',
8525
+ ' - `main` or `[role="main"]`',
8526
+ ' - `nav` or `[role="navigation"]`',
8527
+ ' - `section` or `[role="region"]`',
8528
+ "",
8529
+ "To fix this either:",
8530
+ "",
8531
+ " - Add `aria-label`.",
8532
+ " - Add `aria-labelledby`.",
8533
+ " - Remove one of the landmarks."
8534
+ ].join("\n"),
8535
+ url: "https://html-validate.org/rules/unique-landmark.html"
8536
+ };
8537
+ }
8538
+ setup() {
8539
+ const selectors = [
8540
+ 'aside, [role="complementary"]',
8541
+ 'footer, [role="contentinfo"]',
8542
+ 'form, [role="form"]',
8543
+ 'header, [role="banner"]',
8544
+ 'main, [role="main"]',
8545
+ 'nav, [role="navigation"]',
8546
+ 'section, [role="region"]'
8547
+ /* <search> does not (yet?) require a unique name */
8548
+ ];
8549
+ this.on("dom:ready", (event) => {
8550
+ const { document } = event;
8551
+ for (const selector of selectors) {
8552
+ const nodes = document.querySelectorAll(selector);
8553
+ if (nodes.length <= 1) {
8554
+ continue;
8555
+ }
8556
+ const entries = nodes.map((it) => getTextEntryFromElement(document, it));
8557
+ for (const entry of entries) {
8558
+ if (entry.text instanceof DynamicValue) {
8559
+ continue;
8560
+ }
8561
+ const dup = entries.filter((it) => it.text === entry.text).length > 1;
8562
+ if (!entry.text || dup) {
8563
+ const message = `Landmarks must have a non-empty and unique accessible name (aria-label or aria-labelledby)`;
8564
+ const location = entry.location;
8565
+ this.report({
8566
+ node: entry.node,
8567
+ message,
8568
+ location
8569
+ });
8570
+ }
8571
+ }
8572
+ }
8573
+ });
8574
+ }
8575
+ }
8576
+
8365
8577
  const defaults$4 = {
8366
8578
  ignoreCase: false,
8367
8579
  requireSemicolon: true
@@ -8751,7 +8963,7 @@ class H32 extends Rule {
8751
8963
  }
8752
8964
  function isSubmit(node) {
8753
8965
  const type = node.getAttribute("type");
8754
- return Boolean(type && type.valueMatches(/submit|image/));
8966
+ return Boolean(!type || type.valueMatches(/submit|image/));
8755
8967
  }
8756
8968
  function isAssociated(id, node) {
8757
8969
  const form = node.getAttribute("form");
@@ -8843,29 +9055,35 @@ class H37 extends Rule {
8843
9055
  };
8844
9056
  }
8845
9057
  setup() {
8846
- this.on("tag:end", (event) => {
8847
- const node = event.previous;
8848
- if (!needsAlt(node)) {
8849
- return;
8850
- }
8851
- if (!inAccessibilityTree(node)) {
8852
- return;
9058
+ this.on("dom:ready", (event) => {
9059
+ const { document } = event;
9060
+ const nodes = document.querySelectorAll("img, input");
9061
+ for (const node of nodes) {
9062
+ this.validateNode(node);
8853
9063
  }
8854
- if (Boolean(node.getAttributeValue("alt")) || Boolean(node.hasAttribute("alt") && this.options.allowEmpty)) {
9064
+ });
9065
+ }
9066
+ validateNode(node) {
9067
+ if (!needsAlt(node)) {
9068
+ return;
9069
+ }
9070
+ if (!inAccessibilityTree(node)) {
9071
+ return;
9072
+ }
9073
+ if (Boolean(node.getAttributeValue("alt")) || Boolean(node.hasAttribute("alt") && this.options.allowEmpty)) {
9074
+ return;
9075
+ }
9076
+ for (const attr of this.options.alias) {
9077
+ if (node.getAttribute(attr)) {
8855
9078
  return;
8856
9079
  }
8857
- for (const attr of this.options.alias) {
8858
- if (node.getAttribute(attr)) {
8859
- return;
8860
- }
8861
- }
8862
- if (node.hasAttribute("alt")) {
8863
- const attr = node.getAttribute("alt");
8864
- this.report(node, `${getTag(node)} cannot have empty "alt" attribute`, attr == null ? void 0 : attr.keyLocation);
8865
- } else {
8866
- this.report(node, `${getTag(node)} is missing required "alt" attribute`, node.location);
8867
- }
8868
- });
9080
+ }
9081
+ if (node.hasAttribute("alt")) {
9082
+ const attr = node.getAttribute("alt");
9083
+ this.report(node, `${getTag(node)} cannot have empty "alt" attribute`, attr == null ? void 0 : attr.keyLocation);
9084
+ } else {
9085
+ this.report(node, `${getTag(node)} is missing required "alt" attribute`, node.location);
9086
+ }
8869
9087
  }
8870
9088
  }
8871
9089
 
@@ -8956,7 +9174,7 @@ class H71 extends Rule {
8956
9174
  }
8957
9175
  }
8958
9176
 
8959
- const bundledRules$2 = {
9177
+ const bundledRules$1 = {
8960
9178
  "wcag/h30": H30,
8961
9179
  "wcag/h32": H32,
8962
9180
  "wcag/h36": H36,
@@ -8965,7 +9183,7 @@ const bundledRules$2 = {
8965
9183
  "wcag/h67": H67,
8966
9184
  "wcag/h71": H71
8967
9185
  };
8968
- var WCAG = bundledRules$2;
9186
+ var WCAG = bundledRules$1;
8969
9187
 
8970
9188
  const bundledRules = {
8971
9189
  "allowed-links": AllowedLinks,
@@ -9001,6 +9219,7 @@ const bundledRules = {
9001
9219
  "empty-title": EmptyTitle,
9002
9220
  "form-dup-name": FormDupName,
9003
9221
  "heading-level": HeadingLevel,
9222
+ "hidden-focusable": HiddenFocusable,
9004
9223
  "id-pattern": IdPattern,
9005
9224
  "input-attributes": InputAttributes,
9006
9225
  "input-missing-label": InputMissingLabel,
@@ -9041,13 +9260,13 @@ const bundledRules = {
9041
9260
  "svg-focusable": SvgFocusable,
9042
9261
  "tel-non-breaking": TelNonBreaking,
9043
9262
  "text-content": TextContent,
9263
+ "unique-landmark": UniqueLandmark,
9044
9264
  "unrecognized-char-ref": UnknownCharReference,
9045
9265
  "valid-id": ValidID,
9046
9266
  "void-content": VoidContent,
9047
9267
  "void-style": VoidStyle,
9048
9268
  ...WCAG
9049
9269
  };
9050
- var bundledRules$1 = bundledRules;
9051
9270
 
9052
9271
  var defaultConfig = {};
9053
9272
 
@@ -9059,6 +9278,7 @@ const config$4 = {
9059
9278
  "deprecated-rule": "warn",
9060
9279
  "empty-heading": "error",
9061
9280
  "empty-title": "error",
9281
+ "hidden-focusable": "error",
9062
9282
  "meta-refresh": "error",
9063
9283
  "multiple-labeled-controls": "error",
9064
9284
  "no-autoplay": ["error", { include: ["audio", "video"] }],
@@ -9070,6 +9290,7 @@ const config$4 = {
9070
9290
  "prefer-native-element": "error",
9071
9291
  "svg-focusable": "off",
9072
9292
  "text-content": "error",
9293
+ "unique-landmark": "error",
9073
9294
  "wcag/h30": "error",
9074
9295
  "wcag/h32": "error",
9075
9296
  "wcag/h36": "error",
@@ -9132,6 +9353,7 @@ const config$1 = {
9132
9353
  "empty-heading": "error",
9133
9354
  "empty-title": "error",
9134
9355
  "form-dup-name": "error",
9356
+ "hidden-focusable": "error",
9135
9357
  "input-attributes": "error",
9136
9358
  "long-title": "error",
9137
9359
  "map-dup-name": "error",
@@ -9164,6 +9386,7 @@ const config$1 = {
9164
9386
  "svg-focusable": "off",
9165
9387
  "tel-non-breaking": "error",
9166
9388
  "text-content": "error",
9389
+ "unique-landmark": "error",
9167
9390
  "unrecognized-char-ref": "error",
9168
9391
  "valid-id": ["error", { relaxed: false }],
9169
9392
  void: "off",
@@ -9508,7 +9731,7 @@ class Config {
9508
9731
  if (configData.rules) {
9509
9732
  const normalizedRules = Config.getRulesObject(configData.rules);
9510
9733
  for (const [ruleId, [, ruleOptions]] of normalizedRules.entries()) {
9511
- const cls = bundledRules$1[ruleId];
9734
+ const cls = bundledRules[ruleId];
9512
9735
  const path = `/rules/${ruleId}/1`;
9513
9736
  Rule.validateOptions(cls, ruleId, path, ruleOptions, filename, configData);
9514
9737
  }
@@ -9881,16 +10104,17 @@ class EventHandler {
9881
10104
  * @returns Unregistration function.
9882
10105
  */
9883
10106
  on(event, callback) {
9884
- const names = event.split(",").map((x) => x.trim());
10107
+ const { listeners } = this;
10108
+ const names = event.split(",").map((it) => it.trim());
9885
10109
  for (const name of names) {
9886
- this.listeners[name] = this.listeners[name] || [];
9887
- this.listeners[name].push(callback);
10110
+ const list = listeners[name] ?? [];
10111
+ listeners[name] = list;
10112
+ list.push(callback);
9888
10113
  }
9889
10114
  return () => {
9890
10115
  for (const name of names) {
9891
- this.listeners[name] = this.listeners[name].filter((fn) => {
9892
- return fn !== callback;
9893
- });
10116
+ const list = listeners[name];
10117
+ this.listeners[name] = list.filter((fn) => fn !== callback);
9894
10118
  }
9895
10119
  };
9896
10120
  }
@@ -9916,10 +10140,15 @@ class EventHandler {
9916
10140
  * @param data - Event data.
9917
10141
  */
9918
10142
  trigger(event, data) {
9919
- const callbacks = [...this.listeners[event] ?? [], ...this.listeners["*"] ?? []];
9920
- callbacks.forEach((listener) => {
10143
+ for (const listener of this.getCallbacks(event)) {
9921
10144
  listener.call(null, event, data);
9922
- });
10145
+ }
10146
+ }
10147
+ getCallbacks(event) {
10148
+ const { listeners } = this;
10149
+ const callbacks = listeners[event] ?? [];
10150
+ const global = listeners["*"] ?? [];
10151
+ return [...callbacks, ...global];
9923
10152
  }
9924
10153
  }
9925
10154
 
@@ -9989,10 +10218,10 @@ class Parser {
9989
10218
  location: null
9990
10219
  });
9991
10220
  this.dom = new DOMTree({
9992
- filename: source.filename ?? "",
9993
- offset: source.offset ?? 0,
9994
- line: source.line ?? 1,
9995
- column: source.column ?? 1,
10221
+ filename: source.filename,
10222
+ offset: source.offset,
10223
+ line: source.line,
10224
+ column: source.column,
9996
10225
  size: 0
9997
10226
  });
9998
10227
  this.trigger("dom:load", {
@@ -10516,7 +10745,7 @@ function isThenable(value) {
10516
10745
  return value && typeof value === "object" && "then" in value && typeof value.then === "function";
10517
10746
  }
10518
10747
 
10519
- const ruleIds = new Set(Object.keys(bundledRules$1));
10748
+ const ruleIds = new Set(Object.keys(bundledRules));
10520
10749
  function ruleExists(ruleId) {
10521
10750
  return ruleIds.has(ruleId);
10522
10751
  }
@@ -10605,9 +10834,7 @@ class Reporter {
10605
10834
  valid: this.isValid(),
10606
10835
  results: Object.keys(this.result).map((filePath) => {
10607
10836
  const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
10608
- const source = (sources ?? []).find(
10609
- (source2) => filePath === (source2.filename ?? "")
10610
- );
10837
+ const source = (sources ?? []).find((source2) => filePath === source2.filename);
10611
10838
  return {
10612
10839
  filePath,
10613
10840
  messages,
@@ -10675,7 +10902,7 @@ class Engine {
10675
10902
  this.ParserClass = ParserClass;
10676
10903
  const result = this.initPlugins(this.config);
10677
10904
  this.availableRules = {
10678
- ...bundledRules$1,
10905
+ ...bundledRules,
10679
10906
  ...result.availableRules
10680
10907
  };
10681
10908
  }
@@ -11104,10 +11331,11 @@ class HtmlValidate {
11104
11331
  * @returns Report output.
11105
11332
  */
11106
11333
  async validateSource(input, configOverride) {
11107
- const config = await this.getConfigFor(input.filename, configOverride);
11108
- const source = config.transformSource(input);
11334
+ const source = normalizeSource(input);
11335
+ const config = await this.getConfigFor(source.filename, configOverride);
11336
+ const transformedSource = config.transformSource(source);
11109
11337
  const engine = new Engine(config, Parser);
11110
- return engine.lint(source);
11338
+ return engine.lint(transformedSource);
11111
11339
  }
11112
11340
  /**
11113
11341
  * Parse and validate HTML from [[Source]].
@@ -11117,10 +11345,11 @@ class HtmlValidate {
11117
11345
  * @returns Report output.
11118
11346
  */
11119
11347
  validateSourceSync(input, configOverride) {
11120
- const config = this.getConfigForSync(input.filename, configOverride);
11121
- const source = config.transformSource(input);
11348
+ const source = normalizeSource(input);
11349
+ const config = this.getConfigForSync(source.filename, configOverride);
11350
+ const transformedSource = config.transformSource(source);
11122
11351
  const engine = new Engine(config, Parser);
11123
- return engine.lint(source);
11352
+ return engine.lint(transformedSource);
11124
11353
  }
11125
11354
  /**
11126
11355
  * Parse and validate HTML from file.
@@ -11429,7 +11658,7 @@ class HtmlValidate {
11429
11658
  }
11430
11659
 
11431
11660
  const name = "html-validate";
11432
- const version = "8.8.0";
11661
+ const version = "8.9.0";
11433
11662
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11434
11663
 
11435
11664
  function definePlugin(plugin) {