html-validate 7.11.1 → 7.12.1

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.d.ts CHANGED
@@ -343,6 +343,14 @@ declare abstract class Rule<ContextType = void, OptionsType = void> {
343
343
  isKeywordIgnored<T extends IncludeExcludeOptions>(this: {
344
344
  options: T;
345
345
  }, keyword: string, matcher?: (list: string[], it: string) => boolean): boolean;
346
+ /**
347
+ * Get [[MetaElement]] for the given tag. If no specific metadata is present
348
+ * the global metadata is returned or null if no global is present.
349
+ *
350
+ * @public
351
+ * @returns A shallow copy of metadata.
352
+ */
353
+ getMetaFor(tagName: string): MetaElement | null;
346
354
  /**
347
355
  * Find all tags which has enabled given property.
348
356
  */
@@ -1002,13 +1010,16 @@ declare class Attribute {
1002
1010
  /**
1003
1011
  * Test attribute value.
1004
1012
  *
1005
- * @param pattern - Pattern to match value against. RegExp or a string (===)
1013
+ * @param pattern - Pattern to match value against. Can be a RegExp, literal
1014
+ * string or an array of strings (returns true if any value matches the
1015
+ * array).
1006
1016
  * @param dynamicMatches - If true `DynamicValue` will always match, if false
1007
1017
  * it never matches.
1008
1018
  * @returns `true` if attribute value matches pattern.
1009
1019
  */
1010
1020
  valueMatches(pattern: RegExp, dynamicMatches?: boolean): boolean;
1011
1021
  valueMatches(pattern: string, dynamicMatches?: boolean): boolean;
1022
+ valueMatches(pattern: string[], dynamicMatches?: boolean): boolean;
1012
1023
  }
1013
1024
 
1014
1025
  interface CSSStyleDeclaration {
@@ -1688,8 +1699,10 @@ declare enum TextContent {
1688
1699
  * description if there is an error.
1689
1700
  *
1690
1701
  * @public
1702
+ * @param node - The node the attribute belongs to.
1703
+ * @param attr - The current attribute being validated.
1691
1704
  */
1692
- type MetaAttributeAllowedCallback = (node: HtmlElement) => string | null | undefined;
1705
+ type MetaAttributeAllowedCallback = (node: HtmlElement, attr: Attribute) => string | null | undefined;
1693
1706
  /**
1694
1707
  * @public
1695
1708
  */
@@ -1708,6 +1721,10 @@ interface DeprecatedElement {
1708
1721
  documentation?: string;
1709
1722
  source?: string;
1710
1723
  }
1724
+ interface FormAssociated {
1725
+ /** Listed elements have a name attribute and is listed in the form and fieldset elements property. */
1726
+ listed: boolean;
1727
+ }
1711
1728
  /**
1712
1729
  * @public
1713
1730
  */
@@ -1727,6 +1744,8 @@ interface MetaData {
1727
1744
  implicitClosed?: string[];
1728
1745
  scriptSupporting?: boolean;
1729
1746
  form?: boolean;
1747
+ /** Mark element as a form-associated element */
1748
+ formAssociated?: Partial<FormAssociated>;
1730
1749
  labelable?: boolean | PropertyExpression;
1731
1750
  deprecatedAttributes?: string[];
1732
1751
  requiredAttributes?: string[];
@@ -1743,7 +1762,7 @@ interface MetaData {
1743
1762
  * Properties listed here can be used to reverse search elements with the given
1744
1763
  * property enabled. See [[MetaTable.getTagsWithProperty]].
1745
1764
  */
1746
- type MetaLookupableProperty = "metadata" | "flow" | "sectioning" | "heading" | "phrasing" | "embedded" | "interactive" | "deprecated" | "foreign" | "void" | "transparent" | "scriptSupporting" | "form" | "labelable";
1765
+ type MetaLookupableProperty = "metadata" | "flow" | "sectioning" | "heading" | "phrasing" | "embedded" | "interactive" | "deprecated" | "foreign" | "void" | "transparent" | "scriptSupporting" | "form" | "formAssociated" | "labelable";
1747
1766
  /**
1748
1767
  * Properties listed here can be copied (loaded) onto another element using
1749
1768
  * [[HtmlElement.loadMeta]].
@@ -1756,6 +1775,7 @@ declare const MetaCopyableProperty: Array<keyof MetaElement>;
1756
1775
  */
1757
1776
  interface MetaElement extends Omit<MetaData, "deprecatedAttributes" | "requiredAttributes"> {
1758
1777
  tagName: string;
1778
+ formAssociated?: FormAssociated;
1759
1779
  attributes: Record<string, MetaAttribute>;
1760
1780
  textContent?: TextContent;
1761
1781
  }
@@ -2042,6 +2062,9 @@ declare class NestedError extends Error {
2042
2062
  */
2043
2063
  declare class UserError extends NestedError {
2044
2064
  constructor(message: string, nested?: Error);
2065
+ /**
2066
+ * @public
2067
+ */
2045
2068
  prettyFormat(): string | undefined;
2046
2069
  }
2047
2070
 
@@ -2396,4 +2419,4 @@ interface AvailableFormatters {
2396
2419
  declare function getFormatter(name: keyof AvailableFormatters): Formatter;
2397
2420
  declare function getFormatter(name: string): Formatter | null;
2398
2421
 
2399
- export { Event as $, AttributeData as A, Message as B, Config as C, DynamicValue as D, EventDump as E, Result as F, DeferredMessage as G, HtmlValidate as H, TransformContext as I, Transformer as J, TemplateExtractor as K, Location as L, MetaData as M, NodeClosed as N, Plugin as O, ProcessAttributeCallback as P, SchemaValidationPatch as Q, Rule as R, Severity as S, TextNode as T, UserError as U, definePlugin as V, WrappedError as W, Parser as X, ruleExists as Y, EventHandler as Z, EventCallback as _, ConfigData as a, ConfigReadyEvent as a0, SourceReadyEvent as a1, TokenEvent as a2, TagStartEvent as a3, TagOpenEvent as a4, TagEndEvent as a5, TagCloseEvent as a6, TagReadyEvent as a7, ElementReadyEvent as a8, AttributeEvent as a9, WhitespaceEvent as aa, ConditionalEvent as ab, DirectiveEvent as ac, DoctypeEvent as ad, DOMLoadEvent as ae, DOMReadyEvent as af, TriggerEventMap as ag, ListenEventMap as ah, FileSystemConfigLoader as ai, Formatter as aj, getFormatter as ak, compatibilityCheck as al, CompatibilityOptions as am, ConfigError as b, ConfigLoader as c, StaticConfigLoader as d, HtmlElement as e, CSSStyleDeclaration as f, TokenDump as g, SchemaValidationError as h, NestedError as i, MetaDataTable as j, MetaElement as k, MetaAttribute as l, MetaAttributeAllowedCallback as m, MetaTable as n, MetaCopyableProperty as o, presets as p, RuleConstructor as q, RuleDocumentation as r, Source as s, SourceHooks as t, ProcessElementCallback as u, version as v, ProcessElementContext as w, sliceLocation as x, Report as y, Reporter as z };
2422
+ export { EventCallback as $, AttributeData as A, Reporter as B, Config as C, DynamicValue as D, EventDump as E, Message as F, Result as G, HtmlValidate as H, DeferredMessage as I, TransformContext as J, Transformer as K, Location as L, MetaData as M, NodeClosed as N, TemplateExtractor as O, ProcessAttributeCallback as P, Plugin as Q, Rule as R, Severity as S, TextNode as T, UserError as U, SchemaValidationPatch as V, WrappedError as W, definePlugin as X, Parser as Y, ruleExists as Z, EventHandler as _, ConfigData as a, Event as a0, ConfigReadyEvent as a1, SourceReadyEvent as a2, TokenEvent as a3, TagStartEvent as a4, TagOpenEvent as a5, TagEndEvent as a6, TagCloseEvent as a7, TagReadyEvent as a8, ElementReadyEvent as a9, AttributeEvent as aa, WhitespaceEvent as ab, ConditionalEvent as ac, DirectiveEvent as ad, DoctypeEvent as ae, DOMLoadEvent as af, DOMReadyEvent as ag, TriggerEventMap as ah, ListenEventMap as ai, FileSystemConfigLoader as aj, Formatter as ak, getFormatter as al, compatibilityCheck as am, CompatibilityOptions as an, ConfigError as b, ConfigLoader as c, StaticConfigLoader as d, Attribute as e, HtmlElement as f, CSSStyleDeclaration as g, TokenDump as h, SchemaValidationError as i, NestedError as j, MetaDataTable as k, MetaElement as l, MetaAttribute as m, MetaAttributeAllowedCallback as n, MetaTable as o, presets as p, MetaCopyableProperty as q, RuleConstructor as r, RuleDocumentation as s, Source as t, SourceHooks as u, version as v, ProcessElementCallback as w, ProcessElementContext as x, sliceLocation as y, Report as z };
package/dist/es/core.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import betterAjvErrors from '@sidvind/better-ajv-errors';
3
- import { i as isKeywordIgnored, C as CaseStyle, n as naturalJoin, c as classifyNodeText, T as TextClassification, h as hasAltText, a as isHTMLHidden, b as isAriaHidden, d as hasAccessibleName, k as keywordPatternMatcher, e as inAccessibilityTree, f as hasAriaLabel } from './rules-helper.js';
3
+ import { i as isKeywordIgnored, C as CaseStyle, n as naturalJoin, c as classifyNodeText, T as TextClassification, h as hasAltText, p as partition, a as isHTMLHidden, b as isAriaHidden, d as hasAccessibleName, k as keywordPatternMatcher, e as inAccessibilityTree, f as hasAriaLabel } from './rules-helper.js';
4
4
  import Ajv from 'ajv';
5
5
  import deepmerge from 'deepmerge';
6
6
  import * as espree from 'espree';
@@ -303,6 +303,10 @@ class Attribute {
303
303
  if (this.value instanceof DynamicValue) {
304
304
  return dynamicMatches;
305
305
  }
306
+ /* test against an array of keywords */
307
+ if (Array.isArray(pattern)) {
308
+ return pattern.includes(this.value);
309
+ }
306
310
  /* test value against pattern */
307
311
  if (pattern instanceof RegExp) {
308
312
  return this.value.match(pattern) !== null;
@@ -466,6 +470,7 @@ const MetaCopyableProperty = [
466
470
  "interactive",
467
471
  "transparent",
468
472
  "form",
473
+ "formAssociated",
469
474
  "labelable",
470
475
  "attributes",
471
476
  "permittedContent",
@@ -1656,6 +1661,10 @@ class UserError extends NestedError {
1656
1661
  Error.captureStackTrace(this, UserError);
1657
1662
  this.name = UserError.name;
1658
1663
  }
1664
+ /**
1665
+ * @public
1666
+ */
1667
+ /* istanbul ignore next: default implementation */
1659
1668
  prettyFormat() {
1660
1669
  return undefined;
1661
1670
  }
@@ -1878,6 +1887,10 @@ const patternProperties = {
1878
1887
  title: "Mark element as a submittable form element",
1879
1888
  type: "boolean"
1880
1889
  },
1890
+ formAssociated: {
1891
+ title: "Mark element as a form-associated element",
1892
+ $ref: "#/definitions/FormAssociated"
1893
+ },
1881
1894
  labelable: {
1882
1895
  title: "Mark this element as labelable",
1883
1896
  description: "This element may contain an associated label element.",
@@ -2023,6 +2036,16 @@ const definitions = {
2023
2036
  }
2024
2037
  }
2025
2038
  },
2039
+ FormAssociated: {
2040
+ type: "object",
2041
+ additionalProperties: false,
2042
+ properties: {
2043
+ listed: {
2044
+ type: "boolean",
2045
+ title: "Listed elements have a name attribute and is listed in the form and fieldset elements property."
2046
+ }
2047
+ }
2048
+ },
2026
2049
  Permitted: {
2027
2050
  type: "array",
2028
2051
  items: {
@@ -2281,6 +2304,9 @@ function migrateAttributes(src) {
2281
2304
  function migrateElement(src) {
2282
2305
  const result = {
2283
2306
  ...src,
2307
+ ...{
2308
+ formAssociated: undefined,
2309
+ },
2284
2310
  attributes: migrateAttributes(src),
2285
2311
  textContent: src.textContent,
2286
2312
  };
@@ -2291,6 +2317,14 @@ function migrateElement(src) {
2291
2317
  if (!result.textContent) {
2292
2318
  delete result.textContent;
2293
2319
  }
2320
+ if (src.formAssociated) {
2321
+ result.formAssociated = {
2322
+ listed: Boolean(src.formAssociated.listed),
2323
+ };
2324
+ }
2325
+ else {
2326
+ delete result.formAssociated;
2327
+ }
2294
2328
  return result;
2295
2329
  }
2296
2330
 
@@ -3453,6 +3487,16 @@ class Rule {
3453
3487
  isKeywordIgnored(keyword, matcher = (list, it) => list.includes(it)) {
3454
3488
  return isKeywordIgnored(this.options, keyword, matcher);
3455
3489
  }
3490
+ /**
3491
+ * Get [[MetaElement]] for the given tag. If no specific metadata is present
3492
+ * the global metadata is returned or null if no global is present.
3493
+ *
3494
+ * @public
3495
+ * @returns A shallow copy of metadata.
3496
+ */
3497
+ getMetaFor(tagName) {
3498
+ return this.meta.getMetaFor(tagName);
3499
+ }
3456
3500
  /**
3457
3501
  * Find all tags which has enabled given property.
3458
3502
  */
@@ -4863,7 +4907,7 @@ class AttributeMisuse extends Rule {
4863
4907
  if (!meta || !meta.allowed) {
4864
4908
  return;
4865
4909
  }
4866
- const details = meta.allowed(node);
4910
+ const details = meta.allowed(node, attr);
4867
4911
  if (details) {
4868
4912
  this.report({
4869
4913
  node,
@@ -5887,12 +5931,135 @@ class EmptyTitle extends Rule {
5887
5931
  }
5888
5932
  }
5889
5933
 
5934
+ const UNIQUE_CACHE_KEY = Symbol("form-elements-unique");
5935
+ const SHARED_CACHE_KEY = Symbol("form-elements-shared");
5936
+ function haveName(name) {
5937
+ return typeof name === "string" && name !== "";
5938
+ }
5939
+ function allowSharedName(node) {
5940
+ const type = node.getAttribute("type");
5941
+ return Boolean(type && type.valueMatches(["radio", "checkbox"], false));
5942
+ }
5943
+ class FormDupName extends Rule {
5944
+ documentation() {
5945
+ return {
5946
+ description: "Each form control must have a unique name.",
5947
+ url: "https://html-validate.org/rules/form-dup-name.html",
5948
+ };
5949
+ }
5950
+ setup() {
5951
+ const selector = this.getSelector();
5952
+ this.on("dom:ready", (event) => {
5953
+ var _a, _b;
5954
+ const { document } = event;
5955
+ const controls = document.querySelectorAll(selector);
5956
+ const [sharedControls, uniqueControls] = partition(controls, allowSharedName);
5957
+ /* validate all form controls which require unique elements first so each
5958
+ * form has a populated list of unique names */
5959
+ for (const control of uniqueControls) {
5960
+ const attr = control.getAttribute("name");
5961
+ const name = attr === null || attr === void 0 ? void 0 : attr.value;
5962
+ if (!attr || !haveName(name)) {
5963
+ continue;
5964
+ }
5965
+ const form = (_a = control.closest("form")) !== null && _a !== void 0 ? _a : document.root;
5966
+ this.validateUniqueName(control, form, attr, name);
5967
+ }
5968
+ /* validate all form controls which allows shared names to ensure there is
5969
+ * no collision with other form controls */
5970
+ for (const control of sharedControls) {
5971
+ const attr = control.getAttribute("name");
5972
+ const name = attr === null || attr === void 0 ? void 0 : attr.value;
5973
+ if (!attr || !haveName(name)) {
5974
+ continue;
5975
+ }
5976
+ const form = (_b = control.closest("form")) !== null && _b !== void 0 ? _b : document.root;
5977
+ this.validateSharedName(control, form, attr, name);
5978
+ }
5979
+ });
5980
+ }
5981
+ validateUniqueName(control, form, attr, name) {
5982
+ const elements = this.getUniqueElements(form);
5983
+ if (elements.has(name)) {
5984
+ const context = {
5985
+ name,
5986
+ };
5987
+ this.report({
5988
+ node: control,
5989
+ location: attr.valueLocation,
5990
+ message: 'Duplicate form control name "{{ name }}"',
5991
+ context,
5992
+ });
5993
+ }
5994
+ else {
5995
+ elements.add(name);
5996
+ }
5997
+ }
5998
+ validateSharedName(control, form, attr, name) {
5999
+ var _a;
6000
+ const uniqueElements = this.getUniqueElements(form);
6001
+ const sharedElements = this.getSharedElements(form);
6002
+ /* istanbul ignore next: type will always be set or shared name wouldn't be allowed */
6003
+ const type = (_a = control.getAttributeValue("type")) !== null && _a !== void 0 ? _a : "";
6004
+ if (uniqueElements.has(name) ||
6005
+ (sharedElements.has(name) && sharedElements.get(name) !== type)) {
6006
+ const context = {
6007
+ name,
6008
+ };
6009
+ this.report({
6010
+ node: control,
6011
+ location: attr.valueLocation,
6012
+ message: 'Duplicate form control name "{{ name }}"',
6013
+ context,
6014
+ });
6015
+ }
6016
+ sharedElements.set(name, type);
6017
+ }
6018
+ getSelector() {
6019
+ const tags = this.getTagsWithProperty("formAssociated").filter((it) => {
6020
+ return this.isListedElement(it);
6021
+ });
6022
+ return tags.join(", ");
6023
+ }
6024
+ isListedElement(tagName) {
6025
+ const meta = this.getMetaFor(tagName);
6026
+ /* istanbul ignore if: the earlier check for getTagsWithProperty ensures
6027
+ * these will actually be set so this is just an untestable fallback */
6028
+ if (!meta || !meta.formAssociated) {
6029
+ return false;
6030
+ }
6031
+ return meta.formAssociated.listed;
6032
+ }
6033
+ getUniqueElements(form) {
6034
+ const existing = form.cacheGet(UNIQUE_CACHE_KEY);
6035
+ if (existing) {
6036
+ return existing;
6037
+ }
6038
+ else {
6039
+ const elements = new Set();
6040
+ form.cacheSet(UNIQUE_CACHE_KEY, elements);
6041
+ return elements;
6042
+ }
6043
+ }
6044
+ getSharedElements(form) {
6045
+ const existing = form.cacheGet(SHARED_CACHE_KEY);
6046
+ if (existing) {
6047
+ return existing;
6048
+ }
6049
+ else {
6050
+ const elements = new Map();
6051
+ form.cacheSet(SHARED_CACHE_KEY, elements);
6052
+ return elements;
6053
+ }
6054
+ }
6055
+ }
6056
+
5890
6057
  const defaults$j = {
5891
6058
  allowMultipleH1: false,
5892
6059
  minInitialRank: "h1",
5893
6060
  sectioningRoots: ["dialog", '[role="dialog"]'],
5894
6061
  };
5895
- function isRelevant$2(event) {
6062
+ function isRelevant$3(event) {
5896
6063
  const node = event.target;
5897
6064
  return Boolean(node.meta && node.meta.heading);
5898
6065
  }
@@ -5960,7 +6127,7 @@ class HeadingLevel extends Rule {
5960
6127
  };
5961
6128
  }
5962
6129
  setup() {
5963
- this.on("tag:start", isRelevant$2, (event) => this.onTagStart(event));
6130
+ this.on("tag:start", isRelevant$3, (event) => this.onTagStart(event));
5964
6131
  this.on("tag:ready", (event) => this.onTagReady(event));
5965
6132
  this.on("tag:close", (event) => this.onTagClose(event));
5966
6133
  }
@@ -6494,6 +6661,42 @@ class MapDupName extends Rule {
6494
6661
  }
6495
6662
  }
6496
6663
 
6664
+ function isRelevant$2(event) {
6665
+ return event.target.is("map");
6666
+ }
6667
+ function hasStaticValue(attr) {
6668
+ return Boolean(attr && !(attr.value instanceof DynamicValue));
6669
+ }
6670
+ class MapIdName extends Rule {
6671
+ documentation() {
6672
+ return {
6673
+ description: "When the `id` attribute is present on a `<map>` element it must be equal to the `name` attribute.",
6674
+ url: "https://html-validate.org/rules/map-id-name.html",
6675
+ };
6676
+ }
6677
+ setup() {
6678
+ this.on("tag:ready", isRelevant$2, (event) => {
6679
+ var _a;
6680
+ const { target } = event;
6681
+ const id = target.getAttribute("id");
6682
+ const name = target.getAttribute("name");
6683
+ // /* ignore missing attributes or dynamic value */
6684
+ if (!hasStaticValue(id) || !hasStaticValue(name)) {
6685
+ return;
6686
+ }
6687
+ /* ignore when id and name is the same */
6688
+ if (id.value === name.value) {
6689
+ return;
6690
+ }
6691
+ this.report({
6692
+ node: event.target,
6693
+ message: `"id" and "name" attribute must be the same on <map> elements`,
6694
+ location: (_a = id.valueLocation) !== null && _a !== void 0 ? _a : name.valueLocation,
6695
+ });
6696
+ });
6697
+ }
6698
+ }
6699
+
6497
6700
  class MissingDoctype extends Rule {
6498
6701
  documentation() {
6499
6702
  return {
@@ -8958,12 +9161,14 @@ const bundledRules = {
8958
9161
  "element-required-content": ElementRequiredContent,
8959
9162
  "empty-heading": EmptyHeading,
8960
9163
  "empty-title": EmptyTitle,
9164
+ "form-dup-name": FormDupName,
8961
9165
  "heading-level": HeadingLevel,
8962
9166
  "id-pattern": IdPattern,
8963
9167
  "input-attributes": InputAttributes,
8964
9168
  "input-missing-label": InputMissingLabel,
8965
9169
  "long-title": LongTitle,
8966
9170
  "map-dup-name": MapDupName,
9171
+ "map-id-name": MapIdName,
8967
9172
  "meta-refresh": MetaRefresh,
8968
9173
  "missing-doctype": MissingDoctype,
8969
9174
  "multiple-labeled-controls": MultipleLabeledControls,
@@ -9072,9 +9277,11 @@ const config$1 = {
9072
9277
  "element-required-content": "error",
9073
9278
  "empty-heading": "error",
9074
9279
  "empty-title": "error",
9280
+ "form-dup-name": "error",
9075
9281
  "input-attributes": "error",
9076
9282
  "long-title": "error",
9077
9283
  "map-dup-name": "error",
9284
+ "map-id-name": "error",
9078
9285
  "meta-refresh": "error",
9079
9286
  "multiple-labeled-controls": "error",
9080
9287
  "no-autoplay": ["error", { include: ["audio", "video"] }],
@@ -9135,6 +9342,7 @@ const config = {
9135
9342
  "element-required-attributes": "error",
9136
9343
  "element-required-content": "error",
9137
9344
  "map-dup-name": "error",
9345
+ "map-id-name": "error",
9138
9346
  "multiple-labeled-controls": "error",
9139
9347
  "no-deprecated-attr": "error",
9140
9348
  "no-dup-attr": "error",
@@ -9964,6 +10172,8 @@ class Parser {
9964
10172
  }
9965
10173
  /* resolve any dynamic meta element properties */
9966
10174
  this.dom.resolveMeta(this.metaTable);
10175
+ /* enable cache on root element, all children already have cached enabled */
10176
+ this.dom.root.cacheEnable();
9967
10177
  /* trigger any rules waiting for DOM ready */
9968
10178
  this.trigger("dom:ready", {
9969
10179
  document: this.dom,
@@ -10795,6 +11005,8 @@ class Engine {
10795
11005
  var _a, _b;
10796
11006
  /* wait for a tag to open and find the current block by using its parent */
10797
11007
  if (directiveBlock === null) {
11008
+ /* istanbul ignore next: there will always be a parent (root element if
11009
+ * nothing else) but typescript doesn't know that */
10798
11010
  directiveBlock = (_b = (_a = data.target.parent) === null || _a === void 0 ? void 0 : _a.unique) !== null && _b !== void 0 ? _b : null;
10799
11011
  }
10800
11012
  /* disable rules directly on the node so it will be recorded for later,
@@ -11223,7 +11435,7 @@ class HtmlValidate {
11223
11435
  /** @public */
11224
11436
  const name = "html-validate";
11225
11437
  /** @public */
11226
- const version = "7.11.1";
11438
+ const version = "7.12.1";
11227
11439
  /** @public */
11228
11440
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11229
11441
 
@@ -11666,5 +11878,5 @@ function getFormatter(name) {
11666
11878
  return (_a = availableFormatters[name]) !== null && _a !== void 0 ? _a : null;
11667
11879
  }
11668
11880
 
11669
- export { Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, HtmlValidate as H, MetaTable as M, NodeClosed as N, Parser as P, Rule as R, Severity as S, TextNode as T, UserError as U, WrappedError as W, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, HtmlElement as d, SchemaValidationError as e, NestedError as f, MetaCopyableProperty as g, Reporter as h, TemplateExtractor as i, definePlugin as j, getFormatter as k, legacyRequire as l, ensureError as m, configDataFromFile as n, compatibilityCheck as o, presets as p, codeframe as q, ruleExists as r, sliceLocation as s, isTextNode as t, isElementNode as u, version as v, generateIdSelector as w, name as x, bugs as y };
11881
+ export { Attribute as A, Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, HtmlValidate as H, MetaTable as M, NodeClosed as N, Parser as P, Rule as R, Severity as S, TextNode as T, UserError as U, WrappedError as W, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, HtmlElement as d, SchemaValidationError as e, NestedError as f, MetaCopyableProperty as g, Reporter as h, TemplateExtractor as i, definePlugin as j, getFormatter as k, legacyRequire as l, ensureError as m, configDataFromFile as n, compatibilityCheck as o, presets as p, codeframe as q, ruleExists as r, sliceLocation as s, isTextNode as t, isElementNode as u, version as v, generateIdSelector as w, name as x, bugs as y };
11670
11882
  //# sourceMappingURL=core.js.map