html-validate 5.5.0 → 6.1.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
@@ -574,18 +574,63 @@ const definitions = {
574
574
  type: "object",
575
575
  patternProperties: {
576
576
  "^.*$": {
577
- type: "array",
578
- uniqueItems: true,
579
- items: {
580
- anyOf: [
581
- {
577
+ anyOf: [
578
+ {
579
+ type: "object",
580
+ additionalProperties: false,
581
+ properties: {
582
+ boolean: {
583
+ type: "boolean",
584
+ title: "Set to true if this is a boolean attribute"
585
+ },
586
+ deprecated: {
587
+ title: "Set to true or string if this attribute is deprecated",
588
+ oneOf: [
589
+ {
590
+ type: "boolean"
591
+ },
592
+ {
593
+ type: "string"
594
+ }
595
+ ]
596
+ },
597
+ list: {
598
+ type: "boolean",
599
+ title: "Set to true if this attribute is a list of space-separated tokens, each which must be valid by itself"
600
+ },
601
+ "enum": {
602
+ type: "array",
603
+ title: "Exhaustive list of values (string or regex) this attribute accepts",
604
+ uniqueItems: true,
605
+ items: {
606
+ anyOf: [
607
+ {
608
+ type: "string"
609
+ },
610
+ {
611
+ regexp: true
612
+ }
613
+ ]
614
+ }
615
+ },
616
+ omit: {
617
+ type: "boolean",
618
+ title: "Set to true if this attribute can optionally omit its value"
619
+ },
620
+ required: {
621
+ type: "boolean",
622
+ title: "Set to true if this attribute is required"
623
+ }
624
+ }
625
+ },
626
+ {
627
+ type: "array",
628
+ uniqueItems: true,
629
+ items: {
582
630
  type: "string"
583
- },
584
- {
585
- regexp: true
586
631
  }
587
- ]
588
- }
632
+ }
633
+ ]
589
634
  }
590
635
  }
591
636
  },
@@ -636,6 +681,111 @@ var schema = {
636
681
  definitions: definitions
637
682
  };
638
683
 
684
+ var TextContent$1;
685
+ (function (TextContent) {
686
+ /* forbid node to have text content, inter-element whitespace is ignored */
687
+ TextContent["NONE"] = "none";
688
+ /* node can have text but not required too */
689
+ TextContent["DEFAULT"] = "default";
690
+ /* node requires text-nodes to be present (direct or by descendant) */
691
+ TextContent["REQUIRED"] = "required";
692
+ /* node requires accessible text (hidden text is ignored, tries to get text from accessibility tree) */
693
+ TextContent["ACCESSIBLE"] = "accessible";
694
+ })(TextContent$1 || (TextContent$1 = {}));
695
+ /**
696
+ * Properties listed here can be copied (loaded) onto another element using
697
+ * [[HtmlElement.loadMeta]].
698
+ */
699
+ const MetaCopyableProperty = [
700
+ "metadata",
701
+ "flow",
702
+ "sectioning",
703
+ "heading",
704
+ "phrasing",
705
+ "embedded",
706
+ "interactive",
707
+ "transparent",
708
+ "form",
709
+ "labelable",
710
+ "attributes",
711
+ "permittedContent",
712
+ "permittedDescendants",
713
+ "permittedOrder",
714
+ "requiredAncestors",
715
+ "requiredContent",
716
+ ];
717
+ /**
718
+ * @internal
719
+ */
720
+ function setMetaProperty(dst, key, value) {
721
+ dst[key] = value;
722
+ }
723
+
724
+ function isSet(value) {
725
+ return typeof value !== "undefined";
726
+ }
727
+ function flag(value) {
728
+ return value ? true : undefined;
729
+ }
730
+ function stripUndefined(src) {
731
+ const entries = Object.entries(src).filter(([, value]) => isSet(value));
732
+ return Object.fromEntries(entries);
733
+ }
734
+ function migrateSingleAttribute(src, key) {
735
+ var _a, _b;
736
+ const result = {};
737
+ result.deprecated = flag((_a = src.deprecatedAttributes) === null || _a === void 0 ? void 0 : _a.includes(key));
738
+ result.required = flag((_b = src.requiredAttributes) === null || _b === void 0 ? void 0 : _b.includes(key));
739
+ result.omit = undefined;
740
+ const attr = src.attributes ? src.attributes[key] : undefined;
741
+ if (typeof attr === "undefined") {
742
+ return stripUndefined(result);
743
+ }
744
+ /* when the attribute is set to null we use a special property "delete" to
745
+ * flag it, if it is still set during merge (inheritance, overwriting, etc) the attribute will be removed */
746
+ if (attr === null) {
747
+ result.delete = true;
748
+ return stripUndefined(result);
749
+ }
750
+ if (Array.isArray(attr)) {
751
+ if (attr.length === 0) {
752
+ result.boolean = true;
753
+ }
754
+ else {
755
+ result.enum = attr.filter((it) => it !== "");
756
+ if (attr.includes("")) {
757
+ result.omit = true;
758
+ }
759
+ }
760
+ return stripUndefined(result);
761
+ }
762
+ else {
763
+ return stripUndefined({ ...result, ...attr });
764
+ }
765
+ }
766
+ function migrateAttributes(src) {
767
+ var _a, _b, _c;
768
+ const keys = [
769
+ ...Object.keys((_a = src.attributes) !== null && _a !== void 0 ? _a : {}),
770
+ ...((_b = src.requiredAttributes) !== null && _b !== void 0 ? _b : []),
771
+ ...((_c = src.deprecatedAttributes) !== null && _c !== void 0 ? _c : []),
772
+ ].sort();
773
+ const entries = keys.map((key) => {
774
+ return [key, migrateSingleAttribute(src, key)];
775
+ });
776
+ return Object.fromEntries(entries);
777
+ }
778
+ function migrateElement(src) {
779
+ const result = {
780
+ ...src,
781
+ attributes: migrateAttributes(src),
782
+ };
783
+ /* removed properties */
784
+ delete result.deprecatedAttributes;
785
+ delete result.requiredAttributes;
786
+ return result;
787
+ }
788
+
639
789
  const dynamicKeys = [
640
790
  "metadata",
641
791
  "flow",
@@ -729,7 +879,7 @@ class MetaTable {
729
879
  for (const [key, value] of Object.entries(obj)) {
730
880
  if (key === "$schema")
731
881
  continue;
732
- this.addEntry(key, value);
882
+ this.addEntry(key, migrateElement(value));
733
883
  }
734
884
  }
735
885
  /**
@@ -757,7 +907,7 @@ class MetaTable {
757
907
  */
758
908
  getMetaFor(tagName) {
759
909
  tagName = tagName.toLowerCase();
760
- return this.elements[tagName] ? Object.assign({}, this.elements[tagName]) : null;
910
+ return this.elements[tagName] ? { ...this.elements[tagName] } : null;
761
911
  }
762
912
  /**
763
913
  * Find all tags which has enabled given property.
@@ -786,7 +936,7 @@ class MetaTable {
786
936
  }
787
937
  }
788
938
  /* merge all sources together */
789
- const expanded = deepmerge(parent, Object.assign(Object.assign({}, entry), { tagName }), { arrayMerge: overwriteMerge$1 });
939
+ const expanded = this.mergeElement(parent, { ...entry, tagName });
790
940
  expandRegex(expanded);
791
941
  this.elements[tagName] = expanded;
792
942
  }
@@ -824,7 +974,16 @@ class MetaTable {
824
974
  }
825
975
  }
826
976
  mergeElement(a, b) {
827
- return deepmerge(a, b, { arrayMerge: overwriteMerge$1 });
977
+ const merged = deepmerge(a, b, { arrayMerge: overwriteMerge$1 });
978
+ /* special handling when removing attributes by setting them to null
979
+ * resulting in the deletion flag being set */
980
+ const filteredAttrs = Object.entries(merged.attributes).filter(([, attr]) => {
981
+ const val = !attr.delete;
982
+ delete attr.delete;
983
+ return val;
984
+ });
985
+ merged.attributes = Object.fromEntries(filteredAttrs);
986
+ return merged;
828
987
  }
829
988
  resolve(node) {
830
989
  if (node.meta) {
@@ -836,7 +995,7 @@ function expandProperties(node, entry) {
836
995
  for (const key of dynamicKeys) {
837
996
  const property = entry[key];
838
997
  if (property && typeof property !== "boolean") {
839
- entry[key] = evaluateProperty(node, property);
998
+ setMetaProperty(entry, key, evaluateProperty(node, property));
840
999
  }
841
1000
  }
842
1001
  }
@@ -862,14 +1021,9 @@ function expandRegexValue(value) {
862
1021
  * Expand all regular expressions in strings ("/../"). This mutates the object.
863
1022
  */
864
1023
  function expandRegex(entry) {
865
- if (!entry.attributes)
866
- return;
867
1024
  for (const [name, values] of Object.entries(entry.attributes)) {
868
- if (values) {
869
- entry.attributes[name] = values.map(expandRegexValue);
870
- }
871
- else {
872
- delete entry.attributes[name];
1025
+ if (values.enum) {
1026
+ entry.attributes[name].enum = values.enum.map(expandRegexValue);
873
1027
  }
874
1028
  }
875
1029
  }
@@ -925,41 +1079,6 @@ function matchAttribute(node, match) {
925
1079
  }
926
1080
  }
927
1081
 
928
- var TextContent$1;
929
- (function (TextContent) {
930
- /* forbid node to have text content, inter-element whitespace is ignored */
931
- TextContent["NONE"] = "none";
932
- /* node can have text but not required too */
933
- TextContent["DEFAULT"] = "default";
934
- /* node requires text-nodes to be present (direct or by descendant) */
935
- TextContent["REQUIRED"] = "required";
936
- /* node requires accessible text (hidden text is ignored, tries to get text from accessibility tree) */
937
- TextContent["ACCESSIBLE"] = "accessible";
938
- })(TextContent$1 || (TextContent$1 = {}));
939
- /**
940
- * Properties listed here can be copied (loaded) onto another element using
941
- * [[HtmlElement.loadMeta]].
942
- */
943
- const MetaCopyableProperty = [
944
- "metadata",
945
- "flow",
946
- "sectioning",
947
- "heading",
948
- "phrasing",
949
- "embedded",
950
- "interactive",
951
- "transparent",
952
- "form",
953
- "labelable",
954
- "requiredAttributes",
955
- "attributes",
956
- "permittedContent",
957
- "permittedDescendants",
958
- "permittedOrder",
959
- "requiredAncestors",
960
- "requiredContent",
961
- ];
962
-
963
1082
  class DynamicValue {
964
1083
  constructor(expr) {
965
1084
  this.expr = expr;
@@ -1811,8 +1930,9 @@ class HtmlElement extends DOMNode {
1811
1930
  this.metaElement = {};
1812
1931
  }
1813
1932
  for (const key of MetaCopyableProperty) {
1814
- if (typeof meta[key] !== "undefined") {
1815
- this.metaElement[key] = meta[key];
1933
+ const value = meta[key];
1934
+ if (typeof value !== "undefined") {
1935
+ setMetaProperty(this.metaElement, key, value);
1816
1936
  }
1817
1937
  else {
1818
1938
  delete this.metaElement[key];
@@ -2228,6 +2348,7 @@ class Validator {
2228
2348
  * @param rules - Element attribute metadta.
2229
2349
  * @returns `true` if attribute passes all tests.
2230
2350
  */
2351
+ /* eslint-disable-next-line complexity */
2231
2352
  static validateAttribute(attr, rules) {
2232
2353
  const rule = rules[attr.key];
2233
2354
  if (!rule) {
@@ -2241,19 +2362,34 @@ class Validator {
2241
2362
  return true;
2242
2363
  }
2243
2364
  const empty = value === null || value === "";
2244
- /* consider an empty array as being a boolean attribute */
2245
- if (rule.length === 0) {
2365
+ /* if boolean is set the value can be either null, empty string or the
2366
+ * attribute key (attribute-boolean-style regulates style) */
2367
+ if (rule.boolean) {
2246
2368
  return empty || value === attr.key;
2247
2369
  }
2248
- /* if the empty string is present allow both "" and null
2249
- * (boolean-attribute-style will regulate which is allowed) */
2250
- if (rule.includes("") && empty) {
2370
+ /* if omit is set the value can be either null or empty string
2371
+ * (attribute-empty style regulates style) */
2372
+ if (rule.omit && empty) {
2373
+ return true;
2374
+ }
2375
+ /* validate each token when using list, all tokens must be valid */
2376
+ if (rule.list) {
2377
+ const tokens = new DOMTokenList(value, attr.valueLocation);
2378
+ return tokens.every((token) => {
2379
+ return this.validateAttributeValue(token, rule);
2380
+ });
2381
+ }
2382
+ return this.validateAttributeValue(value, rule);
2383
+ }
2384
+ static validateAttributeValue(value, rule) {
2385
+ /* skip attribute if it not have enumerated list */
2386
+ if (!rule.enum) {
2251
2387
  return true;
2252
2388
  }
2253
2389
  if (value === null || value === undefined) {
2254
2390
  return false;
2255
2391
  }
2256
- return rule.some((entry) => {
2392
+ return rule.enum.some((entry) => {
2257
2393
  if (entry instanceof RegExp) {
2258
2394
  return !!value.match(entry);
2259
2395
  }
@@ -2519,12 +2655,12 @@ var configurationSchema = {
2519
2655
  const espree = legacyRequire("espree");
2520
2656
  const walk = legacyRequire("acorn-walk");
2521
2657
  function joinTemplateLiteral(nodes) {
2522
- let offset = nodes[0].start;
2658
+ let offset = nodes[0].start + 1;
2523
2659
  let output = "";
2524
2660
  for (const node of nodes) {
2525
- output += " ".repeat(node.start - offset);
2661
+ output += " ".repeat(node.start + 1 - offset);
2526
2662
  output += node.value.raw;
2527
- offset = node.end;
2663
+ offset = node.end - 2;
2528
2664
  }
2529
2665
  return output;
2530
2666
  }
@@ -2716,7 +2852,7 @@ var TRANSFORMER_API;
2716
2852
  })(TRANSFORMER_API || (TRANSFORMER_API = {}));
2717
2853
 
2718
2854
  const name = "html-validate";
2719
- const version = "5.5.0";
2855
+ const version = "6.1.0";
2720
2856
  const homepage = "https://html-validate.org";
2721
2857
  const bugs = {
2722
2858
  url: "https://gitlab.com/html-validate/html-validate/issues/new"
@@ -3006,24 +3142,60 @@ const description = {
3006
3142
  ["absolute" /* ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
3007
3143
  ["anchor" /* ANCHOR */]: null,
3008
3144
  };
3145
+ function parseAllow(value) {
3146
+ if (typeof value === "boolean") {
3147
+ return value;
3148
+ }
3149
+ return {
3150
+ /* eslint-disable security/detect-non-literal-regexp */
3151
+ include: value.include ? value.include.map((it) => new RegExp(it)) : null,
3152
+ exclude: value.exclude ? value.exclude.map((it) => new RegExp(it)) : null,
3153
+ /* eslint-enable security/detect-non-literal-regexp */
3154
+ };
3155
+ }
3156
+ /**
3157
+ * @internal
3158
+ */
3159
+ function matchList(value, list) {
3160
+ if (list.include && !list.include.some((it) => it.test(value))) {
3161
+ return false;
3162
+ }
3163
+ if (list.exclude && list.exclude.some((it) => it.test(value))) {
3164
+ return false;
3165
+ }
3166
+ return true;
3167
+ }
3009
3168
  class AllowedLinks extends Rule {
3010
3169
  constructor(options) {
3011
- super(Object.assign(Object.assign({}, defaults$p), options));
3170
+ super({ ...defaults$p, ...options });
3171
+ this.allowExternal = parseAllow(this.options.allowExternal);
3172
+ this.allowRelative = parseAllow(this.options.allowRelative);
3173
+ this.allowAbsolute = parseAllow(this.options.allowAbsolute);
3012
3174
  }
3013
3175
  static schema() {
3176
+ const booleanOrObject = {
3177
+ anyOf: [
3178
+ { type: "boolean" },
3179
+ {
3180
+ type: "object",
3181
+ properties: {
3182
+ include: {
3183
+ type: "array",
3184
+ items: { type: "string" },
3185
+ },
3186
+ exclude: {
3187
+ type: "array",
3188
+ items: { type: "string" },
3189
+ },
3190
+ },
3191
+ },
3192
+ ],
3193
+ };
3014
3194
  return {
3015
- allowAbsolute: {
3016
- type: "boolean",
3017
- },
3018
- allowBase: {
3019
- type: "boolean",
3020
- },
3021
- allowExternal: {
3022
- type: "boolean",
3023
- },
3024
- allowRelative: {
3025
- type: "boolean",
3026
- },
3195
+ allowExternal: { ...booleanOrObject },
3196
+ allowRelative: { ...booleanOrObject },
3197
+ allowAbsolute: { ...booleanOrObject },
3198
+ allowBase: { type: "boolean" },
3027
3199
  };
3028
3200
  }
3029
3201
  documentation(context) {
@@ -3045,16 +3217,16 @@ class AllowedLinks extends Rule {
3045
3217
  /* anchor links are always allowed by this rule */
3046
3218
  break;
3047
3219
  case "absolute" /* ABSOLUTE */:
3048
- this.handleAbsolute(event, style);
3220
+ this.handleAbsolute(link, event, style);
3049
3221
  break;
3050
3222
  case "external" /* EXTERNAL */:
3051
- this.handleExternal(event, style);
3223
+ this.handleExternal(link, event, style);
3052
3224
  break;
3053
3225
  case "relative-base" /* RELATIVE_BASE */:
3054
- this.handleRelativeBase(event, style);
3226
+ this.handleRelativeBase(link, event, style);
3055
3227
  break;
3056
3228
  case "relative-path" /* RELATIVE_PATH */:
3057
- this.handleRelativePath(event, style);
3229
+ this.handleRelativePath(link, event, style);
3058
3230
  break;
3059
3231
  }
3060
3232
  });
@@ -3088,28 +3260,49 @@ class AllowedLinks extends Rule {
3088
3260
  return "relative-base" /* RELATIVE_BASE */;
3089
3261
  }
3090
3262
  }
3091
- handleAbsolute(event, style) {
3092
- const { allowAbsolute } = this.options;
3093
- if (!allowAbsolute) {
3263
+ handleAbsolute(target, event, style) {
3264
+ const { allowAbsolute } = this;
3265
+ if (allowAbsolute === true) {
3266
+ return;
3267
+ }
3268
+ else if (allowAbsolute === false) {
3094
3269
  this.report(event.target, "Link destination must not be absolute url", event.valueLocation, style);
3095
3270
  }
3271
+ else if (!matchList(target, allowAbsolute)) {
3272
+ this.report(event.target, "Absolute link to this destination is not allowed by current configuration", event.valueLocation, style);
3273
+ }
3096
3274
  }
3097
- handleExternal(event, style) {
3098
- const { allowExternal } = this.options;
3099
- if (!allowExternal) {
3275
+ handleExternal(target, event, style) {
3276
+ const { allowExternal } = this;
3277
+ if (allowExternal === true) {
3278
+ return;
3279
+ }
3280
+ else if (allowExternal === false) {
3100
3281
  this.report(event.target, "Link destination must not be external url", event.valueLocation, style);
3101
3282
  }
3283
+ else if (!matchList(target, allowExternal)) {
3284
+ this.report(event.target, "External link to this destination is not allowed by current configuration", event.valueLocation, style);
3285
+ }
3102
3286
  }
3103
- handleRelativePath(event, style) {
3104
- const { allowRelative } = this.options;
3105
- if (!allowRelative) {
3287
+ handleRelativePath(target, event, style) {
3288
+ const { allowRelative } = this;
3289
+ if (allowRelative === true) {
3290
+ return false;
3291
+ }
3292
+ else if (allowRelative === false) {
3106
3293
  this.report(event.target, "Link destination must not be relative url", event.valueLocation, style);
3294
+ return true;
3295
+ }
3296
+ else if (!matchList(target, allowRelative)) {
3297
+ this.report(event.target, "Relative link to this destination is not allowed by current configuration", event.valueLocation, style);
3298
+ return true;
3107
3299
  }
3300
+ return false;
3108
3301
  }
3109
- handleRelativeBase(event, style) {
3110
- const { allowRelative, allowBase } = this.options;
3111
- if (!allowRelative) {
3112
- this.report(event.target, "Link destination must not be relative url", event.valueLocation, style);
3302
+ handleRelativeBase(target, event, style) {
3303
+ const { allowBase } = this.options;
3304
+ if (this.handleRelativePath(target, event, style)) {
3305
+ return;
3113
3306
  }
3114
3307
  else if (!allowBase) {
3115
3308
  this.report(event.target, "Relative links must be relative to current folder", event.valueLocation, style);
@@ -3256,7 +3449,7 @@ const defaults$o = {
3256
3449
  };
3257
3450
  class AttrCase extends Rule {
3258
3451
  constructor(options) {
3259
- super(Object.assign(Object.assign({}, defaults$o), options));
3452
+ super({ ...defaults$o, ...options });
3260
3453
  this.style = new CaseStyle(this.options.style, "attr-case");
3261
3454
  }
3262
3455
  static schema() {
@@ -3346,7 +3539,7 @@ const MATCH_XML_TAG = /^<\?xml.*?\?>\s+/;
3346
3539
  const MATCH_TAG_OPEN = /^<(\/?)([a-zA-Z0-9\-:]+)/; // https://www.w3.org/TR/html/syntax.html#start-tags
3347
3540
  const MATCH_TAG_CLOSE = /^\/?>/;
3348
3541
  const MATCH_TEXT = /^[^]*?(?=(?:[ \t]*(?:\r\n|\r|\n)|<[^ ]|$))/;
3349
- const MATCH_TEMPLATING = /^(?:<%.*?%>|<\?.*?\?>|<\$.*?\$>)/;
3542
+ const MATCH_TEMPLATING = /^(?:<%.*?%>|<\?.*?\?>|<\$.*?\$>)/s;
3350
3543
  const MATCH_TAG_LOOKAHEAD = /^[^]*?(?=<|$)/;
3351
3544
  const MATCH_ATTR_START = /^([^\t\r\n\f \/><"'=]+)/; // https://www.w3.org/TR/html/syntax.html#elements-attributes
3352
3545
  const MATCH_ATTR_SINGLE = /^(\s*=\s*)'([^']*?)(')/;
@@ -3609,7 +3802,7 @@ function generateDescription(name, pattern) {
3609
3802
  }
3610
3803
  class AttrPattern extends Rule {
3611
3804
  constructor(options) {
3612
- super(Object.assign(Object.assign({}, defaults$n), options));
3805
+ super({ ...defaults$n, ...options });
3613
3806
  this.pattern = generateRegexp(this.options.pattern);
3614
3807
  }
3615
3808
  static schema() {
@@ -3675,7 +3868,7 @@ const defaults$m = {
3675
3868
  };
3676
3869
  class AttrQuotes extends Rule {
3677
3870
  constructor(options) {
3678
- super(Object.assign(Object.assign({}, defaults$m), options));
3871
+ super({ ...defaults$m, ...options });
3679
3872
  this.style = parseStyle$4(this.options.style);
3680
3873
  }
3681
3874
  static schema() {
@@ -3771,11 +3964,11 @@ class AttributeAllowedValues extends Rule {
3771
3964
  if (!context) {
3772
3965
  return docs;
3773
3966
  }
3774
- if (context.allowed.length > 0) {
3775
- const allowed = context.allowed.map((val) => `- \`${val}\``);
3967
+ if (context.allowed.enum) {
3968
+ const allowed = context.allowed.enum.map((val) => `- \`${val}\``);
3776
3969
  docs.description = `Element <${context.element}> does not allow attribute \`${context.attribute}\` to have the value \`"${context.value}"\`, it must match one of the following:\n\n${allowed.join("\n")}`;
3777
3970
  }
3778
- else {
3971
+ else if (context.allowed.boolean) {
3779
3972
  docs.description = `Element <${context.element}> attribute \`${context.attribute}\` must be a boolean attribute, e.g. \`<${context.element} ${context.attribute}>\``;
3780
3973
  }
3781
3974
  return docs;
@@ -3831,7 +4024,7 @@ const defaults$l = {
3831
4024
  };
3832
4025
  class AttributeBooleanStyle extends Rule {
3833
4026
  constructor(options) {
3834
- super(Object.assign(Object.assign({}, defaults$l), options));
4027
+ super({ ...defaults$l, ...options });
3835
4028
  this.hasInvalidStyle = parseStyle$3(this.options.style);
3836
4029
  }
3837
4030
  static schema() {
@@ -3876,7 +4069,8 @@ class AttributeBooleanStyle extends Rule {
3876
4069
  });
3877
4070
  }
3878
4071
  isBoolean(attr, rules) {
3879
- return rules[attr.key] && rules[attr.key].length === 0;
4072
+ var _a;
4073
+ return Boolean((_a = rules[attr.key]) === null || _a === void 0 ? void 0 : _a.boolean);
3880
4074
  }
3881
4075
  }
3882
4076
  function parseStyle$3(style) {
@@ -3911,7 +4105,7 @@ const defaults$k = {
3911
4105
  };
3912
4106
  class AttributeEmptyStyle extends Rule {
3913
4107
  constructor(options) {
3914
- super(Object.assign(Object.assign({}, defaults$k), options));
4108
+ super({ ...defaults$k, ...options });
3915
4109
  this.hasInvalidStyle = parseStyle$2(this.options.style);
3916
4110
  }
3917
4111
  static schema() {
@@ -3960,7 +4154,8 @@ class AttributeEmptyStyle extends Rule {
3960
4154
  }
3961
4155
  }
3962
4156
  function allowsEmpty(attr, rules) {
3963
- return rules[attr.key] && rules[attr.key].includes("");
4157
+ var _a;
4158
+ return Boolean((_a = rules[attr.key]) === null || _a === void 0 ? void 0 : _a.omit);
3964
4159
  }
3965
4160
  function isEmptyValue(attr) {
3966
4161
  /* dynamic values are ignored, assumed to contain a value */
@@ -4023,7 +4218,7 @@ const defaults$j = {
4023
4218
  };
4024
4219
  class ClassPattern extends Rule {
4025
4220
  constructor(options) {
4026
- super(Object.assign(Object.assign({}, defaults$j), options));
4221
+ super({ ...defaults$j, ...options });
4027
4222
  this.pattern = parsePattern(this.options.pattern);
4028
4223
  }
4029
4224
  static schema() {
@@ -4136,7 +4331,7 @@ const defaults$i = {
4136
4331
  };
4137
4332
  class Deprecated extends Rule {
4138
4333
  constructor(options) {
4139
- super(Object.assign(Object.assign({}, defaults$i), options));
4334
+ super({ ...defaults$i, ...options });
4140
4335
  }
4141
4336
  static schema() {
4142
4337
  return {
@@ -4230,7 +4425,7 @@ class Deprecated extends Rule {
4230
4425
  this.report(node, message, location, context);
4231
4426
  }
4232
4427
  reportObject(deprecated, node, location) {
4233
- const context = Object.assign(Object.assign({}, deprecated), { tagName: node.tagName });
4428
+ const context = { ...deprecated, tagName: node.tagName };
4234
4429
  const notice = deprecated.message ? `: ${deprecated.message}` : "";
4235
4430
  const message = `<${node.tagName}> is deprecated${notice}`;
4236
4431
  this.report(node, message, location, context);
@@ -4304,7 +4499,7 @@ const defaults$h = {
4304
4499
  };
4305
4500
  class DoctypeStyle extends Rule {
4306
4501
  constructor(options) {
4307
- super(Object.assign(Object.assign({}, defaults$h), options));
4502
+ super({ ...defaults$h, ...options });
4308
4503
  }
4309
4504
  static schema() {
4310
4505
  return {
@@ -4341,7 +4536,7 @@ const defaults$g = {
4341
4536
  };
4342
4537
  class ElementCase extends Rule {
4343
4538
  constructor(options) {
4344
- super(Object.assign(Object.assign({}, defaults$g), options));
4539
+ super({ ...defaults$g, ...options });
4345
4540
  this.style = new CaseStyle(this.options.style, "element-case");
4346
4541
  }
4347
4542
  static schema() {
@@ -4411,7 +4606,7 @@ const defaults$f = {
4411
4606
  };
4412
4607
  class ElementName extends Rule {
4413
4608
  constructor(options) {
4414
- super(Object.assign(Object.assign({}, defaults$f), options));
4609
+ super({ ...defaults$f, ...options });
4415
4610
  // eslint-disable-next-line security/detect-non-literal-regexp
4416
4611
  this.pattern = new RegExp(this.options.pattern);
4417
4612
  }
@@ -4683,9 +4878,14 @@ class ElementRequiredAttributes extends Rule {
4683
4878
  this.on("tag:end", (event) => {
4684
4879
  const node = event.previous;
4685
4880
  const meta = node.meta;
4686
- if (!meta || !meta.requiredAttributes)
4881
+ /* handle missing metadata and missing attributes */
4882
+ if (!meta || !meta.attributes) {
4687
4883
  return;
4688
- for (const key of meta.requiredAttributes) {
4884
+ }
4885
+ for (const [key, attr] of Object.entries(meta.attributes)) {
4886
+ if (!attr.required) {
4887
+ continue;
4888
+ }
4689
4889
  if (node.hasAttribute(key))
4690
4890
  continue;
4691
4891
  const context = {
@@ -4868,7 +5068,7 @@ function parseMaxInitial(value) {
4868
5068
  }
4869
5069
  class HeadingLevel extends Rule {
4870
5070
  constructor(options) {
4871
- super(Object.assign(Object.assign({}, defaults$e), options));
5071
+ super({ ...defaults$e, ...options });
4872
5072
  this.stack = [];
4873
5073
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
4874
5074
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -5031,7 +5231,7 @@ const defaults$d = {
5031
5231
  };
5032
5232
  class IdPattern extends Rule {
5033
5233
  constructor(options) {
5034
- super(Object.assign(Object.assign({}, defaults$d), options));
5234
+ super({ ...defaults$d, ...options });
5035
5235
  this.pattern = parsePattern(this.options.pattern);
5036
5236
  }
5037
5237
  static schema() {
@@ -5383,7 +5583,7 @@ const defaults$c = {
5383
5583
  };
5384
5584
  class LongTitle extends Rule {
5385
5585
  constructor(options) {
5386
- super(Object.assign(Object.assign({}, defaults$c), options));
5586
+ super({ ...defaults$c, ...options });
5387
5587
  this.maxlength = this.options.maxlength;
5388
5588
  }
5389
5589
  static schema() {
@@ -5536,7 +5736,7 @@ const defaults$b = {
5536
5736
  };
5537
5737
  class NoAutoplay extends Rule {
5538
5738
  constructor(options) {
5539
- super(Object.assign(Object.assign({}, defaults$b), options));
5739
+ super({ ...defaults$b, ...options });
5540
5740
  }
5541
5741
  documentation(context) {
5542
5742
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -5632,8 +5832,12 @@ class NoDeprecatedAttr extends Rule {
5632
5832
  if (meta === null) {
5633
5833
  return;
5634
5834
  }
5635
- const deprecated = meta.deprecatedAttributes || [];
5636
- if (deprecated.includes(attr)) {
5835
+ const metaAttribute = meta.attributes && meta.attributes[attr];
5836
+ if (!metaAttribute) {
5837
+ return;
5838
+ }
5839
+ const deprecated = metaAttribute.deprecated;
5840
+ if (deprecated) {
5637
5841
  this.report(node, `Attribute "${event.key}" is deprecated on <${node.tagName}> element`, event.keyLocation);
5638
5842
  }
5639
5843
  });
@@ -5790,7 +5994,7 @@ function getCSSDeclarations(value) {
5790
5994
  }
5791
5995
  class NoInlineStyle extends Rule {
5792
5996
  constructor(options) {
5793
- super(Object.assign(Object.assign({}, defaults$a), options));
5997
+ super({ ...defaults$a, ...options });
5794
5998
  }
5795
5999
  static schema() {
5796
6000
  return {
@@ -6000,7 +6204,7 @@ const defaults$9 = {
6000
6204
  };
6001
6205
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
6002
6206
  const unquotedAttrRegexp = /([<>"'=`]|&(?![a-zA-Z0-9#]+;))/g;
6003
- const matchTemplate = /^(<%.*?%>|<\?.*?\?>|<\$.*?\$>)$/;
6207
+ const matchTemplate = /^(<%.*?%>|<\?.*?\?>|<\$.*?\$>)$/s;
6004
6208
  const replacementTable = new Map([
6005
6209
  ['"', "&quot;"],
6006
6210
  ["&", "&amp;"],
@@ -6012,7 +6216,7 @@ const replacementTable = new Map([
6012
6216
  ]);
6013
6217
  class NoRawCharacters extends Rule {
6014
6218
  constructor(options) {
6015
- super(Object.assign(Object.assign({}, defaults$9), options));
6219
+ super({ ...defaults$9, ...options });
6016
6220
  this.relaxed = this.options.relaxed;
6017
6221
  }
6018
6222
  static schema() {
@@ -6197,7 +6401,7 @@ const defaults$8 = {
6197
6401
  };
6198
6402
  class NoSelfClosing extends Rule {
6199
6403
  constructor(options) {
6200
- super(Object.assign(Object.assign({}, defaults$8), options));
6404
+ super({ ...defaults$8, ...options });
6201
6405
  }
6202
6406
  static schema() {
6203
6407
  return {
@@ -6336,7 +6540,7 @@ const defaults$7 = {
6336
6540
  };
6337
6541
  class PreferButton extends Rule {
6338
6542
  constructor(options) {
6339
- super(Object.assign(Object.assign({}, defaults$7), options));
6543
+ super({ ...defaults$7, ...options });
6340
6544
  }
6341
6545
  static schema() {
6342
6546
  return {
@@ -6441,7 +6645,7 @@ const defaults$6 = {
6441
6645
  };
6442
6646
  class PreferNativeElement extends Rule {
6443
6647
  constructor(options) {
6444
- super(Object.assign(Object.assign({}, defaults$6), options));
6648
+ super({ ...defaults$6, ...options });
6445
6649
  }
6446
6650
  static schema() {
6447
6651
  return {
@@ -6571,7 +6775,7 @@ const supportSri = {
6571
6775
  };
6572
6776
  class RequireSri extends Rule {
6573
6777
  constructor(options) {
6574
- super(Object.assign(Object.assign({}, defaults$5), options));
6778
+ super({ ...defaults$5, ...options });
6575
6779
  this.target = this.options.target;
6576
6780
  }
6577
6781
  static schema() {
@@ -8666,7 +8870,7 @@ const defaults$4 = {
8666
8870
  };
8667
8871
  class Void extends Rule {
8668
8872
  constructor(options) {
8669
- super(Object.assign(Object.assign({}, defaults$4), options));
8873
+ super({ ...defaults$4, ...options });
8670
8874
  this.style = parseStyle$1(this.options.style);
8671
8875
  }
8672
8876
  get deprecated() {
@@ -8775,7 +8979,7 @@ const defaults$3 = {
8775
8979
  };
8776
8980
  class VoidStyle extends Rule {
8777
8981
  constructor(options) {
8778
- super(Object.assign(Object.assign({}, defaults$3), options));
8982
+ super({ ...defaults$3, ...options });
8779
8983
  this.style = parseStyle(this.options.style);
8780
8984
  }
8781
8985
  static schema() {
@@ -8978,7 +9182,7 @@ const defaults$2 = {
8978
9182
  };
8979
9183
  class H37 extends Rule {
8980
9184
  constructor(options) {
8981
- super(Object.assign(Object.assign({}, defaults$2), options));
9185
+ super({ ...defaults$2, ...options });
8982
9186
  /* ensure alias is array */
8983
9187
  if (!Array.isArray(this.options.alias)) {
8984
9188
  this.options.alias = [this.options.alias];
@@ -9105,7 +9309,73 @@ const bundledRules$1 = {
9105
9309
  "wcag/h71": H71,
9106
9310
  };
9107
9311
 
9108
- const bundledRules = Object.assign({ "allowed-links": AllowedLinks, "aria-label-misuse": AriaLabelMisuse, "attr-case": AttrCase, "attr-delimiter": AttrDelimiter, "attr-pattern": AttrPattern, "attr-quotes": AttrQuotes, "attr-spacing": AttrSpacing, "attribute-allowed-values": AttributeAllowedValues, "attribute-boolean-style": AttributeBooleanStyle, "attribute-empty-style": AttributeEmptyStyle, "class-pattern": ClassPattern, "close-attr": CloseAttr, "close-order": CloseOrder, deprecated: Deprecated, "deprecated-rule": DeprecatedRule, "doctype-html": NoStyleTag$1, "doctype-style": DoctypeStyle, "element-case": ElementCase, "element-name": ElementName, "element-permitted-content": ElementPermittedContent, "element-permitted-occurrences": ElementPermittedOccurrences, "element-permitted-order": ElementPermittedOrder, "element-required-attributes": ElementRequiredAttributes, "element-required-content": ElementRequiredContent, "empty-heading": EmptyHeading, "empty-title": EmptyTitle, "heading-level": HeadingLevel, "id-pattern": IdPattern, "input-attributes": InputAttributes, "input-missing-label": InputMissingLabel, "long-title": LongTitle, "meta-refresh": MetaRefresh, "missing-doctype": MissingDoctype, "multiple-labeled-controls": MultipleLabeledControls, "no-autoplay": NoAutoplay, "no-conditional-comment": NoConditionalComment, "no-deprecated-attr": NoDeprecatedAttr, "no-dup-attr": NoDupAttr, "no-dup-class": NoDupClass, "no-dup-id": NoDupID, "no-implicit-close": NoImplicitClose, "no-inline-style": NoInlineStyle, "no-missing-references": NoMissingReferences, "no-multiple-main": NoMultipleMain, "no-raw-characters": NoRawCharacters, "no-redundant-for": NoRedundantFor, "no-redundant-role": NoRedundantRole, "no-self-closing": NoSelfClosing, "no-style-tag": NoStyleTag, "no-trailing-whitespace": NoTrailingWhitespace, "no-unknown-elements": NoUnknownElements, "no-utf8-bom": NoUtf8Bom, "prefer-button": PreferButton, "prefer-native-element": PreferNativeElement, "prefer-tbody": PreferTbody, "require-sri": RequireSri, "script-element": ScriptElement, "script-type": ScriptType, "svg-focusable": SvgFocusable, "text-content": TextContent, "unrecognized-char-ref": UnknownCharReference, void: Void, "void-content": VoidContent, "void-style": VoidStyle }, bundledRules$1);
9312
+ const bundledRules = {
9313
+ "allowed-links": AllowedLinks,
9314
+ "aria-label-misuse": AriaLabelMisuse,
9315
+ "attr-case": AttrCase,
9316
+ "attr-delimiter": AttrDelimiter,
9317
+ "attr-pattern": AttrPattern,
9318
+ "attr-quotes": AttrQuotes,
9319
+ "attr-spacing": AttrSpacing,
9320
+ "attribute-allowed-values": AttributeAllowedValues,
9321
+ "attribute-boolean-style": AttributeBooleanStyle,
9322
+ "attribute-empty-style": AttributeEmptyStyle,
9323
+ "class-pattern": ClassPattern,
9324
+ "close-attr": CloseAttr,
9325
+ "close-order": CloseOrder,
9326
+ deprecated: Deprecated,
9327
+ "deprecated-rule": DeprecatedRule,
9328
+ "doctype-html": NoStyleTag$1,
9329
+ "doctype-style": DoctypeStyle,
9330
+ "element-case": ElementCase,
9331
+ "element-name": ElementName,
9332
+ "element-permitted-content": ElementPermittedContent,
9333
+ "element-permitted-occurrences": ElementPermittedOccurrences,
9334
+ "element-permitted-order": ElementPermittedOrder,
9335
+ "element-required-attributes": ElementRequiredAttributes,
9336
+ "element-required-content": ElementRequiredContent,
9337
+ "empty-heading": EmptyHeading,
9338
+ "empty-title": EmptyTitle,
9339
+ "heading-level": HeadingLevel,
9340
+ "id-pattern": IdPattern,
9341
+ "input-attributes": InputAttributes,
9342
+ "input-missing-label": InputMissingLabel,
9343
+ "long-title": LongTitle,
9344
+ "meta-refresh": MetaRefresh,
9345
+ "missing-doctype": MissingDoctype,
9346
+ "multiple-labeled-controls": MultipleLabeledControls,
9347
+ "no-autoplay": NoAutoplay,
9348
+ "no-conditional-comment": NoConditionalComment,
9349
+ "no-deprecated-attr": NoDeprecatedAttr,
9350
+ "no-dup-attr": NoDupAttr,
9351
+ "no-dup-class": NoDupClass,
9352
+ "no-dup-id": NoDupID,
9353
+ "no-implicit-close": NoImplicitClose,
9354
+ "no-inline-style": NoInlineStyle,
9355
+ "no-missing-references": NoMissingReferences,
9356
+ "no-multiple-main": NoMultipleMain,
9357
+ "no-raw-characters": NoRawCharacters,
9358
+ "no-redundant-for": NoRedundantFor,
9359
+ "no-redundant-role": NoRedundantRole,
9360
+ "no-self-closing": NoSelfClosing,
9361
+ "no-style-tag": NoStyleTag,
9362
+ "no-trailing-whitespace": NoTrailingWhitespace,
9363
+ "no-unknown-elements": NoUnknownElements,
9364
+ "no-utf8-bom": NoUtf8Bom,
9365
+ "prefer-button": PreferButton,
9366
+ "prefer-native-element": PreferNativeElement,
9367
+ "prefer-tbody": PreferTbody,
9368
+ "require-sri": RequireSri,
9369
+ "script-element": ScriptElement,
9370
+ "script-type": ScriptType,
9371
+ "svg-focusable": SvgFocusable,
9372
+ "text-content": TextContent,
9373
+ "unrecognized-char-ref": UnknownCharReference,
9374
+ void: Void,
9375
+ "void-content": VoidContent,
9376
+ "void-style": VoidStyle,
9377
+ ...bundledRules$1,
9378
+ };
9109
9379
 
9110
9380
  var defaultConfig = {};
9111
9381
 
@@ -9343,7 +9613,7 @@ function overwriteMerge(a, b) {
9343
9613
  return b;
9344
9614
  }
9345
9615
  function mergeInternal(base, rhs) {
9346
- const dst = deepmerge(base, Object.assign(Object.assign({}, rhs), { rules: {} }));
9616
+ const dst = deepmerge(base, { ...rhs, rules: {} });
9347
9617
  /* rules need some special care, should overwrite arrays instead of
9348
9618
  * concaternation, i.e. ["error", {...options}] should not be merged by
9349
9619
  * appending to old value */
@@ -9568,7 +9838,7 @@ class Config {
9568
9838
  * @internal primary purpose is unittests
9569
9839
  */
9570
9840
  get() {
9571
- const config = Object.assign({}, this.config);
9841
+ const config = { ...this.config };
9572
9842
  if (config.elements) {
9573
9843
  config.elements = config.elements.map((cur) => {
9574
9844
  if (typeof cur === "string") {
@@ -9654,7 +9924,11 @@ class Config {
9654
9924
  if (!properties) {
9655
9925
  continue;
9656
9926
  }
9657
- for (const [key, schema] of Object.entries(properties)) {
9927
+ for (const [raw, schema] of Object.entries(properties)) {
9928
+ /* at compile time this is a fixed list but the point of this method is
9929
+ * to augment the runtime with additional keys so it is a bit of lying
9930
+ * to typescript */
9931
+ const key = raw;
9658
9932
  if (schema.copyable && !MetaCopyableProperty.includes(key)) {
9659
9933
  MetaCopyableProperty.push(key);
9660
9934
  }
@@ -9850,31 +10124,6 @@ class ConfigLoader {
9850
10124
  }
9851
10125
  }
9852
10126
 
9853
- /*! *****************************************************************************
9854
- Copyright (c) Microsoft Corporation. All rights reserved.
9855
- Licensed under the Apache License, Version 2.0 (the "License"); you may not use
9856
- this file except in compliance with the License. You may obtain a copy of the
9857
- License at http://www.apache.org/licenses/LICENSE-2.0
9858
-
9859
- THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
9860
- KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
9861
- WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
9862
- MERCHANTABLITY OR NON-INFRINGEMENT.
9863
-
9864
- See the Apache Version 2.0 License for specific language governing permissions
9865
- and limitations under the License.
9866
- ***************************************************************************** */
9867
-
9868
- function __rest(s, e) {
9869
- var t = {};
9870
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
9871
- t[p] = s[p];
9872
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
9873
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
9874
- t[p[i]] = s[p[i]];
9875
- return t;
9876
- }
9877
-
9878
10127
  class EventHandler {
9879
10128
  constructor() {
9880
10129
  this.listeners = {};
@@ -10448,7 +10697,7 @@ class Reporter {
10448
10697
  merged[key].messages = [...merged[key].messages, ...result.messages];
10449
10698
  }
10450
10699
  else {
10451
- merged[key] = Object.assign({}, result);
10700
+ merged[key] = { ...result };
10452
10701
  }
10453
10702
  });
10454
10703
  });
@@ -10550,14 +10799,16 @@ function messageSort(a, b) {
10550
10799
  }
10551
10800
 
10552
10801
  class Engine {
10553
- constructor(config, configData, ParserClass) {
10802
+ constructor(config, ParserClass) {
10554
10803
  this.report = new Reporter();
10555
- this.configData = configData;
10556
10804
  this.config = config;
10557
10805
  this.ParserClass = ParserClass;
10558
10806
  /* initialize plugins and rules */
10559
10807
  const result = this.initPlugins(this.config);
10560
- this.availableRules = Object.assign(Object.assign({}, bundledRules), result.availableRules);
10808
+ this.availableRules = {
10809
+ ...bundledRules,
10810
+ ...result.availableRules,
10811
+ };
10561
10812
  }
10562
10813
  /**
10563
10814
  * Lint sources and return report
@@ -10582,13 +10833,13 @@ class Engine {
10582
10833
  /* trigger configuration ready event */
10583
10834
  const configEvent = {
10584
10835
  location,
10585
- config: this.configData,
10836
+ config: this.config,
10586
10837
  rules,
10587
10838
  };
10588
10839
  parser.trigger("config:ready", configEvent);
10589
10840
  /* trigger source ready event */
10590
10841
  /* eslint-disable-next-line @typescript-eslint/no-unused-vars -- object destructured on purpose to remove property */
10591
- const sourceData = __rest(source, ["hooks"]);
10842
+ const { hooks: _, ...sourceData } = source;
10592
10843
  const sourceEvent = {
10593
10844
  location,
10594
10845
  source: sourceData,
@@ -10895,133 +11146,31 @@ class Engine {
10895
11146
  }
10896
11147
 
10897
11148
  /**
10898
- * @internal
10899
- */
10900
- function findConfigurationFiles(directory) {
10901
- return ["json", "cjs", "js"]
10902
- .map((extension) => path.join(directory, `.htmlvalidate.${extension}`))
10903
- .filter((filePath) => fs.existsSync(filePath));
10904
- }
10905
- /**
10906
- * Loads configuration by traversing filesystem.
10907
- *
10908
- * Configuration is read from three sources and in the following order:
10909
- *
10910
- * 1. Global configuration passed to constructor.
10911
- * 2. Configuration files found when traversing the directory structure.
10912
- * 3. Override passed to this function.
10913
- *
10914
- * The following configuration filenames are searched:
10915
- *
10916
- * - `.htmlvalidate.json`
10917
- * - `.htmlvalidate.js`
10918
- * - `.htmlvalidate.cjs`
10919
- *
10920
- * Global configuration is used when no configuration file is found. The
10921
- * result is always merged with override if present.
10922
- *
10923
- * The `root` property set to `true` affects the configuration as following:
11149
+ * The static configuration loader does not do any per-handle lookup. Only the
11150
+ * global or per-call configuration is used.
10924
11151
  *
10925
- * 1. If set in override the override is returned as-is.
10926
- * 2. If set in the global config the override is merged into global and
10927
- * returned. No configuration files are searched.
10928
- * 3. Setting `root` in configuration file only stops directory traversal.
11152
+ * In practice this means no configuration is fetch by traversing the
11153
+ * filesystem.
10929
11154
  */
10930
- class FileSystemConfigLoader extends ConfigLoader {
10931
- /**
10932
- * @param config - Global configuration
10933
- * @param configFactory - Optional configuration factory
10934
- */
10935
- constructor(config, configFactory = Config) {
10936
- super(config, configFactory);
10937
- this.cache = new Map();
10938
- }
10939
- /**
10940
- * Get configuration for given filename.
10941
- *
10942
- * @param filename - Filename to get configuration for.
10943
- * @param configOverride - Configuration to merge final result with.
10944
- */
10945
- getConfigFor(filename, configOverride) {
10946
- /* special case when the overridden configuration is marked as root, should
10947
- * not try to load any more configuration files */
11155
+ class StaticConfigLoader extends ConfigLoader {
11156
+ getConfigFor(handle, configOverride) {
10948
11157
  const override = this.loadFromObject(configOverride || {});
10949
11158
  if (override.isRootFound()) {
10950
11159
  override.init();
10951
11160
  return override;
10952
11161
  }
10953
- /* special case when the global configuration is marked as root, should not
10954
- * try to load and more configuration files */
10955
- if (this.globalConfig.isRootFound()) {
10956
- const merged = this.globalConfig.merge(override);
10957
- merged.init();
10958
- return merged;
10959
- }
10960
- const config = this.fromFilename(filename);
10961
- const merged = config ? config.merge(override) : this.globalConfig.merge(override);
11162
+ const merged = this.globalConfig.merge(override);
10962
11163
  merged.init();
10963
11164
  return merged;
10964
11165
  }
10965
- /**
10966
- * Flush configuration cache.
10967
- *
10968
- * @param filename - If given only the cache for that file is flushed.
10969
- */
10970
- flushCache(filename) {
10971
- if (filename) {
10972
- this.cache.delete(filename);
10973
- }
10974
- else {
10975
- this.cache.clear();
10976
- }
10977
- }
10978
- /**
10979
- * Load raw configuration from directory traversal.
10980
- *
10981
- * This configuration is not merged with global configuration and may return
10982
- * `null` if no configuration files are found.
10983
- */
10984
- fromFilename(filename) {
10985
- var _a;
10986
- if (filename === "inline") {
10987
- return null;
10988
- }
10989
- if (this.cache.has(filename)) {
10990
- return (_a = this.cache.get(filename)) !== null && _a !== void 0 ? _a : null;
10991
- }
10992
- let found = false;
10993
- let current = path.resolve(path.dirname(filename));
10994
- let config = this.empty();
10995
- // eslint-disable-next-line no-constant-condition
10996
- while (true) {
10997
- /* search configuration files in current directory */
10998
- for (const configFile of findConfigurationFiles(current)) {
10999
- const local = this.loadFromFile(configFile);
11000
- found = true;
11001
- config = local.merge(config);
11002
- }
11003
- /* stop if a configuration with "root" is set to true */
11004
- if (config.isRootFound()) {
11005
- break;
11006
- }
11007
- /* get the parent directory */
11008
- const child = current;
11009
- current = path.dirname(current);
11010
- /* stop if this is the root directory */
11011
- if (current === child) {
11012
- break;
11013
- }
11014
- }
11015
- /* no config was found by loader, return null and let caller decide what to do */
11016
- if (!found) {
11017
- this.cache.set(filename, null);
11018
- return null;
11019
- }
11020
- this.cache.set(filename, config);
11021
- return config;
11166
+ flushCache() {
11167
+ /* do nothing */
11022
11168
  }
11023
11169
  defaultConfig() {
11024
- return this.configFactory.defaultConfig();
11170
+ return this.loadFromObject({
11171
+ extends: ["html-validate:recommended"],
11172
+ elements: ["html5"],
11173
+ });
11025
11174
  }
11026
11175
  }
11027
11176
 
@@ -11045,7 +11194,7 @@ function isConfigData(value) {
11045
11194
  class HtmlValidate {
11046
11195
  constructor(arg) {
11047
11196
  const [loader, config] = arg instanceof ConfigLoader ? [arg, undefined] : [undefined, arg];
11048
- this.configLoader = loader !== null && loader !== void 0 ? loader : new FileSystemConfigLoader(config);
11197
+ this.configLoader = loader !== null && loader !== void 0 ? loader : new StaticConfigLoader(config);
11049
11198
  }
11050
11199
  validateString(str, arg1, arg2, arg3) {
11051
11200
  const filename = typeof arg1 === "string" ? arg1 : "inline";
@@ -11072,7 +11221,7 @@ class HtmlValidate {
11072
11221
  const config = this.getConfigFor(input.filename, configOverride);
11073
11222
  const resolved = config.resolve();
11074
11223
  const source = resolved.transformSource(input);
11075
- const engine = new Engine(resolved, config.get(), Parser);
11224
+ const engine = new Engine(resolved, Parser);
11076
11225
  return engine.lint(source);
11077
11226
  }
11078
11227
  /**
@@ -11086,7 +11235,7 @@ class HtmlValidate {
11086
11235
  const config = this.getConfigFor(filename);
11087
11236
  const resolved = config.resolve();
11088
11237
  const source = resolved.transformFilename(filename);
11089
- const engine = new Engine(resolved, config.get(), Parser);
11238
+ const engine = new Engine(resolved, Parser);
11090
11239
  return engine.lint(source);
11091
11240
  }
11092
11241
  /**
@@ -11131,7 +11280,7 @@ class HtmlValidate {
11131
11280
  const config = this.getConfigFor(filename);
11132
11281
  const resolved = config.resolve();
11133
11282
  const source = resolved.transformFilename(filename);
11134
- const engine = new Engine(resolved, config.get(), Parser);
11283
+ const engine = new Engine(resolved, Parser);
11135
11284
  return engine.dumpTokens(source);
11136
11285
  }
11137
11286
  /**
@@ -11146,7 +11295,7 @@ class HtmlValidate {
11146
11295
  const config = this.getConfigFor(filename);
11147
11296
  const resolved = config.resolve();
11148
11297
  const source = resolved.transformFilename(filename);
11149
- const engine = new Engine(resolved, config.get(), Parser);
11298
+ const engine = new Engine(resolved, Parser);
11150
11299
  return engine.dumpEvents(source);
11151
11300
  }
11152
11301
  /**
@@ -11161,7 +11310,7 @@ class HtmlValidate {
11161
11310
  const config = this.getConfigFor(filename);
11162
11311
  const resolved = config.resolve();
11163
11312
  const source = resolved.transformFilename(filename);
11164
- const engine = new Engine(resolved, config.get(), Parser);
11313
+ const engine = new Engine(resolved, Parser);
11165
11314
  return engine.dumpTree(source);
11166
11315
  }
11167
11316
  /**
@@ -11239,7 +11388,7 @@ class HtmlValidate {
11239
11388
  */
11240
11389
  getRuleDocumentation(ruleId, config = null, context = null) {
11241
11390
  const c = config || this.getConfigFor("inline");
11242
- const engine = new Engine(c.resolve(), c.get(), Parser);
11391
+ const engine = new Engine(c.resolve(), Parser);
11243
11392
  return engine.getRuleDocumentation(ruleId, context);
11244
11393
  }
11245
11394
  /**
@@ -11276,35 +11425,6 @@ class HtmlValidate {
11276
11425
  }
11277
11426
  }
11278
11427
 
11279
- /**
11280
- * The static configuration loader does not do any per-handle lookup. Only the
11281
- * global or per-call configuration is used.
11282
- *
11283
- * In practice this means no configuration is fetch by traversing the
11284
- * filesystem.
11285
- */
11286
- class StaticConfigLoader extends ConfigLoader {
11287
- getConfigFor(handle, configOverride) {
11288
- const override = this.loadFromObject(configOverride || {});
11289
- if (override.isRootFound()) {
11290
- override.init();
11291
- return override;
11292
- }
11293
- const merged = this.globalConfig.merge(override);
11294
- merged.init();
11295
- return merged;
11296
- }
11297
- flushCache() {
11298
- /* do nothing */
11299
- }
11300
- defaultConfig() {
11301
- return this.loadFromObject({
11302
- extends: ["html-validate:recommended"],
11303
- elements: ["html5"],
11304
- });
11305
- }
11306
- }
11307
-
11308
11428
  const defaults$1 = {
11309
11429
  silent: false,
11310
11430
  version,
@@ -11322,7 +11442,7 @@ const defaults$1 = {
11322
11442
  * @returns - `true` if version is compatible
11323
11443
  */
11324
11444
  function compatibilityCheck(name, declared, options) {
11325
- const { silent, version: current, logger } = Object.assign(Object.assign({}, defaults$1), options);
11445
+ const { silent, version: current, logger } = { ...defaults$1, ...options };
11326
11446
  const valid = semver.satisfies(current, declared);
11327
11447
  if (valid || silent) {
11328
11448
  return valid;
@@ -11352,6 +11472,137 @@ function ruleExists(ruleId) {
11352
11472
  return ruleIds.has(ruleId);
11353
11473
  }
11354
11474
 
11475
+ /**
11476
+ * @internal
11477
+ */
11478
+ function findConfigurationFiles(directory) {
11479
+ return ["json", "cjs", "js"]
11480
+ .map((extension) => path.join(directory, `.htmlvalidate.${extension}`))
11481
+ .filter((filePath) => fs.existsSync(filePath));
11482
+ }
11483
+ /**
11484
+ * Loads configuration by traversing filesystem.
11485
+ *
11486
+ * Configuration is read from three sources and in the following order:
11487
+ *
11488
+ * 1. Global configuration passed to constructor.
11489
+ * 2. Configuration files found when traversing the directory structure.
11490
+ * 3. Override passed to this function.
11491
+ *
11492
+ * The following configuration filenames are searched:
11493
+ *
11494
+ * - `.htmlvalidate.json`
11495
+ * - `.htmlvalidate.js`
11496
+ * - `.htmlvalidate.cjs`
11497
+ *
11498
+ * Global configuration is used when no configuration file is found. The
11499
+ * result is always merged with override if present.
11500
+ *
11501
+ * The `root` property set to `true` affects the configuration as following:
11502
+ *
11503
+ * 1. If set in override the override is returned as-is.
11504
+ * 2. If set in the global config the override is merged into global and
11505
+ * returned. No configuration files are searched.
11506
+ * 3. Setting `root` in configuration file only stops directory traversal.
11507
+ */
11508
+ class FileSystemConfigLoader extends ConfigLoader {
11509
+ /**
11510
+ * @param config - Global configuration
11511
+ * @param configFactory - Optional configuration factory
11512
+ */
11513
+ constructor(config, configFactory = Config) {
11514
+ super(config, configFactory);
11515
+ this.cache = new Map();
11516
+ }
11517
+ /**
11518
+ * Get configuration for given filename.
11519
+ *
11520
+ * @param filename - Filename to get configuration for.
11521
+ * @param configOverride - Configuration to merge final result with.
11522
+ */
11523
+ getConfigFor(filename, configOverride) {
11524
+ /* special case when the overridden configuration is marked as root, should
11525
+ * not try to load any more configuration files */
11526
+ const override = this.loadFromObject(configOverride || {});
11527
+ if (override.isRootFound()) {
11528
+ override.init();
11529
+ return override;
11530
+ }
11531
+ /* special case when the global configuration is marked as root, should not
11532
+ * try to load and more configuration files */
11533
+ if (this.globalConfig.isRootFound()) {
11534
+ const merged = this.globalConfig.merge(override);
11535
+ merged.init();
11536
+ return merged;
11537
+ }
11538
+ const config = this.fromFilename(filename);
11539
+ const merged = config ? config.merge(override) : this.globalConfig.merge(override);
11540
+ merged.init();
11541
+ return merged;
11542
+ }
11543
+ /**
11544
+ * Flush configuration cache.
11545
+ *
11546
+ * @param filename - If given only the cache for that file is flushed.
11547
+ */
11548
+ flushCache(filename) {
11549
+ if (filename) {
11550
+ this.cache.delete(filename);
11551
+ }
11552
+ else {
11553
+ this.cache.clear();
11554
+ }
11555
+ }
11556
+ /**
11557
+ * Load raw configuration from directory traversal.
11558
+ *
11559
+ * This configuration is not merged with global configuration and may return
11560
+ * `null` if no configuration files are found.
11561
+ */
11562
+ fromFilename(filename) {
11563
+ var _a;
11564
+ if (filename === "inline") {
11565
+ return null;
11566
+ }
11567
+ if (this.cache.has(filename)) {
11568
+ return (_a = this.cache.get(filename)) !== null && _a !== void 0 ? _a : null;
11569
+ }
11570
+ let found = false;
11571
+ let current = path.resolve(path.dirname(filename));
11572
+ let config = this.empty();
11573
+ // eslint-disable-next-line no-constant-condition
11574
+ while (true) {
11575
+ /* search configuration files in current directory */
11576
+ for (const configFile of findConfigurationFiles(current)) {
11577
+ const local = this.loadFromFile(configFile);
11578
+ found = true;
11579
+ config = local.merge(config);
11580
+ }
11581
+ /* stop if a configuration with "root" is set to true */
11582
+ if (config.isRootFound()) {
11583
+ break;
11584
+ }
11585
+ /* get the parent directory */
11586
+ const child = current;
11587
+ current = path.dirname(current);
11588
+ /* stop if this is the root directory */
11589
+ if (current === child) {
11590
+ break;
11591
+ }
11592
+ }
11593
+ /* no config was found by loader, return null and let caller decide what to do */
11594
+ if (!found) {
11595
+ this.cache.set(filename, null);
11596
+ return null;
11597
+ }
11598
+ this.cache.set(filename, config);
11599
+ return config;
11600
+ }
11601
+ defaultConfig() {
11602
+ return this.configFactory.defaultConfig();
11603
+ }
11604
+ }
11605
+
11355
11606
  const entities = {
11356
11607
  ">": "&gt;",
11357
11608
  "<": "&lt;",
@@ -11502,7 +11753,7 @@ function formatSummary(errors, warnings) {
11502
11753
  return kleur[summaryColor]().bold(`${summary.join(" and ")} found.`);
11503
11754
  }
11504
11755
  function codeframe(results, options) {
11505
- const merged = Object.assign(Object.assign({}, defaults), options);
11756
+ const merged = { ...defaults, ...options };
11506
11757
  let errors = 0;
11507
11758
  let warnings = 0;
11508
11759
  const resultsWithMessages = results.filter((result) => result.messages.length > 0);
@@ -11543,7 +11794,11 @@ function linkSummary(results) {
11543
11794
  return `\n${kleur.bold("More information")}:\n${lines.join("")}\n`;
11544
11795
  }
11545
11796
  function stylish(results) {
11546
- const errors = stylishImpl(results.map((it) => (Object.assign(Object.assign({}, it), { fixableErrorCount: 0, fixableWarningCount: 0 }))));
11797
+ const errors = stylishImpl(results.map((it) => ({
11798
+ ...it,
11799
+ fixableErrorCount: 0,
11800
+ fixableWarningCount: 0,
11801
+ })));
11547
11802
  const links = linkSummary(results);
11548
11803
  return `${errors}${links}`;
11549
11804
  }