html-validate 8.13.0 → 8.15.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/core.js +579 -63
- package/dist/cjs/core.js.map +1 -1
- package/dist/es/core.js +579 -63
- package/dist/es/core.js.map +1 -1
- package/package.json +10 -10
package/dist/es/core.js
CHANGED
|
@@ -3844,7 +3844,7 @@ class Rule {
|
|
|
3844
3844
|
}
|
|
3845
3845
|
}
|
|
3846
3846
|
|
|
3847
|
-
const defaults$
|
|
3847
|
+
const defaults$w = {
|
|
3848
3848
|
allowExternal: true,
|
|
3849
3849
|
allowRelative: true,
|
|
3850
3850
|
allowAbsolute: true,
|
|
@@ -3885,7 +3885,7 @@ function matchList(value, list) {
|
|
|
3885
3885
|
}
|
|
3886
3886
|
class AllowedLinks extends Rule {
|
|
3887
3887
|
constructor(options) {
|
|
3888
|
-
super({ ...defaults$
|
|
3888
|
+
super({ ...defaults$w, ...options });
|
|
3889
3889
|
this.allowExternal = parseAllow(this.options.allowExternal);
|
|
3890
3890
|
this.allowRelative = parseAllow(this.options.allowRelative);
|
|
3891
3891
|
this.allowAbsolute = parseAllow(this.options.allowAbsolute);
|
|
@@ -4049,7 +4049,7 @@ class AllowedLinks extends Rule {
|
|
|
4049
4049
|
}
|
|
4050
4050
|
}
|
|
4051
4051
|
|
|
4052
|
-
const defaults$
|
|
4052
|
+
const defaults$v = {
|
|
4053
4053
|
accessible: true
|
|
4054
4054
|
};
|
|
4055
4055
|
function findByTarget(target, siblings) {
|
|
@@ -4079,7 +4079,7 @@ function getDescription$1(context) {
|
|
|
4079
4079
|
}
|
|
4080
4080
|
class AreaAlt extends Rule {
|
|
4081
4081
|
constructor(options) {
|
|
4082
|
-
super({ ...defaults$
|
|
4082
|
+
super({ ...defaults$v, ...options });
|
|
4083
4083
|
}
|
|
4084
4084
|
static schema() {
|
|
4085
4085
|
return {
|
|
@@ -4158,7 +4158,7 @@ class AriaHiddenBody extends Rule {
|
|
|
4158
4158
|
}
|
|
4159
4159
|
}
|
|
4160
4160
|
|
|
4161
|
-
const defaults$
|
|
4161
|
+
const defaults$u = {
|
|
4162
4162
|
allowAnyNamable: false
|
|
4163
4163
|
};
|
|
4164
4164
|
const whitelisted = [
|
|
@@ -4200,7 +4200,7 @@ function isValidUsage(target, meta) {
|
|
|
4200
4200
|
}
|
|
4201
4201
|
class AriaLabelMisuse extends Rule {
|
|
4202
4202
|
constructor(options) {
|
|
4203
|
-
super({ ...defaults$
|
|
4203
|
+
super({ ...defaults$u, ...options });
|
|
4204
4204
|
}
|
|
4205
4205
|
documentation() {
|
|
4206
4206
|
const valid = [
|
|
@@ -4310,13 +4310,13 @@ class CaseStyle {
|
|
|
4310
4310
|
}
|
|
4311
4311
|
}
|
|
4312
4312
|
|
|
4313
|
-
const defaults$
|
|
4313
|
+
const defaults$t = {
|
|
4314
4314
|
style: "lowercase",
|
|
4315
4315
|
ignoreForeign: true
|
|
4316
4316
|
};
|
|
4317
4317
|
class AttrCase extends Rule {
|
|
4318
4318
|
constructor(options) {
|
|
4319
|
-
super({ ...defaults$
|
|
4319
|
+
super({ ...defaults$t, ...options });
|
|
4320
4320
|
this.style = new CaseStyle(this.options.style, "attr-case");
|
|
4321
4321
|
}
|
|
4322
4322
|
static schema() {
|
|
@@ -4672,7 +4672,7 @@ class AttrDelimiter extends Rule {
|
|
|
4672
4672
|
}
|
|
4673
4673
|
|
|
4674
4674
|
const DEFAULT_PATTERN = "[a-z0-9-:]+";
|
|
4675
|
-
const defaults$
|
|
4675
|
+
const defaults$s = {
|
|
4676
4676
|
pattern: DEFAULT_PATTERN,
|
|
4677
4677
|
ignoreForeign: true
|
|
4678
4678
|
};
|
|
@@ -4704,7 +4704,7 @@ function generateDescription(name, pattern) {
|
|
|
4704
4704
|
}
|
|
4705
4705
|
class AttrPattern extends Rule {
|
|
4706
4706
|
constructor(options) {
|
|
4707
|
-
super({ ...defaults$
|
|
4707
|
+
super({ ...defaults$s, ...options });
|
|
4708
4708
|
this.pattern = generateRegexp(this.options.pattern);
|
|
4709
4709
|
}
|
|
4710
4710
|
static schema() {
|
|
@@ -4751,7 +4751,7 @@ class AttrPattern extends Rule {
|
|
|
4751
4751
|
}
|
|
4752
4752
|
}
|
|
4753
4753
|
|
|
4754
|
-
const defaults$
|
|
4754
|
+
const defaults$r = {
|
|
4755
4755
|
style: "auto",
|
|
4756
4756
|
unquoted: false
|
|
4757
4757
|
};
|
|
@@ -4790,7 +4790,7 @@ function describeStyle(style, unquoted) {
|
|
|
4790
4790
|
}
|
|
4791
4791
|
class AttrQuotes extends Rule {
|
|
4792
4792
|
constructor(options) {
|
|
4793
|
-
super({ ...defaults$
|
|
4793
|
+
super({ ...defaults$r, ...options });
|
|
4794
4794
|
this.style = parseStyle$3(this.options.style);
|
|
4795
4795
|
}
|
|
4796
4796
|
static schema() {
|
|
@@ -4974,12 +4974,12 @@ class AttributeAllowedValues extends Rule {
|
|
|
4974
4974
|
}
|
|
4975
4975
|
}
|
|
4976
4976
|
|
|
4977
|
-
const defaults$
|
|
4977
|
+
const defaults$q = {
|
|
4978
4978
|
style: "omit"
|
|
4979
4979
|
};
|
|
4980
4980
|
class AttributeBooleanStyle extends Rule {
|
|
4981
4981
|
constructor(options) {
|
|
4982
|
-
super({ ...defaults$
|
|
4982
|
+
super({ ...defaults$q, ...options });
|
|
4983
4983
|
this.hasInvalidStyle = parseStyle$2(this.options.style);
|
|
4984
4984
|
}
|
|
4985
4985
|
static schema() {
|
|
@@ -5046,12 +5046,12 @@ function reportMessage$1(attr, style) {
|
|
|
5046
5046
|
return "";
|
|
5047
5047
|
}
|
|
5048
5048
|
|
|
5049
|
-
const defaults$
|
|
5049
|
+
const defaults$p = {
|
|
5050
5050
|
style: "omit"
|
|
5051
5051
|
};
|
|
5052
5052
|
class AttributeEmptyStyle extends Rule {
|
|
5053
5053
|
constructor(options) {
|
|
5054
|
-
super({ ...defaults$
|
|
5054
|
+
super({ ...defaults$p, ...options });
|
|
5055
5055
|
this.hasInvalidStyle = parseStyle$1(this.options.style);
|
|
5056
5056
|
}
|
|
5057
5057
|
static schema() {
|
|
@@ -5191,12 +5191,12 @@ function describePattern(pattern) {
|
|
|
5191
5191
|
}
|
|
5192
5192
|
}
|
|
5193
5193
|
|
|
5194
|
-
const defaults$
|
|
5194
|
+
const defaults$o = {
|
|
5195
5195
|
pattern: "kebabcase"
|
|
5196
5196
|
};
|
|
5197
5197
|
class ClassPattern extends Rule {
|
|
5198
5198
|
constructor(options) {
|
|
5199
|
-
super({ ...defaults$
|
|
5199
|
+
super({ ...defaults$o, ...options });
|
|
5200
5200
|
this.pattern = parsePattern(this.options.pattern);
|
|
5201
5201
|
}
|
|
5202
5202
|
static schema() {
|
|
@@ -5302,13 +5302,13 @@ class CloseOrder extends Rule {
|
|
|
5302
5302
|
}
|
|
5303
5303
|
}
|
|
5304
5304
|
|
|
5305
|
-
const defaults$
|
|
5305
|
+
const defaults$n = {
|
|
5306
5306
|
include: null,
|
|
5307
5307
|
exclude: null
|
|
5308
5308
|
};
|
|
5309
5309
|
class Deprecated extends Rule {
|
|
5310
5310
|
constructor(options) {
|
|
5311
|
-
super({ ...defaults$
|
|
5311
|
+
super({ ...defaults$n, ...options });
|
|
5312
5312
|
}
|
|
5313
5313
|
static schema() {
|
|
5314
5314
|
return {
|
|
@@ -5462,12 +5462,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
|
|
|
5462
5462
|
}
|
|
5463
5463
|
};
|
|
5464
5464
|
|
|
5465
|
-
const defaults$
|
|
5465
|
+
const defaults$m = {
|
|
5466
5466
|
style: "uppercase"
|
|
5467
5467
|
};
|
|
5468
5468
|
class DoctypeStyle extends Rule {
|
|
5469
5469
|
constructor(options) {
|
|
5470
|
-
super({ ...defaults$
|
|
5470
|
+
super({ ...defaults$m, ...options });
|
|
5471
5471
|
}
|
|
5472
5472
|
static schema() {
|
|
5473
5473
|
return {
|
|
@@ -5495,12 +5495,12 @@ class DoctypeStyle extends Rule {
|
|
|
5495
5495
|
}
|
|
5496
5496
|
}
|
|
5497
5497
|
|
|
5498
|
-
const defaults$
|
|
5498
|
+
const defaults$l = {
|
|
5499
5499
|
style: "lowercase"
|
|
5500
5500
|
};
|
|
5501
5501
|
class ElementCase extends Rule {
|
|
5502
5502
|
constructor(options) {
|
|
5503
|
-
super({ ...defaults$
|
|
5503
|
+
super({ ...defaults$l, ...options });
|
|
5504
5504
|
this.style = new CaseStyle(this.options.style, "element-case");
|
|
5505
5505
|
}
|
|
5506
5506
|
static schema() {
|
|
@@ -5560,14 +5560,14 @@ class ElementCase extends Rule {
|
|
|
5560
5560
|
}
|
|
5561
5561
|
}
|
|
5562
5562
|
|
|
5563
|
-
const defaults$
|
|
5563
|
+
const defaults$k = {
|
|
5564
5564
|
pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
|
|
5565
5565
|
whitelist: [],
|
|
5566
5566
|
blacklist: []
|
|
5567
5567
|
};
|
|
5568
5568
|
class ElementName extends Rule {
|
|
5569
5569
|
constructor(options) {
|
|
5570
|
-
super({ ...defaults$
|
|
5570
|
+
super({ ...defaults$k, ...options });
|
|
5571
5571
|
this.pattern = new RegExp(this.options.pattern);
|
|
5572
5572
|
}
|
|
5573
5573
|
static schema() {
|
|
@@ -5604,7 +5604,7 @@ class ElementName extends Rule {
|
|
|
5604
5604
|
...context.blacklist.map((cur) => `- ${cur}`)
|
|
5605
5605
|
];
|
|
5606
5606
|
}
|
|
5607
|
-
if (context.pattern !== defaults$
|
|
5607
|
+
if (context.pattern !== defaults$k.pattern) {
|
|
5608
5608
|
return [
|
|
5609
5609
|
`<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
|
|
5610
5610
|
"",
|
|
@@ -6093,7 +6093,7 @@ class EmptyTitle extends Rule {
|
|
|
6093
6093
|
}
|
|
6094
6094
|
}
|
|
6095
6095
|
|
|
6096
|
-
const defaults$
|
|
6096
|
+
const defaults$j = {
|
|
6097
6097
|
allowArrayBrackets: true,
|
|
6098
6098
|
shared: ["radio", "button", "reset", "submit"]
|
|
6099
6099
|
};
|
|
@@ -6121,7 +6121,7 @@ function getDocumentation(context) {
|
|
|
6121
6121
|
}
|
|
6122
6122
|
class FormDupName extends Rule {
|
|
6123
6123
|
constructor(options) {
|
|
6124
|
-
super({ ...defaults$
|
|
6124
|
+
super({ ...defaults$j, ...options });
|
|
6125
6125
|
}
|
|
6126
6126
|
static schema() {
|
|
6127
6127
|
return {
|
|
@@ -6270,7 +6270,7 @@ class FormDupName extends Rule {
|
|
|
6270
6270
|
}
|
|
6271
6271
|
}
|
|
6272
6272
|
|
|
6273
|
-
const defaults$
|
|
6273
|
+
const defaults$i = {
|
|
6274
6274
|
allowMultipleH1: false,
|
|
6275
6275
|
minInitialRank: "h1",
|
|
6276
6276
|
sectioningRoots: ["dialog", '[role="dialog"]', '[role="alertdialog"]']
|
|
@@ -6299,7 +6299,7 @@ function parseMaxInitial(value) {
|
|
|
6299
6299
|
}
|
|
6300
6300
|
class HeadingLevel extends Rule {
|
|
6301
6301
|
constructor(options) {
|
|
6302
|
-
super({ ...defaults$
|
|
6302
|
+
super({ ...defaults$i, ...options });
|
|
6303
6303
|
this.stack = [];
|
|
6304
6304
|
this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
|
|
6305
6305
|
this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
|
|
@@ -6502,12 +6502,12 @@ class HiddenFocusable extends Rule {
|
|
|
6502
6502
|
}
|
|
6503
6503
|
}
|
|
6504
6504
|
|
|
6505
|
-
const defaults$
|
|
6505
|
+
const defaults$h = {
|
|
6506
6506
|
pattern: "kebabcase"
|
|
6507
6507
|
};
|
|
6508
6508
|
class IdPattern extends Rule {
|
|
6509
6509
|
constructor(options) {
|
|
6510
|
-
super({ ...defaults$
|
|
6510
|
+
super({ ...defaults$h, ...options });
|
|
6511
6511
|
this.pattern = parsePattern(this.options.pattern);
|
|
6512
6512
|
}
|
|
6513
6513
|
static schema() {
|
|
@@ -6546,26 +6546,6 @@ class IdPattern extends Rule {
|
|
|
6546
6546
|
const restricted = /* @__PURE__ */ new Map([
|
|
6547
6547
|
["accept", ["file"]],
|
|
6548
6548
|
["alt", ["image"]],
|
|
6549
|
-
[
|
|
6550
|
-
"autocomplete",
|
|
6551
|
-
[
|
|
6552
|
-
"hidden",
|
|
6553
|
-
"text",
|
|
6554
|
-
"search",
|
|
6555
|
-
"url",
|
|
6556
|
-
"tel",
|
|
6557
|
-
"email",
|
|
6558
|
-
"password",
|
|
6559
|
-
"date",
|
|
6560
|
-
"month",
|
|
6561
|
-
"week",
|
|
6562
|
-
"time",
|
|
6563
|
-
"datetime-local",
|
|
6564
|
-
"number",
|
|
6565
|
-
"range",
|
|
6566
|
-
"color"
|
|
6567
|
-
]
|
|
6568
|
-
],
|
|
6569
6549
|
["capture", ["file"]],
|
|
6570
6550
|
["checked", ["checkbox", "radio"]],
|
|
6571
6551
|
["dirname", ["text", "search"]],
|
|
@@ -6860,12 +6840,12 @@ function findLabelByParent(el) {
|
|
|
6860
6840
|
return [];
|
|
6861
6841
|
}
|
|
6862
6842
|
|
|
6863
|
-
const defaults$
|
|
6843
|
+
const defaults$g = {
|
|
6864
6844
|
maxlength: 70
|
|
6865
6845
|
};
|
|
6866
6846
|
class LongTitle extends Rule {
|
|
6867
6847
|
constructor(options) {
|
|
6868
|
-
super({ ...defaults$
|
|
6848
|
+
super({ ...defaults$g, ...options });
|
|
6869
6849
|
this.maxlength = this.options.maxlength;
|
|
6870
6850
|
}
|
|
6871
6851
|
static schema() {
|
|
@@ -6894,7 +6874,13 @@ class LongTitle extends Rule {
|
|
|
6894
6874
|
}
|
|
6895
6875
|
}
|
|
6896
6876
|
|
|
6877
|
+
const defaults$f = {
|
|
6878
|
+
allowLongDelay: false
|
|
6879
|
+
};
|
|
6897
6880
|
class MetaRefresh extends Rule {
|
|
6881
|
+
constructor(options) {
|
|
6882
|
+
super({ ...defaults$f, ...options });
|
|
6883
|
+
}
|
|
6898
6884
|
documentation() {
|
|
6899
6885
|
return {
|
|
6900
6886
|
description: `Meta refresh directive must use the \`0;url=...\` format. Non-zero values for time interval is disallowed as people with assistive technology might be unable to read and understand the page content before automatically reloading. For the same reason skipping the url is disallowed as it would put the browser in an infinite loop reloading the same page over and over again.`,
|
|
@@ -6920,17 +6906,27 @@ class MetaRefresh extends Rule {
|
|
|
6920
6906
|
this.report(target, "Malformed meta refresh directive", location);
|
|
6921
6907
|
return;
|
|
6922
6908
|
}
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
}
|
|
6926
|
-
if (value.delay !== 0) {
|
|
6927
|
-
this.report(target, "Meta refresh must use 0 second delay", location);
|
|
6928
|
-
}
|
|
6909
|
+
const { delay, url } = value;
|
|
6910
|
+
this.validateDelay(target, location, delay, url);
|
|
6929
6911
|
});
|
|
6930
6912
|
}
|
|
6913
|
+
validateDelay(target, location, delay, url) {
|
|
6914
|
+
const { allowLongDelay } = this.options;
|
|
6915
|
+
if (allowLongDelay && delay > 72e3) {
|
|
6916
|
+
return;
|
|
6917
|
+
}
|
|
6918
|
+
if (!url && delay === 0) {
|
|
6919
|
+
this.report(target, "Don't use instant meta refresh to reload the page", location);
|
|
6920
|
+
return;
|
|
6921
|
+
}
|
|
6922
|
+
if (delay !== 0) {
|
|
6923
|
+
const message = allowLongDelay ? "Meta refresh must be instant (0 second delay) or greater than 20 hours (72000 second delay)" : "Meta refresh must be instant (0 second delay)";
|
|
6924
|
+
this.report(target, message, location);
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6931
6927
|
}
|
|
6932
6928
|
function parseContent(text) {
|
|
6933
|
-
const match = text.match(/^(\d+)(?:\s*;\s*url=(.*))?/);
|
|
6929
|
+
const match = text.match(/^(\d+)(?:\s*;\s*url=(.*))?/i);
|
|
6934
6930
|
if (match) {
|
|
6935
6931
|
return {
|
|
6936
6932
|
delay: parseInt(match[1], 10),
|
|
@@ -9017,6 +9013,522 @@ class UnknownCharReference extends Rule {
|
|
|
9017
9013
|
}
|
|
9018
9014
|
}
|
|
9019
9015
|
|
|
9016
|
+
const expectedOrder = ["section", "hint", "contact", "field1", "field2", "webauthn"];
|
|
9017
|
+
const fieldNames1 = [
|
|
9018
|
+
"name",
|
|
9019
|
+
"honorific-prefix",
|
|
9020
|
+
"given-name",
|
|
9021
|
+
"additional-name",
|
|
9022
|
+
"family-name",
|
|
9023
|
+
"honorific-suffix",
|
|
9024
|
+
"nickname",
|
|
9025
|
+
"username",
|
|
9026
|
+
"new-password",
|
|
9027
|
+
"current-password",
|
|
9028
|
+
"one-time-code",
|
|
9029
|
+
"organization-title",
|
|
9030
|
+
"organization",
|
|
9031
|
+
"street-address",
|
|
9032
|
+
"address-line1",
|
|
9033
|
+
"address-line2",
|
|
9034
|
+
"address-line3",
|
|
9035
|
+
"address-level4",
|
|
9036
|
+
"address-level3",
|
|
9037
|
+
"address-level2",
|
|
9038
|
+
"address-level1",
|
|
9039
|
+
"country",
|
|
9040
|
+
"country-name",
|
|
9041
|
+
"postal-code",
|
|
9042
|
+
"cc-name",
|
|
9043
|
+
"cc-given-name",
|
|
9044
|
+
"cc-additional-name",
|
|
9045
|
+
"cc-family-name",
|
|
9046
|
+
"cc-number",
|
|
9047
|
+
"cc-exp",
|
|
9048
|
+
"cc-exp-month",
|
|
9049
|
+
"cc-exp-year",
|
|
9050
|
+
"cc-csc",
|
|
9051
|
+
"cc-type",
|
|
9052
|
+
"transaction-currency",
|
|
9053
|
+
"transaction-amount",
|
|
9054
|
+
"language",
|
|
9055
|
+
"bday",
|
|
9056
|
+
"bday-day",
|
|
9057
|
+
"bday-month",
|
|
9058
|
+
"bday-year",
|
|
9059
|
+
"sex",
|
|
9060
|
+
"url",
|
|
9061
|
+
"photo"
|
|
9062
|
+
];
|
|
9063
|
+
const fieldNames2 = [
|
|
9064
|
+
"tel",
|
|
9065
|
+
"tel-country-code",
|
|
9066
|
+
"tel-national",
|
|
9067
|
+
"tel-area-code",
|
|
9068
|
+
"tel-local",
|
|
9069
|
+
"tel-local-prefix",
|
|
9070
|
+
"tel-local-suffix",
|
|
9071
|
+
"tel-extension",
|
|
9072
|
+
"email",
|
|
9073
|
+
"impp"
|
|
9074
|
+
];
|
|
9075
|
+
const fieldNameGroup = {
|
|
9076
|
+
name: "text",
|
|
9077
|
+
"honorific-prefix": "text",
|
|
9078
|
+
"given-name": "text",
|
|
9079
|
+
"additional-name": "text",
|
|
9080
|
+
"family-name": "text",
|
|
9081
|
+
"honorific-suffix": "text",
|
|
9082
|
+
nickname: "text",
|
|
9083
|
+
username: "username",
|
|
9084
|
+
"new-password": "password",
|
|
9085
|
+
"current-password": "password",
|
|
9086
|
+
"one-time-code": "password",
|
|
9087
|
+
"organization-title": "text",
|
|
9088
|
+
organization: "text",
|
|
9089
|
+
"street-address": "multiline",
|
|
9090
|
+
"address-line1": "text",
|
|
9091
|
+
"address-line2": "text",
|
|
9092
|
+
"address-line3": "text",
|
|
9093
|
+
"address-level4": "text",
|
|
9094
|
+
"address-level3": "text",
|
|
9095
|
+
"address-level2": "text",
|
|
9096
|
+
"address-level1": "text",
|
|
9097
|
+
country: "text",
|
|
9098
|
+
"country-name": "text",
|
|
9099
|
+
"postal-code": "text",
|
|
9100
|
+
"cc-name": "text",
|
|
9101
|
+
"cc-given-name": "text",
|
|
9102
|
+
"cc-additional-name": "text",
|
|
9103
|
+
"cc-family-name": "text",
|
|
9104
|
+
"cc-number": "text",
|
|
9105
|
+
"cc-exp": "month",
|
|
9106
|
+
"cc-exp-month": "numeric",
|
|
9107
|
+
"cc-exp-year": "numeric",
|
|
9108
|
+
"cc-csc": "text",
|
|
9109
|
+
"cc-type": "text",
|
|
9110
|
+
"transaction-currency": "text",
|
|
9111
|
+
"transaction-amount": "numeric",
|
|
9112
|
+
language: "text",
|
|
9113
|
+
bday: "date",
|
|
9114
|
+
"bday-day": "numeric",
|
|
9115
|
+
"bday-month": "numeric",
|
|
9116
|
+
"bday-year": "numeric",
|
|
9117
|
+
sex: "text",
|
|
9118
|
+
url: "url",
|
|
9119
|
+
photo: "url",
|
|
9120
|
+
tel: "tel",
|
|
9121
|
+
"tel-country-code": "text",
|
|
9122
|
+
"tel-national": "text",
|
|
9123
|
+
"tel-area-code": "text",
|
|
9124
|
+
"tel-local": "text",
|
|
9125
|
+
"tel-local-prefix": "text",
|
|
9126
|
+
"tel-local-suffix": "text",
|
|
9127
|
+
"tel-extension": "text",
|
|
9128
|
+
email: "username",
|
|
9129
|
+
impp: "url"
|
|
9130
|
+
};
|
|
9131
|
+
const disallowedInputTypes = ["checkbox", "radio", "file", "submit", "image", "reset", "button"];
|
|
9132
|
+
function matchSection(token) {
|
|
9133
|
+
return token.startsWith("section-");
|
|
9134
|
+
}
|
|
9135
|
+
function matchHint(token) {
|
|
9136
|
+
return token === "shipping" || token === "billing";
|
|
9137
|
+
}
|
|
9138
|
+
function matchFieldNames1(token) {
|
|
9139
|
+
return fieldNames1.includes(token);
|
|
9140
|
+
}
|
|
9141
|
+
function matchContact(token) {
|
|
9142
|
+
const haystack = ["home", "work", "mobile", "fax", "pager"];
|
|
9143
|
+
return haystack.includes(token);
|
|
9144
|
+
}
|
|
9145
|
+
function matchFieldNames2(token) {
|
|
9146
|
+
return fieldNames2.includes(token);
|
|
9147
|
+
}
|
|
9148
|
+
function matchWebauthn(token) {
|
|
9149
|
+
return token === "webauthn";
|
|
9150
|
+
}
|
|
9151
|
+
function matchToken(token) {
|
|
9152
|
+
if (matchSection(token)) {
|
|
9153
|
+
return "section";
|
|
9154
|
+
}
|
|
9155
|
+
if (matchHint(token)) {
|
|
9156
|
+
return "hint";
|
|
9157
|
+
}
|
|
9158
|
+
if (matchFieldNames1(token)) {
|
|
9159
|
+
return "field1";
|
|
9160
|
+
}
|
|
9161
|
+
if (matchFieldNames2(token)) {
|
|
9162
|
+
return "field2";
|
|
9163
|
+
}
|
|
9164
|
+
if (matchContact(token)) {
|
|
9165
|
+
return "contact";
|
|
9166
|
+
}
|
|
9167
|
+
if (matchWebauthn(token)) {
|
|
9168
|
+
return "webauthn";
|
|
9169
|
+
}
|
|
9170
|
+
return null;
|
|
9171
|
+
}
|
|
9172
|
+
function getControlGroups(type) {
|
|
9173
|
+
const allGroups = [
|
|
9174
|
+
"text",
|
|
9175
|
+
"multiline",
|
|
9176
|
+
"password",
|
|
9177
|
+
"url",
|
|
9178
|
+
"username",
|
|
9179
|
+
"tel",
|
|
9180
|
+
"numeric",
|
|
9181
|
+
"month",
|
|
9182
|
+
"date"
|
|
9183
|
+
];
|
|
9184
|
+
const mapping = {
|
|
9185
|
+
hidden: allGroups,
|
|
9186
|
+
text: allGroups.filter((it) => it !== "multiline"),
|
|
9187
|
+
search: allGroups.filter((it) => it !== "multiline"),
|
|
9188
|
+
password: ["password"],
|
|
9189
|
+
url: ["url"],
|
|
9190
|
+
email: ["username"],
|
|
9191
|
+
tel: ["tel"],
|
|
9192
|
+
number: ["numeric"],
|
|
9193
|
+
month: ["month"],
|
|
9194
|
+
date: ["date"]
|
|
9195
|
+
};
|
|
9196
|
+
const groups = mapping[type];
|
|
9197
|
+
if (groups) {
|
|
9198
|
+
return groups;
|
|
9199
|
+
}
|
|
9200
|
+
return [];
|
|
9201
|
+
}
|
|
9202
|
+
function isDisallowedType(node, type) {
|
|
9203
|
+
if (!node.is("input")) {
|
|
9204
|
+
return false;
|
|
9205
|
+
}
|
|
9206
|
+
return disallowedInputTypes.includes(type);
|
|
9207
|
+
}
|
|
9208
|
+
function getTerminalMessage(context) {
|
|
9209
|
+
switch (context.msg) {
|
|
9210
|
+
case 0 /* InvalidAttribute */:
|
|
9211
|
+
return "autocomplete attribute cannot be used on {{ what }}";
|
|
9212
|
+
case 1 /* InvalidValue */:
|
|
9213
|
+
return '"{{ value }}" cannot be used on {{ what }}';
|
|
9214
|
+
case 2 /* InvalidOrder */:
|
|
9215
|
+
return '"{{ second }}" must appear before "{{ first }}"';
|
|
9216
|
+
case 3 /* InvalidToken */:
|
|
9217
|
+
return '"{{ token }}" is not a valid autocomplete token or field name';
|
|
9218
|
+
case 4 /* InvalidCombination */:
|
|
9219
|
+
return '"{{ second }}" cannot be combined with "{{ first }}"';
|
|
9220
|
+
case 5 /* MissingField */:
|
|
9221
|
+
return "autocomplete attribute is missing field name";
|
|
9222
|
+
}
|
|
9223
|
+
}
|
|
9224
|
+
function getMarkdownMessage(context) {
|
|
9225
|
+
switch (context.msg) {
|
|
9226
|
+
case 0 /* InvalidAttribute */:
|
|
9227
|
+
return [
|
|
9228
|
+
`\`autocomplete\` attribute cannot be used on \`${context.what}\``,
|
|
9229
|
+
"",
|
|
9230
|
+
"The following input types cannot use the `autocomplete` attribute:",
|
|
9231
|
+
"",
|
|
9232
|
+
...disallowedInputTypes.map((it) => `- \`${it}\``)
|
|
9233
|
+
].join("\n");
|
|
9234
|
+
case 1 /* InvalidValue */: {
|
|
9235
|
+
const message = `\`"${context.value}"\` cannot be used on \`${context.what}\``;
|
|
9236
|
+
if (context.type === "form") {
|
|
9237
|
+
return [
|
|
9238
|
+
message,
|
|
9239
|
+
"",
|
|
9240
|
+
'The `<form>` element can only use the values `"on"` and `"off"`.'
|
|
9241
|
+
].join("\n");
|
|
9242
|
+
}
|
|
9243
|
+
if (context.type === "hidden") {
|
|
9244
|
+
return [
|
|
9245
|
+
message,
|
|
9246
|
+
"",
|
|
9247
|
+
'`<input type="hidden">` cannot use the values `"on"` and `"off"`.'
|
|
9248
|
+
].join("\n");
|
|
9249
|
+
}
|
|
9250
|
+
const controlGroups = getControlGroups(context.type);
|
|
9251
|
+
const currentGroup = fieldNameGroup[context.value];
|
|
9252
|
+
return [
|
|
9253
|
+
message,
|
|
9254
|
+
"",
|
|
9255
|
+
`\`${context.what}\` allows autocomplete fields from the following group${controlGroups.length > 1 ? "s" : ""}:`,
|
|
9256
|
+
"",
|
|
9257
|
+
...controlGroups.map((it) => `- ${it}`),
|
|
9258
|
+
"",
|
|
9259
|
+
`The field \`"${context.value}"\` belongs to the group /${currentGroup}/ which cannot be used with this input type.`
|
|
9260
|
+
].join("\n");
|
|
9261
|
+
}
|
|
9262
|
+
case 2 /* InvalidOrder */:
|
|
9263
|
+
return [
|
|
9264
|
+
`\`"${context.second}"\` must appear before \`"${context.first}"\``,
|
|
9265
|
+
"",
|
|
9266
|
+
"The autocomplete tokens must appear in the following order:",
|
|
9267
|
+
"",
|
|
9268
|
+
"- Optional section name (`section-` prefix).",
|
|
9269
|
+
"- Optional `shipping` or `billing` token.",
|
|
9270
|
+
"- Optional `home`, `work`, `mobile`, `fax` or `pager` token (for fields supporting it).",
|
|
9271
|
+
"- Field name",
|
|
9272
|
+
"- Optional `webauthn` token."
|
|
9273
|
+
].join("\n");
|
|
9274
|
+
case 3 /* InvalidToken */:
|
|
9275
|
+
return `\`"${context.token}"\` is not a valid autocomplete token or field name`;
|
|
9276
|
+
case 4 /* InvalidCombination */:
|
|
9277
|
+
return `\`"${context.second}"\` cannot be combined with \`"${context.first}"\``;
|
|
9278
|
+
case 5 /* MissingField */:
|
|
9279
|
+
return "Autocomplete attribute is missing field name";
|
|
9280
|
+
}
|
|
9281
|
+
}
|
|
9282
|
+
class ValidAutocomplete extends Rule {
|
|
9283
|
+
documentation(context) {
|
|
9284
|
+
return {
|
|
9285
|
+
description: getMarkdownMessage(context),
|
|
9286
|
+
url: "https://html-validate.org/rules/valid-autocomplete.html"
|
|
9287
|
+
};
|
|
9288
|
+
}
|
|
9289
|
+
setup() {
|
|
9290
|
+
this.on("dom:ready", (event) => {
|
|
9291
|
+
const { document } = event;
|
|
9292
|
+
const elements = document.querySelectorAll("[autocomplete]");
|
|
9293
|
+
for (const element of elements) {
|
|
9294
|
+
const autocomplete = element.getAttribute("autocomplete");
|
|
9295
|
+
if (autocomplete.value === null || autocomplete.value instanceof DynamicValue) {
|
|
9296
|
+
continue;
|
|
9297
|
+
}
|
|
9298
|
+
const location = autocomplete.valueLocation;
|
|
9299
|
+
const value = autocomplete.value.toLowerCase();
|
|
9300
|
+
const tokens = new DOMTokenList(value, location);
|
|
9301
|
+
if (tokens.length === 0) {
|
|
9302
|
+
continue;
|
|
9303
|
+
}
|
|
9304
|
+
this.validate(element, value, tokens, autocomplete.keyLocation, location);
|
|
9305
|
+
}
|
|
9306
|
+
});
|
|
9307
|
+
}
|
|
9308
|
+
validate(node, value, tokens, keyLocation, valueLocation) {
|
|
9309
|
+
switch (node.tagName) {
|
|
9310
|
+
case "form":
|
|
9311
|
+
this.validateFormAutocomplete(node, value, valueLocation);
|
|
9312
|
+
break;
|
|
9313
|
+
case "input":
|
|
9314
|
+
case "textarea":
|
|
9315
|
+
case "select":
|
|
9316
|
+
this.validateControlAutocomplete(node, tokens, keyLocation);
|
|
9317
|
+
break;
|
|
9318
|
+
}
|
|
9319
|
+
}
|
|
9320
|
+
validateControlAutocomplete(node, tokens, keyLocation) {
|
|
9321
|
+
const type = node.getAttributeValue("type") ?? "text";
|
|
9322
|
+
const mantle = type !== "hidden" ? "expectation" : "anchor";
|
|
9323
|
+
if (isDisallowedType(node, type)) {
|
|
9324
|
+
const context = {
|
|
9325
|
+
msg: 0 /* InvalidAttribute */,
|
|
9326
|
+
what: `<input type="${type}">`
|
|
9327
|
+
};
|
|
9328
|
+
this.report({
|
|
9329
|
+
node,
|
|
9330
|
+
message: getTerminalMessage(context),
|
|
9331
|
+
location: keyLocation,
|
|
9332
|
+
context
|
|
9333
|
+
});
|
|
9334
|
+
return;
|
|
9335
|
+
}
|
|
9336
|
+
if (tokens.includes("on") || tokens.includes("off")) {
|
|
9337
|
+
this.validateOnOff(node, mantle, tokens);
|
|
9338
|
+
return;
|
|
9339
|
+
}
|
|
9340
|
+
this.validateTokens(node, tokens, keyLocation);
|
|
9341
|
+
}
|
|
9342
|
+
validateFormAutocomplete(node, value, location) {
|
|
9343
|
+
const trimmed = value.trim();
|
|
9344
|
+
if (["on", "off"].includes(trimmed)) {
|
|
9345
|
+
return;
|
|
9346
|
+
}
|
|
9347
|
+
const context = {
|
|
9348
|
+
msg: 1 /* InvalidValue */,
|
|
9349
|
+
type: "form",
|
|
9350
|
+
value: trimmed,
|
|
9351
|
+
what: "<form>"
|
|
9352
|
+
};
|
|
9353
|
+
this.report({
|
|
9354
|
+
node,
|
|
9355
|
+
message: getTerminalMessage(context),
|
|
9356
|
+
location,
|
|
9357
|
+
context
|
|
9358
|
+
});
|
|
9359
|
+
}
|
|
9360
|
+
validateOnOff(node, mantle, tokens) {
|
|
9361
|
+
const index = tokens.findIndex((it) => it === "on" || it === "off");
|
|
9362
|
+
const value = tokens.item(index);
|
|
9363
|
+
const location = tokens.location(index);
|
|
9364
|
+
if (tokens.length > 1) {
|
|
9365
|
+
const context = {
|
|
9366
|
+
msg: 4 /* InvalidCombination */,
|
|
9367
|
+
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- it must be present of it wouldn't be found */
|
|
9368
|
+
first: tokens.item(index > 0 ? 0 : 1),
|
|
9369
|
+
second: value
|
|
9370
|
+
};
|
|
9371
|
+
this.report({
|
|
9372
|
+
node,
|
|
9373
|
+
message: getTerminalMessage(context),
|
|
9374
|
+
location,
|
|
9375
|
+
context
|
|
9376
|
+
});
|
|
9377
|
+
}
|
|
9378
|
+
switch (mantle) {
|
|
9379
|
+
case "expectation":
|
|
9380
|
+
return;
|
|
9381
|
+
case "anchor": {
|
|
9382
|
+
const context = {
|
|
9383
|
+
msg: 1 /* InvalidValue */,
|
|
9384
|
+
type: "hidden",
|
|
9385
|
+
value,
|
|
9386
|
+
what: `<input type="hidden">`
|
|
9387
|
+
};
|
|
9388
|
+
this.report({
|
|
9389
|
+
node,
|
|
9390
|
+
message: getTerminalMessage(context),
|
|
9391
|
+
location: tokens.location(0),
|
|
9392
|
+
context
|
|
9393
|
+
});
|
|
9394
|
+
}
|
|
9395
|
+
}
|
|
9396
|
+
}
|
|
9397
|
+
validateTokens(node, tokens, keyLocation) {
|
|
9398
|
+
const order = [];
|
|
9399
|
+
for (const { item, location } of tokens.iterator()) {
|
|
9400
|
+
const tokenType = matchToken(item);
|
|
9401
|
+
if (tokenType) {
|
|
9402
|
+
order.push(tokenType);
|
|
9403
|
+
} else {
|
|
9404
|
+
const context = {
|
|
9405
|
+
msg: 3 /* InvalidToken */,
|
|
9406
|
+
token: item
|
|
9407
|
+
};
|
|
9408
|
+
this.report({
|
|
9409
|
+
node,
|
|
9410
|
+
message: getTerminalMessage(context),
|
|
9411
|
+
location,
|
|
9412
|
+
context
|
|
9413
|
+
});
|
|
9414
|
+
return;
|
|
9415
|
+
}
|
|
9416
|
+
}
|
|
9417
|
+
const fieldTokens = order.map((it) => it === "field1" || it === "field2");
|
|
9418
|
+
this.validateFieldPresence(node, tokens, fieldTokens, keyLocation);
|
|
9419
|
+
this.validateContact(node, tokens, order);
|
|
9420
|
+
this.validateOrder(node, tokens, order);
|
|
9421
|
+
this.validateControlGroup(node, tokens, fieldTokens);
|
|
9422
|
+
}
|
|
9423
|
+
/**
|
|
9424
|
+
* Ensure that exactly one field name is present from the two field lists.
|
|
9425
|
+
*/
|
|
9426
|
+
validateFieldPresence(node, tokens, fieldTokens, keyLocation) {
|
|
9427
|
+
const numFields = fieldTokens.filter(Boolean).length;
|
|
9428
|
+
if (numFields === 0) {
|
|
9429
|
+
const context = {
|
|
9430
|
+
msg: 5 /* MissingField */
|
|
9431
|
+
};
|
|
9432
|
+
this.report({
|
|
9433
|
+
node,
|
|
9434
|
+
message: getTerminalMessage(context),
|
|
9435
|
+
location: keyLocation,
|
|
9436
|
+
context
|
|
9437
|
+
});
|
|
9438
|
+
} else if (numFields > 1) {
|
|
9439
|
+
const a = fieldTokens.indexOf(true);
|
|
9440
|
+
const b = fieldTokens.lastIndexOf(true);
|
|
9441
|
+
const context = {
|
|
9442
|
+
msg: 4 /* InvalidCombination */,
|
|
9443
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion -- it must be present of it wouldn't be found */
|
|
9444
|
+
first: tokens.item(a),
|
|
9445
|
+
second: tokens.item(b)
|
|
9446
|
+
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
|
9447
|
+
};
|
|
9448
|
+
this.report({
|
|
9449
|
+
node,
|
|
9450
|
+
message: getTerminalMessage(context),
|
|
9451
|
+
location: tokens.location(b),
|
|
9452
|
+
context
|
|
9453
|
+
});
|
|
9454
|
+
}
|
|
9455
|
+
}
|
|
9456
|
+
/**
|
|
9457
|
+
* Ensure contact token is only used with field names from the second list.
|
|
9458
|
+
*/
|
|
9459
|
+
validateContact(node, tokens, order) {
|
|
9460
|
+
if (order.includes("contact") && order.includes("field1")) {
|
|
9461
|
+
const a = order.indexOf("field1");
|
|
9462
|
+
const b = order.indexOf("contact");
|
|
9463
|
+
const context = {
|
|
9464
|
+
msg: 4 /* InvalidCombination */,
|
|
9465
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion -- it must be present of it wouldn't be found */
|
|
9466
|
+
first: tokens.item(a),
|
|
9467
|
+
second: tokens.item(b)
|
|
9468
|
+
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
|
9469
|
+
};
|
|
9470
|
+
this.report({
|
|
9471
|
+
node,
|
|
9472
|
+
message: getTerminalMessage(context),
|
|
9473
|
+
location: tokens.location(b),
|
|
9474
|
+
context
|
|
9475
|
+
});
|
|
9476
|
+
}
|
|
9477
|
+
}
|
|
9478
|
+
validateOrder(node, tokens, order) {
|
|
9479
|
+
const indicies = order.map((it) => expectedOrder.indexOf(it));
|
|
9480
|
+
for (let i = 0; i < indicies.length - 1; i++) {
|
|
9481
|
+
if (indicies[0] > indicies[i + 1]) {
|
|
9482
|
+
const context = {
|
|
9483
|
+
msg: 2 /* InvalidOrder */,
|
|
9484
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion -- it must be present of it wouldn't be found */
|
|
9485
|
+
first: tokens.item(i),
|
|
9486
|
+
second: tokens.item(i + 1)
|
|
9487
|
+
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
|
9488
|
+
};
|
|
9489
|
+
this.report({
|
|
9490
|
+
node,
|
|
9491
|
+
message: getTerminalMessage(context),
|
|
9492
|
+
location: tokens.location(i + 1),
|
|
9493
|
+
context
|
|
9494
|
+
});
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
}
|
|
9498
|
+
validateControlGroup(node, tokens, fieldTokens) {
|
|
9499
|
+
const numFields = fieldTokens.filter(Boolean).length;
|
|
9500
|
+
if (numFields === 0) {
|
|
9501
|
+
return;
|
|
9502
|
+
}
|
|
9503
|
+
if (!node.is("input")) {
|
|
9504
|
+
return;
|
|
9505
|
+
}
|
|
9506
|
+
const attr = node.getAttribute("type");
|
|
9507
|
+
const type = (attr == null ? void 0 : attr.value) ?? "text";
|
|
9508
|
+
if (type instanceof DynamicValue) {
|
|
9509
|
+
return;
|
|
9510
|
+
}
|
|
9511
|
+
const controlGroups = getControlGroups(type);
|
|
9512
|
+
const fieldIndex = fieldTokens.indexOf(true);
|
|
9513
|
+
const fieldToken = tokens.item(fieldIndex);
|
|
9514
|
+
const fieldGroup = fieldNameGroup[fieldToken];
|
|
9515
|
+
if (!controlGroups.includes(fieldGroup)) {
|
|
9516
|
+
const context = {
|
|
9517
|
+
msg: 1 /* InvalidValue */,
|
|
9518
|
+
type,
|
|
9519
|
+
value: fieldToken,
|
|
9520
|
+
what: `<input type="${type}">`
|
|
9521
|
+
};
|
|
9522
|
+
this.report({
|
|
9523
|
+
node,
|
|
9524
|
+
message: getTerminalMessage(context),
|
|
9525
|
+
location: tokens.location(fieldIndex),
|
|
9526
|
+
context
|
|
9527
|
+
});
|
|
9528
|
+
}
|
|
9529
|
+
}
|
|
9530
|
+
}
|
|
9531
|
+
|
|
9020
9532
|
const defaults$3 = {
|
|
9021
9533
|
relaxed: false
|
|
9022
9534
|
};
|
|
@@ -9571,6 +10083,7 @@ const bundledRules = {
|
|
|
9571
10083
|
"text-content": TextContent,
|
|
9572
10084
|
"unique-landmark": UniqueLandmark,
|
|
9573
10085
|
"unrecognized-char-ref": UnknownCharReference,
|
|
10086
|
+
"valid-autocomplete": ValidAutocomplete,
|
|
9574
10087
|
"valid-id": ValidID,
|
|
9575
10088
|
"void-content": VoidContent,
|
|
9576
10089
|
"void-style": VoidStyle,
|
|
@@ -9601,6 +10114,7 @@ const config$4 = {
|
|
|
9601
10114
|
"svg-focusable": "off",
|
|
9602
10115
|
"text-content": "error",
|
|
9603
10116
|
"unique-landmark": "error",
|
|
10117
|
+
"valid-autocomplete": "error",
|
|
9604
10118
|
"wcag/h30": "error",
|
|
9605
10119
|
"wcag/h32": "error",
|
|
9606
10120
|
"wcag/h36": "error",
|
|
@@ -9700,6 +10214,7 @@ const config$1 = {
|
|
|
9700
10214
|
"text-content": "error",
|
|
9701
10215
|
"unique-landmark": "error",
|
|
9702
10216
|
"unrecognized-char-ref": "error",
|
|
10217
|
+
"valid-autocomplete": "error",
|
|
9703
10218
|
"valid-id": ["error", { relaxed: false }],
|
|
9704
10219
|
void: "off",
|
|
9705
10220
|
"void-content": "error",
|
|
@@ -9747,6 +10262,7 @@ const config = {
|
|
|
9747
10262
|
"no-unused-disable": "error",
|
|
9748
10263
|
"script-element": "error",
|
|
9749
10264
|
"unrecognized-char-ref": "error",
|
|
10265
|
+
"valid-autocomplete": "error",
|
|
9750
10266
|
"valid-id": ["error", { relaxed: true }],
|
|
9751
10267
|
"void-content": "error"
|
|
9752
10268
|
}
|
|
@@ -11972,7 +12488,7 @@ class HtmlValidate {
|
|
|
11972
12488
|
}
|
|
11973
12489
|
|
|
11974
12490
|
const name = "html-validate";
|
|
11975
|
-
const version = "8.
|
|
12491
|
+
const version = "8.15.0";
|
|
11976
12492
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
11977
12493
|
|
|
11978
12494
|
function definePlugin(plugin) {
|