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/es/core.js
CHANGED
|
@@ -1732,7 +1732,7 @@ function stripslashes(value) {
|
|
|
1732
1732
|
return value.replace(/\\(.)/g, "$1");
|
|
1733
1733
|
}
|
|
1734
1734
|
function escapeSelectorComponent(text) {
|
|
1735
|
-
return text.toString().replace(/([
|
|
1735
|
+
return text.toString().replace(/([^a-z0-9_-])/gi, "\\$1");
|
|
1736
1736
|
}
|
|
1737
1737
|
class Matcher {
|
|
1738
1738
|
}
|
|
@@ -2615,12 +2615,15 @@ class Validator {
|
|
|
2615
2615
|
if (value === null || value === undefined) {
|
|
2616
2616
|
return false;
|
|
2617
2617
|
}
|
|
2618
|
+
const caseInsensitiveValue = value.toLowerCase();
|
|
2618
2619
|
return rule.enum.some((entry) => {
|
|
2619
2620
|
if (entry instanceof RegExp) {
|
|
2621
|
+
/* regular expressions are matched case-sensitive */
|
|
2620
2622
|
return !!value.match(entry);
|
|
2621
2623
|
}
|
|
2622
2624
|
else {
|
|
2623
|
-
|
|
2625
|
+
/* strings matched case-insensitive */
|
|
2626
|
+
return caseInsensitiveValue === entry;
|
|
2624
2627
|
}
|
|
2625
2628
|
});
|
|
2626
2629
|
}
|
|
@@ -3083,7 +3086,7 @@ var TRANSFORMER_API;
|
|
|
3083
3086
|
/** @public */
|
|
3084
3087
|
const name = "html-validate";
|
|
3085
3088
|
/** @public */
|
|
3086
|
-
const version = "7.
|
|
3089
|
+
const version = "7.2.0";
|
|
3087
3090
|
/** @public */
|
|
3088
3091
|
const homepage = "https://html-validate.org";
|
|
3089
3092
|
/** @public */
|
|
@@ -3184,6 +3187,18 @@ function getSchemaValidator(ruleId, properties) {
|
|
|
3184
3187
|
};
|
|
3185
3188
|
return ajv$1.compile(schema);
|
|
3186
3189
|
}
|
|
3190
|
+
function isErrorDescriptor(value) {
|
|
3191
|
+
return Boolean(value[0] && value[0].message);
|
|
3192
|
+
}
|
|
3193
|
+
function unpackErrorDescriptor(value) {
|
|
3194
|
+
if (isErrorDescriptor(value)) {
|
|
3195
|
+
return value[0];
|
|
3196
|
+
}
|
|
3197
|
+
else {
|
|
3198
|
+
const [node, message, location, context] = value;
|
|
3199
|
+
return { node, message, location, context };
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3187
3202
|
/**
|
|
3188
3203
|
* @public
|
|
3189
3204
|
*/
|
|
@@ -3284,13 +3299,8 @@ class Rule {
|
|
|
3284
3299
|
static schema() {
|
|
3285
3300
|
return null;
|
|
3286
3301
|
}
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
*
|
|
3290
|
-
* Rule must be enabled both globally and on the specific node for this to
|
|
3291
|
-
* have any effect.
|
|
3292
|
-
*/
|
|
3293
|
-
report(node, message, location, context) {
|
|
3302
|
+
report(...args) {
|
|
3303
|
+
const { node, message, location, context } = unpackErrorDescriptor(args);
|
|
3294
3304
|
if (this.isEnabled() && (!node || node.ruleEnabled(this.name))) {
|
|
3295
3305
|
const where = this.findLocation({ node, location, event: this.event });
|
|
3296
3306
|
const interpolated = interpolate(message, context !== null && context !== void 0 ? context : {});
|
|
@@ -3418,11 +3428,11 @@ const mapping$1 = {
|
|
|
3418
3428
|
script: "src",
|
|
3419
3429
|
};
|
|
3420
3430
|
const description = {
|
|
3421
|
-
["external" /* EXTERNAL */]: "External links are not allowed by current configuration.",
|
|
3422
|
-
["relative-base" /* RELATIVE_BASE */]: "Links relative to <base> are not allowed by current configuration.",
|
|
3423
|
-
["relative-path" /* RELATIVE_PATH */]: "Relative links are not allowed by current configuration.",
|
|
3424
|
-
["absolute" /* ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
|
|
3425
|
-
["anchor" /* ANCHOR */]: null,
|
|
3431
|
+
["external" /* Style.EXTERNAL */]: "External links are not allowed by current configuration.",
|
|
3432
|
+
["relative-base" /* Style.RELATIVE_BASE */]: "Links relative to <base> are not allowed by current configuration.",
|
|
3433
|
+
["relative-path" /* Style.RELATIVE_PATH */]: "Relative links are not allowed by current configuration.",
|
|
3434
|
+
["absolute" /* Style.ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
|
|
3435
|
+
["anchor" /* Style.ANCHOR */]: null,
|
|
3426
3436
|
};
|
|
3427
3437
|
function parseAllow(value) {
|
|
3428
3438
|
if (typeof value === "boolean") {
|
|
@@ -3495,19 +3505,19 @@ class AllowedLinks extends Rule {
|
|
|
3495
3505
|
const link = event.value.toString();
|
|
3496
3506
|
const style = this.getStyle(link);
|
|
3497
3507
|
switch (style) {
|
|
3498
|
-
case "anchor" /* ANCHOR */:
|
|
3508
|
+
case "anchor" /* Style.ANCHOR */:
|
|
3499
3509
|
/* anchor links are always allowed by this rule */
|
|
3500
3510
|
break;
|
|
3501
|
-
case "absolute" /* ABSOLUTE */:
|
|
3511
|
+
case "absolute" /* Style.ABSOLUTE */:
|
|
3502
3512
|
this.handleAbsolute(link, event, style);
|
|
3503
3513
|
break;
|
|
3504
|
-
case "external" /* EXTERNAL */:
|
|
3514
|
+
case "external" /* Style.EXTERNAL */:
|
|
3505
3515
|
this.handleExternal(link, event, style);
|
|
3506
3516
|
break;
|
|
3507
|
-
case "relative-base" /* RELATIVE_BASE */:
|
|
3517
|
+
case "relative-base" /* Style.RELATIVE_BASE */:
|
|
3508
3518
|
this.handleRelativeBase(link, event, style);
|
|
3509
3519
|
break;
|
|
3510
|
-
case "relative-path" /* RELATIVE_PATH */:
|
|
3520
|
+
case "relative-path" /* Style.RELATIVE_PATH */:
|
|
3511
3521
|
this.handleRelativePath(link, event, style);
|
|
3512
3522
|
break;
|
|
3513
3523
|
}
|
|
@@ -3525,21 +3535,21 @@ class AllowedLinks extends Rule {
|
|
|
3525
3535
|
getStyle(value) {
|
|
3526
3536
|
/* http://example.net or //example.net */
|
|
3527
3537
|
if (value.match(/^([a-z]+:)?\/\//g)) {
|
|
3528
|
-
return "external" /* EXTERNAL */;
|
|
3538
|
+
return "external" /* Style.EXTERNAL */;
|
|
3529
3539
|
}
|
|
3530
3540
|
switch (value[0]) {
|
|
3531
3541
|
/* /foo/bar */
|
|
3532
3542
|
case "/":
|
|
3533
|
-
return "absolute" /* ABSOLUTE */;
|
|
3543
|
+
return "absolute" /* Style.ABSOLUTE */;
|
|
3534
3544
|
/* ../foo/bar */
|
|
3535
3545
|
case ".":
|
|
3536
|
-
return "relative-path" /* RELATIVE_PATH */;
|
|
3546
|
+
return "relative-path" /* Style.RELATIVE_PATH */;
|
|
3537
3547
|
/* #foo */
|
|
3538
3548
|
case "#":
|
|
3539
|
-
return "anchor" /* ANCHOR */;
|
|
3549
|
+
return "anchor" /* Style.ANCHOR */;
|
|
3540
3550
|
/* foo/bar */
|
|
3541
3551
|
default:
|
|
3542
|
-
return "relative-base" /* RELATIVE_BASE */;
|
|
3552
|
+
return "relative-base" /* Style.RELATIVE_BASE */;
|
|
3543
3553
|
}
|
|
3544
3554
|
}
|
|
3545
3555
|
handleAbsolute(target, event, style) {
|
|
@@ -3803,9 +3813,14 @@ class AttrCase extends Rule {
|
|
|
3803
3813
|
return;
|
|
3804
3814
|
}
|
|
3805
3815
|
const letters = event.key.replace(/[^a-z]+/gi, "");
|
|
3806
|
-
if (
|
|
3807
|
-
|
|
3816
|
+
if (this.style.match(letters)) {
|
|
3817
|
+
return;
|
|
3808
3818
|
}
|
|
3819
|
+
this.report({
|
|
3820
|
+
node: event.target,
|
|
3821
|
+
message: `Attribute "${event.key}" should be ${this.style.name}`,
|
|
3822
|
+
location: event.keyLocation,
|
|
3823
|
+
});
|
|
3809
3824
|
});
|
|
3810
3825
|
}
|
|
3811
3826
|
isIgnored(node) {
|
|
@@ -5105,6 +5120,11 @@ class ElementName extends Rule {
|
|
|
5105
5120
|
}
|
|
5106
5121
|
}
|
|
5107
5122
|
|
|
5123
|
+
var ErrorKind;
|
|
5124
|
+
(function (ErrorKind) {
|
|
5125
|
+
ErrorKind["CONTENT"] = "content";
|
|
5126
|
+
ErrorKind["DESCENDANT"] = "descendant";
|
|
5127
|
+
})(ErrorKind || (ErrorKind = {}));
|
|
5108
5128
|
function getTransparentChildren(node, transparent) {
|
|
5109
5129
|
if (typeof transparent === "boolean") {
|
|
5110
5130
|
return node.childElements;
|
|
@@ -5118,10 +5138,28 @@ function getTransparentChildren(node, transparent) {
|
|
|
5118
5138
|
});
|
|
5119
5139
|
}
|
|
5120
5140
|
}
|
|
5141
|
+
function getRuleDescription$1(context) {
|
|
5142
|
+
if (!context) {
|
|
5143
|
+
return [
|
|
5144
|
+
"Some elements has restrictions on what content is allowed.",
|
|
5145
|
+
"This can include both direct children or descendant elements.",
|
|
5146
|
+
];
|
|
5147
|
+
}
|
|
5148
|
+
switch (context.kind) {
|
|
5149
|
+
case ErrorKind.CONTENT:
|
|
5150
|
+
return [
|
|
5151
|
+
`The \`${context.child}\` element is not permitted as content under the parent \`${context.parent}\` element.`,
|
|
5152
|
+
];
|
|
5153
|
+
case ErrorKind.DESCENDANT:
|
|
5154
|
+
return [
|
|
5155
|
+
`The \`${context.child}\` element is not permitted as a descendant of the \`${context.ancestor}\` element.`,
|
|
5156
|
+
];
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5121
5159
|
class ElementPermittedContent extends Rule {
|
|
5122
|
-
documentation() {
|
|
5160
|
+
documentation(context) {
|
|
5123
5161
|
return {
|
|
5124
|
-
description:
|
|
5162
|
+
description: getRuleDescription$1(context).join("\n"),
|
|
5125
5163
|
url: ruleDocumentationUrl("@/rules/element-permitted-content.ts"),
|
|
5126
5164
|
};
|
|
5127
5165
|
}
|
|
@@ -5130,8 +5168,9 @@ class ElementPermittedContent extends Rule {
|
|
|
5130
5168
|
const doc = event.document;
|
|
5131
5169
|
doc.visitDepthFirst((node) => {
|
|
5132
5170
|
const parent = node.parent;
|
|
5133
|
-
/*
|
|
5134
|
-
|
|
5171
|
+
/* istanbul ignore next: satisfy typescript but will visitDepthFirst()
|
|
5172
|
+
* will not yield nodes without a parent */
|
|
5173
|
+
if (!parent) {
|
|
5135
5174
|
return;
|
|
5136
5175
|
}
|
|
5137
5176
|
/* Run each validation step, stop as soon as any errors are
|
|
@@ -5141,7 +5180,6 @@ class ElementPermittedContent extends Rule {
|
|
|
5141
5180
|
[
|
|
5142
5181
|
() => this.validatePermittedContent(node, parent),
|
|
5143
5182
|
() => this.validatePermittedDescendant(node, parent),
|
|
5144
|
-
() => this.validatePermittedAncestors(node),
|
|
5145
5183
|
].some((fn) => fn());
|
|
5146
5184
|
});
|
|
5147
5185
|
});
|
|
@@ -5158,7 +5196,14 @@ class ElementPermittedContent extends Rule {
|
|
|
5158
5196
|
}
|
|
5159
5197
|
validatePermittedContentImpl(cur, parent, rules) {
|
|
5160
5198
|
if (!Validator.validatePermitted(cur, rules)) {
|
|
5161
|
-
|
|
5199
|
+
const child = `<${cur.tagName}>`;
|
|
5200
|
+
const message = `${child} element is not permitted as content under ${parent.annotatedName}`;
|
|
5201
|
+
const context = {
|
|
5202
|
+
kind: ErrorKind.CONTENT,
|
|
5203
|
+
parent: parent.annotatedName,
|
|
5204
|
+
child,
|
|
5205
|
+
};
|
|
5206
|
+
this.report(cur, message, null, context);
|
|
5162
5207
|
return true;
|
|
5163
5208
|
}
|
|
5164
5209
|
/* for transparent elements all/listed children must be validated against
|
|
@@ -5189,21 +5234,15 @@ class ElementPermittedContent extends Rule {
|
|
|
5189
5234
|
if (Validator.validatePermitted(node, rules)) {
|
|
5190
5235
|
continue;
|
|
5191
5236
|
}
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
const rules = node.meta.requiredAncestors;
|
|
5202
|
-
if (!rules) {
|
|
5203
|
-
return false;
|
|
5204
|
-
}
|
|
5205
|
-
if (!Validator.validateAncestors(node, rules)) {
|
|
5206
|
-
this.report(node, `Element <${node.tagName}> requires an "${rules[0]}" ancestor`);
|
|
5237
|
+
const child = `<${node.tagName}>`;
|
|
5238
|
+
const ancestor = cur.annotatedName;
|
|
5239
|
+
const message = `${child} element is not permitted as a descendant of ${ancestor}`;
|
|
5240
|
+
const context = {
|
|
5241
|
+
kind: ErrorKind.DESCENDANT,
|
|
5242
|
+
ancestor,
|
|
5243
|
+
child,
|
|
5244
|
+
};
|
|
5245
|
+
this.report(node, message, null, context);
|
|
5207
5246
|
return true;
|
|
5208
5247
|
}
|
|
5209
5248
|
return false;
|
|
@@ -5270,6 +5309,152 @@ class ElementPermittedOrder extends Rule {
|
|
|
5270
5309
|
}
|
|
5271
5310
|
}
|
|
5272
5311
|
|
|
5312
|
+
const CACHE_KEY = Symbol(classifyNodeText.name);
|
|
5313
|
+
var TextClassification;
|
|
5314
|
+
(function (TextClassification) {
|
|
5315
|
+
TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
|
|
5316
|
+
TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
|
|
5317
|
+
TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
|
|
5318
|
+
})(TextClassification || (TextClassification = {}));
|
|
5319
|
+
/**
|
|
5320
|
+
* Checks text content of an element.
|
|
5321
|
+
*
|
|
5322
|
+
* Any text is considered including text from descendant elements. Whitespace is
|
|
5323
|
+
* ignored.
|
|
5324
|
+
*
|
|
5325
|
+
* If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
|
|
5326
|
+
*/
|
|
5327
|
+
function classifyNodeText(node) {
|
|
5328
|
+
if (node.cacheExists(CACHE_KEY)) {
|
|
5329
|
+
return node.cacheGet(CACHE_KEY);
|
|
5330
|
+
}
|
|
5331
|
+
const text = findTextNodes(node);
|
|
5332
|
+
/* if any text is dynamic classify as dynamic */
|
|
5333
|
+
if (text.some((cur) => cur.isDynamic)) {
|
|
5334
|
+
return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
|
|
5335
|
+
}
|
|
5336
|
+
/* if any text has non-whitespace character classify as static */
|
|
5337
|
+
if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
|
|
5338
|
+
return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
|
|
5339
|
+
}
|
|
5340
|
+
/* default to empty */
|
|
5341
|
+
return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
|
|
5342
|
+
}
|
|
5343
|
+
function findTextNodes(node) {
|
|
5344
|
+
let text = [];
|
|
5345
|
+
for (const child of node.childNodes) {
|
|
5346
|
+
switch (child.nodeType) {
|
|
5347
|
+
case NodeType.TEXT_NODE:
|
|
5348
|
+
text.push(child);
|
|
5349
|
+
break;
|
|
5350
|
+
case NodeType.ELEMENT_NODE:
|
|
5351
|
+
text = text.concat(findTextNodes(child));
|
|
5352
|
+
break;
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
return text;
|
|
5356
|
+
}
|
|
5357
|
+
|
|
5358
|
+
function hasAltText(image) {
|
|
5359
|
+
const alt = image.getAttribute("alt");
|
|
5360
|
+
/* missing or boolean */
|
|
5361
|
+
if (alt === null || alt.value === null) {
|
|
5362
|
+
return false;
|
|
5363
|
+
}
|
|
5364
|
+
return alt.isDynamic || alt.value.toString() !== "";
|
|
5365
|
+
}
|
|
5366
|
+
|
|
5367
|
+
function hasAriaLabel(node) {
|
|
5368
|
+
const label = node.getAttribute("aria-label");
|
|
5369
|
+
/* missing or boolean */
|
|
5370
|
+
if (label === null || label.value === null) {
|
|
5371
|
+
return false;
|
|
5372
|
+
}
|
|
5373
|
+
return label.isDynamic || label.value.toString() !== "";
|
|
5374
|
+
}
|
|
5375
|
+
|
|
5376
|
+
/**
|
|
5377
|
+
* Joins a list of words into natural language.
|
|
5378
|
+
*
|
|
5379
|
+
* - `["foo"]` becomes `"foo"`
|
|
5380
|
+
* - `["foo", "bar"]` becomes `"foo or bar"`
|
|
5381
|
+
* - `["foo", "bar", "baz"]` becomes `"foo, bar or baz"`
|
|
5382
|
+
* - and so on...
|
|
5383
|
+
*
|
|
5384
|
+
* @internal
|
|
5385
|
+
* @param values - List of words to join
|
|
5386
|
+
* @param conjunction - Conjunction for the last element.
|
|
5387
|
+
* @returns String with the words naturally joined with a conjunction.
|
|
5388
|
+
*/
|
|
5389
|
+
function naturalJoin(values, conjunction = "or") {
|
|
5390
|
+
switch (values.length) {
|
|
5391
|
+
case 0:
|
|
5392
|
+
return "";
|
|
5393
|
+
case 1:
|
|
5394
|
+
return values[0];
|
|
5395
|
+
case 2:
|
|
5396
|
+
return `${values[0]} ${conjunction} ${values[1]}`;
|
|
5397
|
+
default:
|
|
5398
|
+
return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.slice(-1)[0]}`;
|
|
5399
|
+
}
|
|
5400
|
+
}
|
|
5401
|
+
|
|
5402
|
+
function isTagnameOnly(value) {
|
|
5403
|
+
return Boolean(value.match(/^[a-zA-Z0-9-]+$/));
|
|
5404
|
+
}
|
|
5405
|
+
function getRuleDescription(context) {
|
|
5406
|
+
if (!context) {
|
|
5407
|
+
return [
|
|
5408
|
+
"Some elements has restrictions on what content is allowed.",
|
|
5409
|
+
"This can include both direct children or descendant elements.",
|
|
5410
|
+
];
|
|
5411
|
+
}
|
|
5412
|
+
const escaped = context.ancestor.map((it) => `\`${it}\``);
|
|
5413
|
+
return [`The \`${context.child}\` element requires a ${naturalJoin(escaped)} ancestor.`];
|
|
5414
|
+
}
|
|
5415
|
+
class ElementRequiredAncestor extends Rule {
|
|
5416
|
+
documentation(context) {
|
|
5417
|
+
return {
|
|
5418
|
+
description: getRuleDescription(context).join("\n"),
|
|
5419
|
+
url: ruleDocumentationUrl("@/rules/element-required-ancestor.ts"),
|
|
5420
|
+
};
|
|
5421
|
+
}
|
|
5422
|
+
setup() {
|
|
5423
|
+
this.on("dom:ready", (event) => {
|
|
5424
|
+
const doc = event.document;
|
|
5425
|
+
doc.visitDepthFirst((node) => {
|
|
5426
|
+
const parent = node.parent;
|
|
5427
|
+
/* istanbul ignore next: satisfy typescript but will visitDepthFirst()
|
|
5428
|
+
* will not yield nodes without a parent */
|
|
5429
|
+
if (!parent) {
|
|
5430
|
+
return;
|
|
5431
|
+
}
|
|
5432
|
+
this.validateRequiredAncestors(node);
|
|
5433
|
+
});
|
|
5434
|
+
});
|
|
5435
|
+
}
|
|
5436
|
+
validateRequiredAncestors(node) {
|
|
5437
|
+
if (!node.meta) {
|
|
5438
|
+
return;
|
|
5439
|
+
}
|
|
5440
|
+
const rules = node.meta.requiredAncestors;
|
|
5441
|
+
if (!rules) {
|
|
5442
|
+
return;
|
|
5443
|
+
}
|
|
5444
|
+
if (Validator.validateAncestors(node, rules)) {
|
|
5445
|
+
return;
|
|
5446
|
+
}
|
|
5447
|
+
const ancestor = rules.map((it) => (isTagnameOnly(it) ? `<${it}>` : `"${it}"`));
|
|
5448
|
+
const child = `<${node.tagName}>`;
|
|
5449
|
+
const message = `<${node.tagName}> element requires a ${naturalJoin(ancestor)} ancestor`;
|
|
5450
|
+
const context = {
|
|
5451
|
+
ancestor,
|
|
5452
|
+
child,
|
|
5453
|
+
};
|
|
5454
|
+
this.report(node, message, null, context);
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5273
5458
|
class ElementRequiredAttributes extends Rule {
|
|
5274
5459
|
documentation(context) {
|
|
5275
5460
|
const docs = {
|
|
@@ -5347,52 +5532,6 @@ class ElementRequiredContent extends Rule {
|
|
|
5347
5532
|
}
|
|
5348
5533
|
}
|
|
5349
5534
|
|
|
5350
|
-
const CACHE_KEY = Symbol(classifyNodeText.name);
|
|
5351
|
-
var TextClassification;
|
|
5352
|
-
(function (TextClassification) {
|
|
5353
|
-
TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
|
|
5354
|
-
TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
|
|
5355
|
-
TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
|
|
5356
|
-
})(TextClassification || (TextClassification = {}));
|
|
5357
|
-
/**
|
|
5358
|
-
* Checks text content of an element.
|
|
5359
|
-
*
|
|
5360
|
-
* Any text is considered including text from descendant elements. Whitespace is
|
|
5361
|
-
* ignored.
|
|
5362
|
-
*
|
|
5363
|
-
* If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
|
|
5364
|
-
*/
|
|
5365
|
-
function classifyNodeText(node) {
|
|
5366
|
-
if (node.cacheExists(CACHE_KEY)) {
|
|
5367
|
-
return node.cacheGet(CACHE_KEY);
|
|
5368
|
-
}
|
|
5369
|
-
const text = findTextNodes(node);
|
|
5370
|
-
/* if any text is dynamic classify as dynamic */
|
|
5371
|
-
if (text.some((cur) => cur.isDynamic)) {
|
|
5372
|
-
return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
|
|
5373
|
-
}
|
|
5374
|
-
/* if any text has non-whitespace character classify as static */
|
|
5375
|
-
if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
|
|
5376
|
-
return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
|
|
5377
|
-
}
|
|
5378
|
-
/* default to empty */
|
|
5379
|
-
return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
|
|
5380
|
-
}
|
|
5381
|
-
function findTextNodes(node) {
|
|
5382
|
-
let text = [];
|
|
5383
|
-
for (const child of node.childNodes) {
|
|
5384
|
-
switch (child.nodeType) {
|
|
5385
|
-
case NodeType.TEXT_NODE:
|
|
5386
|
-
text.push(child);
|
|
5387
|
-
break;
|
|
5388
|
-
case NodeType.ELEMENT_NODE:
|
|
5389
|
-
text = text.concat(findTextNodes(child));
|
|
5390
|
-
break;
|
|
5391
|
-
}
|
|
5392
|
-
}
|
|
5393
|
-
return text;
|
|
5394
|
-
}
|
|
5395
|
-
|
|
5396
5535
|
const selector = ["h1", "h2", "h3", "h4", "h5", "h6"].join(",");
|
|
5397
5536
|
class EmptyHeading extends Rule {
|
|
5398
5537
|
documentation() {
|
|
@@ -7573,24 +7712,6 @@ class TelNonBreaking extends Rule {
|
|
|
7573
7712
|
}
|
|
7574
7713
|
}
|
|
7575
7714
|
|
|
7576
|
-
function hasAltText(image) {
|
|
7577
|
-
const alt = image.getAttribute("alt");
|
|
7578
|
-
/* missing or boolean */
|
|
7579
|
-
if (alt === null || alt.value === null) {
|
|
7580
|
-
return false;
|
|
7581
|
-
}
|
|
7582
|
-
return alt.isDynamic || alt.value.toString() !== "";
|
|
7583
|
-
}
|
|
7584
|
-
|
|
7585
|
-
function hasAriaLabel(node) {
|
|
7586
|
-
const label = node.getAttribute("aria-label");
|
|
7587
|
-
/* missing or boolean */
|
|
7588
|
-
if (label === null || label.value === null) {
|
|
7589
|
-
return false;
|
|
7590
|
-
}
|
|
7591
|
-
return label.isDynamic || label.value.toString() !== "";
|
|
7592
|
-
}
|
|
7593
|
-
|
|
7594
7715
|
/**
|
|
7595
7716
|
* Check if attribute is present and non-empty or dynamic.
|
|
7596
7717
|
*/
|
|
@@ -9768,13 +9889,13 @@ class VoidStyle extends Rule {
|
|
|
9768
9889
|
return;
|
|
9769
9890
|
}
|
|
9770
9891
|
if (this.shouldBeOmitted(node)) {
|
|
9771
|
-
this.
|
|
9892
|
+
this.reportError(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
|
|
9772
9893
|
}
|
|
9773
9894
|
if (this.shouldBeSelfClosed(node)) {
|
|
9774
|
-
this.
|
|
9895
|
+
this.reportError(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
|
|
9775
9896
|
}
|
|
9776
9897
|
}
|
|
9777
|
-
|
|
9898
|
+
reportError(node, message) {
|
|
9778
9899
|
const context = {
|
|
9779
9900
|
style: this.style,
|
|
9780
9901
|
tagName: node.tagName,
|
|
@@ -10086,6 +10207,7 @@ const bundledRules = {
|
|
|
10086
10207
|
"element-permitted-content": ElementPermittedContent,
|
|
10087
10208
|
"element-permitted-occurrences": ElementPermittedOccurrences,
|
|
10088
10209
|
"element-permitted-order": ElementPermittedOrder,
|
|
10210
|
+
"element-required-ancestor": ElementRequiredAncestor,
|
|
10089
10211
|
"element-required-attributes": ElementRequiredAttributes,
|
|
10090
10212
|
"element-required-content": ElementRequiredContent,
|
|
10091
10213
|
"empty-heading": EmptyHeading,
|
|
@@ -10193,6 +10315,7 @@ const config$1 = {
|
|
|
10193
10315
|
"element-permitted-content": "error",
|
|
10194
10316
|
"element-permitted-occurrences": "error",
|
|
10195
10317
|
"element-permitted-order": "error",
|
|
10318
|
+
"element-required-ancestor": "error",
|
|
10196
10319
|
"element-required-attributes": "error",
|
|
10197
10320
|
"element-required-content": "error",
|
|
10198
10321
|
"empty-heading": "error",
|
|
@@ -10251,6 +10374,7 @@ const config = {
|
|
|
10251
10374
|
"element-permitted-content": "error",
|
|
10252
10375
|
"element-permitted-occurrences": "error",
|
|
10253
10376
|
"element-permitted-order": "error",
|
|
10377
|
+
"element-required-ancestor": "error",
|
|
10254
10378
|
"element-required-attributes": "error",
|
|
10255
10379
|
"element-required-content": "error",
|
|
10256
10380
|
"multiple-labeled-controls": "error",
|
|
@@ -10330,6 +10454,7 @@ class ResolvedConfig {
|
|
|
10330
10454
|
});
|
|
10331
10455
|
}
|
|
10332
10456
|
catch (err) {
|
|
10457
|
+
/* istanbul ignore next: only used as a fallback */
|
|
10333
10458
|
const message = err instanceof Error ? err.message : String(err);
|
|
10334
10459
|
throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
|
|
10335
10460
|
}
|
|
@@ -10342,7 +10467,7 @@ class ResolvedConfig {
|
|
|
10342
10467
|
* Wrapper around [[transformSource]] which reads a file before passing it
|
|
10343
10468
|
* as-is to transformSource.
|
|
10344
10469
|
*
|
|
10345
|
-
* @param
|
|
10470
|
+
* @param filename - Filename to transform (according to configured
|
|
10346
10471
|
* transformations)
|
|
10347
10472
|
* @returns A list of transformed sources ready for validation.
|
|
10348
10473
|
*/
|
|
@@ -10498,7 +10623,9 @@ class Config {
|
|
|
10498
10623
|
var _a;
|
|
10499
10624
|
const valid = validator(configData);
|
|
10500
10625
|
if (!valid) {
|
|
10501
|
-
throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema,
|
|
10626
|
+
throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema,
|
|
10627
|
+
/* istanbul ignore next: will be set when a validation error has occurred */
|
|
10628
|
+
(_a = validator.errors) !== null && _a !== void 0 ? _a : []);
|
|
10502
10629
|
}
|
|
10503
10630
|
if (configData.rules) {
|
|
10504
10631
|
const normalizedRules = Config.getRulesObject(configData.rules);
|
|
@@ -10607,6 +10734,7 @@ class Config {
|
|
|
10607
10734
|
metaTable.loadFromObject(legacyRequire(entry));
|
|
10608
10735
|
}
|
|
10609
10736
|
catch (err) {
|
|
10737
|
+
/* istanbul ignore next: only used as a fallback */
|
|
10610
10738
|
const message = err instanceof Error ? err.message : String(err);
|
|
10611
10739
|
throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, ensureError(err));
|
|
10612
10740
|
}
|
|
@@ -10628,6 +10756,7 @@ class Config {
|
|
|
10628
10756
|
*
|
|
10629
10757
|
* @internal primary purpose is unittests
|
|
10630
10758
|
*/
|
|
10759
|
+
/* istanbul ignore next: used for testing only */
|
|
10631
10760
|
get() {
|
|
10632
10761
|
const config = { ...this.config };
|
|
10633
10762
|
if (config.elements) {
|
|
@@ -10650,6 +10779,7 @@ class Config {
|
|
|
10650
10779
|
*/
|
|
10651
10780
|
getRules() {
|
|
10652
10781
|
var _a;
|
|
10782
|
+
/* istanbul ignore next: only used as a fallback */
|
|
10653
10783
|
return Config.getRulesObject((_a = this.config.rules) !== null && _a !== void 0 ? _a : {});
|
|
10654
10784
|
}
|
|
10655
10785
|
static getRulesObject(src) {
|
|
@@ -10684,6 +10814,7 @@ class Config {
|
|
|
10684
10814
|
return plugin;
|
|
10685
10815
|
}
|
|
10686
10816
|
catch (err) {
|
|
10817
|
+
/* istanbul ignore next: only used as a fallback */
|
|
10687
10818
|
const message = err instanceof Error ? err.message : String(err);
|
|
10688
10819
|
throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, ensureError(err));
|
|
10689
10820
|
}
|
|
@@ -11599,9 +11730,9 @@ class Reporter {
|
|
|
11599
11730
|
if (!(location.filename in this.result)) {
|
|
11600
11731
|
this.result[location.filename] = [];
|
|
11601
11732
|
}
|
|
11602
|
-
|
|
11733
|
+
const ruleUrl = (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url;
|
|
11734
|
+
const entry = {
|
|
11603
11735
|
ruleId: rule.name,
|
|
11604
|
-
ruleUrl: (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url,
|
|
11605
11736
|
severity,
|
|
11606
11737
|
message,
|
|
11607
11738
|
offset: location.offset,
|
|
@@ -11611,8 +11742,14 @@ class Reporter {
|
|
|
11611
11742
|
selector() {
|
|
11612
11743
|
return node ? node.generateSelector() : null;
|
|
11613
11744
|
},
|
|
11614
|
-
|
|
11615
|
-
|
|
11745
|
+
};
|
|
11746
|
+
if (ruleUrl) {
|
|
11747
|
+
entry.ruleUrl = ruleUrl;
|
|
11748
|
+
}
|
|
11749
|
+
if (context) {
|
|
11750
|
+
entry.context = context;
|
|
11751
|
+
}
|
|
11752
|
+
this.result[location.filename].push(entry);
|
|
11616
11753
|
}
|
|
11617
11754
|
addManual(filename, message) {
|
|
11618
11755
|
if (!(filename in this.result)) {
|
|
@@ -12026,7 +12163,7 @@ class Engine {
|
|
|
12026
12163
|
offset: location.offset,
|
|
12027
12164
|
line: location.line,
|
|
12028
12165
|
column: location.column,
|
|
12029
|
-
size: location.size
|
|
12166
|
+
size: location.size,
|
|
12030
12167
|
selector: () => null,
|
|
12031
12168
|
});
|
|
12032
12169
|
}
|
|
@@ -12456,12 +12593,12 @@ class FileSystemConfigLoader extends ConfigLoader {
|
|
|
12456
12593
|
* `null` if no configuration files are found.
|
|
12457
12594
|
*/
|
|
12458
12595
|
fromFilename(filename) {
|
|
12459
|
-
var _a;
|
|
12460
12596
|
if (filename === "inline") {
|
|
12461
12597
|
return null;
|
|
12462
12598
|
}
|
|
12463
|
-
|
|
12464
|
-
|
|
12599
|
+
const cache = this.cache.get(filename);
|
|
12600
|
+
if (cache) {
|
|
12601
|
+
return cache;
|
|
12465
12602
|
}
|
|
12466
12603
|
let found = false;
|
|
12467
12604
|
let current = path.resolve(path.dirname(filename));
|