html-validate 6.11.1 → 7.1.1
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.d.ts +2 -1
- package/dist/cjs/core.js +223 -68
- package/dist/cjs/core.js.map +1 -1
- package/dist/es/core.d.ts +2 -1
- package/dist/es/core.js +223 -68
- package/dist/es/core.js.map +1 -1
- package/package.json +27 -27
package/dist/es/core.d.ts
CHANGED
|
@@ -333,12 +333,13 @@ declare abstract class Rule<ContextType = void, OptionsType = void> {
|
|
|
333
333
|
* not `"foo"`.
|
|
334
334
|
*
|
|
335
335
|
* @param keyword - Keyword to match against `include` and `exclude` options.
|
|
336
|
+
* @param matcher - Optional function to compare items with.
|
|
336
337
|
* @returns `true` if keyword is not present in `include` or is present in
|
|
337
338
|
* `exclude`.
|
|
338
339
|
*/
|
|
339
340
|
isKeywordIgnored<T extends IncludeExcludeOptions>(this: {
|
|
340
341
|
options: T;
|
|
341
|
-
}, keyword: string): boolean;
|
|
342
|
+
}, keyword: string, matcher?: (list: string[], it: string) => boolean): boolean;
|
|
342
343
|
/**
|
|
343
344
|
* Find all tags which has enabled given property.
|
|
344
345
|
*/
|
package/dist/es/core.js
CHANGED
|
@@ -2071,6 +2071,7 @@ class HtmlElement extends DOMNode {
|
|
|
2071
2071
|
}
|
|
2072
2072
|
const parts = [];
|
|
2073
2073
|
let root;
|
|
2074
|
+
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
|
|
2074
2075
|
for (root = this; root.parent; root = root.parent) {
|
|
2075
2076
|
/* .. */
|
|
2076
2077
|
}
|
|
@@ -2614,12 +2615,15 @@ class Validator {
|
|
|
2614
2615
|
if (value === null || value === undefined) {
|
|
2615
2616
|
return false;
|
|
2616
2617
|
}
|
|
2618
|
+
const caseInsensitiveValue = value.toLowerCase();
|
|
2617
2619
|
return rule.enum.some((entry) => {
|
|
2618
2620
|
if (entry instanceof RegExp) {
|
|
2621
|
+
/* regular expressions are matched case-sensitive */
|
|
2619
2622
|
return !!value.match(entry);
|
|
2620
2623
|
}
|
|
2621
2624
|
else {
|
|
2622
|
-
|
|
2625
|
+
/* strings matched case-insensitive */
|
|
2626
|
+
return caseInsensitiveValue === entry;
|
|
2623
2627
|
}
|
|
2624
2628
|
});
|
|
2625
2629
|
}
|
|
@@ -3082,7 +3086,7 @@ var TRANSFORMER_API;
|
|
|
3082
3086
|
/** @public */
|
|
3083
3087
|
const name = "html-validate";
|
|
3084
3088
|
/** @public */
|
|
3085
|
-
const version = "
|
|
3089
|
+
const version = "7.1.1";
|
|
3086
3090
|
/** @public */
|
|
3087
3091
|
const homepage = "https://html-validate.org";
|
|
3088
3092
|
/** @public */
|
|
@@ -3245,17 +3249,18 @@ class Rule {
|
|
|
3245
3249
|
* not `"foo"`.
|
|
3246
3250
|
*
|
|
3247
3251
|
* @param keyword - Keyword to match against `include` and `exclude` options.
|
|
3252
|
+
* @param matcher - Optional function to compare items with.
|
|
3248
3253
|
* @returns `true` if keyword is not present in `include` or is present in
|
|
3249
3254
|
* `exclude`.
|
|
3250
3255
|
*/
|
|
3251
|
-
isKeywordIgnored(keyword) {
|
|
3256
|
+
isKeywordIgnored(keyword, matcher = (list, it) => list.includes(it)) {
|
|
3252
3257
|
const { include, exclude } = this.options;
|
|
3253
3258
|
/* ignore keyword if not present in "include" */
|
|
3254
|
-
if (include && !include
|
|
3259
|
+
if (include && !matcher(include, keyword)) {
|
|
3255
3260
|
return true;
|
|
3256
3261
|
}
|
|
3257
3262
|
/* ignore keyword if present in "excludes" */
|
|
3258
|
-
if (exclude && exclude
|
|
3263
|
+
if (exclude && matcher(exclude, keyword)) {
|
|
3259
3264
|
return true;
|
|
3260
3265
|
}
|
|
3261
3266
|
return false;
|
|
@@ -3403,7 +3408,7 @@ function ruleDocumentationUrl(filename) {
|
|
|
3403
3408
|
return `${homepage}/rules/${normalized}.html`;
|
|
3404
3409
|
}
|
|
3405
3410
|
|
|
3406
|
-
const defaults$
|
|
3411
|
+
const defaults$s = {
|
|
3407
3412
|
allowExternal: true,
|
|
3408
3413
|
allowRelative: true,
|
|
3409
3414
|
allowAbsolute: true,
|
|
@@ -3447,7 +3452,7 @@ function matchList(value, list) {
|
|
|
3447
3452
|
}
|
|
3448
3453
|
class AllowedLinks extends Rule {
|
|
3449
3454
|
constructor(options) {
|
|
3450
|
-
super({ ...defaults$
|
|
3455
|
+
super({ ...defaults$s, ...options });
|
|
3451
3456
|
this.allowExternal = parseAllow(this.options.allowExternal);
|
|
3452
3457
|
this.allowRelative = parseAllow(this.options.allowRelative);
|
|
3453
3458
|
this.allowAbsolute = parseAllow(this.options.allowAbsolute);
|
|
@@ -3748,13 +3753,13 @@ class CaseStyle {
|
|
|
3748
3753
|
}
|
|
3749
3754
|
}
|
|
3750
3755
|
|
|
3751
|
-
const defaults$
|
|
3756
|
+
const defaults$r = {
|
|
3752
3757
|
style: "lowercase",
|
|
3753
3758
|
ignoreForeign: true,
|
|
3754
3759
|
};
|
|
3755
3760
|
class AttrCase extends Rule {
|
|
3756
3761
|
constructor(options) {
|
|
3757
|
-
super({ ...defaults$
|
|
3762
|
+
super({ ...defaults$r, ...options });
|
|
3758
3763
|
this.style = new CaseStyle(this.options.style, "attr-case");
|
|
3759
3764
|
}
|
|
3760
3765
|
static schema() {
|
|
@@ -4094,7 +4099,7 @@ class AttrDelimiter extends Rule {
|
|
|
4094
4099
|
}
|
|
4095
4100
|
|
|
4096
4101
|
const DEFAULT_PATTERN = "[a-z0-9-:]+";
|
|
4097
|
-
const defaults$
|
|
4102
|
+
const defaults$q = {
|
|
4098
4103
|
pattern: DEFAULT_PATTERN,
|
|
4099
4104
|
ignoreForeign: true,
|
|
4100
4105
|
};
|
|
@@ -4131,7 +4136,7 @@ function generateDescription(name, pattern) {
|
|
|
4131
4136
|
}
|
|
4132
4137
|
class AttrPattern extends Rule {
|
|
4133
4138
|
constructor(options) {
|
|
4134
|
-
super({ ...defaults$
|
|
4139
|
+
super({ ...defaults$q, ...options });
|
|
4135
4140
|
this.pattern = generateRegexp(this.options.pattern);
|
|
4136
4141
|
}
|
|
4137
4142
|
static schema() {
|
|
@@ -4190,20 +4195,56 @@ var QuoteStyle;
|
|
|
4190
4195
|
QuoteStyle["SINGLE_QUOTE"] = "'";
|
|
4191
4196
|
QuoteStyle["DOUBLE_QUOTE"] = "\"";
|
|
4192
4197
|
QuoteStyle["AUTO_QUOTE"] = "auto";
|
|
4198
|
+
QuoteStyle["ANY_QUOTE"] = "any";
|
|
4193
4199
|
})(QuoteStyle || (QuoteStyle = {}));
|
|
4194
|
-
const defaults$
|
|
4200
|
+
const defaults$p = {
|
|
4195
4201
|
style: "auto",
|
|
4196
4202
|
unquoted: false,
|
|
4197
4203
|
};
|
|
4204
|
+
function describeError(context) {
|
|
4205
|
+
if (context) {
|
|
4206
|
+
switch (context.error) {
|
|
4207
|
+
case "style":
|
|
4208
|
+
return `Attribute \`${context.attr}\` must use \`${context.expected}\` instead of \`${context.actual}\`.`;
|
|
4209
|
+
case "unquoted":
|
|
4210
|
+
return `Attribute \`${context.attr}\` must not be unquoted.`;
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
else {
|
|
4214
|
+
return "This attribute is not quoted properly.";
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
function describeStyle(style, unquoted) {
|
|
4218
|
+
const description = [];
|
|
4219
|
+
switch (style) {
|
|
4220
|
+
case QuoteStyle.AUTO_QUOTE:
|
|
4221
|
+
description.push("- quoted with double quotes `\"` unless the value contains double quotes in which case single quotes `'` should be used instead");
|
|
4222
|
+
break;
|
|
4223
|
+
case QuoteStyle.ANY_QUOTE:
|
|
4224
|
+
description.push("- quoted with single quotes `'`");
|
|
4225
|
+
description.push('- quoted with double quotes `"`');
|
|
4226
|
+
break;
|
|
4227
|
+
case QuoteStyle.SINGLE_QUOTE:
|
|
4228
|
+
case QuoteStyle.DOUBLE_QUOTE: {
|
|
4229
|
+
const name = style === QuoteStyle.SINGLE_QUOTE ? "single" : "double";
|
|
4230
|
+
description.push(`- quoted with ${name} quotes \`${style}\``);
|
|
4231
|
+
break;
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
if (unquoted) {
|
|
4235
|
+
description.push("- unquoted (if applicable)");
|
|
4236
|
+
}
|
|
4237
|
+
return `${description.join(" or\n")}\n`;
|
|
4238
|
+
}
|
|
4198
4239
|
class AttrQuotes extends Rule {
|
|
4199
4240
|
constructor(options) {
|
|
4200
|
-
super({ ...defaults$
|
|
4241
|
+
super({ ...defaults$p, ...options });
|
|
4201
4242
|
this.style = parseStyle$4(this.options.style);
|
|
4202
4243
|
}
|
|
4203
4244
|
static schema() {
|
|
4204
4245
|
return {
|
|
4205
4246
|
style: {
|
|
4206
|
-
enum: ["auto", "double", "single"],
|
|
4247
|
+
enum: ["auto", "double", "single", "any"],
|
|
4207
4248
|
type: "string",
|
|
4208
4249
|
},
|
|
4209
4250
|
unquoted: {
|
|
@@ -4211,19 +4252,20 @@ class AttrQuotes extends Rule {
|
|
|
4211
4252
|
},
|
|
4212
4253
|
};
|
|
4213
4254
|
}
|
|
4214
|
-
documentation() {
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4255
|
+
documentation(context) {
|
|
4256
|
+
const { style } = this;
|
|
4257
|
+
const { unquoted } = this.options;
|
|
4258
|
+
const description = [
|
|
4259
|
+
describeError(context),
|
|
4260
|
+
"",
|
|
4261
|
+
"Under the current configuration attributes must be:",
|
|
4262
|
+
"",
|
|
4263
|
+
describeStyle(style, unquoted),
|
|
4264
|
+
];
|
|
4265
|
+
return {
|
|
4266
|
+
description: description.join("\n"),
|
|
4267
|
+
url: ruleDocumentationUrl("@/rules/attr-quotes.ts"),
|
|
4268
|
+
};
|
|
4227
4269
|
}
|
|
4228
4270
|
setup() {
|
|
4229
4271
|
this.on("attr", (event) => {
|
|
@@ -4233,13 +4275,31 @@ class AttrQuotes extends Rule {
|
|
|
4233
4275
|
}
|
|
4234
4276
|
if (!event.quote) {
|
|
4235
4277
|
if (this.options.unquoted === false) {
|
|
4236
|
-
|
|
4278
|
+
const message = `Attribute "${event.key}" using unquoted value`;
|
|
4279
|
+
const context = {
|
|
4280
|
+
error: "unquoted",
|
|
4281
|
+
attr: event.key,
|
|
4282
|
+
};
|
|
4283
|
+
this.report(event.target, message, null, context);
|
|
4237
4284
|
}
|
|
4238
4285
|
return;
|
|
4239
4286
|
}
|
|
4287
|
+
/* if the style is set to any we skip the rest of the rule as the only
|
|
4288
|
+
* thing that matters is if the "unquoted" options triggers an error or
|
|
4289
|
+
* not */
|
|
4290
|
+
if (this.style === QuoteStyle.ANY_QUOTE) {
|
|
4291
|
+
return;
|
|
4292
|
+
}
|
|
4240
4293
|
const expected = this.resolveQuotemark(event.value.toString(), this.style);
|
|
4241
4294
|
if (event.quote !== expected) {
|
|
4242
|
-
|
|
4295
|
+
const message = `Attribute "${event.key}" used ${event.quote} instead of expected ${expected}`;
|
|
4296
|
+
const context = {
|
|
4297
|
+
error: "style",
|
|
4298
|
+
attr: event.key,
|
|
4299
|
+
actual: event.quote,
|
|
4300
|
+
expected,
|
|
4301
|
+
};
|
|
4302
|
+
this.report(event.target, message, null, context);
|
|
4243
4303
|
}
|
|
4244
4304
|
});
|
|
4245
4305
|
}
|
|
@@ -4260,6 +4320,8 @@ function parseStyle$4(style) {
|
|
|
4260
4320
|
return QuoteStyle.DOUBLE_QUOTE;
|
|
4261
4321
|
case "single":
|
|
4262
4322
|
return QuoteStyle.SINGLE_QUOTE;
|
|
4323
|
+
case "any":
|
|
4324
|
+
return QuoteStyle.ANY_QUOTE;
|
|
4263
4325
|
/* istanbul ignore next: covered by schema validation */
|
|
4264
4326
|
default:
|
|
4265
4327
|
throw new ConfigError(`Invalid style "${style}" for "attr-quotes" rule`);
|
|
@@ -4362,12 +4424,12 @@ class AttributeAllowedValues extends Rule {
|
|
|
4362
4424
|
}
|
|
4363
4425
|
}
|
|
4364
4426
|
|
|
4365
|
-
const defaults$
|
|
4427
|
+
const defaults$o = {
|
|
4366
4428
|
style: "omit",
|
|
4367
4429
|
};
|
|
4368
4430
|
class AttributeBooleanStyle extends Rule {
|
|
4369
4431
|
constructor(options) {
|
|
4370
|
-
super({ ...defaults$
|
|
4432
|
+
super({ ...defaults$o, ...options });
|
|
4371
4433
|
this.hasInvalidStyle = parseStyle$3(this.options.style);
|
|
4372
4434
|
}
|
|
4373
4435
|
static schema() {
|
|
@@ -4443,12 +4505,12 @@ function reportMessage$1(attr, style) {
|
|
|
4443
4505
|
return "";
|
|
4444
4506
|
}
|
|
4445
4507
|
|
|
4446
|
-
const defaults$
|
|
4508
|
+
const defaults$n = {
|
|
4447
4509
|
style: "omit",
|
|
4448
4510
|
};
|
|
4449
4511
|
class AttributeEmptyStyle extends Rule {
|
|
4450
4512
|
constructor(options) {
|
|
4451
|
-
super({ ...defaults$
|
|
4513
|
+
super({ ...defaults$n, ...options });
|
|
4452
4514
|
this.hasInvalidStyle = parseStyle$2(this.options.style);
|
|
4453
4515
|
}
|
|
4454
4516
|
static schema() {
|
|
@@ -4556,12 +4618,12 @@ function describePattern(pattern) {
|
|
|
4556
4618
|
}
|
|
4557
4619
|
}
|
|
4558
4620
|
|
|
4559
|
-
const defaults$
|
|
4621
|
+
const defaults$m = {
|
|
4560
4622
|
pattern: "kebabcase",
|
|
4561
4623
|
};
|
|
4562
4624
|
class ClassPattern extends Rule {
|
|
4563
4625
|
constructor(options) {
|
|
4564
|
-
super({ ...defaults$
|
|
4626
|
+
super({ ...defaults$m, ...options });
|
|
4565
4627
|
this.pattern = parsePattern(this.options.pattern);
|
|
4566
4628
|
}
|
|
4567
4629
|
static schema() {
|
|
@@ -4670,13 +4732,13 @@ class CloseOrder extends Rule {
|
|
|
4670
4732
|
}
|
|
4671
4733
|
}
|
|
4672
4734
|
|
|
4673
|
-
const defaults$
|
|
4735
|
+
const defaults$l = {
|
|
4674
4736
|
include: null,
|
|
4675
4737
|
exclude: null,
|
|
4676
4738
|
};
|
|
4677
4739
|
class Deprecated extends Rule {
|
|
4678
4740
|
constructor(options) {
|
|
4679
|
-
super({ ...defaults$
|
|
4741
|
+
super({ ...defaults$l, ...options });
|
|
4680
4742
|
}
|
|
4681
4743
|
static schema() {
|
|
4682
4744
|
return {
|
|
@@ -4839,12 +4901,12 @@ class NoStyleTag$1 extends Rule {
|
|
|
4839
4901
|
}
|
|
4840
4902
|
}
|
|
4841
4903
|
|
|
4842
|
-
const defaults$
|
|
4904
|
+
const defaults$k = {
|
|
4843
4905
|
style: "uppercase",
|
|
4844
4906
|
};
|
|
4845
4907
|
class DoctypeStyle extends Rule {
|
|
4846
4908
|
constructor(options) {
|
|
4847
|
-
super({ ...defaults$
|
|
4909
|
+
super({ ...defaults$k, ...options });
|
|
4848
4910
|
}
|
|
4849
4911
|
static schema() {
|
|
4850
4912
|
return {
|
|
@@ -4876,12 +4938,12 @@ class DoctypeStyle extends Rule {
|
|
|
4876
4938
|
}
|
|
4877
4939
|
}
|
|
4878
4940
|
|
|
4879
|
-
const defaults$
|
|
4941
|
+
const defaults$j = {
|
|
4880
4942
|
style: "lowercase",
|
|
4881
4943
|
};
|
|
4882
4944
|
class ElementCase extends Rule {
|
|
4883
4945
|
constructor(options) {
|
|
4884
|
-
super({ ...defaults$
|
|
4946
|
+
super({ ...defaults$j, ...options });
|
|
4885
4947
|
this.style = new CaseStyle(this.options.style, "element-case");
|
|
4886
4948
|
}
|
|
4887
4949
|
static schema() {
|
|
@@ -4947,14 +5009,14 @@ class ElementCase extends Rule {
|
|
|
4947
5009
|
}
|
|
4948
5010
|
}
|
|
4949
5011
|
|
|
4950
|
-
const defaults$
|
|
5012
|
+
const defaults$i = {
|
|
4951
5013
|
pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
|
|
4952
5014
|
whitelist: [],
|
|
4953
5015
|
blacklist: [],
|
|
4954
5016
|
};
|
|
4955
5017
|
class ElementName extends Rule {
|
|
4956
5018
|
constructor(options) {
|
|
4957
|
-
super({ ...defaults$
|
|
5019
|
+
super({ ...defaults$i, ...options });
|
|
4958
5020
|
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
4959
5021
|
this.pattern = new RegExp(this.options.pattern);
|
|
4960
5022
|
}
|
|
@@ -4995,7 +5057,7 @@ class ElementName extends Rule {
|
|
|
4995
5057
|
...context.blacklist.map((cur) => `- ${cur}`),
|
|
4996
5058
|
];
|
|
4997
5059
|
}
|
|
4998
|
-
if (context.pattern !== defaults$
|
|
5060
|
+
if (context.pattern !== defaults$i.pattern) {
|
|
4999
5061
|
return [
|
|
5000
5062
|
`<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
|
|
5001
5063
|
"",
|
|
@@ -5397,7 +5459,7 @@ class EmptyTitle extends Rule {
|
|
|
5397
5459
|
}
|
|
5398
5460
|
}
|
|
5399
5461
|
|
|
5400
|
-
const defaults$
|
|
5462
|
+
const defaults$h = {
|
|
5401
5463
|
allowMultipleH1: false,
|
|
5402
5464
|
minInitialRank: "h1",
|
|
5403
5465
|
sectioningRoots: ["dialog", '[role="dialog"]'],
|
|
@@ -5428,7 +5490,7 @@ function parseMaxInitial(value) {
|
|
|
5428
5490
|
}
|
|
5429
5491
|
class HeadingLevel extends Rule {
|
|
5430
5492
|
constructor(options) {
|
|
5431
|
-
super({ ...defaults$
|
|
5493
|
+
super({ ...defaults$h, ...options });
|
|
5432
5494
|
this.stack = [];
|
|
5433
5495
|
this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
|
|
5434
5496
|
this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
|
|
@@ -5586,12 +5648,12 @@ class HeadingLevel extends Rule {
|
|
|
5586
5648
|
}
|
|
5587
5649
|
}
|
|
5588
5650
|
|
|
5589
|
-
const defaults$
|
|
5651
|
+
const defaults$g = {
|
|
5590
5652
|
pattern: "kebabcase",
|
|
5591
5653
|
};
|
|
5592
5654
|
class IdPattern extends Rule {
|
|
5593
5655
|
constructor(options) {
|
|
5594
|
-
super({ ...defaults$
|
|
5656
|
+
super({ ...defaults$g, ...options });
|
|
5595
5657
|
this.pattern = parsePattern(this.options.pattern);
|
|
5596
5658
|
}
|
|
5597
5659
|
static schema() {
|
|
@@ -5942,12 +6004,12 @@ function findLabelByParent(el) {
|
|
|
5942
6004
|
return [];
|
|
5943
6005
|
}
|
|
5944
6006
|
|
|
5945
|
-
const defaults$
|
|
6007
|
+
const defaults$f = {
|
|
5946
6008
|
maxlength: 70,
|
|
5947
6009
|
};
|
|
5948
6010
|
class LongTitle extends Rule {
|
|
5949
6011
|
constructor(options) {
|
|
5950
|
-
super({ ...defaults$
|
|
6012
|
+
super({ ...defaults$f, ...options });
|
|
5951
6013
|
this.maxlength = this.options.maxlength;
|
|
5952
6014
|
}
|
|
5953
6015
|
static schema() {
|
|
@@ -6094,13 +6156,13 @@ class MultipleLabeledControls extends Rule {
|
|
|
6094
6156
|
}
|
|
6095
6157
|
}
|
|
6096
6158
|
|
|
6097
|
-
const defaults$
|
|
6159
|
+
const defaults$e = {
|
|
6098
6160
|
include: null,
|
|
6099
6161
|
exclude: null,
|
|
6100
6162
|
};
|
|
6101
6163
|
class NoAutoplay extends Rule {
|
|
6102
6164
|
constructor(options) {
|
|
6103
|
-
super({ ...defaults$
|
|
6165
|
+
super({ ...defaults$e, ...options });
|
|
6104
6166
|
}
|
|
6105
6167
|
documentation(context) {
|
|
6106
6168
|
const tagName = context ? ` on <${context.tagName}>` : "";
|
|
@@ -6341,14 +6403,14 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
|
|
|
6341
6403
|
}
|
|
6342
6404
|
}
|
|
6343
6405
|
|
|
6344
|
-
const defaults$
|
|
6406
|
+
const defaults$d = {
|
|
6345
6407
|
include: null,
|
|
6346
6408
|
exclude: null,
|
|
6347
6409
|
allowedProperties: ["display"],
|
|
6348
6410
|
};
|
|
6349
6411
|
class NoInlineStyle extends Rule {
|
|
6350
6412
|
constructor(options) {
|
|
6351
|
-
super({ ...defaults$
|
|
6413
|
+
super({ ...defaults$d, ...options });
|
|
6352
6414
|
}
|
|
6353
6415
|
static schema() {
|
|
6354
6416
|
return {
|
|
@@ -6550,7 +6612,7 @@ class NoMultipleMain extends Rule {
|
|
|
6550
6612
|
}
|
|
6551
6613
|
}
|
|
6552
6614
|
|
|
6553
|
-
const defaults$
|
|
6615
|
+
const defaults$c = {
|
|
6554
6616
|
relaxed: false,
|
|
6555
6617
|
};
|
|
6556
6618
|
const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
|
|
@@ -6567,7 +6629,7 @@ const replacementTable = {
|
|
|
6567
6629
|
};
|
|
6568
6630
|
class NoRawCharacters extends Rule {
|
|
6569
6631
|
constructor(options) {
|
|
6570
|
-
super({ ...defaults$
|
|
6632
|
+
super({ ...defaults$c, ...options });
|
|
6571
6633
|
this.relaxed = this.options.relaxed;
|
|
6572
6634
|
}
|
|
6573
6635
|
static schema() {
|
|
@@ -6745,13 +6807,13 @@ class NoRedundantRole extends Rule {
|
|
|
6745
6807
|
}
|
|
6746
6808
|
|
|
6747
6809
|
const xmlns = /^(.+):.+$/;
|
|
6748
|
-
const defaults$
|
|
6810
|
+
const defaults$b = {
|
|
6749
6811
|
ignoreForeign: true,
|
|
6750
6812
|
ignoreXML: true,
|
|
6751
6813
|
};
|
|
6752
6814
|
class NoSelfClosing extends Rule {
|
|
6753
6815
|
constructor(options) {
|
|
6754
|
-
super({ ...defaults$
|
|
6816
|
+
super({ ...defaults$b, ...options });
|
|
6755
6817
|
}
|
|
6756
6818
|
static schema() {
|
|
6757
6819
|
return {
|
|
@@ -6884,13 +6946,13 @@ const replacement = {
|
|
|
6884
6946
|
reset: '<button type="reset">',
|
|
6885
6947
|
image: '<button type="button">',
|
|
6886
6948
|
};
|
|
6887
|
-
const defaults$
|
|
6949
|
+
const defaults$a = {
|
|
6888
6950
|
include: null,
|
|
6889
6951
|
exclude: null,
|
|
6890
6952
|
};
|
|
6891
6953
|
class PreferButton extends Rule {
|
|
6892
6954
|
constructor(options) {
|
|
6893
|
-
super({ ...defaults$
|
|
6955
|
+
super({ ...defaults$a, ...options });
|
|
6894
6956
|
}
|
|
6895
6957
|
static schema() {
|
|
6896
6958
|
return {
|
|
@@ -6965,7 +7027,7 @@ class PreferButton extends Rule {
|
|
|
6965
7027
|
}
|
|
6966
7028
|
}
|
|
6967
7029
|
|
|
6968
|
-
const defaults$
|
|
7030
|
+
const defaults$9 = {
|
|
6969
7031
|
mapping: {
|
|
6970
7032
|
article: "article",
|
|
6971
7033
|
banner: "header",
|
|
@@ -6995,7 +7057,7 @@ const defaults$8 = {
|
|
|
6995
7057
|
};
|
|
6996
7058
|
class PreferNativeElement extends Rule {
|
|
6997
7059
|
constructor(options) {
|
|
6998
|
-
super({ ...defaults$
|
|
7060
|
+
super({ ...defaults$9, ...options });
|
|
6999
7061
|
}
|
|
7000
7062
|
static schema() {
|
|
7001
7063
|
return {
|
|
@@ -7115,8 +7177,66 @@ class PreferTbody extends Rule {
|
|
|
7115
7177
|
}
|
|
7116
7178
|
}
|
|
7117
7179
|
|
|
7180
|
+
const defaults$8 = {
|
|
7181
|
+
tags: ["script", "style"],
|
|
7182
|
+
};
|
|
7183
|
+
class RequireCSPNonce extends Rule {
|
|
7184
|
+
constructor(options) {
|
|
7185
|
+
super({ ...defaults$8, ...options });
|
|
7186
|
+
}
|
|
7187
|
+
static schema() {
|
|
7188
|
+
return {
|
|
7189
|
+
tags: {
|
|
7190
|
+
type: "array",
|
|
7191
|
+
items: {
|
|
7192
|
+
enum: ["script", "style"],
|
|
7193
|
+
type: "string",
|
|
7194
|
+
},
|
|
7195
|
+
},
|
|
7196
|
+
};
|
|
7197
|
+
}
|
|
7198
|
+
documentation() {
|
|
7199
|
+
return {
|
|
7200
|
+
description: [
|
|
7201
|
+
"Required Content-Security-Policy (CSP) nonce is missing or empty.",
|
|
7202
|
+
"",
|
|
7203
|
+
"This is set by the `nonce` attribute and must match the `Content-Security-Policy` header.",
|
|
7204
|
+
"For instance, if the header contains `script-src 'nonce-r4nd0m'` the `nonce` attribute must be set to `nonce=\"r4nd0m\">`",
|
|
7205
|
+
"",
|
|
7206
|
+
"The nonce should be unique per each request and set to a cryptography secure random token.",
|
|
7207
|
+
"It is used to prevent cross site scripting (XSS) by preventing malicious actors from injecting scripts onto the page.",
|
|
7208
|
+
].join("\n"),
|
|
7209
|
+
url: ruleDocumentationUrl("@/rules/require-csp-nonce.ts"),
|
|
7210
|
+
};
|
|
7211
|
+
}
|
|
7212
|
+
setup() {
|
|
7213
|
+
this.on("tag:end", (event) => {
|
|
7214
|
+
var _a;
|
|
7215
|
+
const { tags } = this.options;
|
|
7216
|
+
const node = event.previous;
|
|
7217
|
+
/* ignore other tags */
|
|
7218
|
+
if (!node || !tags.includes(node.tagName)) {
|
|
7219
|
+
return;
|
|
7220
|
+
}
|
|
7221
|
+
/* ignore if nonce is set to non-empty value (or dynamic) */
|
|
7222
|
+
const nonce = (_a = node.getAttribute("nonce")) === null || _a === void 0 ? void 0 : _a.value;
|
|
7223
|
+
if (nonce && nonce !== "") {
|
|
7224
|
+
return;
|
|
7225
|
+
}
|
|
7226
|
+
/* ignore <script src> */
|
|
7227
|
+
if (node.is("script") && node.hasAttribute("src")) {
|
|
7228
|
+
return;
|
|
7229
|
+
}
|
|
7230
|
+
const message = `required CSP nonce is missing`;
|
|
7231
|
+
this.report(node, message, node.location);
|
|
7232
|
+
});
|
|
7233
|
+
}
|
|
7234
|
+
}
|
|
7235
|
+
|
|
7118
7236
|
const defaults$7 = {
|
|
7119
7237
|
target: "all",
|
|
7238
|
+
include: null,
|
|
7239
|
+
exclude: null,
|
|
7120
7240
|
};
|
|
7121
7241
|
const crossorigin = new RegExp("^(\\w+://|//)"); /* e.g. https:// or // */
|
|
7122
7242
|
const supportSri = {
|
|
@@ -7134,6 +7254,32 @@ class RequireSri extends Rule {
|
|
|
7134
7254
|
enum: ["all", "crossorigin"],
|
|
7135
7255
|
type: "string",
|
|
7136
7256
|
},
|
|
7257
|
+
include: {
|
|
7258
|
+
anyOf: [
|
|
7259
|
+
{
|
|
7260
|
+
items: {
|
|
7261
|
+
type: "string",
|
|
7262
|
+
},
|
|
7263
|
+
type: "array",
|
|
7264
|
+
},
|
|
7265
|
+
{
|
|
7266
|
+
type: "null",
|
|
7267
|
+
},
|
|
7268
|
+
],
|
|
7269
|
+
},
|
|
7270
|
+
exclude: {
|
|
7271
|
+
anyOf: [
|
|
7272
|
+
{
|
|
7273
|
+
items: {
|
|
7274
|
+
type: "string",
|
|
7275
|
+
},
|
|
7276
|
+
type: "array",
|
|
7277
|
+
},
|
|
7278
|
+
{
|
|
7279
|
+
type: "null",
|
|
7280
|
+
},
|
|
7281
|
+
],
|
|
7282
|
+
},
|
|
7137
7283
|
};
|
|
7138
7284
|
}
|
|
7139
7285
|
documentation() {
|
|
@@ -7146,11 +7292,13 @@ class RequireSri extends Rule {
|
|
|
7146
7292
|
this.on("tag:end", (event) => {
|
|
7147
7293
|
/* only handle thats supporting and requires sri */
|
|
7148
7294
|
const node = event.previous;
|
|
7149
|
-
if (!(this.supportSri(node) && this.needSri(node)))
|
|
7295
|
+
if (!(this.supportSri(node) && this.needSri(node))) {
|
|
7150
7296
|
return;
|
|
7297
|
+
}
|
|
7151
7298
|
/* check if sri attribute is present */
|
|
7152
|
-
if (node.hasAttribute("integrity"))
|
|
7299
|
+
if (node.hasAttribute("integrity")) {
|
|
7153
7300
|
return;
|
|
7301
|
+
}
|
|
7154
7302
|
this.report(node, `SRI "integrity" attribute is required on <${node.tagName}> element`, node.location);
|
|
7155
7303
|
});
|
|
7156
7304
|
}
|
|
@@ -7158,19 +7306,25 @@ class RequireSri extends Rule {
|
|
|
7158
7306
|
return Object.keys(supportSri).includes(node.tagName);
|
|
7159
7307
|
}
|
|
7160
7308
|
needSri(node) {
|
|
7161
|
-
if (this.target === "all")
|
|
7162
|
-
return true;
|
|
7163
7309
|
const attr = this.elementSourceAttr(node);
|
|
7164
|
-
if (!attr || attr.value === null || attr.isDynamic) {
|
|
7310
|
+
if (!attr || attr.value === null || attr.value === "" || attr.isDynamic) {
|
|
7165
7311
|
return false;
|
|
7166
7312
|
}
|
|
7167
7313
|
const url = attr.value.toString();
|
|
7168
|
-
|
|
7314
|
+
if (this.target === "all" || crossorigin.test(url)) {
|
|
7315
|
+
return !this.isIgnored(url);
|
|
7316
|
+
}
|
|
7317
|
+
return false;
|
|
7169
7318
|
}
|
|
7170
7319
|
elementSourceAttr(node) {
|
|
7171
7320
|
const key = supportSri[node.tagName];
|
|
7172
7321
|
return node.getAttribute(key);
|
|
7173
7322
|
}
|
|
7323
|
+
isIgnored(url) {
|
|
7324
|
+
return this.isKeywordIgnored(url, (list, it) => {
|
|
7325
|
+
return list.some((pattern) => it.includes(pattern));
|
|
7326
|
+
});
|
|
7327
|
+
}
|
|
7174
7328
|
}
|
|
7175
7329
|
|
|
7176
7330
|
class ScriptElement extends Rule {
|
|
@@ -9968,6 +10122,7 @@ const bundledRules = {
|
|
|
9968
10122
|
"prefer-button": PreferButton,
|
|
9969
10123
|
"prefer-native-element": PreferNativeElement,
|
|
9970
10124
|
"prefer-tbody": PreferTbody,
|
|
10125
|
+
"require-csp-nonce": RequireCSPNonce,
|
|
9971
10126
|
"require-sri": RequireSri,
|
|
9972
10127
|
"script-element": ScriptElement,
|
|
9973
10128
|
"script-type": ScriptType,
|