html-validate 7.12.1 → 7.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/browser.d.ts +1 -1
- package/dist/cjs/browser.js +1 -0
- package/dist/cjs/browser.js.map +1 -1
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/core.d.ts +212 -19
- package/dist/cjs/core.js +336 -57
- package/dist/cjs/core.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/browser.d.ts +1 -1
- package/dist/es/browser.js +1 -1
- package/dist/es/cli.js +1 -1
- package/dist/es/cli.js.map +1 -1
- package/dist/es/core.d.ts +212 -19
- package/dist/es/core.js +336 -58
- package/dist/es/core.js.map +1 -1
- package/dist/es/index.d.ts +1 -1
- package/dist/es/index.js +1 -1
- package/package.json +8 -8
package/dist/cjs/core.js
CHANGED
|
@@ -543,6 +543,7 @@ class DOMNode {
|
|
|
543
543
|
this.nodeName = nodeName !== null && nodeName !== void 0 ? nodeName : DOCUMENT_NODE_NAME;
|
|
544
544
|
this.location = location;
|
|
545
545
|
this.disabledRules = new Set();
|
|
546
|
+
this.blockedRules = new Map();
|
|
546
547
|
this.childNodes = [];
|
|
547
548
|
this.unique = counter++;
|
|
548
549
|
this.cache = null;
|
|
@@ -618,14 +619,42 @@ class DOMNode {
|
|
|
618
619
|
get lastChild() {
|
|
619
620
|
return this.childNodes[this.childNodes.length - 1] || null;
|
|
620
621
|
}
|
|
622
|
+
/**
|
|
623
|
+
* Block a rule for this node.
|
|
624
|
+
*
|
|
625
|
+
* @internal
|
|
626
|
+
*/
|
|
627
|
+
blockRule(ruleId, blocker) {
|
|
628
|
+
const current = this.blockedRules.get(ruleId);
|
|
629
|
+
if (current) {
|
|
630
|
+
current.push(blocker);
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
this.blockedRules.set(ruleId, [blocker]);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Blocks multiple rules.
|
|
638
|
+
*
|
|
639
|
+
* @internal
|
|
640
|
+
*/
|
|
641
|
+
blockRules(rules, blocker) {
|
|
642
|
+
for (const rule of rules) {
|
|
643
|
+
this.blockRule(rule, blocker);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
621
646
|
/**
|
|
622
647
|
* Disable a rule for this node.
|
|
648
|
+
*
|
|
649
|
+
* @internal
|
|
623
650
|
*/
|
|
624
651
|
disableRule(ruleId) {
|
|
625
652
|
this.disabledRules.add(ruleId);
|
|
626
653
|
}
|
|
627
654
|
/**
|
|
628
655
|
* Disables multiple rules.
|
|
656
|
+
*
|
|
657
|
+
* @internal
|
|
629
658
|
*/
|
|
630
659
|
disableRules(rules) {
|
|
631
660
|
for (const rule of rules) {
|
|
@@ -648,10 +677,21 @@ class DOMNode {
|
|
|
648
677
|
}
|
|
649
678
|
/**
|
|
650
679
|
* Test if a rule is enabled for this node.
|
|
680
|
+
*
|
|
681
|
+
* @internal
|
|
651
682
|
*/
|
|
652
683
|
ruleEnabled(ruleId) {
|
|
653
684
|
return !this.disabledRules.has(ruleId);
|
|
654
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* Test if a rule is blocked for this node.
|
|
688
|
+
*
|
|
689
|
+
* @internal
|
|
690
|
+
*/
|
|
691
|
+
ruleBlockers(ruleId) {
|
|
692
|
+
var _a;
|
|
693
|
+
return (_a = this.blockedRules.get(ruleId)) !== null && _a !== void 0 ? _a : [];
|
|
694
|
+
}
|
|
655
695
|
generateSelector() {
|
|
656
696
|
return null;
|
|
657
697
|
}
|
|
@@ -2707,6 +2747,8 @@ function matchAttributeFacade(node, match) {
|
|
|
2707
2747
|
const allowedKeys = ["exclude"];
|
|
2708
2748
|
/**
|
|
2709
2749
|
* Helper class to validate elements against metadata rules.
|
|
2750
|
+
*
|
|
2751
|
+
* @public
|
|
2710
2752
|
*/
|
|
2711
2753
|
class Validator {
|
|
2712
2754
|
/**
|
|
@@ -3462,6 +3504,7 @@ class Rule {
|
|
|
3462
3504
|
this.event = null;
|
|
3463
3505
|
this.options = options;
|
|
3464
3506
|
this.enabled = true;
|
|
3507
|
+
this.blockers = [];
|
|
3465
3508
|
this.severity = 0;
|
|
3466
3509
|
this.name = "";
|
|
3467
3510
|
}
|
|
@@ -3471,6 +3514,26 @@ class Rule {
|
|
|
3471
3514
|
setServerity(severity) {
|
|
3472
3515
|
this.severity = severity;
|
|
3473
3516
|
}
|
|
3517
|
+
/**
|
|
3518
|
+
* Block this rule from generating errors. Pass in an id generated by {@link
|
|
3519
|
+
* createBlocker}. Can be unblocked by {@link unblock}.
|
|
3520
|
+
*
|
|
3521
|
+
* A blocked rule is similar to disabling it but it will still receive parser
|
|
3522
|
+
* events. A list of all blockers is passed to the `rule:error` event.
|
|
3523
|
+
*
|
|
3524
|
+
* @internal
|
|
3525
|
+
*/
|
|
3526
|
+
block(id) {
|
|
3527
|
+
this.blockers.push(id);
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3530
|
+
* Unblock a rule previously blocked by {@link block}.
|
|
3531
|
+
*
|
|
3532
|
+
* @internal
|
|
3533
|
+
*/
|
|
3534
|
+
unblock(id) {
|
|
3535
|
+
this.blockers = this.blockers.filter((it) => it !== id);
|
|
3536
|
+
}
|
|
3474
3537
|
setEnabled(enabled) {
|
|
3475
3538
|
this.enabled = enabled;
|
|
3476
3539
|
}
|
|
@@ -3487,9 +3550,36 @@ class Rule {
|
|
|
3487
3550
|
*
|
|
3488
3551
|
* To be considered enabled the enabled flag must be true and the severity at
|
|
3489
3552
|
* least warning.
|
|
3553
|
+
*
|
|
3554
|
+
* @internal
|
|
3555
|
+
*/
|
|
3556
|
+
isEnabled(node) {
|
|
3557
|
+
return this.enabled && this.severity >= exports.Severity.WARN && (!node || node.ruleEnabled(this.name));
|
|
3558
|
+
}
|
|
3559
|
+
/**
|
|
3560
|
+
* Test if rule is enabled.
|
|
3561
|
+
*
|
|
3562
|
+
* To be considered enabled the enabled flag must be true and the severity at
|
|
3563
|
+
* least warning.
|
|
3564
|
+
*
|
|
3565
|
+
* @internal
|
|
3490
3566
|
*/
|
|
3491
|
-
|
|
3492
|
-
|
|
3567
|
+
isBlocked(node) {
|
|
3568
|
+
if (this.blockers.length > 0) {
|
|
3569
|
+
return true;
|
|
3570
|
+
}
|
|
3571
|
+
if (node && node.ruleBlockers(this.name).length > 0) {
|
|
3572
|
+
return true;
|
|
3573
|
+
}
|
|
3574
|
+
return false;
|
|
3575
|
+
}
|
|
3576
|
+
/**
|
|
3577
|
+
* Get a list of all blockers currently active this rule.
|
|
3578
|
+
*
|
|
3579
|
+
* @internal
|
|
3580
|
+
*/
|
|
3581
|
+
getBlockers(node) {
|
|
3582
|
+
return [...this.blockers, ...(node ? node.ruleBlockers(this.name) : [])];
|
|
3493
3583
|
}
|
|
3494
3584
|
/**
|
|
3495
3585
|
* Check if keyword is being ignored by the current rule configuration.
|
|
@@ -3553,8 +3643,16 @@ class Rule {
|
|
|
3553
3643
|
}
|
|
3554
3644
|
report(...args) {
|
|
3555
3645
|
const { node, message, location, context } = unpackErrorDescriptor(args);
|
|
3556
|
-
|
|
3557
|
-
|
|
3646
|
+
const enabled = this.isEnabled(node);
|
|
3647
|
+
const blocked = this.isBlocked(node);
|
|
3648
|
+
const where = this.findLocation({ node, location, event: this.event });
|
|
3649
|
+
this.parser.trigger("rule:error", {
|
|
3650
|
+
location: where,
|
|
3651
|
+
ruleId: this.name,
|
|
3652
|
+
enabled,
|
|
3653
|
+
blockers: this.getBlockers(node),
|
|
3654
|
+
});
|
|
3655
|
+
if (enabled && !blocked) {
|
|
3558
3656
|
const interpolated = interpolate(message, context !== null && context !== void 0 ? context : {});
|
|
3559
3657
|
this.reporter.add(this, interpolated, this.severity, node, where, context);
|
|
3560
3658
|
}
|
|
@@ -3653,7 +3751,7 @@ class Rule {
|
|
|
3653
3751
|
}
|
|
3654
3752
|
}
|
|
3655
3753
|
|
|
3656
|
-
const defaults$
|
|
3754
|
+
const defaults$w = {
|
|
3657
3755
|
allowExternal: true,
|
|
3658
3756
|
allowRelative: true,
|
|
3659
3757
|
allowAbsolute: true,
|
|
@@ -3697,7 +3795,7 @@ function matchList(value, list) {
|
|
|
3697
3795
|
}
|
|
3698
3796
|
class AllowedLinks extends Rule {
|
|
3699
3797
|
constructor(options) {
|
|
3700
|
-
super({ ...defaults$
|
|
3798
|
+
super({ ...defaults$w, ...options });
|
|
3701
3799
|
this.allowExternal = parseAllow(this.options.allowExternal);
|
|
3702
3800
|
this.allowRelative = parseAllow(this.options.allowRelative);
|
|
3703
3801
|
this.allowAbsolute = parseAllow(this.options.allowAbsolute);
|
|
@@ -3845,7 +3943,7 @@ var RuleContext$1;
|
|
|
3845
3943
|
RuleContext["MISSING_ALT"] = "missing-alt";
|
|
3846
3944
|
RuleContext["MISSING_HREF"] = "missing-href";
|
|
3847
3945
|
})(RuleContext$1 || (RuleContext$1 = {}));
|
|
3848
|
-
const defaults$
|
|
3946
|
+
const defaults$v = {
|
|
3849
3947
|
accessible: true,
|
|
3850
3948
|
};
|
|
3851
3949
|
function findByTarget(target, siblings) {
|
|
@@ -3883,7 +3981,7 @@ function getDescription$1(context) {
|
|
|
3883
3981
|
}
|
|
3884
3982
|
class AreaAlt extends Rule {
|
|
3885
3983
|
constructor(options) {
|
|
3886
|
-
super({ ...defaults$
|
|
3984
|
+
super({ ...defaults$v, ...options });
|
|
3887
3985
|
}
|
|
3888
3986
|
static schema() {
|
|
3889
3987
|
return {
|
|
@@ -4052,13 +4150,13 @@ class ConfigError extends UserError {
|
|
|
4052
4150
|
}
|
|
4053
4151
|
}
|
|
4054
4152
|
|
|
4055
|
-
const defaults$
|
|
4153
|
+
const defaults$u = {
|
|
4056
4154
|
style: "lowercase",
|
|
4057
4155
|
ignoreForeign: true,
|
|
4058
4156
|
};
|
|
4059
4157
|
class AttrCase extends Rule {
|
|
4060
4158
|
constructor(options) {
|
|
4061
|
-
super({ ...defaults$
|
|
4159
|
+
super({ ...defaults$u, ...options });
|
|
4062
4160
|
this.style = new rulesHelper.CaseStyle(this.options.style, "attr-case");
|
|
4063
4161
|
}
|
|
4064
4162
|
static schema() {
|
|
@@ -4168,7 +4266,7 @@ const MATCH_SCRIPT_DATA = /^[^]*?(?=<\/script)/;
|
|
|
4168
4266
|
const MATCH_SCRIPT_END = /^<(\/)(script)/;
|
|
4169
4267
|
const MATCH_STYLE_DATA = /^[^]*?(?=<\/style)/;
|
|
4170
4268
|
const MATCH_STYLE_END = /^<(\/)(style)/;
|
|
4171
|
-
const MATCH_DIRECTIVE =
|
|
4269
|
+
const MATCH_DIRECTIVE = /^(<!--\s*\[html-validate-)([a-z0-9-]+)(\s*)(.*?)(]?\s*-->)/;
|
|
4172
4270
|
const MATCH_COMMENT = /^<!--([^]*?)-->/;
|
|
4173
4271
|
const MATCH_CONDITIONAL = /^<!\[([^\]]*?)\]>/;
|
|
4174
4272
|
class InvalidTokenError extends Error {
|
|
@@ -4403,7 +4501,7 @@ class AttrDelimiter extends Rule {
|
|
|
4403
4501
|
}
|
|
4404
4502
|
|
|
4405
4503
|
const DEFAULT_PATTERN = "[a-z0-9-:]+";
|
|
4406
|
-
const defaults$
|
|
4504
|
+
const defaults$t = {
|
|
4407
4505
|
pattern: DEFAULT_PATTERN,
|
|
4408
4506
|
ignoreForeign: true,
|
|
4409
4507
|
};
|
|
@@ -4440,7 +4538,7 @@ function generateDescription(name, pattern) {
|
|
|
4440
4538
|
}
|
|
4441
4539
|
class AttrPattern extends Rule {
|
|
4442
4540
|
constructor(options) {
|
|
4443
|
-
super({ ...defaults$
|
|
4541
|
+
super({ ...defaults$t, ...options });
|
|
4444
4542
|
this.pattern = generateRegexp(this.options.pattern);
|
|
4445
4543
|
}
|
|
4446
4544
|
static schema() {
|
|
@@ -4501,7 +4599,7 @@ var QuoteStyle;
|
|
|
4501
4599
|
QuoteStyle["AUTO_QUOTE"] = "auto";
|
|
4502
4600
|
QuoteStyle["ANY_QUOTE"] = "any";
|
|
4503
4601
|
})(QuoteStyle || (QuoteStyle = {}));
|
|
4504
|
-
const defaults$
|
|
4602
|
+
const defaults$s = {
|
|
4505
4603
|
style: "auto",
|
|
4506
4604
|
unquoted: false,
|
|
4507
4605
|
};
|
|
@@ -4568,7 +4666,7 @@ class AttrQuotes extends Rule {
|
|
|
4568
4666
|
};
|
|
4569
4667
|
}
|
|
4570
4668
|
constructor(options) {
|
|
4571
|
-
super({ ...defaults$
|
|
4669
|
+
super({ ...defaults$s, ...options });
|
|
4572
4670
|
this.style = parseStyle$4(this.options.style);
|
|
4573
4671
|
}
|
|
4574
4672
|
setup() {
|
|
@@ -4738,12 +4836,12 @@ class AttributeAllowedValues extends Rule {
|
|
|
4738
4836
|
}
|
|
4739
4837
|
}
|
|
4740
4838
|
|
|
4741
|
-
const defaults$
|
|
4839
|
+
const defaults$r = {
|
|
4742
4840
|
style: "omit",
|
|
4743
4841
|
};
|
|
4744
4842
|
class AttributeBooleanStyle extends Rule {
|
|
4745
4843
|
constructor(options) {
|
|
4746
|
-
super({ ...defaults$
|
|
4844
|
+
super({ ...defaults$r, ...options });
|
|
4747
4845
|
this.hasInvalidStyle = parseStyle$3(this.options.style);
|
|
4748
4846
|
}
|
|
4749
4847
|
static schema() {
|
|
@@ -4819,12 +4917,12 @@ function reportMessage$1(attr, style) {
|
|
|
4819
4917
|
return "";
|
|
4820
4918
|
}
|
|
4821
4919
|
|
|
4822
|
-
const defaults$
|
|
4920
|
+
const defaults$q = {
|
|
4823
4921
|
style: "omit",
|
|
4824
4922
|
};
|
|
4825
4923
|
class AttributeEmptyStyle extends Rule {
|
|
4826
4924
|
constructor(options) {
|
|
4827
|
-
super({ ...defaults$
|
|
4925
|
+
super({ ...defaults$q, ...options });
|
|
4828
4926
|
this.hasInvalidStyle = parseStyle$2(this.options.style);
|
|
4829
4927
|
}
|
|
4830
4928
|
static schema() {
|
|
@@ -4980,12 +5078,12 @@ function describePattern(pattern) {
|
|
|
4980
5078
|
}
|
|
4981
5079
|
}
|
|
4982
5080
|
|
|
4983
|
-
const defaults$
|
|
5081
|
+
const defaults$p = {
|
|
4984
5082
|
pattern: "kebabcase",
|
|
4985
5083
|
};
|
|
4986
5084
|
class ClassPattern extends Rule {
|
|
4987
5085
|
constructor(options) {
|
|
4988
|
-
super({ ...defaults$
|
|
5086
|
+
super({ ...defaults$p, ...options });
|
|
4989
5087
|
this.pattern = parsePattern(this.options.pattern);
|
|
4990
5088
|
}
|
|
4991
5089
|
static schema() {
|
|
@@ -5094,13 +5192,13 @@ class CloseOrder extends Rule {
|
|
|
5094
5192
|
}
|
|
5095
5193
|
}
|
|
5096
5194
|
|
|
5097
|
-
const defaults$
|
|
5195
|
+
const defaults$o = {
|
|
5098
5196
|
include: null,
|
|
5099
5197
|
exclude: null,
|
|
5100
5198
|
};
|
|
5101
5199
|
class Deprecated extends Rule {
|
|
5102
5200
|
constructor(options) {
|
|
5103
|
-
super({ ...defaults$
|
|
5201
|
+
super({ ...defaults$o, ...options });
|
|
5104
5202
|
}
|
|
5105
5203
|
static schema() {
|
|
5106
5204
|
return {
|
|
@@ -5263,12 +5361,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
|
|
|
5263
5361
|
}
|
|
5264
5362
|
};
|
|
5265
5363
|
|
|
5266
|
-
const defaults$
|
|
5364
|
+
const defaults$n = {
|
|
5267
5365
|
style: "uppercase",
|
|
5268
5366
|
};
|
|
5269
5367
|
class DoctypeStyle extends Rule {
|
|
5270
5368
|
constructor(options) {
|
|
5271
|
-
super({ ...defaults$
|
|
5369
|
+
super({ ...defaults$n, ...options });
|
|
5272
5370
|
}
|
|
5273
5371
|
static schema() {
|
|
5274
5372
|
return {
|
|
@@ -5300,12 +5398,12 @@ class DoctypeStyle extends Rule {
|
|
|
5300
5398
|
}
|
|
5301
5399
|
}
|
|
5302
5400
|
|
|
5303
|
-
const defaults$
|
|
5401
|
+
const defaults$m = {
|
|
5304
5402
|
style: "lowercase",
|
|
5305
5403
|
};
|
|
5306
5404
|
class ElementCase extends Rule {
|
|
5307
5405
|
constructor(options) {
|
|
5308
|
-
super({ ...defaults$
|
|
5406
|
+
super({ ...defaults$m, ...options });
|
|
5309
5407
|
this.style = new rulesHelper.CaseStyle(this.options.style, "element-case");
|
|
5310
5408
|
}
|
|
5311
5409
|
static schema() {
|
|
@@ -5371,14 +5469,14 @@ class ElementCase extends Rule {
|
|
|
5371
5469
|
}
|
|
5372
5470
|
}
|
|
5373
5471
|
|
|
5374
|
-
const defaults$
|
|
5472
|
+
const defaults$l = {
|
|
5375
5473
|
pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
|
|
5376
5474
|
whitelist: [],
|
|
5377
5475
|
blacklist: [],
|
|
5378
5476
|
};
|
|
5379
5477
|
class ElementName extends Rule {
|
|
5380
5478
|
constructor(options) {
|
|
5381
|
-
super({ ...defaults$
|
|
5479
|
+
super({ ...defaults$l, ...options });
|
|
5382
5480
|
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
5383
5481
|
this.pattern = new RegExp(this.options.pattern);
|
|
5384
5482
|
}
|
|
@@ -5419,7 +5517,7 @@ class ElementName extends Rule {
|
|
|
5419
5517
|
...context.blacklist.map((cur) => `- ${cur}`),
|
|
5420
5518
|
];
|
|
5421
5519
|
}
|
|
5422
|
-
if (context.pattern !== defaults$
|
|
5520
|
+
if (context.pattern !== defaults$l.pattern) {
|
|
5423
5521
|
return [
|
|
5424
5522
|
`<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
|
|
5425
5523
|
"",
|
|
@@ -5963,29 +6061,70 @@ class EmptyTitle extends Rule {
|
|
|
5963
6061
|
}
|
|
5964
6062
|
}
|
|
5965
6063
|
|
|
6064
|
+
const defaults$k = {
|
|
6065
|
+
allowArrayBrackets: true,
|
|
6066
|
+
shared: ["radio"],
|
|
6067
|
+
};
|
|
5966
6068
|
const UNIQUE_CACHE_KEY = Symbol("form-elements-unique");
|
|
5967
6069
|
const SHARED_CACHE_KEY = Symbol("form-elements-shared");
|
|
5968
6070
|
function haveName(name) {
|
|
5969
6071
|
return typeof name === "string" && name !== "";
|
|
5970
6072
|
}
|
|
5971
|
-
function allowSharedName(node) {
|
|
6073
|
+
function allowSharedName(node, shared) {
|
|
5972
6074
|
const type = node.getAttribute("type");
|
|
5973
|
-
return Boolean(type && type.valueMatches(
|
|
6075
|
+
return Boolean(type && type.valueMatches(shared, false));
|
|
6076
|
+
}
|
|
6077
|
+
function getDocumentation(context) {
|
|
6078
|
+
const trailer = "Each form control must have a unique name.";
|
|
6079
|
+
if (!context) {
|
|
6080
|
+
return trailer;
|
|
6081
|
+
}
|
|
6082
|
+
else {
|
|
6083
|
+
const { name } = context;
|
|
6084
|
+
switch (context.kind) {
|
|
6085
|
+
case "duplicate":
|
|
6086
|
+
return [`Duplicate form control name "${name}"`, trailer].join("\n");
|
|
6087
|
+
case "mix":
|
|
6088
|
+
return [
|
|
6089
|
+
`Form control name cannot mix regular name "{{ name }}" with array brackets "{{ name }}[]"`,
|
|
6090
|
+
trailer,
|
|
6091
|
+
].join("\n");
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
5974
6094
|
}
|
|
5975
6095
|
class FormDupName extends Rule {
|
|
5976
|
-
|
|
6096
|
+
constructor(options) {
|
|
6097
|
+
super({ ...defaults$k, ...options });
|
|
6098
|
+
}
|
|
6099
|
+
static schema() {
|
|
5977
6100
|
return {
|
|
5978
|
-
|
|
6101
|
+
allowArrayBrackets: {
|
|
6102
|
+
type: "boolean",
|
|
6103
|
+
},
|
|
6104
|
+
shared: {
|
|
6105
|
+
type: "array",
|
|
6106
|
+
items: {
|
|
6107
|
+
enum: ["radio", "checkbox", "submit"],
|
|
6108
|
+
},
|
|
6109
|
+
},
|
|
6110
|
+
};
|
|
6111
|
+
}
|
|
6112
|
+
documentation(context) {
|
|
6113
|
+
return {
|
|
6114
|
+
description: getDocumentation(context),
|
|
5979
6115
|
url: "https://html-validate.org/rules/form-dup-name.html",
|
|
5980
6116
|
};
|
|
5981
6117
|
}
|
|
5982
6118
|
setup() {
|
|
5983
6119
|
const selector = this.getSelector();
|
|
6120
|
+
const { shared } = this.options;
|
|
5984
6121
|
this.on("dom:ready", (event) => {
|
|
5985
6122
|
var _a, _b;
|
|
5986
6123
|
const { document } = event;
|
|
5987
6124
|
const controls = document.querySelectorAll(selector);
|
|
5988
|
-
const [sharedControls, uniqueControls] = rulesHelper.partition(controls,
|
|
6125
|
+
const [sharedControls, uniqueControls] = rulesHelper.partition(controls, (it) => {
|
|
6126
|
+
return allowSharedName(it, shared);
|
|
6127
|
+
});
|
|
5989
6128
|
/* validate all form controls which require unique elements first so each
|
|
5990
6129
|
* form has a populated list of unique names */
|
|
5991
6130
|
for (const control of uniqueControls) {
|
|
@@ -6012,9 +6151,35 @@ class FormDupName extends Rule {
|
|
|
6012
6151
|
}
|
|
6013
6152
|
validateUniqueName(control, form, attr, name) {
|
|
6014
6153
|
const elements = this.getUniqueElements(form);
|
|
6154
|
+
const { allowArrayBrackets } = this.options;
|
|
6155
|
+
if (allowArrayBrackets) {
|
|
6156
|
+
const isarray = name.endsWith("[]");
|
|
6157
|
+
const basename = isarray ? name.slice(0, -2) : name;
|
|
6158
|
+
const details = elements.get(basename);
|
|
6159
|
+
if (details && details.array !== isarray) {
|
|
6160
|
+
const context = {
|
|
6161
|
+
name: basename,
|
|
6162
|
+
kind: "mix",
|
|
6163
|
+
};
|
|
6164
|
+
this.report({
|
|
6165
|
+
node: control,
|
|
6166
|
+
location: attr.valueLocation,
|
|
6167
|
+
message: 'Cannot mix "{{ name }}[]" and "{{ name }}"',
|
|
6168
|
+
context,
|
|
6169
|
+
});
|
|
6170
|
+
return;
|
|
6171
|
+
}
|
|
6172
|
+
else if (!details && isarray) {
|
|
6173
|
+
elements.set(basename, {
|
|
6174
|
+
array: true,
|
|
6175
|
+
});
|
|
6176
|
+
return;
|
|
6177
|
+
}
|
|
6178
|
+
}
|
|
6015
6179
|
if (elements.has(name)) {
|
|
6016
6180
|
const context = {
|
|
6017
6181
|
name,
|
|
6182
|
+
kind: "duplicate",
|
|
6018
6183
|
};
|
|
6019
6184
|
this.report({
|
|
6020
6185
|
node: control,
|
|
@@ -6024,7 +6189,9 @@ class FormDupName extends Rule {
|
|
|
6024
6189
|
});
|
|
6025
6190
|
}
|
|
6026
6191
|
else {
|
|
6027
|
-
elements.
|
|
6192
|
+
elements.set(name, {
|
|
6193
|
+
array: false,
|
|
6194
|
+
});
|
|
6028
6195
|
}
|
|
6029
6196
|
}
|
|
6030
6197
|
validateSharedName(control, form, attr, name) {
|
|
@@ -6037,6 +6204,7 @@ class FormDupName extends Rule {
|
|
|
6037
6204
|
(sharedElements.has(name) && sharedElements.get(name) !== type)) {
|
|
6038
6205
|
const context = {
|
|
6039
6206
|
name,
|
|
6207
|
+
kind: "duplicate",
|
|
6040
6208
|
};
|
|
6041
6209
|
this.report({
|
|
6042
6210
|
node: control,
|
|
@@ -6068,7 +6236,7 @@ class FormDupName extends Rule {
|
|
|
6068
6236
|
return existing;
|
|
6069
6237
|
}
|
|
6070
6238
|
else {
|
|
6071
|
-
const elements = new
|
|
6239
|
+
const elements = new Map();
|
|
6072
6240
|
form.cacheSet(UNIQUE_CACHE_KEY, elements);
|
|
6073
6241
|
return elements;
|
|
6074
6242
|
}
|
|
@@ -7598,6 +7766,39 @@ class NoUnknownElements extends Rule {
|
|
|
7598
7766
|
}
|
|
7599
7767
|
}
|
|
7600
7768
|
|
|
7769
|
+
class NoUnusedDisable extends Rule {
|
|
7770
|
+
documentation(context) {
|
|
7771
|
+
return {
|
|
7772
|
+
description: context
|
|
7773
|
+
? `\`${context.ruleId}\` rule is disabled but no error was reported.`
|
|
7774
|
+
: "Rule is disabled but no error was reported.",
|
|
7775
|
+
url: "https://html-validate.org/rules/no-unused-disable.html",
|
|
7776
|
+
};
|
|
7777
|
+
}
|
|
7778
|
+
setup() {
|
|
7779
|
+
/* this is a special rule, the `Engine` class directly emits errors on this
|
|
7780
|
+
* rule, it exists only to be able to configure whenever the rule is enabled
|
|
7781
|
+
* or not and to get the regular documentation and contextual help. */
|
|
7782
|
+
}
|
|
7783
|
+
reportUnused(unused, options, location) {
|
|
7784
|
+
const tokens = new DOMTokenList(options.replace(",", " "), location);
|
|
7785
|
+
for (const ruleId of unused) {
|
|
7786
|
+
const index = tokens.indexOf(ruleId);
|
|
7787
|
+
/* istanbul ignore next: the token should be present or it wouldn't be
|
|
7788
|
+
* reported as unused, this is just a sanity check and fallback */
|
|
7789
|
+
const tokenLocation = index >= 0 ? tokens.location(index) : location;
|
|
7790
|
+
this.report({
|
|
7791
|
+
node: null,
|
|
7792
|
+
message: '"{{ ruleId }}" rule is disabled but no error was reported',
|
|
7793
|
+
location: tokenLocation,
|
|
7794
|
+
context: {
|
|
7795
|
+
ruleId: ruleId,
|
|
7796
|
+
},
|
|
7797
|
+
});
|
|
7798
|
+
}
|
|
7799
|
+
}
|
|
7800
|
+
}
|
|
7801
|
+
|
|
7601
7802
|
class NoUtf8Bom extends Rule {
|
|
7602
7803
|
documentation() {
|
|
7603
7804
|
return {
|
|
@@ -9221,6 +9422,7 @@ const bundledRules = {
|
|
|
9221
9422
|
"no-style-tag": NoStyleTag,
|
|
9222
9423
|
"no-trailing-whitespace": NoTrailingWhitespace,
|
|
9223
9424
|
"no-unknown-elements": NoUnknownElements,
|
|
9425
|
+
"no-unused-disable": NoUnusedDisable,
|
|
9224
9426
|
"no-utf8-bom": NoUtf8Bom,
|
|
9225
9427
|
"prefer-button": PreferButton,
|
|
9226
9428
|
"prefer-native-element": PreferNativeElement,
|
|
@@ -9331,6 +9533,7 @@ const config$1 = {
|
|
|
9331
9533
|
"no-self-closing": "error",
|
|
9332
9534
|
"no-trailing-whitespace": "error",
|
|
9333
9535
|
"no-utf8-bom": "error",
|
|
9536
|
+
"no-unused-disable": "error",
|
|
9334
9537
|
"prefer-button": "error",
|
|
9335
9538
|
"prefer-native-element": "error",
|
|
9336
9539
|
"prefer-tbody": "error",
|
|
@@ -9381,6 +9584,7 @@ const config = {
|
|
|
9381
9584
|
"no-dup-id": "error",
|
|
9382
9585
|
"no-multiple-main": "error",
|
|
9383
9586
|
"no-raw-characters": ["error", { relaxed: true }],
|
|
9587
|
+
"no-unused-disable": "error",
|
|
9384
9588
|
"script-element": "error",
|
|
9385
9589
|
"unrecognized-char-ref": "error",
|
|
9386
9590
|
"valid-id": ["error", { relaxed: true }],
|
|
@@ -10180,6 +10384,10 @@ class Parser {
|
|
|
10180
10384
|
offset: 0,
|
|
10181
10385
|
};
|
|
10182
10386
|
}
|
|
10387
|
+
/* trigger starting event */
|
|
10388
|
+
this.trigger("parse:begin", {
|
|
10389
|
+
location: null,
|
|
10390
|
+
});
|
|
10183
10391
|
/* reset DOM in case there are multiple calls in the same session */
|
|
10184
10392
|
this.dom = new DOMTree({
|
|
10185
10393
|
filename: (_a = source.filename) !== null && _a !== void 0 ? _a : "",
|
|
@@ -10214,6 +10422,10 @@ class Parser {
|
|
|
10214
10422
|
* instead */
|
|
10215
10423
|
location: null,
|
|
10216
10424
|
});
|
|
10425
|
+
/* trigger ending event */
|
|
10426
|
+
this.trigger("parse:end", {
|
|
10427
|
+
location: null,
|
|
10428
|
+
});
|
|
10217
10429
|
return this.dom.root;
|
|
10218
10430
|
}
|
|
10219
10431
|
/**
|
|
@@ -10524,11 +10736,11 @@ class Parser {
|
|
|
10524
10736
|
};
|
|
10525
10737
|
}
|
|
10526
10738
|
consumeDirective(token) {
|
|
10527
|
-
const [text, , action, directive,
|
|
10528
|
-
if (
|
|
10739
|
+
const [text, preamble, action, separator1, directive, postamble] = token.data;
|
|
10740
|
+
if (!postamble.startsWith("]")) {
|
|
10529
10741
|
throw new ParserError(token.location, `Missing end bracket "]" on directive "${text}"`);
|
|
10530
10742
|
}
|
|
10531
|
-
const match = directive.match(/^(.*?)(
|
|
10743
|
+
const match = directive.match(/^(.*?)(?:(\s*(?:--|:)\s*)(.*))?$/);
|
|
10532
10744
|
/* istanbul ignore next: should not be possible, would be emitted as comment token */
|
|
10533
10745
|
if (!match) {
|
|
10534
10746
|
throw new Error(`Failed to parse directive "${text}"`);
|
|
@@ -10536,12 +10748,32 @@ class Parser {
|
|
|
10536
10748
|
if (!isValidDirective(action)) {
|
|
10537
10749
|
throw new ParserError(token.location, `Unknown directive "${action}"`);
|
|
10538
10750
|
}
|
|
10539
|
-
const [, data, comment] = match;
|
|
10751
|
+
const [, data, separator2, comment] = match;
|
|
10752
|
+
const prefix = "html-validate-";
|
|
10753
|
+
/* <!-- [html-validate-action options -- comment] -->
|
|
10754
|
+
* ^ ^ ^--------------- comment offset
|
|
10755
|
+
* | \-------------------------- options offset
|
|
10756
|
+
* \--------------------------------- action offset
|
|
10757
|
+
*/
|
|
10758
|
+
const actionOffset = preamble.length;
|
|
10759
|
+
const optionsOffset = actionOffset + action.length + separator1.length;
|
|
10760
|
+
const commentOffset = optionsOffset + data.length + (separator2 || "").length;
|
|
10761
|
+
const location = sliceLocation(token.location, preamble.length - prefix.length - 1, -postamble.length + 1);
|
|
10762
|
+
const actionLocation = sliceLocation(token.location, actionOffset, actionOffset + action.length);
|
|
10763
|
+
const optionsLocation = data
|
|
10764
|
+
? sliceLocation(token.location, optionsOffset, optionsOffset + data.length)
|
|
10765
|
+
: undefined;
|
|
10766
|
+
const commentLocation = comment
|
|
10767
|
+
? sliceLocation(token.location, commentOffset, commentOffset + comment.length)
|
|
10768
|
+
: undefined;
|
|
10540
10769
|
this.trigger("directive", {
|
|
10541
10770
|
action,
|
|
10542
10771
|
data,
|
|
10543
10772
|
comment: comment || "",
|
|
10544
|
-
location
|
|
10773
|
+
location,
|
|
10774
|
+
actionLocation,
|
|
10775
|
+
optionsLocation,
|
|
10776
|
+
commentLocation,
|
|
10545
10777
|
});
|
|
10546
10778
|
}
|
|
10547
10779
|
/**
|
|
@@ -10827,6 +11059,18 @@ function messageSort(a, b) {
|
|
|
10827
11059
|
return 0;
|
|
10828
11060
|
}
|
|
10829
11061
|
|
|
11062
|
+
let blockerCounter = 1;
|
|
11063
|
+
/**
|
|
11064
|
+
* Creates a new rule blocker for using when blocking rules from generating
|
|
11065
|
+
* errors.
|
|
11066
|
+
*
|
|
11067
|
+
* @internal
|
|
11068
|
+
*/
|
|
11069
|
+
function createBlocker() {
|
|
11070
|
+
const id = blockerCounter++;
|
|
11071
|
+
return id;
|
|
11072
|
+
}
|
|
11073
|
+
|
|
10830
11074
|
/**
|
|
10831
11075
|
* @internal
|
|
10832
11076
|
*/
|
|
@@ -10854,6 +11098,15 @@ class Engine {
|
|
|
10854
11098
|
const parser = this.instantiateParser();
|
|
10855
11099
|
/* setup plugins and rules */
|
|
10856
11100
|
const { rules } = this.setupPlugins(source, this.config, parser);
|
|
11101
|
+
const noUnusedDisable = rules["no-unused-disable"];
|
|
11102
|
+
const directiveContext = {
|
|
11103
|
+
rules,
|
|
11104
|
+
reportUnused(unused, options, location) {
|
|
11105
|
+
if (noUnusedDisable) {
|
|
11106
|
+
noUnusedDisable.reportUnused(unused, options, location);
|
|
11107
|
+
}
|
|
11108
|
+
},
|
|
11109
|
+
};
|
|
10857
11110
|
/* create a faux location at the start of the stream for the next events */
|
|
10858
11111
|
const location = {
|
|
10859
11112
|
filename: source.filename,
|
|
@@ -10879,7 +11132,7 @@ class Engine {
|
|
|
10879
11132
|
parser.trigger("source:ready", sourceEvent);
|
|
10880
11133
|
/* setup directive handling */
|
|
10881
11134
|
parser.on("directive", (_, event) => {
|
|
10882
|
-
this.processDirective(event, parser,
|
|
11135
|
+
this.processDirective(event, parser, directiveContext);
|
|
10883
11136
|
});
|
|
10884
11137
|
/* parse token stream */
|
|
10885
11138
|
try {
|
|
@@ -10986,12 +11239,15 @@ class Engine {
|
|
|
10986
11239
|
instantiateParser() {
|
|
10987
11240
|
return new this.ParserClass(this.config);
|
|
10988
11241
|
}
|
|
10989
|
-
processDirective(event, parser,
|
|
11242
|
+
processDirective(event, parser, context) {
|
|
11243
|
+
var _a;
|
|
10990
11244
|
const rules = event.data
|
|
10991
11245
|
.split(",")
|
|
10992
11246
|
.map((name) => name.trim())
|
|
10993
|
-
.map((name) =>
|
|
11247
|
+
.map((name) => context.rules[name])
|
|
10994
11248
|
.filter((rule) => rule); /* filter out missing rules */
|
|
11249
|
+
/* istanbul ignore next: option must be present or there would be no rules to disable */
|
|
11250
|
+
const location = (_a = event.optionsLocation) !== null && _a !== void 0 ? _a : event.location;
|
|
10995
11251
|
switch (event.action) {
|
|
10996
11252
|
case "enable":
|
|
10997
11253
|
this.processEnableDirective(rules, parser);
|
|
@@ -11000,10 +11256,10 @@ class Engine {
|
|
|
11000
11256
|
this.processDisableDirective(rules, parser);
|
|
11001
11257
|
break;
|
|
11002
11258
|
case "disable-block":
|
|
11003
|
-
this.processDisableBlockDirective(rules, parser);
|
|
11259
|
+
this.processDisableBlockDirective(context, rules, parser, event.data, location);
|
|
11004
11260
|
break;
|
|
11005
11261
|
case "disable-next":
|
|
11006
|
-
this.processDisableNextDirective(rules, parser);
|
|
11262
|
+
this.processDisableNextDirective(context, rules, parser, event.data, location);
|
|
11007
11263
|
break;
|
|
11008
11264
|
}
|
|
11009
11265
|
}
|
|
@@ -11028,10 +11284,13 @@ class Engine {
|
|
|
11028
11284
|
data.target.disableRules(rules.map((rule) => rule.name));
|
|
11029
11285
|
});
|
|
11030
11286
|
}
|
|
11031
|
-
processDisableBlockDirective(rules, parser) {
|
|
11287
|
+
processDisableBlockDirective(context, rules, parser, options, location) {
|
|
11288
|
+
const ruleIds = rules.map((it) => it.name);
|
|
11289
|
+
const blocker = createBlocker();
|
|
11290
|
+
const unused = new Set(ruleIds);
|
|
11032
11291
|
let directiveBlock = null;
|
|
11033
11292
|
for (const rule of rules) {
|
|
11034
|
-
rule.
|
|
11293
|
+
rule.block(blocker);
|
|
11035
11294
|
}
|
|
11036
11295
|
const unregisterOpen = parser.on("tag:start", (event, data) => {
|
|
11037
11296
|
var _a, _b;
|
|
@@ -11043,7 +11302,7 @@ class Engine {
|
|
|
11043
11302
|
}
|
|
11044
11303
|
/* disable rules directly on the node so it will be recorded for later,
|
|
11045
11304
|
* more specifically when using the domtree to trigger errors */
|
|
11046
|
-
data.target.
|
|
11305
|
+
data.target.blockRules(ruleIds, blocker);
|
|
11047
11306
|
});
|
|
11048
11307
|
const unregisterClose = parser.on("tag:end", (event, data) => {
|
|
11049
11308
|
/* if the directive is the last thing in a block no id would be set */
|
|
@@ -11056,26 +11315,45 @@ class Engine {
|
|
|
11056
11315
|
unregisterClose();
|
|
11057
11316
|
unregisterOpen();
|
|
11058
11317
|
for (const rule of rules) {
|
|
11059
|
-
rule.
|
|
11318
|
+
rule.unblock(blocker);
|
|
11060
11319
|
}
|
|
11061
11320
|
}
|
|
11062
11321
|
});
|
|
11322
|
+
parser.on("rule:error", (event, data) => {
|
|
11323
|
+
if (data.blockers.includes(blocker)) {
|
|
11324
|
+
unused.delete(data.ruleId);
|
|
11325
|
+
}
|
|
11326
|
+
});
|
|
11327
|
+
parser.on("parse:end", () => {
|
|
11328
|
+
context.reportUnused(unused, options, location);
|
|
11329
|
+
});
|
|
11063
11330
|
}
|
|
11064
|
-
processDisableNextDirective(rules, parser) {
|
|
11331
|
+
processDisableNextDirective(context, rules, parser, options, location) {
|
|
11332
|
+
const ruleIds = rules.map((it) => it.name);
|
|
11333
|
+
const blocker = createBlocker();
|
|
11334
|
+
const unused = new Set(ruleIds);
|
|
11065
11335
|
for (const rule of rules) {
|
|
11066
|
-
rule.
|
|
11336
|
+
rule.block(blocker);
|
|
11067
11337
|
}
|
|
11068
|
-
/*
|
|
11338
|
+
/* block rules directly on the node so it will be recorded for later,
|
|
11069
11339
|
* more specifically when using the domtree to trigger errors */
|
|
11070
11340
|
const unregister = parser.on("tag:start", (event, data) => {
|
|
11071
|
-
data.target.
|
|
11341
|
+
data.target.blockRules(ruleIds, blocker);
|
|
11342
|
+
});
|
|
11343
|
+
parser.on("rule:error", (event, data) => {
|
|
11344
|
+
if (data.blockers.includes(blocker)) {
|
|
11345
|
+
unused.delete(data.ruleId);
|
|
11346
|
+
}
|
|
11347
|
+
});
|
|
11348
|
+
parser.on("parse:end", () => {
|
|
11349
|
+
context.reportUnused(unused, options, location);
|
|
11072
11350
|
});
|
|
11073
11351
|
/* disable directive after next event occurs */
|
|
11074
11352
|
parser.once("tag:ready, tag:end, attr", () => {
|
|
11075
11353
|
unregister();
|
|
11076
11354
|
parser.defer(() => {
|
|
11077
11355
|
for (const rule of rules) {
|
|
11078
|
-
rule.
|
|
11356
|
+
rule.unblock(blocker);
|
|
11079
11357
|
}
|
|
11080
11358
|
});
|
|
11081
11359
|
});
|
|
@@ -11467,7 +11745,7 @@ class HtmlValidate {
|
|
|
11467
11745
|
/** @public */
|
|
11468
11746
|
const name = "html-validate";
|
|
11469
11747
|
/** @public */
|
|
11470
|
-
const version = "7.
|
|
11748
|
+
const version = "7.13.0";
|
|
11471
11749
|
/** @public */
|
|
11472
11750
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
11473
11751
|
|
|
@@ -11930,6 +12208,7 @@ exports.StaticConfigLoader = StaticConfigLoader;
|
|
|
11930
12208
|
exports.TemplateExtractor = TemplateExtractor;
|
|
11931
12209
|
exports.TextNode = TextNode;
|
|
11932
12210
|
exports.UserError = UserError;
|
|
12211
|
+
exports.Validator = Validator;
|
|
11933
12212
|
exports.WrappedError = WrappedError;
|
|
11934
12213
|
exports.bugs = bugs;
|
|
11935
12214
|
exports.codeframe = codeframe;
|