html-validate 7.12.0 → 7.12.2

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/README.md CHANGED
@@ -16,7 +16,7 @@ Offline HTML5 validator. Validates either a full document or a smaller
16
16
  ## Usage
17
17
 
18
18
  npm install -g html-validate
19
- html-validate FILENAME..
19
+ html-validate [OPTIONS] [FILENAME..] [DIR..]
20
20
 
21
21
  ## Configuration
22
22
 
package/dist/cjs/core.js CHANGED
@@ -3653,7 +3653,7 @@ class Rule {
3653
3653
  }
3654
3654
  }
3655
3655
 
3656
- const defaults$v = {
3656
+ const defaults$w = {
3657
3657
  allowExternal: true,
3658
3658
  allowRelative: true,
3659
3659
  allowAbsolute: true,
@@ -3697,7 +3697,7 @@ function matchList(value, list) {
3697
3697
  }
3698
3698
  class AllowedLinks extends Rule {
3699
3699
  constructor(options) {
3700
- super({ ...defaults$v, ...options });
3700
+ super({ ...defaults$w, ...options });
3701
3701
  this.allowExternal = parseAllow(this.options.allowExternal);
3702
3702
  this.allowRelative = parseAllow(this.options.allowRelative);
3703
3703
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3845,7 +3845,7 @@ var RuleContext$1;
3845
3845
  RuleContext["MISSING_ALT"] = "missing-alt";
3846
3846
  RuleContext["MISSING_HREF"] = "missing-href";
3847
3847
  })(RuleContext$1 || (RuleContext$1 = {}));
3848
- const defaults$u = {
3848
+ const defaults$v = {
3849
3849
  accessible: true,
3850
3850
  };
3851
3851
  function findByTarget(target, siblings) {
@@ -3883,7 +3883,7 @@ function getDescription$1(context) {
3883
3883
  }
3884
3884
  class AreaAlt extends Rule {
3885
3885
  constructor(options) {
3886
- super({ ...defaults$u, ...options });
3886
+ super({ ...defaults$v, ...options });
3887
3887
  }
3888
3888
  static schema() {
3889
3889
  return {
@@ -4052,13 +4052,13 @@ class ConfigError extends UserError {
4052
4052
  }
4053
4053
  }
4054
4054
 
4055
- const defaults$t = {
4055
+ const defaults$u = {
4056
4056
  style: "lowercase",
4057
4057
  ignoreForeign: true,
4058
4058
  };
4059
4059
  class AttrCase extends Rule {
4060
4060
  constructor(options) {
4061
- super({ ...defaults$t, ...options });
4061
+ super({ ...defaults$u, ...options });
4062
4062
  this.style = new rulesHelper.CaseStyle(this.options.style, "attr-case");
4063
4063
  }
4064
4064
  static schema() {
@@ -4403,7 +4403,7 @@ class AttrDelimiter extends Rule {
4403
4403
  }
4404
4404
 
4405
4405
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4406
- const defaults$s = {
4406
+ const defaults$t = {
4407
4407
  pattern: DEFAULT_PATTERN,
4408
4408
  ignoreForeign: true,
4409
4409
  };
@@ -4440,7 +4440,7 @@ function generateDescription(name, pattern) {
4440
4440
  }
4441
4441
  class AttrPattern extends Rule {
4442
4442
  constructor(options) {
4443
- super({ ...defaults$s, ...options });
4443
+ super({ ...defaults$t, ...options });
4444
4444
  this.pattern = generateRegexp(this.options.pattern);
4445
4445
  }
4446
4446
  static schema() {
@@ -4501,7 +4501,7 @@ var QuoteStyle;
4501
4501
  QuoteStyle["AUTO_QUOTE"] = "auto";
4502
4502
  QuoteStyle["ANY_QUOTE"] = "any";
4503
4503
  })(QuoteStyle || (QuoteStyle = {}));
4504
- const defaults$r = {
4504
+ const defaults$s = {
4505
4505
  style: "auto",
4506
4506
  unquoted: false,
4507
4507
  };
@@ -4568,7 +4568,7 @@ class AttrQuotes extends Rule {
4568
4568
  };
4569
4569
  }
4570
4570
  constructor(options) {
4571
- super({ ...defaults$r, ...options });
4571
+ super({ ...defaults$s, ...options });
4572
4572
  this.style = parseStyle$4(this.options.style);
4573
4573
  }
4574
4574
  setup() {
@@ -4738,12 +4738,12 @@ class AttributeAllowedValues extends Rule {
4738
4738
  }
4739
4739
  }
4740
4740
 
4741
- const defaults$q = {
4741
+ const defaults$r = {
4742
4742
  style: "omit",
4743
4743
  };
4744
4744
  class AttributeBooleanStyle extends Rule {
4745
4745
  constructor(options) {
4746
- super({ ...defaults$q, ...options });
4746
+ super({ ...defaults$r, ...options });
4747
4747
  this.hasInvalidStyle = parseStyle$3(this.options.style);
4748
4748
  }
4749
4749
  static schema() {
@@ -4819,12 +4819,12 @@ function reportMessage$1(attr, style) {
4819
4819
  return "";
4820
4820
  }
4821
4821
 
4822
- const defaults$p = {
4822
+ const defaults$q = {
4823
4823
  style: "omit",
4824
4824
  };
4825
4825
  class AttributeEmptyStyle extends Rule {
4826
4826
  constructor(options) {
4827
- super({ ...defaults$p, ...options });
4827
+ super({ ...defaults$q, ...options });
4828
4828
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4829
4829
  }
4830
4830
  static schema() {
@@ -4980,12 +4980,12 @@ function describePattern(pattern) {
4980
4980
  }
4981
4981
  }
4982
4982
 
4983
- const defaults$o = {
4983
+ const defaults$p = {
4984
4984
  pattern: "kebabcase",
4985
4985
  };
4986
4986
  class ClassPattern extends Rule {
4987
4987
  constructor(options) {
4988
- super({ ...defaults$o, ...options });
4988
+ super({ ...defaults$p, ...options });
4989
4989
  this.pattern = parsePattern(this.options.pattern);
4990
4990
  }
4991
4991
  static schema() {
@@ -5094,13 +5094,13 @@ class CloseOrder extends Rule {
5094
5094
  }
5095
5095
  }
5096
5096
 
5097
- const defaults$n = {
5097
+ const defaults$o = {
5098
5098
  include: null,
5099
5099
  exclude: null,
5100
5100
  };
5101
5101
  class Deprecated extends Rule {
5102
5102
  constructor(options) {
5103
- super({ ...defaults$n, ...options });
5103
+ super({ ...defaults$o, ...options });
5104
5104
  }
5105
5105
  static schema() {
5106
5106
  return {
@@ -5263,12 +5263,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
5263
5263
  }
5264
5264
  };
5265
5265
 
5266
- const defaults$m = {
5266
+ const defaults$n = {
5267
5267
  style: "uppercase",
5268
5268
  };
5269
5269
  class DoctypeStyle extends Rule {
5270
5270
  constructor(options) {
5271
- super({ ...defaults$m, ...options });
5271
+ super({ ...defaults$n, ...options });
5272
5272
  }
5273
5273
  static schema() {
5274
5274
  return {
@@ -5300,12 +5300,12 @@ class DoctypeStyle extends Rule {
5300
5300
  }
5301
5301
  }
5302
5302
 
5303
- const defaults$l = {
5303
+ const defaults$m = {
5304
5304
  style: "lowercase",
5305
5305
  };
5306
5306
  class ElementCase extends Rule {
5307
5307
  constructor(options) {
5308
- super({ ...defaults$l, ...options });
5308
+ super({ ...defaults$m, ...options });
5309
5309
  this.style = new rulesHelper.CaseStyle(this.options.style, "element-case");
5310
5310
  }
5311
5311
  static schema() {
@@ -5371,14 +5371,14 @@ class ElementCase extends Rule {
5371
5371
  }
5372
5372
  }
5373
5373
 
5374
- const defaults$k = {
5374
+ const defaults$l = {
5375
5375
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
5376
5376
  whitelist: [],
5377
5377
  blacklist: [],
5378
5378
  };
5379
5379
  class ElementName extends Rule {
5380
5380
  constructor(options) {
5381
- super({ ...defaults$k, ...options });
5381
+ super({ ...defaults$l, ...options });
5382
5382
  // eslint-disable-next-line security/detect-non-literal-regexp
5383
5383
  this.pattern = new RegExp(this.options.pattern);
5384
5384
  }
@@ -5419,7 +5419,7 @@ class ElementName extends Rule {
5419
5419
  ...context.blacklist.map((cur) => `- ${cur}`),
5420
5420
  ];
5421
5421
  }
5422
- if (context.pattern !== defaults$k.pattern) {
5422
+ if (context.pattern !== defaults$l.pattern) {
5423
5423
  return [
5424
5424
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
5425
5425
  "",
@@ -5963,46 +5963,125 @@ class EmptyTitle extends Rule {
5963
5963
  }
5964
5964
  }
5965
5965
 
5966
- const CACHE_KEY = Symbol("form-elements");
5966
+ const defaults$k = {
5967
+ allowArrayBrackets: true,
5968
+ shared: ["radio"],
5969
+ };
5970
+ const UNIQUE_CACHE_KEY = Symbol("form-elements-unique");
5971
+ const SHARED_CACHE_KEY = Symbol("form-elements-shared");
5967
5972
  function haveName(name) {
5968
5973
  return typeof name === "string" && name !== "";
5969
5974
  }
5970
- function isRelevant$4(node) {
5971
- if (node.is("input")) {
5972
- /* ignore radiobuttons and checkboxes */
5973
- const type = node.getAttribute("type");
5974
- return !type || !type.valueMatches(["radio", "checkbox"], true);
5975
+ function allowSharedName(node, shared) {
5976
+ const type = node.getAttribute("type");
5977
+ return Boolean(type && type.valueMatches(shared, false));
5978
+ }
5979
+ function getDocumentation(context) {
5980
+ const trailer = "Each form control must have a unique name.";
5981
+ if (!context) {
5982
+ return trailer;
5983
+ }
5984
+ else {
5985
+ const { name } = context;
5986
+ switch (context.kind) {
5987
+ case "duplicate":
5988
+ return [`Duplicate form control name "${name}"`, trailer].join("\n");
5989
+ case "mix":
5990
+ return [
5991
+ `Form control name cannot mix regular name "{{ name }}" with array brackets "{{ name }}[]"`,
5992
+ trailer,
5993
+ ].join("\n");
5994
+ }
5975
5995
  }
5976
- return true;
5977
5996
  }
5978
5997
  class FormDupName extends Rule {
5979
- documentation() {
5998
+ constructor(options) {
5999
+ super({ ...defaults$k, ...options });
6000
+ }
6001
+ static schema() {
6002
+ return {
6003
+ allowArrayBrackets: {
6004
+ type: "boolean",
6005
+ },
6006
+ shared: {
6007
+ type: "array",
6008
+ items: {
6009
+ enum: ["radio", "checkbox", "submit"],
6010
+ },
6011
+ },
6012
+ };
6013
+ }
6014
+ documentation(context) {
5980
6015
  return {
5981
- description: "Each form control must have a unique name.",
6016
+ description: getDocumentation(context),
5982
6017
  url: "https://html-validate.org/rules/form-dup-name.html",
5983
6018
  };
5984
6019
  }
5985
6020
  setup() {
5986
6021
  const selector = this.getSelector();
6022
+ const { shared } = this.options;
5987
6023
  this.on("dom:ready", (event) => {
5988
- var _a;
6024
+ var _a, _b;
5989
6025
  const { document } = event;
5990
- const controls = document.querySelectorAll(selector).filter(isRelevant$4);
5991
- for (const control of controls) {
6026
+ const controls = document.querySelectorAll(selector);
6027
+ const [sharedControls, uniqueControls] = rulesHelper.partition(controls, (it) => {
6028
+ return allowSharedName(it, shared);
6029
+ });
6030
+ /* validate all form controls which require unique elements first so each
6031
+ * form has a populated list of unique names */
6032
+ for (const control of uniqueControls) {
6033
+ const attr = control.getAttribute("name");
6034
+ const name = attr === null || attr === void 0 ? void 0 : attr.value;
6035
+ if (!attr || !haveName(name)) {
6036
+ continue;
6037
+ }
6038
+ const form = (_a = control.closest("form")) !== null && _a !== void 0 ? _a : document.root;
6039
+ this.validateUniqueName(control, form, attr, name);
6040
+ }
6041
+ /* validate all form controls which allows shared names to ensure there is
6042
+ * no collision with other form controls */
6043
+ for (const control of sharedControls) {
5992
6044
  const attr = control.getAttribute("name");
5993
6045
  const name = attr === null || attr === void 0 ? void 0 : attr.value;
5994
- if (attr && haveName(name)) {
5995
- const form = (_a = control.closest("form")) !== null && _a !== void 0 ? _a : document.root;
5996
- this.validateName(control, form, attr, name);
6046
+ if (!attr || !haveName(name)) {
6047
+ continue;
5997
6048
  }
6049
+ const form = (_b = control.closest("form")) !== null && _b !== void 0 ? _b : document.root;
6050
+ this.validateSharedName(control, form, attr, name);
5998
6051
  }
5999
6052
  });
6000
6053
  }
6001
- validateName(control, form, attr, name) {
6002
- const elements = this.getElements(form);
6054
+ validateUniqueName(control, form, attr, name) {
6055
+ const elements = this.getUniqueElements(form);
6056
+ const { allowArrayBrackets } = this.options;
6057
+ if (allowArrayBrackets) {
6058
+ const isarray = name.endsWith("[]");
6059
+ const basename = isarray ? name.slice(0, -2) : name;
6060
+ const details = elements.get(basename);
6061
+ if (details && details.array !== isarray) {
6062
+ const context = {
6063
+ name: basename,
6064
+ kind: "mix",
6065
+ };
6066
+ this.report({
6067
+ node: control,
6068
+ location: attr.valueLocation,
6069
+ message: 'Cannot mix "{{ name }}[]" and "{{ name }}"',
6070
+ context,
6071
+ });
6072
+ return;
6073
+ }
6074
+ else if (!details && isarray) {
6075
+ elements.set(basename, {
6076
+ array: true,
6077
+ });
6078
+ return;
6079
+ }
6080
+ }
6003
6081
  if (elements.has(name)) {
6004
6082
  const context = {
6005
6083
  name,
6084
+ kind: "duplicate",
6006
6085
  };
6007
6086
  this.report({
6008
6087
  node: control,
@@ -6012,9 +6091,32 @@ class FormDupName extends Rule {
6012
6091
  });
6013
6092
  }
6014
6093
  else {
6015
- elements.add(name);
6094
+ elements.set(name, {
6095
+ array: false,
6096
+ });
6016
6097
  }
6017
6098
  }
6099
+ validateSharedName(control, form, attr, name) {
6100
+ var _a;
6101
+ const uniqueElements = this.getUniqueElements(form);
6102
+ const sharedElements = this.getSharedElements(form);
6103
+ /* istanbul ignore next: type will always be set or shared name wouldn't be allowed */
6104
+ const type = (_a = control.getAttributeValue("type")) !== null && _a !== void 0 ? _a : "";
6105
+ if (uniqueElements.has(name) ||
6106
+ (sharedElements.has(name) && sharedElements.get(name) !== type)) {
6107
+ const context = {
6108
+ name,
6109
+ kind: "duplicate",
6110
+ };
6111
+ this.report({
6112
+ node: control,
6113
+ location: attr.valueLocation,
6114
+ message: 'Duplicate form control name "{{ name }}"',
6115
+ context,
6116
+ });
6117
+ }
6118
+ sharedElements.set(name, type);
6119
+ }
6018
6120
  getSelector() {
6019
6121
  const tags = this.getTagsWithProperty("formAssociated").filter((it) => {
6020
6122
  return this.isListedElement(it);
@@ -6030,14 +6132,25 @@ class FormDupName extends Rule {
6030
6132
  }
6031
6133
  return meta.formAssociated.listed;
6032
6134
  }
6033
- getElements(form) {
6034
- const existing = form.cacheGet(CACHE_KEY);
6135
+ getUniqueElements(form) {
6136
+ const existing = form.cacheGet(UNIQUE_CACHE_KEY);
6137
+ if (existing) {
6138
+ return existing;
6139
+ }
6140
+ else {
6141
+ const elements = new Map();
6142
+ form.cacheSet(UNIQUE_CACHE_KEY, elements);
6143
+ return elements;
6144
+ }
6145
+ }
6146
+ getSharedElements(form) {
6147
+ const existing = form.cacheGet(SHARED_CACHE_KEY);
6035
6148
  if (existing) {
6036
6149
  return existing;
6037
6150
  }
6038
6151
  else {
6039
- const elements = new Set();
6040
- form.cacheSet(CACHE_KEY, elements);
6152
+ const elements = new Map();
6153
+ form.cacheSet(SHARED_CACHE_KEY, elements);
6041
6154
  return elements;
6042
6155
  }
6043
6156
  }
@@ -11424,7 +11537,7 @@ class HtmlValidate {
11424
11537
  /** @public */
11425
11538
  const name = "html-validate";
11426
11539
  /** @public */
11427
- const version = "7.12.0";
11540
+ const version = "7.12.2";
11428
11541
  /** @public */
11429
11542
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11430
11543