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