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/es/core.js CHANGED
@@ -591,6 +591,18 @@ const patternProperties = {
591
591
  description: "Script-supporting elements are elements which can be inserted where othersise not permitted to assist in templating",
592
592
  type: "boolean"
593
593
  },
594
+ focusable: {
595
+ title: "Mark this element as focusable",
596
+ description: "This element may contain an associated label element.",
597
+ anyOf: [
598
+ {
599
+ type: "boolean"
600
+ },
601
+ {
602
+ "function": true
603
+ }
604
+ ]
605
+ },
594
606
  form: {
595
607
  title: "Mark element as a submittable form element",
596
608
  type: "boolean"
@@ -965,6 +977,7 @@ const MetaCopyableProperty = [
965
977
  "embedded",
966
978
  "interactive",
967
979
  "transparent",
980
+ "focusable",
968
981
  "form",
969
982
  "formAssociated",
970
983
  "labelable",
@@ -1037,6 +1050,7 @@ function migrateElement(src) {
1037
1050
  },
1038
1051
  attributes: migrateAttributes(src),
1039
1052
  textContent: src.textContent,
1053
+ focusable: src.focusable ?? false,
1040
1054
  implicitRole: src.implicitRole ?? (() => null)
1041
1055
  };
1042
1056
  delete result.deprecatedAttributes;
@@ -1292,6 +1306,9 @@ function expandProperties(node, entry) {
1292
1306
  setMetaProperty(entry, key, evaluateProperty(node, property));
1293
1307
  }
1294
1308
  }
1309
+ if (typeof entry.focusable === "function") {
1310
+ setMetaProperty(entry, "focusable", entry.focusable(node._adapter));
1311
+ }
1295
1312
  }
1296
1313
  function expandRegexValue(value) {
1297
1314
  if (value instanceof RegExp) {
@@ -1506,10 +1523,10 @@ class Context {
1506
1523
  constructor(source) {
1507
1524
  this.state = State.INITIAL;
1508
1525
  this.string = source.data;
1509
- this.filename = source.filename ?? "";
1510
- this.offset = source.offset ?? 0;
1511
- this.line = source.line ?? 1;
1512
- this.column = source.column ?? 1;
1526
+ this.filename = source.filename;
1527
+ this.offset = source.offset;
1528
+ this.line = source.line;
1529
+ this.column = source.column;
1513
1530
  this.contentModel = 1 /* TEXT */;
1514
1531
  }
1515
1532
  getTruncatedLine(n = 13) {
@@ -1542,6 +1559,16 @@ class Context {
1542
1559
  }
1543
1560
  }
1544
1561
 
1562
+ function normalizeSource(source) {
1563
+ return {
1564
+ filename: "",
1565
+ offset: 0,
1566
+ line: 1,
1567
+ column: 1,
1568
+ ...source
1569
+ };
1570
+ }
1571
+
1545
1572
  var NodeType = /* @__PURE__ */ ((NodeType2) => {
1546
1573
  NodeType2[NodeType2["ELEMENT_NODE"] = 1] = "ELEMENT_NODE";
1547
1574
  NodeType2[NodeType2["TEXT_NODE"] = 3] = "TEXT_NODE";
@@ -3135,8 +3162,21 @@ function isKeywordIgnored(options, keyword, matcher = (list, it) => list.include
3135
3162
  const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
3136
3163
  const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
3137
3164
  const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
3165
+ const STYLE_HIDDEN_CACHE = Symbol(isStyleHidden.name);
3138
3166
  function inAccessibilityTree(node) {
3139
- return !isAriaHidden(node) && !isPresentation(node);
3167
+ if (isAriaHidden(node)) {
3168
+ return false;
3169
+ }
3170
+ if (isPresentation(node)) {
3171
+ return false;
3172
+ }
3173
+ if (isHTMLHidden(node)) {
3174
+ return false;
3175
+ }
3176
+ if (isStyleHidden(node)) {
3177
+ return false;
3178
+ }
3179
+ return true;
3140
3180
  }
3141
3181
  function isAriaHiddenImpl(node) {
3142
3182
  const isHidden = (node2) => {
@@ -3174,22 +3214,41 @@ function isHTMLHidden(node, details) {
3174
3214
  const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
3175
3215
  return details ? result : result.byParent || result.bySelf;
3176
3216
  }
3217
+ function isStyleHiddenImpl(node) {
3218
+ const isHidden = (node2) => {
3219
+ const style = node2.getAttribute("style");
3220
+ const { display, visibility } = parseCssDeclaration(style == null ? void 0 : style.value);
3221
+ return display === "none" || visibility === "hidden";
3222
+ };
3223
+ const byParent = node.parent ? isStyleHidden(node.parent) : false;
3224
+ const bySelf = isHidden(node);
3225
+ return byParent || bySelf;
3226
+ }
3227
+ function isStyleHidden(node) {
3228
+ const cached = node.cacheGet(STYLE_HIDDEN_CACHE);
3229
+ if (cached) {
3230
+ return cached;
3231
+ }
3232
+ return node.cacheSet(STYLE_HIDDEN_CACHE, isStyleHiddenImpl(node));
3233
+ }
3177
3234
  function isPresentation(node) {
3178
3235
  if (node.cacheExists(ROLE_PRESENTATION_CACHE)) {
3179
3236
  return Boolean(node.cacheGet(ROLE_PRESENTATION_CACHE));
3180
3237
  }
3181
- let cur = node;
3182
- do {
3183
- const role = cur.getAttribute("role");
3184
- if (role && role.value === "presentation") {
3185
- return cur.cacheSet(ROLE_PRESENTATION_CACHE, true);
3186
- }
3187
- if (!cur.parent) {
3188
- break;
3189
- }
3190
- cur = cur.parent;
3191
- } while (!cur.isRootElement());
3192
- return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
3238
+ const meta = node.meta;
3239
+ if (meta && meta.interactive) {
3240
+ return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
3241
+ }
3242
+ const tabindex = node.getAttribute("tabindex");
3243
+ if (tabindex) {
3244
+ return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
3245
+ }
3246
+ const role = node.getAttribute("role");
3247
+ if (role && (role.value === "presentation" || role.value === "none")) {
3248
+ return node.cacheSet(ROLE_PRESENTATION_CACHE, true);
3249
+ } else {
3250
+ return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
3251
+ }
3193
3252
  }
3194
3253
 
3195
3254
  const cachePrefix = classifyNodeText.name;
@@ -6177,6 +6236,58 @@ class HeadingLevel extends Rule {
6177
6236
  }
6178
6237
  }
6179
6238
 
6239
+ class HiddenFocusable extends Rule {
6240
+ documentation(context) {
6241
+ const byParent = context === "parent" ? " In this case it is being hidden by an ancestor with `aria-hidden.`" : "";
6242
+ return {
6243
+ description: [
6244
+ `\`aria-hidden\` cannot be used on focusable elements.${byParent}`,
6245
+ "",
6246
+ "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).",
6247
+ "This is often confusing for users of AT such as screenreaders.",
6248
+ "",
6249
+ "To fix this either:",
6250
+ " - Remove `aria-hidden`.",
6251
+ " - Remove the element from the DOM instead.",
6252
+ " - Use the `hidden` attribute or similar means to hide the element."
6253
+ ].join("\n"),
6254
+ url: "https://html-validate.org/rules/hidden-focusable.html"
6255
+ };
6256
+ }
6257
+ setup() {
6258
+ const focusable = this.getTagsWithProperty("focusable");
6259
+ const selector = ["[tabindex]", ...focusable].join(",");
6260
+ this.on("dom:ready", (event) => {
6261
+ const { document } = event;
6262
+ for (const element of document.querySelectorAll(selector)) {
6263
+ if (isHTMLHidden(element) || isStyleHidden(element)) {
6264
+ continue;
6265
+ }
6266
+ if (isAriaHidden(element)) {
6267
+ this.validateElement(element);
6268
+ }
6269
+ }
6270
+ });
6271
+ }
6272
+ validateElement(element) {
6273
+ const { meta } = element;
6274
+ const tabindex = element.getAttribute("tabindex");
6275
+ if (meta && !meta.focusable && !tabindex) {
6276
+ return;
6277
+ }
6278
+ const attribute = element.getAttribute("aria-hidden");
6279
+ const message = attribute ? `aria-hidden cannot be used on focusable elements` : `aria-hidden cannot be used on focusable elements (hidden by ancestor element)`;
6280
+ const location = attribute ? attribute.keyLocation : element.location;
6281
+ const context = attribute ? "self" : "parent";
6282
+ this.report({
6283
+ node: element,
6284
+ message,
6285
+ location,
6286
+ context
6287
+ });
6288
+ }
6289
+ }
6290
+
6180
6291
  const defaults$g = {
6181
6292
  pattern: "kebabcase"
6182
6293
  };
@@ -6362,7 +6473,7 @@ function isHidden(node, context) {
6362
6473
  if (reference && reference.isSameNode(node)) {
6363
6474
  return false;
6364
6475
  } else {
6365
- return isHTMLHidden(node) || !inAccessibilityTree(node);
6476
+ return !inAccessibilityTree(node);
6366
6477
  }
6367
6478
  }
6368
6479
  function hasImgAltText(node, context) {
@@ -7939,7 +8050,7 @@ class RequireSri extends Rule {
7939
8050
  }
7940
8051
  documentation() {
7941
8052
  return {
7942
- description: `Subresource Integrity (SRI) \`integrity\` attribute is required to prevent manipulation from Content Delivery Networks or other third-party hosting.`,
8053
+ description: `Subresource Integrity (SRI) \`integrity\` attribute is required to prevent tampering or manipulation from Content Delivery Networks (CDN), rouge proxies, malicious entities, etc.`,
7943
8054
  url: "https://html-validate.org/rules/require-sri.html"
7944
8055
  };
7945
8056
  }
@@ -8352,6 +8463,107 @@ class TextContent extends Rule {
8352
8463
  }
8353
8464
  }
8354
8465
 
8466
+ function getTextFromReference(document, id) {
8467
+ if (!id || id instanceof DynamicValue) {
8468
+ return id;
8469
+ }
8470
+ const selector = `#${id}`;
8471
+ const ref = document.querySelector(selector);
8472
+ if (ref) {
8473
+ return ref.textContent;
8474
+ } else {
8475
+ return selector;
8476
+ }
8477
+ }
8478
+ function getTextEntryFromElement(document, node) {
8479
+ const ariaLabel = node.getAttribute("aria-label");
8480
+ if (ariaLabel) {
8481
+ return {
8482
+ node,
8483
+ text: ariaLabel.value,
8484
+ location: ariaLabel.keyLocation
8485
+ };
8486
+ }
8487
+ const ariaLabelledby = node.getAttribute("aria-labelledby");
8488
+ if (ariaLabelledby) {
8489
+ const text = getTextFromReference(document, ariaLabelledby.value);
8490
+ return {
8491
+ node,
8492
+ text,
8493
+ location: ariaLabelledby.keyLocation
8494
+ };
8495
+ }
8496
+ return {
8497
+ node,
8498
+ text: null,
8499
+ location: node.location
8500
+ };
8501
+ }
8502
+ class UniqueLandmark extends Rule {
8503
+ documentation() {
8504
+ return {
8505
+ description: [
8506
+ "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.",
8507
+ "For instance, if the document has two `<nav>` elements each of them need an accessible name to be distinguished from each other.",
8508
+ "",
8509
+ "The following elements / roles are considered landmarks:",
8510
+ "",
8511
+ ' - `aside` or `[role="complementary"]`',
8512
+ ' - `footer` or `[role="contentinfo"]`',
8513
+ ' - `form` or `[role="form"]`',
8514
+ ' - `header` or `[role="banner"]`',
8515
+ ' - `main` or `[role="main"]`',
8516
+ ' - `nav` or `[role="navigation"]`',
8517
+ ' - `section` or `[role="region"]`',
8518
+ "",
8519
+ "To fix this either:",
8520
+ "",
8521
+ " - Add `aria-label`.",
8522
+ " - Add `aria-labelledby`.",
8523
+ " - Remove one of the landmarks."
8524
+ ].join("\n"),
8525
+ url: "https://html-validate.org/rules/unique-landmark.html"
8526
+ };
8527
+ }
8528
+ setup() {
8529
+ const selectors = [
8530
+ 'aside, [role="complementary"]',
8531
+ 'footer, [role="contentinfo"]',
8532
+ 'form, [role="form"]',
8533
+ 'header, [role="banner"]',
8534
+ 'main, [role="main"]',
8535
+ 'nav, [role="navigation"]',
8536
+ 'section, [role="region"]'
8537
+ /* <search> does not (yet?) require a unique name */
8538
+ ];
8539
+ this.on("dom:ready", (event) => {
8540
+ const { document } = event;
8541
+ for (const selector of selectors) {
8542
+ const nodes = document.querySelectorAll(selector);
8543
+ if (nodes.length <= 1) {
8544
+ continue;
8545
+ }
8546
+ const entries = nodes.map((it) => getTextEntryFromElement(document, it));
8547
+ for (const entry of entries) {
8548
+ if (entry.text instanceof DynamicValue) {
8549
+ continue;
8550
+ }
8551
+ const dup = entries.filter((it) => it.text === entry.text).length > 1;
8552
+ if (!entry.text || dup) {
8553
+ const message = `Landmarks must have a non-empty and unique accessible name (aria-label or aria-labelledby)`;
8554
+ const location = entry.location;
8555
+ this.report({
8556
+ node: entry.node,
8557
+ message,
8558
+ location
8559
+ });
8560
+ }
8561
+ }
8562
+ }
8563
+ });
8564
+ }
8565
+ }
8566
+
8355
8567
  const defaults$4 = {
8356
8568
  ignoreCase: false,
8357
8569
  requireSemicolon: true
@@ -8741,7 +8953,7 @@ class H32 extends Rule {
8741
8953
  }
8742
8954
  function isSubmit(node) {
8743
8955
  const type = node.getAttribute("type");
8744
- return Boolean(type && type.valueMatches(/submit|image/));
8956
+ return Boolean(!type || type.valueMatches(/submit|image/));
8745
8957
  }
8746
8958
  function isAssociated(id, node) {
8747
8959
  const form = node.getAttribute("form");
@@ -8833,29 +9045,35 @@ class H37 extends Rule {
8833
9045
  };
8834
9046
  }
8835
9047
  setup() {
8836
- this.on("tag:end", (event) => {
8837
- const node = event.previous;
8838
- if (!needsAlt(node)) {
8839
- return;
8840
- }
8841
- if (!inAccessibilityTree(node)) {
8842
- return;
9048
+ this.on("dom:ready", (event) => {
9049
+ const { document } = event;
9050
+ const nodes = document.querySelectorAll("img, input");
9051
+ for (const node of nodes) {
9052
+ this.validateNode(node);
8843
9053
  }
8844
- if (Boolean(node.getAttributeValue("alt")) || Boolean(node.hasAttribute("alt") && this.options.allowEmpty)) {
9054
+ });
9055
+ }
9056
+ validateNode(node) {
9057
+ if (!needsAlt(node)) {
9058
+ return;
9059
+ }
9060
+ if (!inAccessibilityTree(node)) {
9061
+ return;
9062
+ }
9063
+ if (Boolean(node.getAttributeValue("alt")) || Boolean(node.hasAttribute("alt") && this.options.allowEmpty)) {
9064
+ return;
9065
+ }
9066
+ for (const attr of this.options.alias) {
9067
+ if (node.getAttribute(attr)) {
8845
9068
  return;
8846
9069
  }
8847
- for (const attr of this.options.alias) {
8848
- if (node.getAttribute(attr)) {
8849
- return;
8850
- }
8851
- }
8852
- if (node.hasAttribute("alt")) {
8853
- const attr = node.getAttribute("alt");
8854
- this.report(node, `${getTag(node)} cannot have empty "alt" attribute`, attr == null ? void 0 : attr.keyLocation);
8855
- } else {
8856
- this.report(node, `${getTag(node)} is missing required "alt" attribute`, node.location);
8857
- }
8858
- });
9070
+ }
9071
+ if (node.hasAttribute("alt")) {
9072
+ const attr = node.getAttribute("alt");
9073
+ this.report(node, `${getTag(node)} cannot have empty "alt" attribute`, attr == null ? void 0 : attr.keyLocation);
9074
+ } else {
9075
+ this.report(node, `${getTag(node)} is missing required "alt" attribute`, node.location);
9076
+ }
8859
9077
  }
8860
9078
  }
8861
9079
 
@@ -8946,7 +9164,7 @@ class H71 extends Rule {
8946
9164
  }
8947
9165
  }
8948
9166
 
8949
- const bundledRules$2 = {
9167
+ const bundledRules$1 = {
8950
9168
  "wcag/h30": H30,
8951
9169
  "wcag/h32": H32,
8952
9170
  "wcag/h36": H36,
@@ -8955,7 +9173,7 @@ const bundledRules$2 = {
8955
9173
  "wcag/h67": H67,
8956
9174
  "wcag/h71": H71
8957
9175
  };
8958
- var WCAG = bundledRules$2;
9176
+ var WCAG = bundledRules$1;
8959
9177
 
8960
9178
  const bundledRules = {
8961
9179
  "allowed-links": AllowedLinks,
@@ -8991,6 +9209,7 @@ const bundledRules = {
8991
9209
  "empty-title": EmptyTitle,
8992
9210
  "form-dup-name": FormDupName,
8993
9211
  "heading-level": HeadingLevel,
9212
+ "hidden-focusable": HiddenFocusable,
8994
9213
  "id-pattern": IdPattern,
8995
9214
  "input-attributes": InputAttributes,
8996
9215
  "input-missing-label": InputMissingLabel,
@@ -9031,13 +9250,13 @@ const bundledRules = {
9031
9250
  "svg-focusable": SvgFocusable,
9032
9251
  "tel-non-breaking": TelNonBreaking,
9033
9252
  "text-content": TextContent,
9253
+ "unique-landmark": UniqueLandmark,
9034
9254
  "unrecognized-char-ref": UnknownCharReference,
9035
9255
  "valid-id": ValidID,
9036
9256
  "void-content": VoidContent,
9037
9257
  "void-style": VoidStyle,
9038
9258
  ...WCAG
9039
9259
  };
9040
- var bundledRules$1 = bundledRules;
9041
9260
 
9042
9261
  var defaultConfig = {};
9043
9262
 
@@ -9049,6 +9268,7 @@ const config$4 = {
9049
9268
  "deprecated-rule": "warn",
9050
9269
  "empty-heading": "error",
9051
9270
  "empty-title": "error",
9271
+ "hidden-focusable": "error",
9052
9272
  "meta-refresh": "error",
9053
9273
  "multiple-labeled-controls": "error",
9054
9274
  "no-autoplay": ["error", { include: ["audio", "video"] }],
@@ -9060,6 +9280,7 @@ const config$4 = {
9060
9280
  "prefer-native-element": "error",
9061
9281
  "svg-focusable": "off",
9062
9282
  "text-content": "error",
9283
+ "unique-landmark": "error",
9063
9284
  "wcag/h30": "error",
9064
9285
  "wcag/h32": "error",
9065
9286
  "wcag/h36": "error",
@@ -9122,6 +9343,7 @@ const config$1 = {
9122
9343
  "empty-heading": "error",
9123
9344
  "empty-title": "error",
9124
9345
  "form-dup-name": "error",
9346
+ "hidden-focusable": "error",
9125
9347
  "input-attributes": "error",
9126
9348
  "long-title": "error",
9127
9349
  "map-dup-name": "error",
@@ -9154,6 +9376,7 @@ const config$1 = {
9154
9376
  "svg-focusable": "off",
9155
9377
  "tel-non-breaking": "error",
9156
9378
  "text-content": "error",
9379
+ "unique-landmark": "error",
9157
9380
  "unrecognized-char-ref": "error",
9158
9381
  "valid-id": ["error", { relaxed: false }],
9159
9382
  void: "off",
@@ -9498,7 +9721,7 @@ class Config {
9498
9721
  if (configData.rules) {
9499
9722
  const normalizedRules = Config.getRulesObject(configData.rules);
9500
9723
  for (const [ruleId, [, ruleOptions]] of normalizedRules.entries()) {
9501
- const cls = bundledRules$1[ruleId];
9724
+ const cls = bundledRules[ruleId];
9502
9725
  const path = `/rules/${ruleId}/1`;
9503
9726
  Rule.validateOptions(cls, ruleId, path, ruleOptions, filename, configData);
9504
9727
  }
@@ -9871,16 +10094,17 @@ class EventHandler {
9871
10094
  * @returns Unregistration function.
9872
10095
  */
9873
10096
  on(event, callback) {
9874
- const names = event.split(",").map((x) => x.trim());
10097
+ const { listeners } = this;
10098
+ const names = event.split(",").map((it) => it.trim());
9875
10099
  for (const name of names) {
9876
- this.listeners[name] = this.listeners[name] || [];
9877
- this.listeners[name].push(callback);
10100
+ const list = listeners[name] ?? [];
10101
+ listeners[name] = list;
10102
+ list.push(callback);
9878
10103
  }
9879
10104
  return () => {
9880
10105
  for (const name of names) {
9881
- this.listeners[name] = this.listeners[name].filter((fn) => {
9882
- return fn !== callback;
9883
- });
10106
+ const list = listeners[name];
10107
+ this.listeners[name] = list.filter((fn) => fn !== callback);
9884
10108
  }
9885
10109
  };
9886
10110
  }
@@ -9906,10 +10130,15 @@ class EventHandler {
9906
10130
  * @param data - Event data.
9907
10131
  */
9908
10132
  trigger(event, data) {
9909
- const callbacks = [...this.listeners[event] ?? [], ...this.listeners["*"] ?? []];
9910
- callbacks.forEach((listener) => {
10133
+ for (const listener of this.getCallbacks(event)) {
9911
10134
  listener.call(null, event, data);
9912
- });
10135
+ }
10136
+ }
10137
+ getCallbacks(event) {
10138
+ const { listeners } = this;
10139
+ const callbacks = listeners[event] ?? [];
10140
+ const global = listeners["*"] ?? [];
10141
+ return [...callbacks, ...global];
9913
10142
  }
9914
10143
  }
9915
10144
 
@@ -9979,10 +10208,10 @@ class Parser {
9979
10208
  location: null
9980
10209
  });
9981
10210
  this.dom = new DOMTree({
9982
- filename: source.filename ?? "",
9983
- offset: source.offset ?? 0,
9984
- line: source.line ?? 1,
9985
- column: source.column ?? 1,
10211
+ filename: source.filename,
10212
+ offset: source.offset,
10213
+ line: source.line,
10214
+ column: source.column,
9986
10215
  size: 0
9987
10216
  });
9988
10217
  this.trigger("dom:load", {
@@ -10506,7 +10735,7 @@ function isThenable(value) {
10506
10735
  return value && typeof value === "object" && "then" in value && typeof value.then === "function";
10507
10736
  }
10508
10737
 
10509
- const ruleIds = new Set(Object.keys(bundledRules$1));
10738
+ const ruleIds = new Set(Object.keys(bundledRules));
10510
10739
  function ruleExists(ruleId) {
10511
10740
  return ruleIds.has(ruleId);
10512
10741
  }
@@ -10595,9 +10824,7 @@ class Reporter {
10595
10824
  valid: this.isValid(),
10596
10825
  results: Object.keys(this.result).map((filePath) => {
10597
10826
  const messages = Array.from(this.result[filePath], freeze).sort(messageSort);
10598
- const source = (sources ?? []).find(
10599
- (source2) => filePath === (source2.filename ?? "")
10600
- );
10827
+ const source = (sources ?? []).find((source2) => filePath === source2.filename);
10601
10828
  return {
10602
10829
  filePath,
10603
10830
  messages,
@@ -10665,7 +10892,7 @@ class Engine {
10665
10892
  this.ParserClass = ParserClass;
10666
10893
  const result = this.initPlugins(this.config);
10667
10894
  this.availableRules = {
10668
- ...bundledRules$1,
10895
+ ...bundledRules,
10669
10896
  ...result.availableRules
10670
10897
  };
10671
10898
  }
@@ -11094,10 +11321,11 @@ class HtmlValidate {
11094
11321
  * @returns Report output.
11095
11322
  */
11096
11323
  async validateSource(input, configOverride) {
11097
- const config = await this.getConfigFor(input.filename, configOverride);
11098
- const source = config.transformSource(input);
11324
+ const source = normalizeSource(input);
11325
+ const config = await this.getConfigFor(source.filename, configOverride);
11326
+ const transformedSource = config.transformSource(source);
11099
11327
  const engine = new Engine(config, Parser);
11100
- return engine.lint(source);
11328
+ return engine.lint(transformedSource);
11101
11329
  }
11102
11330
  /**
11103
11331
  * Parse and validate HTML from [[Source]].
@@ -11107,10 +11335,11 @@ class HtmlValidate {
11107
11335
  * @returns Report output.
11108
11336
  */
11109
11337
  validateSourceSync(input, configOverride) {
11110
- const config = this.getConfigForSync(input.filename, configOverride);
11111
- const source = config.transformSource(input);
11338
+ const source = normalizeSource(input);
11339
+ const config = this.getConfigForSync(source.filename, configOverride);
11340
+ const transformedSource = config.transformSource(source);
11112
11341
  const engine = new Engine(config, Parser);
11113
- return engine.lint(source);
11342
+ return engine.lint(transformedSource);
11114
11343
  }
11115
11344
  /**
11116
11345
  * Parse and validate HTML from file.
@@ -11419,7 +11648,7 @@ class HtmlValidate {
11419
11648
  }
11420
11649
 
11421
11650
  const name = "html-validate";
11422
- const version = "8.8.0";
11651
+ const version = "8.9.0";
11423
11652
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11424
11653
 
11425
11654
  function definePlugin(plugin) {