html-validate 7.0.0 → 7.1.2

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.js CHANGED
@@ -1763,7 +1763,7 @@ function stripslashes(value) {
1763
1763
  return value.replace(/\\(.)/g, "$1");
1764
1764
  }
1765
1765
  function escapeSelectorComponent(text) {
1766
- return text.toString().replace(/([:[\] ])/g, "\\$1");
1766
+ return text.toString().replace(/([^a-z0-9_-])/gi, "\\$1");
1767
1767
  }
1768
1768
  class Matcher {
1769
1769
  }
@@ -2102,6 +2102,7 @@ class HtmlElement extends DOMNode {
2102
2102
  }
2103
2103
  const parts = [];
2104
2104
  let root;
2105
+ /* eslint-disable-next-line @typescript-eslint/no-this-alias */
2105
2106
  for (root = this; root.parent; root = root.parent) {
2106
2107
  /* .. */
2107
2108
  }
@@ -2645,12 +2646,15 @@ class Validator {
2645
2646
  if (value === null || value === undefined) {
2646
2647
  return false;
2647
2648
  }
2649
+ const caseInsensitiveValue = value.toLowerCase();
2648
2650
  return rule.enum.some((entry) => {
2649
2651
  if (entry instanceof RegExp) {
2652
+ /* regular expressions are matched case-sensitive */
2650
2653
  return !!value.match(entry);
2651
2654
  }
2652
2655
  else {
2653
- return value === entry;
2656
+ /* strings matched case-insensitive */
2657
+ return caseInsensitiveValue === entry;
2654
2658
  }
2655
2659
  });
2656
2660
  }
@@ -3113,7 +3117,7 @@ var TRANSFORMER_API;
3113
3117
  /** @public */
3114
3118
  const name = "html-validate";
3115
3119
  /** @public */
3116
- const version = "7.0.0";
3120
+ const version = "7.1.2";
3117
3121
  /** @public */
3118
3122
  const homepage = "https://html-validate.org";
3119
3123
  /** @public */
@@ -3276,17 +3280,18 @@ class Rule {
3276
3280
  * not `"foo"`.
3277
3281
  *
3278
3282
  * @param keyword - Keyword to match against `include` and `exclude` options.
3283
+ * @param matcher - Optional function to compare items with.
3279
3284
  * @returns `true` if keyword is not present in `include` or is present in
3280
3285
  * `exclude`.
3281
3286
  */
3282
- isKeywordIgnored(keyword) {
3287
+ isKeywordIgnored(keyword, matcher = (list, it) => list.includes(it)) {
3283
3288
  const { include, exclude } = this.options;
3284
3289
  /* ignore keyword if not present in "include" */
3285
- if (include && !include.includes(keyword)) {
3290
+ if (include && !matcher(include, keyword)) {
3286
3291
  return true;
3287
3292
  }
3288
3293
  /* ignore keyword if present in "excludes" */
3289
- if (exclude && exclude.includes(keyword)) {
3294
+ if (exclude && matcher(exclude, keyword)) {
3290
3295
  return true;
3291
3296
  }
3292
3297
  return false;
@@ -3434,7 +3439,7 @@ function ruleDocumentationUrl(filename) {
3434
3439
  return `${homepage}/rules/${normalized}.html`;
3435
3440
  }
3436
3441
 
3437
- const defaults$r = {
3442
+ const defaults$s = {
3438
3443
  allowExternal: true,
3439
3444
  allowRelative: true,
3440
3445
  allowAbsolute: true,
@@ -3447,11 +3452,11 @@ const mapping$1 = {
3447
3452
  script: "src",
3448
3453
  };
3449
3454
  const description = {
3450
- ["external" /* EXTERNAL */]: "External links are not allowed by current configuration.",
3451
- ["relative-base" /* RELATIVE_BASE */]: "Links relative to <base> are not allowed by current configuration.",
3452
- ["relative-path" /* RELATIVE_PATH */]: "Relative links are not allowed by current configuration.",
3453
- ["absolute" /* ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
3454
- ["anchor" /* ANCHOR */]: null,
3455
+ ["external" /* Style.EXTERNAL */]: "External links are not allowed by current configuration.",
3456
+ ["relative-base" /* Style.RELATIVE_BASE */]: "Links relative to <base> are not allowed by current configuration.",
3457
+ ["relative-path" /* Style.RELATIVE_PATH */]: "Relative links are not allowed by current configuration.",
3458
+ ["absolute" /* Style.ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
3459
+ ["anchor" /* Style.ANCHOR */]: null,
3455
3460
  };
3456
3461
  function parseAllow(value) {
3457
3462
  if (typeof value === "boolean") {
@@ -3478,7 +3483,7 @@ function matchList(value, list) {
3478
3483
  }
3479
3484
  class AllowedLinks extends Rule {
3480
3485
  constructor(options) {
3481
- super({ ...defaults$r, ...options });
3486
+ super({ ...defaults$s, ...options });
3482
3487
  this.allowExternal = parseAllow(this.options.allowExternal);
3483
3488
  this.allowRelative = parseAllow(this.options.allowRelative);
3484
3489
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3524,19 +3529,19 @@ class AllowedLinks extends Rule {
3524
3529
  const link = event.value.toString();
3525
3530
  const style = this.getStyle(link);
3526
3531
  switch (style) {
3527
- case "anchor" /* ANCHOR */:
3532
+ case "anchor" /* Style.ANCHOR */:
3528
3533
  /* anchor links are always allowed by this rule */
3529
3534
  break;
3530
- case "absolute" /* ABSOLUTE */:
3535
+ case "absolute" /* Style.ABSOLUTE */:
3531
3536
  this.handleAbsolute(link, event, style);
3532
3537
  break;
3533
- case "external" /* EXTERNAL */:
3538
+ case "external" /* Style.EXTERNAL */:
3534
3539
  this.handleExternal(link, event, style);
3535
3540
  break;
3536
- case "relative-base" /* RELATIVE_BASE */:
3541
+ case "relative-base" /* Style.RELATIVE_BASE */:
3537
3542
  this.handleRelativeBase(link, event, style);
3538
3543
  break;
3539
- case "relative-path" /* RELATIVE_PATH */:
3544
+ case "relative-path" /* Style.RELATIVE_PATH */:
3540
3545
  this.handleRelativePath(link, event, style);
3541
3546
  break;
3542
3547
  }
@@ -3554,21 +3559,21 @@ class AllowedLinks extends Rule {
3554
3559
  getStyle(value) {
3555
3560
  /* http://example.net or //example.net */
3556
3561
  if (value.match(/^([a-z]+:)?\/\//g)) {
3557
- return "external" /* EXTERNAL */;
3562
+ return "external" /* Style.EXTERNAL */;
3558
3563
  }
3559
3564
  switch (value[0]) {
3560
3565
  /* /foo/bar */
3561
3566
  case "/":
3562
- return "absolute" /* ABSOLUTE */;
3567
+ return "absolute" /* Style.ABSOLUTE */;
3563
3568
  /* ../foo/bar */
3564
3569
  case ".":
3565
- return "relative-path" /* RELATIVE_PATH */;
3570
+ return "relative-path" /* Style.RELATIVE_PATH */;
3566
3571
  /* #foo */
3567
3572
  case "#":
3568
- return "anchor" /* ANCHOR */;
3573
+ return "anchor" /* Style.ANCHOR */;
3569
3574
  /* foo/bar */
3570
3575
  default:
3571
- return "relative-base" /* RELATIVE_BASE */;
3576
+ return "relative-base" /* Style.RELATIVE_BASE */;
3572
3577
  }
3573
3578
  }
3574
3579
  handleAbsolute(target, event, style) {
@@ -3779,13 +3784,13 @@ class CaseStyle {
3779
3784
  }
3780
3785
  }
3781
3786
 
3782
- const defaults$q = {
3787
+ const defaults$r = {
3783
3788
  style: "lowercase",
3784
3789
  ignoreForeign: true,
3785
3790
  };
3786
3791
  class AttrCase extends Rule {
3787
3792
  constructor(options) {
3788
- super({ ...defaults$q, ...options });
3793
+ super({ ...defaults$r, ...options });
3789
3794
  this.style = new CaseStyle(this.options.style, "attr-case");
3790
3795
  }
3791
3796
  static schema() {
@@ -4125,7 +4130,7 @@ class AttrDelimiter extends Rule {
4125
4130
  }
4126
4131
 
4127
4132
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4128
- const defaults$p = {
4133
+ const defaults$q = {
4129
4134
  pattern: DEFAULT_PATTERN,
4130
4135
  ignoreForeign: true,
4131
4136
  };
@@ -4162,7 +4167,7 @@ function generateDescription(name, pattern) {
4162
4167
  }
4163
4168
  class AttrPattern extends Rule {
4164
4169
  constructor(options) {
4165
- super({ ...defaults$p, ...options });
4170
+ super({ ...defaults$q, ...options });
4166
4171
  this.pattern = generateRegexp(this.options.pattern);
4167
4172
  }
4168
4173
  static schema() {
@@ -4221,20 +4226,56 @@ var QuoteStyle;
4221
4226
  QuoteStyle["SINGLE_QUOTE"] = "'";
4222
4227
  QuoteStyle["DOUBLE_QUOTE"] = "\"";
4223
4228
  QuoteStyle["AUTO_QUOTE"] = "auto";
4229
+ QuoteStyle["ANY_QUOTE"] = "any";
4224
4230
  })(QuoteStyle || (QuoteStyle = {}));
4225
- const defaults$o = {
4231
+ const defaults$p = {
4226
4232
  style: "auto",
4227
4233
  unquoted: false,
4228
4234
  };
4235
+ function describeError(context) {
4236
+ if (context) {
4237
+ switch (context.error) {
4238
+ case "style":
4239
+ return `Attribute \`${context.attr}\` must use \`${context.expected}\` instead of \`${context.actual}\`.`;
4240
+ case "unquoted":
4241
+ return `Attribute \`${context.attr}\` must not be unquoted.`;
4242
+ }
4243
+ }
4244
+ else {
4245
+ return "This attribute is not quoted properly.";
4246
+ }
4247
+ }
4248
+ function describeStyle(style, unquoted) {
4249
+ const description = [];
4250
+ switch (style) {
4251
+ case QuoteStyle.AUTO_QUOTE:
4252
+ description.push("- quoted with double quotes `\"` unless the value contains double quotes in which case single quotes `'` should be used instead");
4253
+ break;
4254
+ case QuoteStyle.ANY_QUOTE:
4255
+ description.push("- quoted with single quotes `'`");
4256
+ description.push('- quoted with double quotes `"`');
4257
+ break;
4258
+ case QuoteStyle.SINGLE_QUOTE:
4259
+ case QuoteStyle.DOUBLE_QUOTE: {
4260
+ const name = style === QuoteStyle.SINGLE_QUOTE ? "single" : "double";
4261
+ description.push(`- quoted with ${name} quotes \`${style}\``);
4262
+ break;
4263
+ }
4264
+ }
4265
+ if (unquoted) {
4266
+ description.push("- unquoted (if applicable)");
4267
+ }
4268
+ return `${description.join(" or\n")}\n`;
4269
+ }
4229
4270
  class AttrQuotes extends Rule {
4230
4271
  constructor(options) {
4231
- super({ ...defaults$o, ...options });
4272
+ super({ ...defaults$p, ...options });
4232
4273
  this.style = parseStyle$4(this.options.style);
4233
4274
  }
4234
4275
  static schema() {
4235
4276
  return {
4236
4277
  style: {
4237
- enum: ["auto", "double", "single"],
4278
+ enum: ["auto", "double", "single", "any"],
4238
4279
  type: "string",
4239
4280
  },
4240
4281
  unquoted: {
@@ -4242,19 +4283,20 @@ class AttrQuotes extends Rule {
4242
4283
  },
4243
4284
  };
4244
4285
  }
4245
- documentation() {
4246
- if (this.options.style === "auto") {
4247
- return {
4248
- description: `Attribute values are required to be quoted with doublequotes unless the attribute value itself contains doublequotes in which case singlequotes should be used.`,
4249
- url: ruleDocumentationUrl("@/rules/attr-quotes.ts"),
4250
- };
4251
- }
4252
- else {
4253
- return {
4254
- description: `Attribute values are required to be quoted with ${this.options.style}quotes.`,
4255
- url: ruleDocumentationUrl("@/rules/attr-quotes.ts"),
4256
- };
4257
- }
4286
+ documentation(context) {
4287
+ const { style } = this;
4288
+ const { unquoted } = this.options;
4289
+ const description = [
4290
+ describeError(context),
4291
+ "",
4292
+ "Under the current configuration attributes must be:",
4293
+ "",
4294
+ describeStyle(style, unquoted),
4295
+ ];
4296
+ return {
4297
+ description: description.join("\n"),
4298
+ url: ruleDocumentationUrl("@/rules/attr-quotes.ts"),
4299
+ };
4258
4300
  }
4259
4301
  setup() {
4260
4302
  this.on("attr", (event) => {
@@ -4264,13 +4306,31 @@ class AttrQuotes extends Rule {
4264
4306
  }
4265
4307
  if (!event.quote) {
4266
4308
  if (this.options.unquoted === false) {
4267
- this.report(event.target, `Attribute "${event.key}" using unquoted value`);
4309
+ const message = `Attribute "${event.key}" using unquoted value`;
4310
+ const context = {
4311
+ error: "unquoted",
4312
+ attr: event.key,
4313
+ };
4314
+ this.report(event.target, message, null, context);
4268
4315
  }
4269
4316
  return;
4270
4317
  }
4318
+ /* if the style is set to any we skip the rest of the rule as the only
4319
+ * thing that matters is if the "unquoted" options triggers an error or
4320
+ * not */
4321
+ if (this.style === QuoteStyle.ANY_QUOTE) {
4322
+ return;
4323
+ }
4271
4324
  const expected = this.resolveQuotemark(event.value.toString(), this.style);
4272
4325
  if (event.quote !== expected) {
4273
- this.report(event.target, `Attribute "${event.key}" used ${event.quote} instead of expected ${expected}`);
4326
+ const message = `Attribute "${event.key}" used ${event.quote} instead of expected ${expected}`;
4327
+ const context = {
4328
+ error: "style",
4329
+ attr: event.key,
4330
+ actual: event.quote,
4331
+ expected,
4332
+ };
4333
+ this.report(event.target, message, null, context);
4274
4334
  }
4275
4335
  });
4276
4336
  }
@@ -4291,6 +4351,8 @@ function parseStyle$4(style) {
4291
4351
  return QuoteStyle.DOUBLE_QUOTE;
4292
4352
  case "single":
4293
4353
  return QuoteStyle.SINGLE_QUOTE;
4354
+ case "any":
4355
+ return QuoteStyle.ANY_QUOTE;
4294
4356
  /* istanbul ignore next: covered by schema validation */
4295
4357
  default:
4296
4358
  throw new ConfigError(`Invalid style "${style}" for "attr-quotes" rule`);
@@ -4393,12 +4455,12 @@ class AttributeAllowedValues extends Rule {
4393
4455
  }
4394
4456
  }
4395
4457
 
4396
- const defaults$n = {
4458
+ const defaults$o = {
4397
4459
  style: "omit",
4398
4460
  };
4399
4461
  class AttributeBooleanStyle extends Rule {
4400
4462
  constructor(options) {
4401
- super({ ...defaults$n, ...options });
4463
+ super({ ...defaults$o, ...options });
4402
4464
  this.hasInvalidStyle = parseStyle$3(this.options.style);
4403
4465
  }
4404
4466
  static schema() {
@@ -4474,12 +4536,12 @@ function reportMessage$1(attr, style) {
4474
4536
  return "";
4475
4537
  }
4476
4538
 
4477
- const defaults$m = {
4539
+ const defaults$n = {
4478
4540
  style: "omit",
4479
4541
  };
4480
4542
  class AttributeEmptyStyle extends Rule {
4481
4543
  constructor(options) {
4482
- super({ ...defaults$m, ...options });
4544
+ super({ ...defaults$n, ...options });
4483
4545
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4484
4546
  }
4485
4547
  static schema() {
@@ -4587,12 +4649,12 @@ function describePattern(pattern) {
4587
4649
  }
4588
4650
  }
4589
4651
 
4590
- const defaults$l = {
4652
+ const defaults$m = {
4591
4653
  pattern: "kebabcase",
4592
4654
  };
4593
4655
  class ClassPattern extends Rule {
4594
4656
  constructor(options) {
4595
- super({ ...defaults$l, ...options });
4657
+ super({ ...defaults$m, ...options });
4596
4658
  this.pattern = parsePattern(this.options.pattern);
4597
4659
  }
4598
4660
  static schema() {
@@ -4701,13 +4763,13 @@ class CloseOrder extends Rule {
4701
4763
  }
4702
4764
  }
4703
4765
 
4704
- const defaults$k = {
4766
+ const defaults$l = {
4705
4767
  include: null,
4706
4768
  exclude: null,
4707
4769
  };
4708
4770
  class Deprecated extends Rule {
4709
4771
  constructor(options) {
4710
- super({ ...defaults$k, ...options });
4772
+ super({ ...defaults$l, ...options });
4711
4773
  }
4712
4774
  static schema() {
4713
4775
  return {
@@ -4870,12 +4932,12 @@ class NoStyleTag$1 extends Rule {
4870
4932
  }
4871
4933
  }
4872
4934
 
4873
- const defaults$j = {
4935
+ const defaults$k = {
4874
4936
  style: "uppercase",
4875
4937
  };
4876
4938
  class DoctypeStyle extends Rule {
4877
4939
  constructor(options) {
4878
- super({ ...defaults$j, ...options });
4940
+ super({ ...defaults$k, ...options });
4879
4941
  }
4880
4942
  static schema() {
4881
4943
  return {
@@ -4907,12 +4969,12 @@ class DoctypeStyle extends Rule {
4907
4969
  }
4908
4970
  }
4909
4971
 
4910
- const defaults$i = {
4972
+ const defaults$j = {
4911
4973
  style: "lowercase",
4912
4974
  };
4913
4975
  class ElementCase extends Rule {
4914
4976
  constructor(options) {
4915
- super({ ...defaults$i, ...options });
4977
+ super({ ...defaults$j, ...options });
4916
4978
  this.style = new CaseStyle(this.options.style, "element-case");
4917
4979
  }
4918
4980
  static schema() {
@@ -4978,14 +5040,14 @@ class ElementCase extends Rule {
4978
5040
  }
4979
5041
  }
4980
5042
 
4981
- const defaults$h = {
5043
+ const defaults$i = {
4982
5044
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
4983
5045
  whitelist: [],
4984
5046
  blacklist: [],
4985
5047
  };
4986
5048
  class ElementName extends Rule {
4987
5049
  constructor(options) {
4988
- super({ ...defaults$h, ...options });
5050
+ super({ ...defaults$i, ...options });
4989
5051
  // eslint-disable-next-line security/detect-non-literal-regexp
4990
5052
  this.pattern = new RegExp(this.options.pattern);
4991
5053
  }
@@ -5026,7 +5088,7 @@ class ElementName extends Rule {
5026
5088
  ...context.blacklist.map((cur) => `- ${cur}`),
5027
5089
  ];
5028
5090
  }
5029
- if (context.pattern !== defaults$h.pattern) {
5091
+ if (context.pattern !== defaults$i.pattern) {
5030
5092
  return [
5031
5093
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
5032
5094
  "",
@@ -5428,7 +5490,7 @@ class EmptyTitle extends Rule {
5428
5490
  }
5429
5491
  }
5430
5492
 
5431
- const defaults$g = {
5493
+ const defaults$h = {
5432
5494
  allowMultipleH1: false,
5433
5495
  minInitialRank: "h1",
5434
5496
  sectioningRoots: ["dialog", '[role="dialog"]'],
@@ -5459,7 +5521,7 @@ function parseMaxInitial(value) {
5459
5521
  }
5460
5522
  class HeadingLevel extends Rule {
5461
5523
  constructor(options) {
5462
- super({ ...defaults$g, ...options });
5524
+ super({ ...defaults$h, ...options });
5463
5525
  this.stack = [];
5464
5526
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
5465
5527
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -5617,12 +5679,12 @@ class HeadingLevel extends Rule {
5617
5679
  }
5618
5680
  }
5619
5681
 
5620
- const defaults$f = {
5682
+ const defaults$g = {
5621
5683
  pattern: "kebabcase",
5622
5684
  };
5623
5685
  class IdPattern extends Rule {
5624
5686
  constructor(options) {
5625
- super({ ...defaults$f, ...options });
5687
+ super({ ...defaults$g, ...options });
5626
5688
  this.pattern = parsePattern(this.options.pattern);
5627
5689
  }
5628
5690
  static schema() {
@@ -5973,12 +6035,12 @@ function findLabelByParent(el) {
5973
6035
  return [];
5974
6036
  }
5975
6037
 
5976
- const defaults$e = {
6038
+ const defaults$f = {
5977
6039
  maxlength: 70,
5978
6040
  };
5979
6041
  class LongTitle extends Rule {
5980
6042
  constructor(options) {
5981
- super({ ...defaults$e, ...options });
6043
+ super({ ...defaults$f, ...options });
5982
6044
  this.maxlength = this.options.maxlength;
5983
6045
  }
5984
6046
  static schema() {
@@ -6125,13 +6187,13 @@ class MultipleLabeledControls extends Rule {
6125
6187
  }
6126
6188
  }
6127
6189
 
6128
- const defaults$d = {
6190
+ const defaults$e = {
6129
6191
  include: null,
6130
6192
  exclude: null,
6131
6193
  };
6132
6194
  class NoAutoplay extends Rule {
6133
6195
  constructor(options) {
6134
- super({ ...defaults$d, ...options });
6196
+ super({ ...defaults$e, ...options });
6135
6197
  }
6136
6198
  documentation(context) {
6137
6199
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -6372,14 +6434,14 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
6372
6434
  }
6373
6435
  }
6374
6436
 
6375
- const defaults$c = {
6437
+ const defaults$d = {
6376
6438
  include: null,
6377
6439
  exclude: null,
6378
6440
  allowedProperties: ["display"],
6379
6441
  };
6380
6442
  class NoInlineStyle extends Rule {
6381
6443
  constructor(options) {
6382
- super({ ...defaults$c, ...options });
6444
+ super({ ...defaults$d, ...options });
6383
6445
  }
6384
6446
  static schema() {
6385
6447
  return {
@@ -6581,7 +6643,7 @@ class NoMultipleMain extends Rule {
6581
6643
  }
6582
6644
  }
6583
6645
 
6584
- const defaults$b = {
6646
+ const defaults$c = {
6585
6647
  relaxed: false,
6586
6648
  };
6587
6649
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
@@ -6598,7 +6660,7 @@ const replacementTable = {
6598
6660
  };
6599
6661
  class NoRawCharacters extends Rule {
6600
6662
  constructor(options) {
6601
- super({ ...defaults$b, ...options });
6663
+ super({ ...defaults$c, ...options });
6602
6664
  this.relaxed = this.options.relaxed;
6603
6665
  }
6604
6666
  static schema() {
@@ -6776,13 +6838,13 @@ class NoRedundantRole extends Rule {
6776
6838
  }
6777
6839
 
6778
6840
  const xmlns = /^(.+):.+$/;
6779
- const defaults$a = {
6841
+ const defaults$b = {
6780
6842
  ignoreForeign: true,
6781
6843
  ignoreXML: true,
6782
6844
  };
6783
6845
  class NoSelfClosing extends Rule {
6784
6846
  constructor(options) {
6785
- super({ ...defaults$a, ...options });
6847
+ super({ ...defaults$b, ...options });
6786
6848
  }
6787
6849
  static schema() {
6788
6850
  return {
@@ -6915,13 +6977,13 @@ const replacement = {
6915
6977
  reset: '<button type="reset">',
6916
6978
  image: '<button type="button">',
6917
6979
  };
6918
- const defaults$9 = {
6980
+ const defaults$a = {
6919
6981
  include: null,
6920
6982
  exclude: null,
6921
6983
  };
6922
6984
  class PreferButton extends Rule {
6923
6985
  constructor(options) {
6924
- super({ ...defaults$9, ...options });
6986
+ super({ ...defaults$a, ...options });
6925
6987
  }
6926
6988
  static schema() {
6927
6989
  return {
@@ -6996,7 +7058,7 @@ class PreferButton extends Rule {
6996
7058
  }
6997
7059
  }
6998
7060
 
6999
- const defaults$8 = {
7061
+ const defaults$9 = {
7000
7062
  mapping: {
7001
7063
  article: "article",
7002
7064
  banner: "header",
@@ -7026,7 +7088,7 @@ const defaults$8 = {
7026
7088
  };
7027
7089
  class PreferNativeElement extends Rule {
7028
7090
  constructor(options) {
7029
- super({ ...defaults$8, ...options });
7091
+ super({ ...defaults$9, ...options });
7030
7092
  }
7031
7093
  static schema() {
7032
7094
  return {
@@ -7146,8 +7208,66 @@ class PreferTbody extends Rule {
7146
7208
  }
7147
7209
  }
7148
7210
 
7211
+ const defaults$8 = {
7212
+ tags: ["script", "style"],
7213
+ };
7214
+ class RequireCSPNonce extends Rule {
7215
+ constructor(options) {
7216
+ super({ ...defaults$8, ...options });
7217
+ }
7218
+ static schema() {
7219
+ return {
7220
+ tags: {
7221
+ type: "array",
7222
+ items: {
7223
+ enum: ["script", "style"],
7224
+ type: "string",
7225
+ },
7226
+ },
7227
+ };
7228
+ }
7229
+ documentation() {
7230
+ return {
7231
+ description: [
7232
+ "Required Content-Security-Policy (CSP) nonce is missing or empty.",
7233
+ "",
7234
+ "This is set by the `nonce` attribute and must match the `Content-Security-Policy` header.",
7235
+ "For instance, if the header contains `script-src 'nonce-r4nd0m'` the `nonce` attribute must be set to `nonce=\"r4nd0m\">`",
7236
+ "",
7237
+ "The nonce should be unique per each request and set to a cryptography secure random token.",
7238
+ "It is used to prevent cross site scripting (XSS) by preventing malicious actors from injecting scripts onto the page.",
7239
+ ].join("\n"),
7240
+ url: ruleDocumentationUrl("@/rules/require-csp-nonce.ts"),
7241
+ };
7242
+ }
7243
+ setup() {
7244
+ this.on("tag:end", (event) => {
7245
+ var _a;
7246
+ const { tags } = this.options;
7247
+ const node = event.previous;
7248
+ /* ignore other tags */
7249
+ if (!node || !tags.includes(node.tagName)) {
7250
+ return;
7251
+ }
7252
+ /* ignore if nonce is set to non-empty value (or dynamic) */
7253
+ const nonce = (_a = node.getAttribute("nonce")) === null || _a === void 0 ? void 0 : _a.value;
7254
+ if (nonce && nonce !== "") {
7255
+ return;
7256
+ }
7257
+ /* ignore <script src> */
7258
+ if (node.is("script") && node.hasAttribute("src")) {
7259
+ return;
7260
+ }
7261
+ const message = `required CSP nonce is missing`;
7262
+ this.report(node, message, node.location);
7263
+ });
7264
+ }
7265
+ }
7266
+
7149
7267
  const defaults$7 = {
7150
7268
  target: "all",
7269
+ include: null,
7270
+ exclude: null,
7151
7271
  };
7152
7272
  const crossorigin = new RegExp("^(\\w+://|//)"); /* e.g. https:// or // */
7153
7273
  const supportSri = {
@@ -7165,6 +7285,32 @@ class RequireSri extends Rule {
7165
7285
  enum: ["all", "crossorigin"],
7166
7286
  type: "string",
7167
7287
  },
7288
+ include: {
7289
+ anyOf: [
7290
+ {
7291
+ items: {
7292
+ type: "string",
7293
+ },
7294
+ type: "array",
7295
+ },
7296
+ {
7297
+ type: "null",
7298
+ },
7299
+ ],
7300
+ },
7301
+ exclude: {
7302
+ anyOf: [
7303
+ {
7304
+ items: {
7305
+ type: "string",
7306
+ },
7307
+ type: "array",
7308
+ },
7309
+ {
7310
+ type: "null",
7311
+ },
7312
+ ],
7313
+ },
7168
7314
  };
7169
7315
  }
7170
7316
  documentation() {
@@ -7177,11 +7323,13 @@ class RequireSri extends Rule {
7177
7323
  this.on("tag:end", (event) => {
7178
7324
  /* only handle thats supporting and requires sri */
7179
7325
  const node = event.previous;
7180
- if (!(this.supportSri(node) && this.needSri(node)))
7326
+ if (!(this.supportSri(node) && this.needSri(node))) {
7181
7327
  return;
7328
+ }
7182
7329
  /* check if sri attribute is present */
7183
- if (node.hasAttribute("integrity"))
7330
+ if (node.hasAttribute("integrity")) {
7184
7331
  return;
7332
+ }
7185
7333
  this.report(node, `SRI "integrity" attribute is required on <${node.tagName}> element`, node.location);
7186
7334
  });
7187
7335
  }
@@ -7189,19 +7337,25 @@ class RequireSri extends Rule {
7189
7337
  return Object.keys(supportSri).includes(node.tagName);
7190
7338
  }
7191
7339
  needSri(node) {
7192
- if (this.target === "all")
7193
- return true;
7194
7340
  const attr = this.elementSourceAttr(node);
7195
- if (!attr || attr.value === null || attr.isDynamic) {
7341
+ if (!attr || attr.value === null || attr.value === "" || attr.isDynamic) {
7196
7342
  return false;
7197
7343
  }
7198
7344
  const url = attr.value.toString();
7199
- return crossorigin.test(url);
7345
+ if (this.target === "all" || crossorigin.test(url)) {
7346
+ return !this.isIgnored(url);
7347
+ }
7348
+ return false;
7200
7349
  }
7201
7350
  elementSourceAttr(node) {
7202
7351
  const key = supportSri[node.tagName];
7203
7352
  return node.getAttribute(key);
7204
7353
  }
7354
+ isIgnored(url) {
7355
+ return this.isKeywordIgnored(url, (list, it) => {
7356
+ return list.some((pattern) => it.includes(pattern));
7357
+ });
7358
+ }
7205
7359
  }
7206
7360
 
7207
7361
  class ScriptElement extends Rule {
@@ -9999,6 +10153,7 @@ const bundledRules = {
9999
10153
  "prefer-button": PreferButton,
10000
10154
  "prefer-native-element": PreferNativeElement,
10001
10155
  "prefer-tbody": PreferTbody,
10156
+ "require-csp-nonce": RequireCSPNonce,
10002
10157
  "require-sri": RequireSri,
10003
10158
  "script-element": ScriptElement,
10004
10159
  "script-type": ScriptType,