html-validate 7.1.0 → 7.2.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/{LICENCE → LICENSE} +0 -0
- package/dist/cjs/cli.js +126 -7
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/core.d.ts +11 -2
- package/dist/cjs/core.js +262 -125
- package/dist/cjs/core.js.map +1 -1
- package/dist/cjs/html-validate.js +49 -135
- package/dist/cjs/html-validate.js.map +1 -1
- package/dist/cjs/jest-lib.js +1 -0
- package/dist/cjs/jest-lib.js.map +1 -1
- package/dist/es/cli.js +122 -8
- package/dist/es/cli.js.map +1 -1
- package/dist/es/core.d.ts +11 -2
- package/dist/es/core.js +262 -125
- package/dist/es/core.js.map +1 -1
- package/dist/es/html-validate.js +34 -120
- package/dist/es/html-validate.js.map +1 -1
- package/dist/es/jest-lib.js +1 -0
- package/dist/es/jest-lib.js.map +1 -1
- package/elements/html5.js +18 -0
- package/package.json +26 -55
package/dist/cjs/core.js
CHANGED
|
@@ -1763,7 +1763,7 @@ function stripslashes(value) {
|
|
|
1763
1763
|
return value.replace(/\\(.)/g, "$1");
|
|
1764
1764
|
}
|
|
1765
1765
|
function escapeSelectorComponent(text) {
|
|
1766
|
-
return text.toString().replace(/([
|
|
1766
|
+
return text.toString().replace(/([^a-z0-9_-])/gi, "\\$1");
|
|
1767
1767
|
}
|
|
1768
1768
|
class Matcher {
|
|
1769
1769
|
}
|
|
@@ -2646,12 +2646,15 @@ class Validator {
|
|
|
2646
2646
|
if (value === null || value === undefined) {
|
|
2647
2647
|
return false;
|
|
2648
2648
|
}
|
|
2649
|
+
const caseInsensitiveValue = value.toLowerCase();
|
|
2649
2650
|
return rule.enum.some((entry) => {
|
|
2650
2651
|
if (entry instanceof RegExp) {
|
|
2652
|
+
/* regular expressions are matched case-sensitive */
|
|
2651
2653
|
return !!value.match(entry);
|
|
2652
2654
|
}
|
|
2653
2655
|
else {
|
|
2654
|
-
|
|
2656
|
+
/* strings matched case-insensitive */
|
|
2657
|
+
return caseInsensitiveValue === entry;
|
|
2655
2658
|
}
|
|
2656
2659
|
});
|
|
2657
2660
|
}
|
|
@@ -3114,7 +3117,7 @@ var TRANSFORMER_API;
|
|
|
3114
3117
|
/** @public */
|
|
3115
3118
|
const name = "html-validate";
|
|
3116
3119
|
/** @public */
|
|
3117
|
-
const version = "7.
|
|
3120
|
+
const version = "7.2.0";
|
|
3118
3121
|
/** @public */
|
|
3119
3122
|
const homepage = "https://html-validate.org";
|
|
3120
3123
|
/** @public */
|
|
@@ -3215,6 +3218,18 @@ function getSchemaValidator(ruleId, properties) {
|
|
|
3215
3218
|
};
|
|
3216
3219
|
return ajv$1.compile(schema);
|
|
3217
3220
|
}
|
|
3221
|
+
function isErrorDescriptor(value) {
|
|
3222
|
+
return Boolean(value[0] && value[0].message);
|
|
3223
|
+
}
|
|
3224
|
+
function unpackErrorDescriptor(value) {
|
|
3225
|
+
if (isErrorDescriptor(value)) {
|
|
3226
|
+
return value[0];
|
|
3227
|
+
}
|
|
3228
|
+
else {
|
|
3229
|
+
const [node, message, location, context] = value;
|
|
3230
|
+
return { node, message, location, context };
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3218
3233
|
/**
|
|
3219
3234
|
* @public
|
|
3220
3235
|
*/
|
|
@@ -3315,13 +3330,8 @@ class Rule {
|
|
|
3315
3330
|
static schema() {
|
|
3316
3331
|
return null;
|
|
3317
3332
|
}
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
*
|
|
3321
|
-
* Rule must be enabled both globally and on the specific node for this to
|
|
3322
|
-
* have any effect.
|
|
3323
|
-
*/
|
|
3324
|
-
report(node, message, location, context) {
|
|
3333
|
+
report(...args) {
|
|
3334
|
+
const { node, message, location, context } = unpackErrorDescriptor(args);
|
|
3325
3335
|
if (this.isEnabled() && (!node || node.ruleEnabled(this.name))) {
|
|
3326
3336
|
const where = this.findLocation({ node, location, event: this.event });
|
|
3327
3337
|
const interpolated = interpolate(message, context !== null && context !== void 0 ? context : {});
|
|
@@ -3449,11 +3459,11 @@ const mapping$1 = {
|
|
|
3449
3459
|
script: "src",
|
|
3450
3460
|
};
|
|
3451
3461
|
const description = {
|
|
3452
|
-
["external" /* EXTERNAL */]: "External links are not allowed by current configuration.",
|
|
3453
|
-
["relative-base" /* RELATIVE_BASE */]: "Links relative to <base> are not allowed by current configuration.",
|
|
3454
|
-
["relative-path" /* RELATIVE_PATH */]: "Relative links are not allowed by current configuration.",
|
|
3455
|
-
["absolute" /* ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
|
|
3456
|
-
["anchor" /* ANCHOR */]: null,
|
|
3462
|
+
["external" /* Style.EXTERNAL */]: "External links are not allowed by current configuration.",
|
|
3463
|
+
["relative-base" /* Style.RELATIVE_BASE */]: "Links relative to <base> are not allowed by current configuration.",
|
|
3464
|
+
["relative-path" /* Style.RELATIVE_PATH */]: "Relative links are not allowed by current configuration.",
|
|
3465
|
+
["absolute" /* Style.ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
|
|
3466
|
+
["anchor" /* Style.ANCHOR */]: null,
|
|
3457
3467
|
};
|
|
3458
3468
|
function parseAllow(value) {
|
|
3459
3469
|
if (typeof value === "boolean") {
|
|
@@ -3526,19 +3536,19 @@ class AllowedLinks extends Rule {
|
|
|
3526
3536
|
const link = event.value.toString();
|
|
3527
3537
|
const style = this.getStyle(link);
|
|
3528
3538
|
switch (style) {
|
|
3529
|
-
case "anchor" /* ANCHOR */:
|
|
3539
|
+
case "anchor" /* Style.ANCHOR */:
|
|
3530
3540
|
/* anchor links are always allowed by this rule */
|
|
3531
3541
|
break;
|
|
3532
|
-
case "absolute" /* ABSOLUTE */:
|
|
3542
|
+
case "absolute" /* Style.ABSOLUTE */:
|
|
3533
3543
|
this.handleAbsolute(link, event, style);
|
|
3534
3544
|
break;
|
|
3535
|
-
case "external" /* EXTERNAL */:
|
|
3545
|
+
case "external" /* Style.EXTERNAL */:
|
|
3536
3546
|
this.handleExternal(link, event, style);
|
|
3537
3547
|
break;
|
|
3538
|
-
case "relative-base" /* RELATIVE_BASE */:
|
|
3548
|
+
case "relative-base" /* Style.RELATIVE_BASE */:
|
|
3539
3549
|
this.handleRelativeBase(link, event, style);
|
|
3540
3550
|
break;
|
|
3541
|
-
case "relative-path" /* RELATIVE_PATH */:
|
|
3551
|
+
case "relative-path" /* Style.RELATIVE_PATH */:
|
|
3542
3552
|
this.handleRelativePath(link, event, style);
|
|
3543
3553
|
break;
|
|
3544
3554
|
}
|
|
@@ -3556,21 +3566,21 @@ class AllowedLinks extends Rule {
|
|
|
3556
3566
|
getStyle(value) {
|
|
3557
3567
|
/* http://example.net or //example.net */
|
|
3558
3568
|
if (value.match(/^([a-z]+:)?\/\//g)) {
|
|
3559
|
-
return "external" /* EXTERNAL */;
|
|
3569
|
+
return "external" /* Style.EXTERNAL */;
|
|
3560
3570
|
}
|
|
3561
3571
|
switch (value[0]) {
|
|
3562
3572
|
/* /foo/bar */
|
|
3563
3573
|
case "/":
|
|
3564
|
-
return "absolute" /* ABSOLUTE */;
|
|
3574
|
+
return "absolute" /* Style.ABSOLUTE */;
|
|
3565
3575
|
/* ../foo/bar */
|
|
3566
3576
|
case ".":
|
|
3567
|
-
return "relative-path" /* RELATIVE_PATH */;
|
|
3577
|
+
return "relative-path" /* Style.RELATIVE_PATH */;
|
|
3568
3578
|
/* #foo */
|
|
3569
3579
|
case "#":
|
|
3570
|
-
return "anchor" /* ANCHOR */;
|
|
3580
|
+
return "anchor" /* Style.ANCHOR */;
|
|
3571
3581
|
/* foo/bar */
|
|
3572
3582
|
default:
|
|
3573
|
-
return "relative-base" /* RELATIVE_BASE */;
|
|
3583
|
+
return "relative-base" /* Style.RELATIVE_BASE */;
|
|
3574
3584
|
}
|
|
3575
3585
|
}
|
|
3576
3586
|
handleAbsolute(target, event, style) {
|
|
@@ -3834,9 +3844,14 @@ class AttrCase extends Rule {
|
|
|
3834
3844
|
return;
|
|
3835
3845
|
}
|
|
3836
3846
|
const letters = event.key.replace(/[^a-z]+/gi, "");
|
|
3837
|
-
if (
|
|
3838
|
-
|
|
3847
|
+
if (this.style.match(letters)) {
|
|
3848
|
+
return;
|
|
3839
3849
|
}
|
|
3850
|
+
this.report({
|
|
3851
|
+
node: event.target,
|
|
3852
|
+
message: `Attribute "${event.key}" should be ${this.style.name}`,
|
|
3853
|
+
location: event.keyLocation,
|
|
3854
|
+
});
|
|
3840
3855
|
});
|
|
3841
3856
|
}
|
|
3842
3857
|
isIgnored(node) {
|
|
@@ -5136,6 +5151,11 @@ class ElementName extends Rule {
|
|
|
5136
5151
|
}
|
|
5137
5152
|
}
|
|
5138
5153
|
|
|
5154
|
+
var ErrorKind;
|
|
5155
|
+
(function (ErrorKind) {
|
|
5156
|
+
ErrorKind["CONTENT"] = "content";
|
|
5157
|
+
ErrorKind["DESCENDANT"] = "descendant";
|
|
5158
|
+
})(ErrorKind || (ErrorKind = {}));
|
|
5139
5159
|
function getTransparentChildren(node, transparent) {
|
|
5140
5160
|
if (typeof transparent === "boolean") {
|
|
5141
5161
|
return node.childElements;
|
|
@@ -5149,10 +5169,28 @@ function getTransparentChildren(node, transparent) {
|
|
|
5149
5169
|
});
|
|
5150
5170
|
}
|
|
5151
5171
|
}
|
|
5172
|
+
function getRuleDescription$1(context) {
|
|
5173
|
+
if (!context) {
|
|
5174
|
+
return [
|
|
5175
|
+
"Some elements has restrictions on what content is allowed.",
|
|
5176
|
+
"This can include both direct children or descendant elements.",
|
|
5177
|
+
];
|
|
5178
|
+
}
|
|
5179
|
+
switch (context.kind) {
|
|
5180
|
+
case ErrorKind.CONTENT:
|
|
5181
|
+
return [
|
|
5182
|
+
`The \`${context.child}\` element is not permitted as content under the parent \`${context.parent}\` element.`,
|
|
5183
|
+
];
|
|
5184
|
+
case ErrorKind.DESCENDANT:
|
|
5185
|
+
return [
|
|
5186
|
+
`The \`${context.child}\` element is not permitted as a descendant of the \`${context.ancestor}\` element.`,
|
|
5187
|
+
];
|
|
5188
|
+
}
|
|
5189
|
+
}
|
|
5152
5190
|
class ElementPermittedContent extends Rule {
|
|
5153
|
-
documentation() {
|
|
5191
|
+
documentation(context) {
|
|
5154
5192
|
return {
|
|
5155
|
-
description:
|
|
5193
|
+
description: getRuleDescription$1(context).join("\n"),
|
|
5156
5194
|
url: ruleDocumentationUrl("@/rules/element-permitted-content.ts"),
|
|
5157
5195
|
};
|
|
5158
5196
|
}
|
|
@@ -5161,8 +5199,9 @@ class ElementPermittedContent extends Rule {
|
|
|
5161
5199
|
const doc = event.document;
|
|
5162
5200
|
doc.visitDepthFirst((node) => {
|
|
5163
5201
|
const parent = node.parent;
|
|
5164
|
-
/*
|
|
5165
|
-
|
|
5202
|
+
/* istanbul ignore next: satisfy typescript but will visitDepthFirst()
|
|
5203
|
+
* will not yield nodes without a parent */
|
|
5204
|
+
if (!parent) {
|
|
5166
5205
|
return;
|
|
5167
5206
|
}
|
|
5168
5207
|
/* Run each validation step, stop as soon as any errors are
|
|
@@ -5172,7 +5211,6 @@ class ElementPermittedContent extends Rule {
|
|
|
5172
5211
|
[
|
|
5173
5212
|
() => this.validatePermittedContent(node, parent),
|
|
5174
5213
|
() => this.validatePermittedDescendant(node, parent),
|
|
5175
|
-
() => this.validatePermittedAncestors(node),
|
|
5176
5214
|
].some((fn) => fn());
|
|
5177
5215
|
});
|
|
5178
5216
|
});
|
|
@@ -5189,7 +5227,14 @@ class ElementPermittedContent extends Rule {
|
|
|
5189
5227
|
}
|
|
5190
5228
|
validatePermittedContentImpl(cur, parent, rules) {
|
|
5191
5229
|
if (!Validator.validatePermitted(cur, rules)) {
|
|
5192
|
-
|
|
5230
|
+
const child = `<${cur.tagName}>`;
|
|
5231
|
+
const message = `${child} element is not permitted as content under ${parent.annotatedName}`;
|
|
5232
|
+
const context = {
|
|
5233
|
+
kind: ErrorKind.CONTENT,
|
|
5234
|
+
parent: parent.annotatedName,
|
|
5235
|
+
child,
|
|
5236
|
+
};
|
|
5237
|
+
this.report(cur, message, null, context);
|
|
5193
5238
|
return true;
|
|
5194
5239
|
}
|
|
5195
5240
|
/* for transparent elements all/listed children must be validated against
|
|
@@ -5220,21 +5265,15 @@ class ElementPermittedContent extends Rule {
|
|
|
5220
5265
|
if (Validator.validatePermitted(node, rules)) {
|
|
5221
5266
|
continue;
|
|
5222
5267
|
}
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
const rules = node.meta.requiredAncestors;
|
|
5233
|
-
if (!rules) {
|
|
5234
|
-
return false;
|
|
5235
|
-
}
|
|
5236
|
-
if (!Validator.validateAncestors(node, rules)) {
|
|
5237
|
-
this.report(node, `Element <${node.tagName}> requires an "${rules[0]}" ancestor`);
|
|
5268
|
+
const child = `<${node.tagName}>`;
|
|
5269
|
+
const ancestor = cur.annotatedName;
|
|
5270
|
+
const message = `${child} element is not permitted as a descendant of ${ancestor}`;
|
|
5271
|
+
const context = {
|
|
5272
|
+
kind: ErrorKind.DESCENDANT,
|
|
5273
|
+
ancestor,
|
|
5274
|
+
child,
|
|
5275
|
+
};
|
|
5276
|
+
this.report(node, message, null, context);
|
|
5238
5277
|
return true;
|
|
5239
5278
|
}
|
|
5240
5279
|
return false;
|
|
@@ -5301,6 +5340,152 @@ class ElementPermittedOrder extends Rule {
|
|
|
5301
5340
|
}
|
|
5302
5341
|
}
|
|
5303
5342
|
|
|
5343
|
+
const CACHE_KEY = Symbol(classifyNodeText.name);
|
|
5344
|
+
var TextClassification;
|
|
5345
|
+
(function (TextClassification) {
|
|
5346
|
+
TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
|
|
5347
|
+
TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
|
|
5348
|
+
TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
|
|
5349
|
+
})(TextClassification || (TextClassification = {}));
|
|
5350
|
+
/**
|
|
5351
|
+
* Checks text content of an element.
|
|
5352
|
+
*
|
|
5353
|
+
* Any text is considered including text from descendant elements. Whitespace is
|
|
5354
|
+
* ignored.
|
|
5355
|
+
*
|
|
5356
|
+
* If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
|
|
5357
|
+
*/
|
|
5358
|
+
function classifyNodeText(node) {
|
|
5359
|
+
if (node.cacheExists(CACHE_KEY)) {
|
|
5360
|
+
return node.cacheGet(CACHE_KEY);
|
|
5361
|
+
}
|
|
5362
|
+
const text = findTextNodes(node);
|
|
5363
|
+
/* if any text is dynamic classify as dynamic */
|
|
5364
|
+
if (text.some((cur) => cur.isDynamic)) {
|
|
5365
|
+
return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
|
|
5366
|
+
}
|
|
5367
|
+
/* if any text has non-whitespace character classify as static */
|
|
5368
|
+
if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
|
|
5369
|
+
return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
|
|
5370
|
+
}
|
|
5371
|
+
/* default to empty */
|
|
5372
|
+
return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
|
|
5373
|
+
}
|
|
5374
|
+
function findTextNodes(node) {
|
|
5375
|
+
let text = [];
|
|
5376
|
+
for (const child of node.childNodes) {
|
|
5377
|
+
switch (child.nodeType) {
|
|
5378
|
+
case NodeType.TEXT_NODE:
|
|
5379
|
+
text.push(child);
|
|
5380
|
+
break;
|
|
5381
|
+
case NodeType.ELEMENT_NODE:
|
|
5382
|
+
text = text.concat(findTextNodes(child));
|
|
5383
|
+
break;
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
return text;
|
|
5387
|
+
}
|
|
5388
|
+
|
|
5389
|
+
function hasAltText(image) {
|
|
5390
|
+
const alt = image.getAttribute("alt");
|
|
5391
|
+
/* missing or boolean */
|
|
5392
|
+
if (alt === null || alt.value === null) {
|
|
5393
|
+
return false;
|
|
5394
|
+
}
|
|
5395
|
+
return alt.isDynamic || alt.value.toString() !== "";
|
|
5396
|
+
}
|
|
5397
|
+
|
|
5398
|
+
function hasAriaLabel(node) {
|
|
5399
|
+
const label = node.getAttribute("aria-label");
|
|
5400
|
+
/* missing or boolean */
|
|
5401
|
+
if (label === null || label.value === null) {
|
|
5402
|
+
return false;
|
|
5403
|
+
}
|
|
5404
|
+
return label.isDynamic || label.value.toString() !== "";
|
|
5405
|
+
}
|
|
5406
|
+
|
|
5407
|
+
/**
|
|
5408
|
+
* Joins a list of words into natural language.
|
|
5409
|
+
*
|
|
5410
|
+
* - `["foo"]` becomes `"foo"`
|
|
5411
|
+
* - `["foo", "bar"]` becomes `"foo or bar"`
|
|
5412
|
+
* - `["foo", "bar", "baz"]` becomes `"foo, bar or baz"`
|
|
5413
|
+
* - and so on...
|
|
5414
|
+
*
|
|
5415
|
+
* @internal
|
|
5416
|
+
* @param values - List of words to join
|
|
5417
|
+
* @param conjunction - Conjunction for the last element.
|
|
5418
|
+
* @returns String with the words naturally joined with a conjunction.
|
|
5419
|
+
*/
|
|
5420
|
+
function naturalJoin(values, conjunction = "or") {
|
|
5421
|
+
switch (values.length) {
|
|
5422
|
+
case 0:
|
|
5423
|
+
return "";
|
|
5424
|
+
case 1:
|
|
5425
|
+
return values[0];
|
|
5426
|
+
case 2:
|
|
5427
|
+
return `${values[0]} ${conjunction} ${values[1]}`;
|
|
5428
|
+
default:
|
|
5429
|
+
return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.slice(-1)[0]}`;
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
|
|
5433
|
+
function isTagnameOnly(value) {
|
|
5434
|
+
return Boolean(value.match(/^[a-zA-Z0-9-]+$/));
|
|
5435
|
+
}
|
|
5436
|
+
function getRuleDescription(context) {
|
|
5437
|
+
if (!context) {
|
|
5438
|
+
return [
|
|
5439
|
+
"Some elements has restrictions on what content is allowed.",
|
|
5440
|
+
"This can include both direct children or descendant elements.",
|
|
5441
|
+
];
|
|
5442
|
+
}
|
|
5443
|
+
const escaped = context.ancestor.map((it) => `\`${it}\``);
|
|
5444
|
+
return [`The \`${context.child}\` element requires a ${naturalJoin(escaped)} ancestor.`];
|
|
5445
|
+
}
|
|
5446
|
+
class ElementRequiredAncestor extends Rule {
|
|
5447
|
+
documentation(context) {
|
|
5448
|
+
return {
|
|
5449
|
+
description: getRuleDescription(context).join("\n"),
|
|
5450
|
+
url: ruleDocumentationUrl("@/rules/element-required-ancestor.ts"),
|
|
5451
|
+
};
|
|
5452
|
+
}
|
|
5453
|
+
setup() {
|
|
5454
|
+
this.on("dom:ready", (event) => {
|
|
5455
|
+
const doc = event.document;
|
|
5456
|
+
doc.visitDepthFirst((node) => {
|
|
5457
|
+
const parent = node.parent;
|
|
5458
|
+
/* istanbul ignore next: satisfy typescript but will visitDepthFirst()
|
|
5459
|
+
* will not yield nodes without a parent */
|
|
5460
|
+
if (!parent) {
|
|
5461
|
+
return;
|
|
5462
|
+
}
|
|
5463
|
+
this.validateRequiredAncestors(node);
|
|
5464
|
+
});
|
|
5465
|
+
});
|
|
5466
|
+
}
|
|
5467
|
+
validateRequiredAncestors(node) {
|
|
5468
|
+
if (!node.meta) {
|
|
5469
|
+
return;
|
|
5470
|
+
}
|
|
5471
|
+
const rules = node.meta.requiredAncestors;
|
|
5472
|
+
if (!rules) {
|
|
5473
|
+
return;
|
|
5474
|
+
}
|
|
5475
|
+
if (Validator.validateAncestors(node, rules)) {
|
|
5476
|
+
return;
|
|
5477
|
+
}
|
|
5478
|
+
const ancestor = rules.map((it) => (isTagnameOnly(it) ? `<${it}>` : `"${it}"`));
|
|
5479
|
+
const child = `<${node.tagName}>`;
|
|
5480
|
+
const message = `<${node.tagName}> element requires a ${naturalJoin(ancestor)} ancestor`;
|
|
5481
|
+
const context = {
|
|
5482
|
+
ancestor,
|
|
5483
|
+
child,
|
|
5484
|
+
};
|
|
5485
|
+
this.report(node, message, null, context);
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
|
|
5304
5489
|
class ElementRequiredAttributes extends Rule {
|
|
5305
5490
|
documentation(context) {
|
|
5306
5491
|
const docs = {
|
|
@@ -5378,52 +5563,6 @@ class ElementRequiredContent extends Rule {
|
|
|
5378
5563
|
}
|
|
5379
5564
|
}
|
|
5380
5565
|
|
|
5381
|
-
const CACHE_KEY = Symbol(classifyNodeText.name);
|
|
5382
|
-
var TextClassification;
|
|
5383
|
-
(function (TextClassification) {
|
|
5384
|
-
TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
|
|
5385
|
-
TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
|
|
5386
|
-
TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
|
|
5387
|
-
})(TextClassification || (TextClassification = {}));
|
|
5388
|
-
/**
|
|
5389
|
-
* Checks text content of an element.
|
|
5390
|
-
*
|
|
5391
|
-
* Any text is considered including text from descendant elements. Whitespace is
|
|
5392
|
-
* ignored.
|
|
5393
|
-
*
|
|
5394
|
-
* If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
|
|
5395
|
-
*/
|
|
5396
|
-
function classifyNodeText(node) {
|
|
5397
|
-
if (node.cacheExists(CACHE_KEY)) {
|
|
5398
|
-
return node.cacheGet(CACHE_KEY);
|
|
5399
|
-
}
|
|
5400
|
-
const text = findTextNodes(node);
|
|
5401
|
-
/* if any text is dynamic classify as dynamic */
|
|
5402
|
-
if (text.some((cur) => cur.isDynamic)) {
|
|
5403
|
-
return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
|
|
5404
|
-
}
|
|
5405
|
-
/* if any text has non-whitespace character classify as static */
|
|
5406
|
-
if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
|
|
5407
|
-
return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
|
|
5408
|
-
}
|
|
5409
|
-
/* default to empty */
|
|
5410
|
-
return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
|
|
5411
|
-
}
|
|
5412
|
-
function findTextNodes(node) {
|
|
5413
|
-
let text = [];
|
|
5414
|
-
for (const child of node.childNodes) {
|
|
5415
|
-
switch (child.nodeType) {
|
|
5416
|
-
case NodeType.TEXT_NODE:
|
|
5417
|
-
text.push(child);
|
|
5418
|
-
break;
|
|
5419
|
-
case NodeType.ELEMENT_NODE:
|
|
5420
|
-
text = text.concat(findTextNodes(child));
|
|
5421
|
-
break;
|
|
5422
|
-
}
|
|
5423
|
-
}
|
|
5424
|
-
return text;
|
|
5425
|
-
}
|
|
5426
|
-
|
|
5427
5566
|
const selector = ["h1", "h2", "h3", "h4", "h5", "h6"].join(",");
|
|
5428
5567
|
class EmptyHeading extends Rule {
|
|
5429
5568
|
documentation() {
|
|
@@ -7604,24 +7743,6 @@ class TelNonBreaking extends Rule {
|
|
|
7604
7743
|
}
|
|
7605
7744
|
}
|
|
7606
7745
|
|
|
7607
|
-
function hasAltText(image) {
|
|
7608
|
-
const alt = image.getAttribute("alt");
|
|
7609
|
-
/* missing or boolean */
|
|
7610
|
-
if (alt === null || alt.value === null) {
|
|
7611
|
-
return false;
|
|
7612
|
-
}
|
|
7613
|
-
return alt.isDynamic || alt.value.toString() !== "";
|
|
7614
|
-
}
|
|
7615
|
-
|
|
7616
|
-
function hasAriaLabel(node) {
|
|
7617
|
-
const label = node.getAttribute("aria-label");
|
|
7618
|
-
/* missing or boolean */
|
|
7619
|
-
if (label === null || label.value === null) {
|
|
7620
|
-
return false;
|
|
7621
|
-
}
|
|
7622
|
-
return label.isDynamic || label.value.toString() !== "";
|
|
7623
|
-
}
|
|
7624
|
-
|
|
7625
7746
|
/**
|
|
7626
7747
|
* Check if attribute is present and non-empty or dynamic.
|
|
7627
7748
|
*/
|
|
@@ -9799,13 +9920,13 @@ class VoidStyle extends Rule {
|
|
|
9799
9920
|
return;
|
|
9800
9921
|
}
|
|
9801
9922
|
if (this.shouldBeOmitted(node)) {
|
|
9802
|
-
this.
|
|
9923
|
+
this.reportError(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
|
|
9803
9924
|
}
|
|
9804
9925
|
if (this.shouldBeSelfClosed(node)) {
|
|
9805
|
-
this.
|
|
9926
|
+
this.reportError(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
|
|
9806
9927
|
}
|
|
9807
9928
|
}
|
|
9808
|
-
|
|
9929
|
+
reportError(node, message) {
|
|
9809
9930
|
const context = {
|
|
9810
9931
|
style: this.style,
|
|
9811
9932
|
tagName: node.tagName,
|
|
@@ -10117,6 +10238,7 @@ const bundledRules = {
|
|
|
10117
10238
|
"element-permitted-content": ElementPermittedContent,
|
|
10118
10239
|
"element-permitted-occurrences": ElementPermittedOccurrences,
|
|
10119
10240
|
"element-permitted-order": ElementPermittedOrder,
|
|
10241
|
+
"element-required-ancestor": ElementRequiredAncestor,
|
|
10120
10242
|
"element-required-attributes": ElementRequiredAttributes,
|
|
10121
10243
|
"element-required-content": ElementRequiredContent,
|
|
10122
10244
|
"empty-heading": EmptyHeading,
|
|
@@ -10224,6 +10346,7 @@ const config$1 = {
|
|
|
10224
10346
|
"element-permitted-content": "error",
|
|
10225
10347
|
"element-permitted-occurrences": "error",
|
|
10226
10348
|
"element-permitted-order": "error",
|
|
10349
|
+
"element-required-ancestor": "error",
|
|
10227
10350
|
"element-required-attributes": "error",
|
|
10228
10351
|
"element-required-content": "error",
|
|
10229
10352
|
"empty-heading": "error",
|
|
@@ -10282,6 +10405,7 @@ const config = {
|
|
|
10282
10405
|
"element-permitted-content": "error",
|
|
10283
10406
|
"element-permitted-occurrences": "error",
|
|
10284
10407
|
"element-permitted-order": "error",
|
|
10408
|
+
"element-required-ancestor": "error",
|
|
10285
10409
|
"element-required-attributes": "error",
|
|
10286
10410
|
"element-required-content": "error",
|
|
10287
10411
|
"multiple-labeled-controls": "error",
|
|
@@ -10361,6 +10485,7 @@ class ResolvedConfig {
|
|
|
10361
10485
|
});
|
|
10362
10486
|
}
|
|
10363
10487
|
catch (err) {
|
|
10488
|
+
/* istanbul ignore next: only used as a fallback */
|
|
10364
10489
|
const message = err instanceof Error ? err.message : String(err);
|
|
10365
10490
|
throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
|
|
10366
10491
|
}
|
|
@@ -10373,7 +10498,7 @@ class ResolvedConfig {
|
|
|
10373
10498
|
* Wrapper around [[transformSource]] which reads a file before passing it
|
|
10374
10499
|
* as-is to transformSource.
|
|
10375
10500
|
*
|
|
10376
|
-
* @param
|
|
10501
|
+
* @param filename - Filename to transform (according to configured
|
|
10377
10502
|
* transformations)
|
|
10378
10503
|
* @returns A list of transformed sources ready for validation.
|
|
10379
10504
|
*/
|
|
@@ -10529,7 +10654,9 @@ class Config {
|
|
|
10529
10654
|
var _a;
|
|
10530
10655
|
const valid = validator(configData);
|
|
10531
10656
|
if (!valid) {
|
|
10532
|
-
throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema,
|
|
10657
|
+
throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema,
|
|
10658
|
+
/* istanbul ignore next: will be set when a validation error has occurred */
|
|
10659
|
+
(_a = validator.errors) !== null && _a !== void 0 ? _a : []);
|
|
10533
10660
|
}
|
|
10534
10661
|
if (configData.rules) {
|
|
10535
10662
|
const normalizedRules = Config.getRulesObject(configData.rules);
|
|
@@ -10638,6 +10765,7 @@ class Config {
|
|
|
10638
10765
|
metaTable.loadFromObject(legacyRequire(entry));
|
|
10639
10766
|
}
|
|
10640
10767
|
catch (err) {
|
|
10768
|
+
/* istanbul ignore next: only used as a fallback */
|
|
10641
10769
|
const message = err instanceof Error ? err.message : String(err);
|
|
10642
10770
|
throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, ensureError(err));
|
|
10643
10771
|
}
|
|
@@ -10659,6 +10787,7 @@ class Config {
|
|
|
10659
10787
|
*
|
|
10660
10788
|
* @internal primary purpose is unittests
|
|
10661
10789
|
*/
|
|
10790
|
+
/* istanbul ignore next: used for testing only */
|
|
10662
10791
|
get() {
|
|
10663
10792
|
const config = { ...this.config };
|
|
10664
10793
|
if (config.elements) {
|
|
@@ -10681,6 +10810,7 @@ class Config {
|
|
|
10681
10810
|
*/
|
|
10682
10811
|
getRules() {
|
|
10683
10812
|
var _a;
|
|
10813
|
+
/* istanbul ignore next: only used as a fallback */
|
|
10684
10814
|
return Config.getRulesObject((_a = this.config.rules) !== null && _a !== void 0 ? _a : {});
|
|
10685
10815
|
}
|
|
10686
10816
|
static getRulesObject(src) {
|
|
@@ -10715,6 +10845,7 @@ class Config {
|
|
|
10715
10845
|
return plugin;
|
|
10716
10846
|
}
|
|
10717
10847
|
catch (err) {
|
|
10848
|
+
/* istanbul ignore next: only used as a fallback */
|
|
10718
10849
|
const message = err instanceof Error ? err.message : String(err);
|
|
10719
10850
|
throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, ensureError(err));
|
|
10720
10851
|
}
|
|
@@ -11630,9 +11761,9 @@ class Reporter {
|
|
|
11630
11761
|
if (!(location.filename in this.result)) {
|
|
11631
11762
|
this.result[location.filename] = [];
|
|
11632
11763
|
}
|
|
11633
|
-
|
|
11764
|
+
const ruleUrl = (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url;
|
|
11765
|
+
const entry = {
|
|
11634
11766
|
ruleId: rule.name,
|
|
11635
|
-
ruleUrl: (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url,
|
|
11636
11767
|
severity,
|
|
11637
11768
|
message,
|
|
11638
11769
|
offset: location.offset,
|
|
@@ -11642,8 +11773,14 @@ class Reporter {
|
|
|
11642
11773
|
selector() {
|
|
11643
11774
|
return node ? node.generateSelector() : null;
|
|
11644
11775
|
},
|
|
11645
|
-
|
|
11646
|
-
|
|
11776
|
+
};
|
|
11777
|
+
if (ruleUrl) {
|
|
11778
|
+
entry.ruleUrl = ruleUrl;
|
|
11779
|
+
}
|
|
11780
|
+
if (context) {
|
|
11781
|
+
entry.context = context;
|
|
11782
|
+
}
|
|
11783
|
+
this.result[location.filename].push(entry);
|
|
11647
11784
|
}
|
|
11648
11785
|
addManual(filename, message) {
|
|
11649
11786
|
if (!(filename in this.result)) {
|
|
@@ -12057,7 +12194,7 @@ class Engine {
|
|
|
12057
12194
|
offset: location.offset,
|
|
12058
12195
|
line: location.line,
|
|
12059
12196
|
column: location.column,
|
|
12060
|
-
size: location.size
|
|
12197
|
+
size: location.size,
|
|
12061
12198
|
selector: () => null,
|
|
12062
12199
|
});
|
|
12063
12200
|
}
|
|
@@ -12487,12 +12624,12 @@ class FileSystemConfigLoader extends ConfigLoader {
|
|
|
12487
12624
|
* `null` if no configuration files are found.
|
|
12488
12625
|
*/
|
|
12489
12626
|
fromFilename(filename) {
|
|
12490
|
-
var _a;
|
|
12491
12627
|
if (filename === "inline") {
|
|
12492
12628
|
return null;
|
|
12493
12629
|
}
|
|
12494
|
-
|
|
12495
|
-
|
|
12630
|
+
const cache = this.cache.get(filename);
|
|
12631
|
+
if (cache) {
|
|
12632
|
+
return cache;
|
|
12496
12633
|
}
|
|
12497
12634
|
let found = false;
|
|
12498
12635
|
let current = path__default["default"].resolve(path__default["default"].dirname(filename));
|