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/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.0.0";
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.includes(keyword)) {
3256
+ if (include && !matcher(include, keyword)) {
3255
3257
  return true;
3256
3258
  }
3257
3259
  /* ignore keyword if present in "excludes" */
3258
- if (exclude && exclude.includes(keyword)) {
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$r = {
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$r, ...options });
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$q = {
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$q, ...options });
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$p = {
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$p, ...options });
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$o = {
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$o, ...options });
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
- 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
- }
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
- this.report(event.target, `Attribute "${event.key}" using unquoted value`);
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
- this.report(event.target, `Attribute "${event.key}" used ${event.quote} instead of expected ${expected}`);
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$n = {
4424
+ const defaults$o = {
4366
4425
  style: "omit",
4367
4426
  };
4368
4427
  class AttributeBooleanStyle extends Rule {
4369
4428
  constructor(options) {
4370
- super({ ...defaults$n, ...options });
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$m = {
4505
+ const defaults$n = {
4447
4506
  style: "omit",
4448
4507
  };
4449
4508
  class AttributeEmptyStyle extends Rule {
4450
4509
  constructor(options) {
4451
- super({ ...defaults$m, ...options });
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$l = {
4618
+ const defaults$m = {
4560
4619
  pattern: "kebabcase",
4561
4620
  };
4562
4621
  class ClassPattern extends Rule {
4563
4622
  constructor(options) {
4564
- super({ ...defaults$l, ...options });
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$k = {
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$k, ...options });
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$j = {
4901
+ const defaults$k = {
4843
4902
  style: "uppercase",
4844
4903
  };
4845
4904
  class DoctypeStyle extends Rule {
4846
4905
  constructor(options) {
4847
- super({ ...defaults$j, ...options });
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$i = {
4938
+ const defaults$j = {
4880
4939
  style: "lowercase",
4881
4940
  };
4882
4941
  class ElementCase extends Rule {
4883
4942
  constructor(options) {
4884
- super({ ...defaults$i, ...options });
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$h = {
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$h, ...options });
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$h.pattern) {
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$g = {
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$g, ...options });
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$f = {
5648
+ const defaults$g = {
5590
5649
  pattern: "kebabcase",
5591
5650
  };
5592
5651
  class IdPattern extends Rule {
5593
5652
  constructor(options) {
5594
- super({ ...defaults$f, ...options });
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$e = {
6004
+ const defaults$f = {
5946
6005
  maxlength: 70,
5947
6006
  };
5948
6007
  class LongTitle extends Rule {
5949
6008
  constructor(options) {
5950
- super({ ...defaults$e, ...options });
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$d = {
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$d, ...options });
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$c = {
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$c, ...options });
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$b = {
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$b, ...options });
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$a = {
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$a, ...options });
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$9 = {
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$9, ...options });
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$8 = {
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$8, ...options });
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
- return crossorigin.test(url);
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,