html-validate 7.1.2 → 7.3.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.js CHANGED
@@ -1734,6 +1734,77 @@ function stripslashes(value) {
1734
1734
  function escapeSelectorComponent(text) {
1735
1735
  return text.toString().replace(/([^a-z0-9_-])/gi, "\\$1");
1736
1736
  }
1737
+ /**
1738
+ * Returns true if the character is a delimiter for different kinds of selectors:
1739
+ *
1740
+ * - `.` - begins a class selector
1741
+ * - `#` - begins an id selector
1742
+ * - `[` - begins an attribute selector
1743
+ * - `:` - begins a pseudo class or element selector
1744
+ */
1745
+ function isDelimiter(ch) {
1746
+ return /[.#[:]/.test(ch);
1747
+ }
1748
+ /**
1749
+ * Returns true if the character is a quotation mark.
1750
+ */
1751
+ function isQuotationMark(ch) {
1752
+ return /['"]/.test(ch);
1753
+ }
1754
+ function isPseudoElement(ch, buffer) {
1755
+ return ch === ":" && buffer === ":";
1756
+ }
1757
+ /**
1758
+ * @internal
1759
+ */
1760
+ function* splitPattern(pattern) {
1761
+ if (pattern === "") {
1762
+ return;
1763
+ }
1764
+ const end = pattern.length;
1765
+ let begin = 0;
1766
+ let cur = 1;
1767
+ let quoted = false;
1768
+ while (cur < end) {
1769
+ const ch = pattern[cur];
1770
+ const buffer = pattern.slice(begin, cur);
1771
+ /* escaped character, ignore whatever is next */
1772
+ if (ch === "\\") {
1773
+ cur += 2;
1774
+ continue;
1775
+ }
1776
+ /* if inside quoted string we only look for the end quotation mark */
1777
+ if (quoted) {
1778
+ if (ch === quoted) {
1779
+ quoted = false;
1780
+ }
1781
+ cur += 1;
1782
+ continue;
1783
+ }
1784
+ /* if the character is a quotation mark we store the character and the above
1785
+ * condition will look for a similar end quotation mark */
1786
+ if (isQuotationMark(ch)) {
1787
+ quoted = ch;
1788
+ cur += 1;
1789
+ continue;
1790
+ }
1791
+ /* special case when using :: pseudo element selector */
1792
+ if (isPseudoElement(ch, buffer)) {
1793
+ cur += 1;
1794
+ continue;
1795
+ }
1796
+ /* if the character is a delimiter we yield the string and reset the
1797
+ * position */
1798
+ if (isDelimiter(ch)) {
1799
+ begin = cur;
1800
+ yield buffer;
1801
+ }
1802
+ cur += 1;
1803
+ }
1804
+ /* yield the rest of the string */
1805
+ const tail = pattern.slice(begin, cur);
1806
+ yield tail;
1807
+ }
1737
1808
  class Matcher {
1738
1809
  }
1739
1810
  class ClassMatcher extends Matcher {
@@ -1799,8 +1870,7 @@ class Pattern {
1799
1870
  this.selector = pattern;
1800
1871
  this.combinator = parseCombinator(match.shift(), pattern);
1801
1872
  this.tagName = match.shift() || "*";
1802
- const p = match[0] ? match[0].split(/(?=(?<!\\)[.#[:])/) : [];
1803
- this.pattern = p.map((cur) => this.createMatcher(cur));
1873
+ this.pattern = Array.from(splitPattern(match[0]), (it) => this.createMatcher(it));
1804
1874
  }
1805
1875
  match(node, context) {
1806
1876
  return node.is(this.tagName) && this.pattern.every((cur) => cur.match(node, context));
@@ -3086,7 +3156,7 @@ var TRANSFORMER_API;
3086
3156
  /** @public */
3087
3157
  const name = "html-validate";
3088
3158
  /** @public */
3089
- const version = "7.1.2";
3159
+ const version = "7.3.1";
3090
3160
  /** @public */
3091
3161
  const homepage = "https://html-validate.org";
3092
3162
  /** @public */
@@ -3187,6 +3257,18 @@ function getSchemaValidator(ruleId, properties) {
3187
3257
  };
3188
3258
  return ajv$1.compile(schema);
3189
3259
  }
3260
+ function isErrorDescriptor(value) {
3261
+ return Boolean(value[0] && value[0].message);
3262
+ }
3263
+ function unpackErrorDescriptor(value) {
3264
+ if (isErrorDescriptor(value)) {
3265
+ return value[0];
3266
+ }
3267
+ else {
3268
+ const [node, message, location, context] = value;
3269
+ return { node, message, location, context };
3270
+ }
3271
+ }
3190
3272
  /**
3191
3273
  * @public
3192
3274
  */
@@ -3287,13 +3369,8 @@ class Rule {
3287
3369
  static schema() {
3288
3370
  return null;
3289
3371
  }
3290
- /**
3291
- * Report a new error.
3292
- *
3293
- * Rule must be enabled both globally and on the specific node for this to
3294
- * have any effect.
3295
- */
3296
- report(node, message, location, context) {
3372
+ report(...args) {
3373
+ const { node, message, location, context } = unpackErrorDescriptor(args);
3297
3374
  if (this.isEnabled() && (!node || node.ruleEnabled(this.name))) {
3298
3375
  const where = this.findLocation({ node, location, event: this.event });
3299
3376
  const interpolated = interpolate(message, context !== null && context !== void 0 ? context : {});
@@ -3806,9 +3883,14 @@ class AttrCase extends Rule {
3806
3883
  return;
3807
3884
  }
3808
3885
  const letters = event.key.replace(/[^a-z]+/gi, "");
3809
- if (!this.style.match(letters)) {
3810
- this.report(event.target, `Attribute "${event.key}" should be ${this.style.name}`, event.keyLocation);
3886
+ if (this.style.match(letters)) {
3887
+ return;
3811
3888
  }
3889
+ this.report({
3890
+ node: event.target,
3891
+ message: `Attribute "${event.key}" should be ${this.style.name}`,
3892
+ location: event.keyLocation,
3893
+ });
3812
3894
  });
3813
3895
  }
3814
3896
  isIgnored(node) {
@@ -5108,6 +5190,11 @@ class ElementName extends Rule {
5108
5190
  }
5109
5191
  }
5110
5192
 
5193
+ var ErrorKind;
5194
+ (function (ErrorKind) {
5195
+ ErrorKind["CONTENT"] = "content";
5196
+ ErrorKind["DESCENDANT"] = "descendant";
5197
+ })(ErrorKind || (ErrorKind = {}));
5111
5198
  function getTransparentChildren(node, transparent) {
5112
5199
  if (typeof transparent === "boolean") {
5113
5200
  return node.childElements;
@@ -5121,10 +5208,28 @@ function getTransparentChildren(node, transparent) {
5121
5208
  });
5122
5209
  }
5123
5210
  }
5211
+ function getRuleDescription$1(context) {
5212
+ if (!context) {
5213
+ return [
5214
+ "Some elements has restrictions on what content is allowed.",
5215
+ "This can include both direct children or descendant elements.",
5216
+ ];
5217
+ }
5218
+ switch (context.kind) {
5219
+ case ErrorKind.CONTENT:
5220
+ return [
5221
+ `The \`${context.child}\` element is not permitted as content under the parent \`${context.parent}\` element.`,
5222
+ ];
5223
+ case ErrorKind.DESCENDANT:
5224
+ return [
5225
+ `The \`${context.child}\` element is not permitted as a descendant of the \`${context.ancestor}\` element.`,
5226
+ ];
5227
+ }
5228
+ }
5124
5229
  class ElementPermittedContent extends Rule {
5125
- documentation() {
5230
+ documentation(context) {
5126
5231
  return {
5127
- description: "Some elements has restrictions on what content is allowed. This can include both direct children or descendant elements.",
5232
+ description: getRuleDescription$1(context).join("\n"),
5128
5233
  url: ruleDocumentationUrl("@/rules/element-permitted-content.ts"),
5129
5234
  };
5130
5235
  }
@@ -5133,8 +5238,9 @@ class ElementPermittedContent extends Rule {
5133
5238
  const doc = event.document;
5134
5239
  doc.visitDepthFirst((node) => {
5135
5240
  const parent = node.parent;
5136
- /* dont verify root element, assume any element is allowed */
5137
- if (!parent || parent.isRootElement()) {
5241
+ /* istanbul ignore next: satisfy typescript but will visitDepthFirst()
5242
+ * will not yield nodes without a parent */
5243
+ if (!parent) {
5138
5244
  return;
5139
5245
  }
5140
5246
  /* Run each validation step, stop as soon as any errors are
@@ -5144,7 +5250,6 @@ class ElementPermittedContent extends Rule {
5144
5250
  [
5145
5251
  () => this.validatePermittedContent(node, parent),
5146
5252
  () => this.validatePermittedDescendant(node, parent),
5147
- () => this.validatePermittedAncestors(node),
5148
5253
  ].some((fn) => fn());
5149
5254
  });
5150
5255
  });
@@ -5161,7 +5266,14 @@ class ElementPermittedContent extends Rule {
5161
5266
  }
5162
5267
  validatePermittedContentImpl(cur, parent, rules) {
5163
5268
  if (!Validator.validatePermitted(cur, rules)) {
5164
- this.report(cur, `Element <${cur.tagName}> is not permitted as content in ${parent.annotatedName}`);
5269
+ const child = `<${cur.tagName}>`;
5270
+ const message = `${child} element is not permitted as content under ${parent.annotatedName}`;
5271
+ const context = {
5272
+ kind: ErrorKind.CONTENT,
5273
+ parent: parent.annotatedName,
5274
+ child,
5275
+ };
5276
+ this.report(cur, message, null, context);
5165
5277
  return true;
5166
5278
  }
5167
5279
  /* for transparent elements all/listed children must be validated against
@@ -5192,21 +5304,15 @@ class ElementPermittedContent extends Rule {
5192
5304
  if (Validator.validatePermitted(node, rules)) {
5193
5305
  continue;
5194
5306
  }
5195
- this.report(node, `Element <${node.tagName}> is not permitted as descendant of ${cur.annotatedName}`);
5196
- return true;
5197
- }
5198
- return false;
5199
- }
5200
- validatePermittedAncestors(node) {
5201
- if (!node.meta) {
5202
- return false;
5203
- }
5204
- const rules = node.meta.requiredAncestors;
5205
- if (!rules) {
5206
- return false;
5207
- }
5208
- if (!Validator.validateAncestors(node, rules)) {
5209
- this.report(node, `Element <${node.tagName}> requires an "${rules[0]}" ancestor`);
5307
+ const child = `<${node.tagName}>`;
5308
+ const ancestor = cur.annotatedName;
5309
+ const message = `${child} element is not permitted as a descendant of ${ancestor}`;
5310
+ const context = {
5311
+ kind: ErrorKind.DESCENDANT,
5312
+ ancestor,
5313
+ child,
5314
+ };
5315
+ this.report(node, message, null, context);
5210
5316
  return true;
5211
5317
  }
5212
5318
  return false;
@@ -5273,6 +5379,152 @@ class ElementPermittedOrder extends Rule {
5273
5379
  }
5274
5380
  }
5275
5381
 
5382
+ const CACHE_KEY = Symbol(classifyNodeText.name);
5383
+ var TextClassification;
5384
+ (function (TextClassification) {
5385
+ TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
5386
+ TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
5387
+ TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
5388
+ })(TextClassification || (TextClassification = {}));
5389
+ /**
5390
+ * Checks text content of an element.
5391
+ *
5392
+ * Any text is considered including text from descendant elements. Whitespace is
5393
+ * ignored.
5394
+ *
5395
+ * If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
5396
+ */
5397
+ function classifyNodeText(node) {
5398
+ if (node.cacheExists(CACHE_KEY)) {
5399
+ return node.cacheGet(CACHE_KEY);
5400
+ }
5401
+ const text = findTextNodes(node);
5402
+ /* if any text is dynamic classify as dynamic */
5403
+ if (text.some((cur) => cur.isDynamic)) {
5404
+ return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
5405
+ }
5406
+ /* if any text has non-whitespace character classify as static */
5407
+ if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
5408
+ return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
5409
+ }
5410
+ /* default to empty */
5411
+ return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
5412
+ }
5413
+ function findTextNodes(node) {
5414
+ let text = [];
5415
+ for (const child of node.childNodes) {
5416
+ switch (child.nodeType) {
5417
+ case NodeType.TEXT_NODE:
5418
+ text.push(child);
5419
+ break;
5420
+ case NodeType.ELEMENT_NODE:
5421
+ text = text.concat(findTextNodes(child));
5422
+ break;
5423
+ }
5424
+ }
5425
+ return text;
5426
+ }
5427
+
5428
+ function hasAltText(image) {
5429
+ const alt = image.getAttribute("alt");
5430
+ /* missing or boolean */
5431
+ if (alt === null || alt.value === null) {
5432
+ return false;
5433
+ }
5434
+ return alt.isDynamic || alt.value.toString() !== "";
5435
+ }
5436
+
5437
+ function hasAriaLabel(node) {
5438
+ const label = node.getAttribute("aria-label");
5439
+ /* missing or boolean */
5440
+ if (label === null || label.value === null) {
5441
+ return false;
5442
+ }
5443
+ return label.isDynamic || label.value.toString() !== "";
5444
+ }
5445
+
5446
+ /**
5447
+ * Joins a list of words into natural language.
5448
+ *
5449
+ * - `["foo"]` becomes `"foo"`
5450
+ * - `["foo", "bar"]` becomes `"foo or bar"`
5451
+ * - `["foo", "bar", "baz"]` becomes `"foo, bar or baz"`
5452
+ * - and so on...
5453
+ *
5454
+ * @internal
5455
+ * @param values - List of words to join
5456
+ * @param conjunction - Conjunction for the last element.
5457
+ * @returns String with the words naturally joined with a conjunction.
5458
+ */
5459
+ function naturalJoin(values, conjunction = "or") {
5460
+ switch (values.length) {
5461
+ case 0:
5462
+ return "";
5463
+ case 1:
5464
+ return values[0];
5465
+ case 2:
5466
+ return `${values[0]} ${conjunction} ${values[1]}`;
5467
+ default:
5468
+ return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.slice(-1)[0]}`;
5469
+ }
5470
+ }
5471
+
5472
+ function isTagnameOnly(value) {
5473
+ return Boolean(value.match(/^[a-zA-Z0-9-]+$/));
5474
+ }
5475
+ function getRuleDescription(context) {
5476
+ if (!context) {
5477
+ return [
5478
+ "Some elements has restrictions on what content is allowed.",
5479
+ "This can include both direct children or descendant elements.",
5480
+ ];
5481
+ }
5482
+ const escaped = context.ancestor.map((it) => `\`${it}\``);
5483
+ return [`The \`${context.child}\` element requires a ${naturalJoin(escaped)} ancestor.`];
5484
+ }
5485
+ class ElementRequiredAncestor extends Rule {
5486
+ documentation(context) {
5487
+ return {
5488
+ description: getRuleDescription(context).join("\n"),
5489
+ url: ruleDocumentationUrl("@/rules/element-required-ancestor.ts"),
5490
+ };
5491
+ }
5492
+ setup() {
5493
+ this.on("dom:ready", (event) => {
5494
+ const doc = event.document;
5495
+ doc.visitDepthFirst((node) => {
5496
+ const parent = node.parent;
5497
+ /* istanbul ignore next: satisfy typescript but will visitDepthFirst()
5498
+ * will not yield nodes without a parent */
5499
+ if (!parent) {
5500
+ return;
5501
+ }
5502
+ this.validateRequiredAncestors(node);
5503
+ });
5504
+ });
5505
+ }
5506
+ validateRequiredAncestors(node) {
5507
+ if (!node.meta) {
5508
+ return;
5509
+ }
5510
+ const rules = node.meta.requiredAncestors;
5511
+ if (!rules) {
5512
+ return;
5513
+ }
5514
+ if (Validator.validateAncestors(node, rules)) {
5515
+ return;
5516
+ }
5517
+ const ancestor = rules.map((it) => (isTagnameOnly(it) ? `<${it}>` : `"${it}"`));
5518
+ const child = `<${node.tagName}>`;
5519
+ const message = `<${node.tagName}> element requires a ${naturalJoin(ancestor)} ancestor`;
5520
+ const context = {
5521
+ ancestor,
5522
+ child,
5523
+ };
5524
+ this.report(node, message, null, context);
5525
+ }
5526
+ }
5527
+
5276
5528
  class ElementRequiredAttributes extends Rule {
5277
5529
  documentation(context) {
5278
5530
  const docs = {
@@ -5350,52 +5602,6 @@ class ElementRequiredContent extends Rule {
5350
5602
  }
5351
5603
  }
5352
5604
 
5353
- const CACHE_KEY = Symbol(classifyNodeText.name);
5354
- var TextClassification;
5355
- (function (TextClassification) {
5356
- TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
5357
- TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
5358
- TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
5359
- })(TextClassification || (TextClassification = {}));
5360
- /**
5361
- * Checks text content of an element.
5362
- *
5363
- * Any text is considered including text from descendant elements. Whitespace is
5364
- * ignored.
5365
- *
5366
- * If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
5367
- */
5368
- function classifyNodeText(node) {
5369
- if (node.cacheExists(CACHE_KEY)) {
5370
- return node.cacheGet(CACHE_KEY);
5371
- }
5372
- const text = findTextNodes(node);
5373
- /* if any text is dynamic classify as dynamic */
5374
- if (text.some((cur) => cur.isDynamic)) {
5375
- return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
5376
- }
5377
- /* if any text has non-whitespace character classify as static */
5378
- if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
5379
- return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
5380
- }
5381
- /* default to empty */
5382
- return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
5383
- }
5384
- function findTextNodes(node) {
5385
- let text = [];
5386
- for (const child of node.childNodes) {
5387
- switch (child.nodeType) {
5388
- case NodeType.TEXT_NODE:
5389
- text.push(child);
5390
- break;
5391
- case NodeType.ELEMENT_NODE:
5392
- text = text.concat(findTextNodes(child));
5393
- break;
5394
- }
5395
- }
5396
- return text;
5397
- }
5398
-
5399
5605
  const selector = ["h1", "h2", "h3", "h4", "h5", "h6"].join(",");
5400
5606
  class EmptyHeading extends Rule {
5401
5607
  documentation() {
@@ -7576,24 +7782,6 @@ class TelNonBreaking extends Rule {
7576
7782
  }
7577
7783
  }
7578
7784
 
7579
- function hasAltText(image) {
7580
- const alt = image.getAttribute("alt");
7581
- /* missing or boolean */
7582
- if (alt === null || alt.value === null) {
7583
- return false;
7584
- }
7585
- return alt.isDynamic || alt.value.toString() !== "";
7586
- }
7587
-
7588
- function hasAriaLabel(node) {
7589
- const label = node.getAttribute("aria-label");
7590
- /* missing or boolean */
7591
- if (label === null || label.value === null) {
7592
- return false;
7593
- }
7594
- return label.isDynamic || label.value.toString() !== "";
7595
- }
7596
-
7597
7785
  /**
7598
7786
  * Check if attribute is present and non-empty or dynamic.
7599
7787
  */
@@ -9771,13 +9959,13 @@ class VoidStyle extends Rule {
9771
9959
  return;
9772
9960
  }
9773
9961
  if (this.shouldBeOmitted(node)) {
9774
- this.report(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
9962
+ this.reportError(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
9775
9963
  }
9776
9964
  if (this.shouldBeSelfClosed(node)) {
9777
- this.report(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
9965
+ this.reportError(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
9778
9966
  }
9779
9967
  }
9780
- report(node, message) {
9968
+ reportError(node, message) {
9781
9969
  const context = {
9782
9970
  style: this.style,
9783
9971
  tagName: node.tagName,
@@ -10089,6 +10277,7 @@ const bundledRules = {
10089
10277
  "element-permitted-content": ElementPermittedContent,
10090
10278
  "element-permitted-occurrences": ElementPermittedOccurrences,
10091
10279
  "element-permitted-order": ElementPermittedOrder,
10280
+ "element-required-ancestor": ElementRequiredAncestor,
10092
10281
  "element-required-attributes": ElementRequiredAttributes,
10093
10282
  "element-required-content": ElementRequiredContent,
10094
10283
  "empty-heading": EmptyHeading,
@@ -10196,6 +10385,7 @@ const config$1 = {
10196
10385
  "element-permitted-content": "error",
10197
10386
  "element-permitted-occurrences": "error",
10198
10387
  "element-permitted-order": "error",
10388
+ "element-required-ancestor": "error",
10199
10389
  "element-required-attributes": "error",
10200
10390
  "element-required-content": "error",
10201
10391
  "empty-heading": "error",
@@ -10254,6 +10444,7 @@ const config = {
10254
10444
  "element-permitted-content": "error",
10255
10445
  "element-permitted-occurrences": "error",
10256
10446
  "element-permitted-order": "error",
10447
+ "element-required-ancestor": "error",
10257
10448
  "element-required-attributes": "error",
10258
10449
  "element-required-content": "error",
10259
10450
  "multiple-labeled-controls": "error",
@@ -10333,6 +10524,7 @@ class ResolvedConfig {
10333
10524
  });
10334
10525
  }
10335
10526
  catch (err) {
10527
+ /* istanbul ignore next: only used as a fallback */
10336
10528
  const message = err instanceof Error ? err.message : String(err);
10337
10529
  throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
10338
10530
  }
@@ -10345,7 +10537,7 @@ class ResolvedConfig {
10345
10537
  * Wrapper around [[transformSource]] which reads a file before passing it
10346
10538
  * as-is to transformSource.
10347
10539
  *
10348
- * @param source - Filename to transform (according to configured
10540
+ * @param filename - Filename to transform (according to configured
10349
10541
  * transformations)
10350
10542
  * @returns A list of transformed sources ready for validation.
10351
10543
  */
@@ -10501,7 +10693,9 @@ class Config {
10501
10693
  var _a;
10502
10694
  const valid = validator(configData);
10503
10695
  if (!valid) {
10504
- throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema, (_a = validator.errors) !== null && _a !== void 0 ? _a : []);
10696
+ throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema,
10697
+ /* istanbul ignore next: will be set when a validation error has occurred */
10698
+ (_a = validator.errors) !== null && _a !== void 0 ? _a : []);
10505
10699
  }
10506
10700
  if (configData.rules) {
10507
10701
  const normalizedRules = Config.getRulesObject(configData.rules);
@@ -10610,6 +10804,7 @@ class Config {
10610
10804
  metaTable.loadFromObject(legacyRequire(entry));
10611
10805
  }
10612
10806
  catch (err) {
10807
+ /* istanbul ignore next: only used as a fallback */
10613
10808
  const message = err instanceof Error ? err.message : String(err);
10614
10809
  throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, ensureError(err));
10615
10810
  }
@@ -10631,6 +10826,7 @@ class Config {
10631
10826
  *
10632
10827
  * @internal primary purpose is unittests
10633
10828
  */
10829
+ /* istanbul ignore next: used for testing only */
10634
10830
  get() {
10635
10831
  const config = { ...this.config };
10636
10832
  if (config.elements) {
@@ -10653,6 +10849,7 @@ class Config {
10653
10849
  */
10654
10850
  getRules() {
10655
10851
  var _a;
10852
+ /* istanbul ignore next: only used as a fallback */
10656
10853
  return Config.getRulesObject((_a = this.config.rules) !== null && _a !== void 0 ? _a : {});
10657
10854
  }
10658
10855
  static getRulesObject(src) {
@@ -10687,6 +10884,7 @@ class Config {
10687
10884
  return plugin;
10688
10885
  }
10689
10886
  catch (err) {
10887
+ /* istanbul ignore next: only used as a fallback */
10690
10888
  const message = err instanceof Error ? err.message : String(err);
10691
10889
  throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, ensureError(err));
10692
10890
  }
@@ -11602,9 +11800,9 @@ class Reporter {
11602
11800
  if (!(location.filename in this.result)) {
11603
11801
  this.result[location.filename] = [];
11604
11802
  }
11605
- this.result[location.filename].push({
11803
+ const ruleUrl = (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url;
11804
+ const entry = {
11606
11805
  ruleId: rule.name,
11607
- ruleUrl: (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url,
11608
11806
  severity,
11609
11807
  message,
11610
11808
  offset: location.offset,
@@ -11614,8 +11812,14 @@ class Reporter {
11614
11812
  selector() {
11615
11813
  return node ? node.generateSelector() : null;
11616
11814
  },
11617
- context,
11618
- });
11815
+ };
11816
+ if (ruleUrl) {
11817
+ entry.ruleUrl = ruleUrl;
11818
+ }
11819
+ if (context) {
11820
+ entry.context = context;
11821
+ }
11822
+ this.result[location.filename].push(entry);
11619
11823
  }
11620
11824
  addManual(filename, message) {
11621
11825
  if (!(filename in this.result)) {
@@ -12029,7 +12233,7 @@ class Engine {
12029
12233
  offset: location.offset,
12030
12234
  line: location.line,
12031
12235
  column: location.column,
12032
- size: location.size || 0,
12236
+ size: location.size,
12033
12237
  selector: () => null,
12034
12238
  });
12035
12239
  }
@@ -12459,12 +12663,12 @@ class FileSystemConfigLoader extends ConfigLoader {
12459
12663
  * `null` if no configuration files are found.
12460
12664
  */
12461
12665
  fromFilename(filename) {
12462
- var _a;
12463
12666
  if (filename === "inline") {
12464
12667
  return null;
12465
12668
  }
12466
- if (this.cache.has(filename)) {
12467
- return (_a = this.cache.get(filename)) !== null && _a !== void 0 ? _a : null;
12669
+ const cache = this.cache.get(filename);
12670
+ if (cache) {
12671
+ return cache;
12468
12672
  }
12469
12673
  let found = false;
12470
12674
  let current = path.resolve(path.dirname(filename));