html-validate 7.1.0 → 7.2.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.js CHANGED
@@ -1732,7 +1732,7 @@ function stripslashes(value) {
1732
1732
  return value.replace(/\\(.)/g, "$1");
1733
1733
  }
1734
1734
  function escapeSelectorComponent(text) {
1735
- return text.toString().replace(/([:[\] ])/g, "\\$1");
1735
+ return text.toString().replace(/([^a-z0-9_-])/gi, "\\$1");
1736
1736
  }
1737
1737
  class Matcher {
1738
1738
  }
@@ -2615,12 +2615,15 @@ class Validator {
2615
2615
  if (value === null || value === undefined) {
2616
2616
  return false;
2617
2617
  }
2618
+ const caseInsensitiveValue = value.toLowerCase();
2618
2619
  return rule.enum.some((entry) => {
2619
2620
  if (entry instanceof RegExp) {
2621
+ /* regular expressions are matched case-sensitive */
2620
2622
  return !!value.match(entry);
2621
2623
  }
2622
2624
  else {
2623
- return value === entry;
2625
+ /* strings matched case-insensitive */
2626
+ return caseInsensitiveValue === entry;
2624
2627
  }
2625
2628
  });
2626
2629
  }
@@ -3083,7 +3086,7 @@ var TRANSFORMER_API;
3083
3086
  /** @public */
3084
3087
  const name = "html-validate";
3085
3088
  /** @public */
3086
- const version = "7.1.0";
3089
+ const version = "7.2.0";
3087
3090
  /** @public */
3088
3091
  const homepage = "https://html-validate.org";
3089
3092
  /** @public */
@@ -3184,6 +3187,18 @@ function getSchemaValidator(ruleId, properties) {
3184
3187
  };
3185
3188
  return ajv$1.compile(schema);
3186
3189
  }
3190
+ function isErrorDescriptor(value) {
3191
+ return Boolean(value[0] && value[0].message);
3192
+ }
3193
+ function unpackErrorDescriptor(value) {
3194
+ if (isErrorDescriptor(value)) {
3195
+ return value[0];
3196
+ }
3197
+ else {
3198
+ const [node, message, location, context] = value;
3199
+ return { node, message, location, context };
3200
+ }
3201
+ }
3187
3202
  /**
3188
3203
  * @public
3189
3204
  */
@@ -3284,13 +3299,8 @@ class Rule {
3284
3299
  static schema() {
3285
3300
  return null;
3286
3301
  }
3287
- /**
3288
- * Report a new error.
3289
- *
3290
- * Rule must be enabled both globally and on the specific node for this to
3291
- * have any effect.
3292
- */
3293
- report(node, message, location, context) {
3302
+ report(...args) {
3303
+ const { node, message, location, context } = unpackErrorDescriptor(args);
3294
3304
  if (this.isEnabled() && (!node || node.ruleEnabled(this.name))) {
3295
3305
  const where = this.findLocation({ node, location, event: this.event });
3296
3306
  const interpolated = interpolate(message, context !== null && context !== void 0 ? context : {});
@@ -3418,11 +3428,11 @@ const mapping$1 = {
3418
3428
  script: "src",
3419
3429
  };
3420
3430
  const description = {
3421
- ["external" /* EXTERNAL */]: "External links are not allowed by current configuration.",
3422
- ["relative-base" /* RELATIVE_BASE */]: "Links relative to <base> are not allowed by current configuration.",
3423
- ["relative-path" /* RELATIVE_PATH */]: "Relative links are not allowed by current configuration.",
3424
- ["absolute" /* ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
3425
- ["anchor" /* ANCHOR */]: null,
3431
+ ["external" /* Style.EXTERNAL */]: "External links are not allowed by current configuration.",
3432
+ ["relative-base" /* Style.RELATIVE_BASE */]: "Links relative to <base> are not allowed by current configuration.",
3433
+ ["relative-path" /* Style.RELATIVE_PATH */]: "Relative links are not allowed by current configuration.",
3434
+ ["absolute" /* Style.ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
3435
+ ["anchor" /* Style.ANCHOR */]: null,
3426
3436
  };
3427
3437
  function parseAllow(value) {
3428
3438
  if (typeof value === "boolean") {
@@ -3495,19 +3505,19 @@ class AllowedLinks extends Rule {
3495
3505
  const link = event.value.toString();
3496
3506
  const style = this.getStyle(link);
3497
3507
  switch (style) {
3498
- case "anchor" /* ANCHOR */:
3508
+ case "anchor" /* Style.ANCHOR */:
3499
3509
  /* anchor links are always allowed by this rule */
3500
3510
  break;
3501
- case "absolute" /* ABSOLUTE */:
3511
+ case "absolute" /* Style.ABSOLUTE */:
3502
3512
  this.handleAbsolute(link, event, style);
3503
3513
  break;
3504
- case "external" /* EXTERNAL */:
3514
+ case "external" /* Style.EXTERNAL */:
3505
3515
  this.handleExternal(link, event, style);
3506
3516
  break;
3507
- case "relative-base" /* RELATIVE_BASE */:
3517
+ case "relative-base" /* Style.RELATIVE_BASE */:
3508
3518
  this.handleRelativeBase(link, event, style);
3509
3519
  break;
3510
- case "relative-path" /* RELATIVE_PATH */:
3520
+ case "relative-path" /* Style.RELATIVE_PATH */:
3511
3521
  this.handleRelativePath(link, event, style);
3512
3522
  break;
3513
3523
  }
@@ -3525,21 +3535,21 @@ class AllowedLinks extends Rule {
3525
3535
  getStyle(value) {
3526
3536
  /* http://example.net or //example.net */
3527
3537
  if (value.match(/^([a-z]+:)?\/\//g)) {
3528
- return "external" /* EXTERNAL */;
3538
+ return "external" /* Style.EXTERNAL */;
3529
3539
  }
3530
3540
  switch (value[0]) {
3531
3541
  /* /foo/bar */
3532
3542
  case "/":
3533
- return "absolute" /* ABSOLUTE */;
3543
+ return "absolute" /* Style.ABSOLUTE */;
3534
3544
  /* ../foo/bar */
3535
3545
  case ".":
3536
- return "relative-path" /* RELATIVE_PATH */;
3546
+ return "relative-path" /* Style.RELATIVE_PATH */;
3537
3547
  /* #foo */
3538
3548
  case "#":
3539
- return "anchor" /* ANCHOR */;
3549
+ return "anchor" /* Style.ANCHOR */;
3540
3550
  /* foo/bar */
3541
3551
  default:
3542
- return "relative-base" /* RELATIVE_BASE */;
3552
+ return "relative-base" /* Style.RELATIVE_BASE */;
3543
3553
  }
3544
3554
  }
3545
3555
  handleAbsolute(target, event, style) {
@@ -3803,9 +3813,14 @@ class AttrCase extends Rule {
3803
3813
  return;
3804
3814
  }
3805
3815
  const letters = event.key.replace(/[^a-z]+/gi, "");
3806
- if (!this.style.match(letters)) {
3807
- this.report(event.target, `Attribute "${event.key}" should be ${this.style.name}`, event.keyLocation);
3816
+ if (this.style.match(letters)) {
3817
+ return;
3808
3818
  }
3819
+ this.report({
3820
+ node: event.target,
3821
+ message: `Attribute "${event.key}" should be ${this.style.name}`,
3822
+ location: event.keyLocation,
3823
+ });
3809
3824
  });
3810
3825
  }
3811
3826
  isIgnored(node) {
@@ -5105,6 +5120,11 @@ class ElementName extends Rule {
5105
5120
  }
5106
5121
  }
5107
5122
 
5123
+ var ErrorKind;
5124
+ (function (ErrorKind) {
5125
+ ErrorKind["CONTENT"] = "content";
5126
+ ErrorKind["DESCENDANT"] = "descendant";
5127
+ })(ErrorKind || (ErrorKind = {}));
5108
5128
  function getTransparentChildren(node, transparent) {
5109
5129
  if (typeof transparent === "boolean") {
5110
5130
  return node.childElements;
@@ -5118,10 +5138,28 @@ function getTransparentChildren(node, transparent) {
5118
5138
  });
5119
5139
  }
5120
5140
  }
5141
+ function getRuleDescription$1(context) {
5142
+ if (!context) {
5143
+ return [
5144
+ "Some elements has restrictions on what content is allowed.",
5145
+ "This can include both direct children or descendant elements.",
5146
+ ];
5147
+ }
5148
+ switch (context.kind) {
5149
+ case ErrorKind.CONTENT:
5150
+ return [
5151
+ `The \`${context.child}\` element is not permitted as content under the parent \`${context.parent}\` element.`,
5152
+ ];
5153
+ case ErrorKind.DESCENDANT:
5154
+ return [
5155
+ `The \`${context.child}\` element is not permitted as a descendant of the \`${context.ancestor}\` element.`,
5156
+ ];
5157
+ }
5158
+ }
5121
5159
  class ElementPermittedContent extends Rule {
5122
- documentation() {
5160
+ documentation(context) {
5123
5161
  return {
5124
- description: "Some elements has restrictions on what content is allowed. This can include both direct children or descendant elements.",
5162
+ description: getRuleDescription$1(context).join("\n"),
5125
5163
  url: ruleDocumentationUrl("@/rules/element-permitted-content.ts"),
5126
5164
  };
5127
5165
  }
@@ -5130,8 +5168,9 @@ class ElementPermittedContent extends Rule {
5130
5168
  const doc = event.document;
5131
5169
  doc.visitDepthFirst((node) => {
5132
5170
  const parent = node.parent;
5133
- /* dont verify root element, assume any element is allowed */
5134
- if (!parent || parent.isRootElement()) {
5171
+ /* istanbul ignore next: satisfy typescript but will visitDepthFirst()
5172
+ * will not yield nodes without a parent */
5173
+ if (!parent) {
5135
5174
  return;
5136
5175
  }
5137
5176
  /* Run each validation step, stop as soon as any errors are
@@ -5141,7 +5180,6 @@ class ElementPermittedContent extends Rule {
5141
5180
  [
5142
5181
  () => this.validatePermittedContent(node, parent),
5143
5182
  () => this.validatePermittedDescendant(node, parent),
5144
- () => this.validatePermittedAncestors(node),
5145
5183
  ].some((fn) => fn());
5146
5184
  });
5147
5185
  });
@@ -5158,7 +5196,14 @@ class ElementPermittedContent extends Rule {
5158
5196
  }
5159
5197
  validatePermittedContentImpl(cur, parent, rules) {
5160
5198
  if (!Validator.validatePermitted(cur, rules)) {
5161
- this.report(cur, `Element <${cur.tagName}> is not permitted as content in ${parent.annotatedName}`);
5199
+ const child = `<${cur.tagName}>`;
5200
+ const message = `${child} element is not permitted as content under ${parent.annotatedName}`;
5201
+ const context = {
5202
+ kind: ErrorKind.CONTENT,
5203
+ parent: parent.annotatedName,
5204
+ child,
5205
+ };
5206
+ this.report(cur, message, null, context);
5162
5207
  return true;
5163
5208
  }
5164
5209
  /* for transparent elements all/listed children must be validated against
@@ -5189,21 +5234,15 @@ class ElementPermittedContent extends Rule {
5189
5234
  if (Validator.validatePermitted(node, rules)) {
5190
5235
  continue;
5191
5236
  }
5192
- this.report(node, `Element <${node.tagName}> is not permitted as descendant of ${cur.annotatedName}`);
5193
- return true;
5194
- }
5195
- return false;
5196
- }
5197
- validatePermittedAncestors(node) {
5198
- if (!node.meta) {
5199
- return false;
5200
- }
5201
- const rules = node.meta.requiredAncestors;
5202
- if (!rules) {
5203
- return false;
5204
- }
5205
- if (!Validator.validateAncestors(node, rules)) {
5206
- this.report(node, `Element <${node.tagName}> requires an "${rules[0]}" ancestor`);
5237
+ const child = `<${node.tagName}>`;
5238
+ const ancestor = cur.annotatedName;
5239
+ const message = `${child} element is not permitted as a descendant of ${ancestor}`;
5240
+ const context = {
5241
+ kind: ErrorKind.DESCENDANT,
5242
+ ancestor,
5243
+ child,
5244
+ };
5245
+ this.report(node, message, null, context);
5207
5246
  return true;
5208
5247
  }
5209
5248
  return false;
@@ -5270,6 +5309,152 @@ class ElementPermittedOrder extends Rule {
5270
5309
  }
5271
5310
  }
5272
5311
 
5312
+ const CACHE_KEY = Symbol(classifyNodeText.name);
5313
+ var TextClassification;
5314
+ (function (TextClassification) {
5315
+ TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
5316
+ TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
5317
+ TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
5318
+ })(TextClassification || (TextClassification = {}));
5319
+ /**
5320
+ * Checks text content of an element.
5321
+ *
5322
+ * Any text is considered including text from descendant elements. Whitespace is
5323
+ * ignored.
5324
+ *
5325
+ * If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
5326
+ */
5327
+ function classifyNodeText(node) {
5328
+ if (node.cacheExists(CACHE_KEY)) {
5329
+ return node.cacheGet(CACHE_KEY);
5330
+ }
5331
+ const text = findTextNodes(node);
5332
+ /* if any text is dynamic classify as dynamic */
5333
+ if (text.some((cur) => cur.isDynamic)) {
5334
+ return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
5335
+ }
5336
+ /* if any text has non-whitespace character classify as static */
5337
+ if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
5338
+ return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
5339
+ }
5340
+ /* default to empty */
5341
+ return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
5342
+ }
5343
+ function findTextNodes(node) {
5344
+ let text = [];
5345
+ for (const child of node.childNodes) {
5346
+ switch (child.nodeType) {
5347
+ case NodeType.TEXT_NODE:
5348
+ text.push(child);
5349
+ break;
5350
+ case NodeType.ELEMENT_NODE:
5351
+ text = text.concat(findTextNodes(child));
5352
+ break;
5353
+ }
5354
+ }
5355
+ return text;
5356
+ }
5357
+
5358
+ function hasAltText(image) {
5359
+ const alt = image.getAttribute("alt");
5360
+ /* missing or boolean */
5361
+ if (alt === null || alt.value === null) {
5362
+ return false;
5363
+ }
5364
+ return alt.isDynamic || alt.value.toString() !== "";
5365
+ }
5366
+
5367
+ function hasAriaLabel(node) {
5368
+ const label = node.getAttribute("aria-label");
5369
+ /* missing or boolean */
5370
+ if (label === null || label.value === null) {
5371
+ return false;
5372
+ }
5373
+ return label.isDynamic || label.value.toString() !== "";
5374
+ }
5375
+
5376
+ /**
5377
+ * Joins a list of words into natural language.
5378
+ *
5379
+ * - `["foo"]` becomes `"foo"`
5380
+ * - `["foo", "bar"]` becomes `"foo or bar"`
5381
+ * - `["foo", "bar", "baz"]` becomes `"foo, bar or baz"`
5382
+ * - and so on...
5383
+ *
5384
+ * @internal
5385
+ * @param values - List of words to join
5386
+ * @param conjunction - Conjunction for the last element.
5387
+ * @returns String with the words naturally joined with a conjunction.
5388
+ */
5389
+ function naturalJoin(values, conjunction = "or") {
5390
+ switch (values.length) {
5391
+ case 0:
5392
+ return "";
5393
+ case 1:
5394
+ return values[0];
5395
+ case 2:
5396
+ return `${values[0]} ${conjunction} ${values[1]}`;
5397
+ default:
5398
+ return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.slice(-1)[0]}`;
5399
+ }
5400
+ }
5401
+
5402
+ function isTagnameOnly(value) {
5403
+ return Boolean(value.match(/^[a-zA-Z0-9-]+$/));
5404
+ }
5405
+ function getRuleDescription(context) {
5406
+ if (!context) {
5407
+ return [
5408
+ "Some elements has restrictions on what content is allowed.",
5409
+ "This can include both direct children or descendant elements.",
5410
+ ];
5411
+ }
5412
+ const escaped = context.ancestor.map((it) => `\`${it}\``);
5413
+ return [`The \`${context.child}\` element requires a ${naturalJoin(escaped)} ancestor.`];
5414
+ }
5415
+ class ElementRequiredAncestor extends Rule {
5416
+ documentation(context) {
5417
+ return {
5418
+ description: getRuleDescription(context).join("\n"),
5419
+ url: ruleDocumentationUrl("@/rules/element-required-ancestor.ts"),
5420
+ };
5421
+ }
5422
+ setup() {
5423
+ this.on("dom:ready", (event) => {
5424
+ const doc = event.document;
5425
+ doc.visitDepthFirst((node) => {
5426
+ const parent = node.parent;
5427
+ /* istanbul ignore next: satisfy typescript but will visitDepthFirst()
5428
+ * will not yield nodes without a parent */
5429
+ if (!parent) {
5430
+ return;
5431
+ }
5432
+ this.validateRequiredAncestors(node);
5433
+ });
5434
+ });
5435
+ }
5436
+ validateRequiredAncestors(node) {
5437
+ if (!node.meta) {
5438
+ return;
5439
+ }
5440
+ const rules = node.meta.requiredAncestors;
5441
+ if (!rules) {
5442
+ return;
5443
+ }
5444
+ if (Validator.validateAncestors(node, rules)) {
5445
+ return;
5446
+ }
5447
+ const ancestor = rules.map((it) => (isTagnameOnly(it) ? `<${it}>` : `"${it}"`));
5448
+ const child = `<${node.tagName}>`;
5449
+ const message = `<${node.tagName}> element requires a ${naturalJoin(ancestor)} ancestor`;
5450
+ const context = {
5451
+ ancestor,
5452
+ child,
5453
+ };
5454
+ this.report(node, message, null, context);
5455
+ }
5456
+ }
5457
+
5273
5458
  class ElementRequiredAttributes extends Rule {
5274
5459
  documentation(context) {
5275
5460
  const docs = {
@@ -5347,52 +5532,6 @@ class ElementRequiredContent extends Rule {
5347
5532
  }
5348
5533
  }
5349
5534
 
5350
- const CACHE_KEY = Symbol(classifyNodeText.name);
5351
- var TextClassification;
5352
- (function (TextClassification) {
5353
- TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
5354
- TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
5355
- TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
5356
- })(TextClassification || (TextClassification = {}));
5357
- /**
5358
- * Checks text content of an element.
5359
- *
5360
- * Any text is considered including text from descendant elements. Whitespace is
5361
- * ignored.
5362
- *
5363
- * If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
5364
- */
5365
- function classifyNodeText(node) {
5366
- if (node.cacheExists(CACHE_KEY)) {
5367
- return node.cacheGet(CACHE_KEY);
5368
- }
5369
- const text = findTextNodes(node);
5370
- /* if any text is dynamic classify as dynamic */
5371
- if (text.some((cur) => cur.isDynamic)) {
5372
- return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
5373
- }
5374
- /* if any text has non-whitespace character classify as static */
5375
- if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
5376
- return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
5377
- }
5378
- /* default to empty */
5379
- return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
5380
- }
5381
- function findTextNodes(node) {
5382
- let text = [];
5383
- for (const child of node.childNodes) {
5384
- switch (child.nodeType) {
5385
- case NodeType.TEXT_NODE:
5386
- text.push(child);
5387
- break;
5388
- case NodeType.ELEMENT_NODE:
5389
- text = text.concat(findTextNodes(child));
5390
- break;
5391
- }
5392
- }
5393
- return text;
5394
- }
5395
-
5396
5535
  const selector = ["h1", "h2", "h3", "h4", "h5", "h6"].join(",");
5397
5536
  class EmptyHeading extends Rule {
5398
5537
  documentation() {
@@ -7573,24 +7712,6 @@ class TelNonBreaking extends Rule {
7573
7712
  }
7574
7713
  }
7575
7714
 
7576
- function hasAltText(image) {
7577
- const alt = image.getAttribute("alt");
7578
- /* missing or boolean */
7579
- if (alt === null || alt.value === null) {
7580
- return false;
7581
- }
7582
- return alt.isDynamic || alt.value.toString() !== "";
7583
- }
7584
-
7585
- function hasAriaLabel(node) {
7586
- const label = node.getAttribute("aria-label");
7587
- /* missing or boolean */
7588
- if (label === null || label.value === null) {
7589
- return false;
7590
- }
7591
- return label.isDynamic || label.value.toString() !== "";
7592
- }
7593
-
7594
7715
  /**
7595
7716
  * Check if attribute is present and non-empty or dynamic.
7596
7717
  */
@@ -9768,13 +9889,13 @@ class VoidStyle extends Rule {
9768
9889
  return;
9769
9890
  }
9770
9891
  if (this.shouldBeOmitted(node)) {
9771
- this.report(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
9892
+ this.reportError(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
9772
9893
  }
9773
9894
  if (this.shouldBeSelfClosed(node)) {
9774
- this.report(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
9895
+ this.reportError(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
9775
9896
  }
9776
9897
  }
9777
- report(node, message) {
9898
+ reportError(node, message) {
9778
9899
  const context = {
9779
9900
  style: this.style,
9780
9901
  tagName: node.tagName,
@@ -10086,6 +10207,7 @@ const bundledRules = {
10086
10207
  "element-permitted-content": ElementPermittedContent,
10087
10208
  "element-permitted-occurrences": ElementPermittedOccurrences,
10088
10209
  "element-permitted-order": ElementPermittedOrder,
10210
+ "element-required-ancestor": ElementRequiredAncestor,
10089
10211
  "element-required-attributes": ElementRequiredAttributes,
10090
10212
  "element-required-content": ElementRequiredContent,
10091
10213
  "empty-heading": EmptyHeading,
@@ -10193,6 +10315,7 @@ const config$1 = {
10193
10315
  "element-permitted-content": "error",
10194
10316
  "element-permitted-occurrences": "error",
10195
10317
  "element-permitted-order": "error",
10318
+ "element-required-ancestor": "error",
10196
10319
  "element-required-attributes": "error",
10197
10320
  "element-required-content": "error",
10198
10321
  "empty-heading": "error",
@@ -10251,6 +10374,7 @@ const config = {
10251
10374
  "element-permitted-content": "error",
10252
10375
  "element-permitted-occurrences": "error",
10253
10376
  "element-permitted-order": "error",
10377
+ "element-required-ancestor": "error",
10254
10378
  "element-required-attributes": "error",
10255
10379
  "element-required-content": "error",
10256
10380
  "multiple-labeled-controls": "error",
@@ -10330,6 +10454,7 @@ class ResolvedConfig {
10330
10454
  });
10331
10455
  }
10332
10456
  catch (err) {
10457
+ /* istanbul ignore next: only used as a fallback */
10333
10458
  const message = err instanceof Error ? err.message : String(err);
10334
10459
  throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
10335
10460
  }
@@ -10342,7 +10467,7 @@ class ResolvedConfig {
10342
10467
  * Wrapper around [[transformSource]] which reads a file before passing it
10343
10468
  * as-is to transformSource.
10344
10469
  *
10345
- * @param source - Filename to transform (according to configured
10470
+ * @param filename - Filename to transform (according to configured
10346
10471
  * transformations)
10347
10472
  * @returns A list of transformed sources ready for validation.
10348
10473
  */
@@ -10498,7 +10623,9 @@ class Config {
10498
10623
  var _a;
10499
10624
  const valid = validator(configData);
10500
10625
  if (!valid) {
10501
- throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema, (_a = validator.errors) !== null && _a !== void 0 ? _a : []);
10626
+ throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema,
10627
+ /* istanbul ignore next: will be set when a validation error has occurred */
10628
+ (_a = validator.errors) !== null && _a !== void 0 ? _a : []);
10502
10629
  }
10503
10630
  if (configData.rules) {
10504
10631
  const normalizedRules = Config.getRulesObject(configData.rules);
@@ -10607,6 +10734,7 @@ class Config {
10607
10734
  metaTable.loadFromObject(legacyRequire(entry));
10608
10735
  }
10609
10736
  catch (err) {
10737
+ /* istanbul ignore next: only used as a fallback */
10610
10738
  const message = err instanceof Error ? err.message : String(err);
10611
10739
  throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, ensureError(err));
10612
10740
  }
@@ -10628,6 +10756,7 @@ class Config {
10628
10756
  *
10629
10757
  * @internal primary purpose is unittests
10630
10758
  */
10759
+ /* istanbul ignore next: used for testing only */
10631
10760
  get() {
10632
10761
  const config = { ...this.config };
10633
10762
  if (config.elements) {
@@ -10650,6 +10779,7 @@ class Config {
10650
10779
  */
10651
10780
  getRules() {
10652
10781
  var _a;
10782
+ /* istanbul ignore next: only used as a fallback */
10653
10783
  return Config.getRulesObject((_a = this.config.rules) !== null && _a !== void 0 ? _a : {});
10654
10784
  }
10655
10785
  static getRulesObject(src) {
@@ -10684,6 +10814,7 @@ class Config {
10684
10814
  return plugin;
10685
10815
  }
10686
10816
  catch (err) {
10817
+ /* istanbul ignore next: only used as a fallback */
10687
10818
  const message = err instanceof Error ? err.message : String(err);
10688
10819
  throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, ensureError(err));
10689
10820
  }
@@ -11599,9 +11730,9 @@ class Reporter {
11599
11730
  if (!(location.filename in this.result)) {
11600
11731
  this.result[location.filename] = [];
11601
11732
  }
11602
- this.result[location.filename].push({
11733
+ const ruleUrl = (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url;
11734
+ const entry = {
11603
11735
  ruleId: rule.name,
11604
- ruleUrl: (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url,
11605
11736
  severity,
11606
11737
  message,
11607
11738
  offset: location.offset,
@@ -11611,8 +11742,14 @@ class Reporter {
11611
11742
  selector() {
11612
11743
  return node ? node.generateSelector() : null;
11613
11744
  },
11614
- context,
11615
- });
11745
+ };
11746
+ if (ruleUrl) {
11747
+ entry.ruleUrl = ruleUrl;
11748
+ }
11749
+ if (context) {
11750
+ entry.context = context;
11751
+ }
11752
+ this.result[location.filename].push(entry);
11616
11753
  }
11617
11754
  addManual(filename, message) {
11618
11755
  if (!(filename in this.result)) {
@@ -12026,7 +12163,7 @@ class Engine {
12026
12163
  offset: location.offset,
12027
12164
  line: location.line,
12028
12165
  column: location.column,
12029
- size: location.size || 0,
12166
+ size: location.size,
12030
12167
  selector: () => null,
12031
12168
  });
12032
12169
  }
@@ -12456,12 +12593,12 @@ class FileSystemConfigLoader extends ConfigLoader {
12456
12593
  * `null` if no configuration files are found.
12457
12594
  */
12458
12595
  fromFilename(filename) {
12459
- var _a;
12460
12596
  if (filename === "inline") {
12461
12597
  return null;
12462
12598
  }
12463
- if (this.cache.has(filename)) {
12464
- return (_a = this.cache.get(filename)) !== null && _a !== void 0 ? _a : null;
12599
+ const cache = this.cache.get(filename);
12600
+ if (cache) {
12601
+ return cache;
12465
12602
  }
12466
12603
  let found = false;
12467
12604
  let current = path.resolve(path.dirname(filename));