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/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
- return value === entry;
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 = "6.11.1";
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.includes(keyword)) {
3259
+ if (include && !matcher(include, keyword)) {
3255
3260
  return true;
3256
3261
  }
3257
3262
  /* ignore keyword if present in "excludes" */
3258
- if (exclude && exclude.includes(keyword)) {
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$r = {
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$r, ...options });
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$q = {
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$q, ...options });
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$p = {
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$p, ...options });
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$o = {
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$o, ...options });
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
- if (this.options.style === "auto") {
4216
- return {
4217
- description: `Attribute values are required to be quoted with doublequotes unless the attribute value itself contains doublequotes in which case singlequotes should be used.`,
4218
- url: ruleDocumentationUrl("@/rules/attr-quotes.ts"),
4219
- };
4220
- }
4221
- else {
4222
- return {
4223
- description: `Attribute values are required to be quoted with ${this.options.style}quotes.`,
4224
- url: ruleDocumentationUrl("@/rules/attr-quotes.ts"),
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
- this.report(event.target, `Attribute "${event.key}" using unquoted value`);
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
- this.report(event.target, `Attribute "${event.key}" used ${event.quote} instead of expected ${expected}`);
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$n = {
4427
+ const defaults$o = {
4366
4428
  style: "omit",
4367
4429
  };
4368
4430
  class AttributeBooleanStyle extends Rule {
4369
4431
  constructor(options) {
4370
- super({ ...defaults$n, ...options });
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$m = {
4508
+ const defaults$n = {
4447
4509
  style: "omit",
4448
4510
  };
4449
4511
  class AttributeEmptyStyle extends Rule {
4450
4512
  constructor(options) {
4451
- super({ ...defaults$m, ...options });
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$l = {
4621
+ const defaults$m = {
4560
4622
  pattern: "kebabcase",
4561
4623
  };
4562
4624
  class ClassPattern extends Rule {
4563
4625
  constructor(options) {
4564
- super({ ...defaults$l, ...options });
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$k = {
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$k, ...options });
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$j = {
4904
+ const defaults$k = {
4843
4905
  style: "uppercase",
4844
4906
  };
4845
4907
  class DoctypeStyle extends Rule {
4846
4908
  constructor(options) {
4847
- super({ ...defaults$j, ...options });
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$i = {
4941
+ const defaults$j = {
4880
4942
  style: "lowercase",
4881
4943
  };
4882
4944
  class ElementCase extends Rule {
4883
4945
  constructor(options) {
4884
- super({ ...defaults$i, ...options });
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$h = {
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$h, ...options });
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$h.pattern) {
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$g = {
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$g, ...options });
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$f = {
5651
+ const defaults$g = {
5590
5652
  pattern: "kebabcase",
5591
5653
  };
5592
5654
  class IdPattern extends Rule {
5593
5655
  constructor(options) {
5594
- super({ ...defaults$f, ...options });
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$e = {
6007
+ const defaults$f = {
5946
6008
  maxlength: 70,
5947
6009
  };
5948
6010
  class LongTitle extends Rule {
5949
6011
  constructor(options) {
5950
- super({ ...defaults$e, ...options });
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$d = {
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$d, ...options });
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$c = {
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$c, ...options });
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$b = {
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$b, ...options });
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$a = {
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$a, ...options });
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$9 = {
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$9, ...options });
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$8 = {
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$8, ...options });
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
- return crossorigin.test(url);
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,