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/cjs/core.js CHANGED
@@ -1765,6 +1765,77 @@ function stripslashes(value) {
1765
1765
  function escapeSelectorComponent(text) {
1766
1766
  return text.toString().replace(/([^a-z0-9_-])/gi, "\\$1");
1767
1767
  }
1768
+ /**
1769
+ * Returns true if the character is a delimiter for different kinds of selectors:
1770
+ *
1771
+ * - `.` - begins a class selector
1772
+ * - `#` - begins an id selector
1773
+ * - `[` - begins an attribute selector
1774
+ * - `:` - begins a pseudo class or element selector
1775
+ */
1776
+ function isDelimiter(ch) {
1777
+ return /[.#[:]/.test(ch);
1778
+ }
1779
+ /**
1780
+ * Returns true if the character is a quotation mark.
1781
+ */
1782
+ function isQuotationMark(ch) {
1783
+ return /['"]/.test(ch);
1784
+ }
1785
+ function isPseudoElement(ch, buffer) {
1786
+ return ch === ":" && buffer === ":";
1787
+ }
1788
+ /**
1789
+ * @internal
1790
+ */
1791
+ function* splitPattern(pattern) {
1792
+ if (pattern === "") {
1793
+ return;
1794
+ }
1795
+ const end = pattern.length;
1796
+ let begin = 0;
1797
+ let cur = 1;
1798
+ let quoted = false;
1799
+ while (cur < end) {
1800
+ const ch = pattern[cur];
1801
+ const buffer = pattern.slice(begin, cur);
1802
+ /* escaped character, ignore whatever is next */
1803
+ if (ch === "\\") {
1804
+ cur += 2;
1805
+ continue;
1806
+ }
1807
+ /* if inside quoted string we only look for the end quotation mark */
1808
+ if (quoted) {
1809
+ if (ch === quoted) {
1810
+ quoted = false;
1811
+ }
1812
+ cur += 1;
1813
+ continue;
1814
+ }
1815
+ /* if the character is a quotation mark we store the character and the above
1816
+ * condition will look for a similar end quotation mark */
1817
+ if (isQuotationMark(ch)) {
1818
+ quoted = ch;
1819
+ cur += 1;
1820
+ continue;
1821
+ }
1822
+ /* special case when using :: pseudo element selector */
1823
+ if (isPseudoElement(ch, buffer)) {
1824
+ cur += 1;
1825
+ continue;
1826
+ }
1827
+ /* if the character is a delimiter we yield the string and reset the
1828
+ * position */
1829
+ if (isDelimiter(ch)) {
1830
+ begin = cur;
1831
+ yield buffer;
1832
+ }
1833
+ cur += 1;
1834
+ }
1835
+ /* yield the rest of the string */
1836
+ const tail = pattern.slice(begin, cur);
1837
+ yield tail;
1838
+ }
1768
1839
  class Matcher {
1769
1840
  }
1770
1841
  class ClassMatcher extends Matcher {
@@ -1830,8 +1901,7 @@ class Pattern {
1830
1901
  this.selector = pattern;
1831
1902
  this.combinator = parseCombinator(match.shift(), pattern);
1832
1903
  this.tagName = match.shift() || "*";
1833
- const p = match[0] ? match[0].split(/(?=(?<!\\)[.#[:])/) : [];
1834
- this.pattern = p.map((cur) => this.createMatcher(cur));
1904
+ this.pattern = Array.from(splitPattern(match[0]), (it) => this.createMatcher(it));
1835
1905
  }
1836
1906
  match(node, context) {
1837
1907
  return node.is(this.tagName) && this.pattern.every((cur) => cur.match(node, context));
@@ -3117,7 +3187,7 @@ var TRANSFORMER_API;
3117
3187
  /** @public */
3118
3188
  const name = "html-validate";
3119
3189
  /** @public */
3120
- const version = "7.1.2";
3190
+ const version = "7.3.1";
3121
3191
  /** @public */
3122
3192
  const homepage = "https://html-validate.org";
3123
3193
  /** @public */
@@ -3218,6 +3288,18 @@ function getSchemaValidator(ruleId, properties) {
3218
3288
  };
3219
3289
  return ajv$1.compile(schema);
3220
3290
  }
3291
+ function isErrorDescriptor(value) {
3292
+ return Boolean(value[0] && value[0].message);
3293
+ }
3294
+ function unpackErrorDescriptor(value) {
3295
+ if (isErrorDescriptor(value)) {
3296
+ return value[0];
3297
+ }
3298
+ else {
3299
+ const [node, message, location, context] = value;
3300
+ return { node, message, location, context };
3301
+ }
3302
+ }
3221
3303
  /**
3222
3304
  * @public
3223
3305
  */
@@ -3318,13 +3400,8 @@ class Rule {
3318
3400
  static schema() {
3319
3401
  return null;
3320
3402
  }
3321
- /**
3322
- * Report a new error.
3323
- *
3324
- * Rule must be enabled both globally and on the specific node for this to
3325
- * have any effect.
3326
- */
3327
- report(node, message, location, context) {
3403
+ report(...args) {
3404
+ const { node, message, location, context } = unpackErrorDescriptor(args);
3328
3405
  if (this.isEnabled() && (!node || node.ruleEnabled(this.name))) {
3329
3406
  const where = this.findLocation({ node, location, event: this.event });
3330
3407
  const interpolated = interpolate(message, context !== null && context !== void 0 ? context : {});
@@ -3837,9 +3914,14 @@ class AttrCase extends Rule {
3837
3914
  return;
3838
3915
  }
3839
3916
  const letters = event.key.replace(/[^a-z]+/gi, "");
3840
- if (!this.style.match(letters)) {
3841
- this.report(event.target, `Attribute "${event.key}" should be ${this.style.name}`, event.keyLocation);
3917
+ if (this.style.match(letters)) {
3918
+ return;
3842
3919
  }
3920
+ this.report({
3921
+ node: event.target,
3922
+ message: `Attribute "${event.key}" should be ${this.style.name}`,
3923
+ location: event.keyLocation,
3924
+ });
3843
3925
  });
3844
3926
  }
3845
3927
  isIgnored(node) {
@@ -5139,6 +5221,11 @@ class ElementName extends Rule {
5139
5221
  }
5140
5222
  }
5141
5223
 
5224
+ var ErrorKind;
5225
+ (function (ErrorKind) {
5226
+ ErrorKind["CONTENT"] = "content";
5227
+ ErrorKind["DESCENDANT"] = "descendant";
5228
+ })(ErrorKind || (ErrorKind = {}));
5142
5229
  function getTransparentChildren(node, transparent) {
5143
5230
  if (typeof transparent === "boolean") {
5144
5231
  return node.childElements;
@@ -5152,10 +5239,28 @@ function getTransparentChildren(node, transparent) {
5152
5239
  });
5153
5240
  }
5154
5241
  }
5242
+ function getRuleDescription$1(context) {
5243
+ if (!context) {
5244
+ return [
5245
+ "Some elements has restrictions on what content is allowed.",
5246
+ "This can include both direct children or descendant elements.",
5247
+ ];
5248
+ }
5249
+ switch (context.kind) {
5250
+ case ErrorKind.CONTENT:
5251
+ return [
5252
+ `The \`${context.child}\` element is not permitted as content under the parent \`${context.parent}\` element.`,
5253
+ ];
5254
+ case ErrorKind.DESCENDANT:
5255
+ return [
5256
+ `The \`${context.child}\` element is not permitted as a descendant of the \`${context.ancestor}\` element.`,
5257
+ ];
5258
+ }
5259
+ }
5155
5260
  class ElementPermittedContent extends Rule {
5156
- documentation() {
5261
+ documentation(context) {
5157
5262
  return {
5158
- description: "Some elements has restrictions on what content is allowed. This can include both direct children or descendant elements.",
5263
+ description: getRuleDescription$1(context).join("\n"),
5159
5264
  url: ruleDocumentationUrl("@/rules/element-permitted-content.ts"),
5160
5265
  };
5161
5266
  }
@@ -5164,8 +5269,9 @@ class ElementPermittedContent extends Rule {
5164
5269
  const doc = event.document;
5165
5270
  doc.visitDepthFirst((node) => {
5166
5271
  const parent = node.parent;
5167
- /* dont verify root element, assume any element is allowed */
5168
- if (!parent || parent.isRootElement()) {
5272
+ /* istanbul ignore next: satisfy typescript but will visitDepthFirst()
5273
+ * will not yield nodes without a parent */
5274
+ if (!parent) {
5169
5275
  return;
5170
5276
  }
5171
5277
  /* Run each validation step, stop as soon as any errors are
@@ -5175,7 +5281,6 @@ class ElementPermittedContent extends Rule {
5175
5281
  [
5176
5282
  () => this.validatePermittedContent(node, parent),
5177
5283
  () => this.validatePermittedDescendant(node, parent),
5178
- () => this.validatePermittedAncestors(node),
5179
5284
  ].some((fn) => fn());
5180
5285
  });
5181
5286
  });
@@ -5192,7 +5297,14 @@ class ElementPermittedContent extends Rule {
5192
5297
  }
5193
5298
  validatePermittedContentImpl(cur, parent, rules) {
5194
5299
  if (!Validator.validatePermitted(cur, rules)) {
5195
- this.report(cur, `Element <${cur.tagName}> is not permitted as content in ${parent.annotatedName}`);
5300
+ const child = `<${cur.tagName}>`;
5301
+ const message = `${child} element is not permitted as content under ${parent.annotatedName}`;
5302
+ const context = {
5303
+ kind: ErrorKind.CONTENT,
5304
+ parent: parent.annotatedName,
5305
+ child,
5306
+ };
5307
+ this.report(cur, message, null, context);
5196
5308
  return true;
5197
5309
  }
5198
5310
  /* for transparent elements all/listed children must be validated against
@@ -5223,21 +5335,15 @@ class ElementPermittedContent extends Rule {
5223
5335
  if (Validator.validatePermitted(node, rules)) {
5224
5336
  continue;
5225
5337
  }
5226
- this.report(node, `Element <${node.tagName}> is not permitted as descendant of ${cur.annotatedName}`);
5227
- return true;
5228
- }
5229
- return false;
5230
- }
5231
- validatePermittedAncestors(node) {
5232
- if (!node.meta) {
5233
- return false;
5234
- }
5235
- const rules = node.meta.requiredAncestors;
5236
- if (!rules) {
5237
- return false;
5238
- }
5239
- if (!Validator.validateAncestors(node, rules)) {
5240
- this.report(node, `Element <${node.tagName}> requires an "${rules[0]}" ancestor`);
5338
+ const child = `<${node.tagName}>`;
5339
+ const ancestor = cur.annotatedName;
5340
+ const message = `${child} element is not permitted as a descendant of ${ancestor}`;
5341
+ const context = {
5342
+ kind: ErrorKind.DESCENDANT,
5343
+ ancestor,
5344
+ child,
5345
+ };
5346
+ this.report(node, message, null, context);
5241
5347
  return true;
5242
5348
  }
5243
5349
  return false;
@@ -5304,6 +5410,152 @@ class ElementPermittedOrder extends Rule {
5304
5410
  }
5305
5411
  }
5306
5412
 
5413
+ const CACHE_KEY = Symbol(classifyNodeText.name);
5414
+ var TextClassification;
5415
+ (function (TextClassification) {
5416
+ TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
5417
+ TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
5418
+ TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
5419
+ })(TextClassification || (TextClassification = {}));
5420
+ /**
5421
+ * Checks text content of an element.
5422
+ *
5423
+ * Any text is considered including text from descendant elements. Whitespace is
5424
+ * ignored.
5425
+ *
5426
+ * If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
5427
+ */
5428
+ function classifyNodeText(node) {
5429
+ if (node.cacheExists(CACHE_KEY)) {
5430
+ return node.cacheGet(CACHE_KEY);
5431
+ }
5432
+ const text = findTextNodes(node);
5433
+ /* if any text is dynamic classify as dynamic */
5434
+ if (text.some((cur) => cur.isDynamic)) {
5435
+ return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
5436
+ }
5437
+ /* if any text has non-whitespace character classify as static */
5438
+ if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
5439
+ return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
5440
+ }
5441
+ /* default to empty */
5442
+ return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
5443
+ }
5444
+ function findTextNodes(node) {
5445
+ let text = [];
5446
+ for (const child of node.childNodes) {
5447
+ switch (child.nodeType) {
5448
+ case NodeType.TEXT_NODE:
5449
+ text.push(child);
5450
+ break;
5451
+ case NodeType.ELEMENT_NODE:
5452
+ text = text.concat(findTextNodes(child));
5453
+ break;
5454
+ }
5455
+ }
5456
+ return text;
5457
+ }
5458
+
5459
+ function hasAltText(image) {
5460
+ const alt = image.getAttribute("alt");
5461
+ /* missing or boolean */
5462
+ if (alt === null || alt.value === null) {
5463
+ return false;
5464
+ }
5465
+ return alt.isDynamic || alt.value.toString() !== "";
5466
+ }
5467
+
5468
+ function hasAriaLabel(node) {
5469
+ const label = node.getAttribute("aria-label");
5470
+ /* missing or boolean */
5471
+ if (label === null || label.value === null) {
5472
+ return false;
5473
+ }
5474
+ return label.isDynamic || label.value.toString() !== "";
5475
+ }
5476
+
5477
+ /**
5478
+ * Joins a list of words into natural language.
5479
+ *
5480
+ * - `["foo"]` becomes `"foo"`
5481
+ * - `["foo", "bar"]` becomes `"foo or bar"`
5482
+ * - `["foo", "bar", "baz"]` becomes `"foo, bar or baz"`
5483
+ * - and so on...
5484
+ *
5485
+ * @internal
5486
+ * @param values - List of words to join
5487
+ * @param conjunction - Conjunction for the last element.
5488
+ * @returns String with the words naturally joined with a conjunction.
5489
+ */
5490
+ function naturalJoin(values, conjunction = "or") {
5491
+ switch (values.length) {
5492
+ case 0:
5493
+ return "";
5494
+ case 1:
5495
+ return values[0];
5496
+ case 2:
5497
+ return `${values[0]} ${conjunction} ${values[1]}`;
5498
+ default:
5499
+ return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.slice(-1)[0]}`;
5500
+ }
5501
+ }
5502
+
5503
+ function isTagnameOnly(value) {
5504
+ return Boolean(value.match(/^[a-zA-Z0-9-]+$/));
5505
+ }
5506
+ function getRuleDescription(context) {
5507
+ if (!context) {
5508
+ return [
5509
+ "Some elements has restrictions on what content is allowed.",
5510
+ "This can include both direct children or descendant elements.",
5511
+ ];
5512
+ }
5513
+ const escaped = context.ancestor.map((it) => `\`${it}\``);
5514
+ return [`The \`${context.child}\` element requires a ${naturalJoin(escaped)} ancestor.`];
5515
+ }
5516
+ class ElementRequiredAncestor extends Rule {
5517
+ documentation(context) {
5518
+ return {
5519
+ description: getRuleDescription(context).join("\n"),
5520
+ url: ruleDocumentationUrl("@/rules/element-required-ancestor.ts"),
5521
+ };
5522
+ }
5523
+ setup() {
5524
+ this.on("dom:ready", (event) => {
5525
+ const doc = event.document;
5526
+ doc.visitDepthFirst((node) => {
5527
+ const parent = node.parent;
5528
+ /* istanbul ignore next: satisfy typescript but will visitDepthFirst()
5529
+ * will not yield nodes without a parent */
5530
+ if (!parent) {
5531
+ return;
5532
+ }
5533
+ this.validateRequiredAncestors(node);
5534
+ });
5535
+ });
5536
+ }
5537
+ validateRequiredAncestors(node) {
5538
+ if (!node.meta) {
5539
+ return;
5540
+ }
5541
+ const rules = node.meta.requiredAncestors;
5542
+ if (!rules) {
5543
+ return;
5544
+ }
5545
+ if (Validator.validateAncestors(node, rules)) {
5546
+ return;
5547
+ }
5548
+ const ancestor = rules.map((it) => (isTagnameOnly(it) ? `<${it}>` : `"${it}"`));
5549
+ const child = `<${node.tagName}>`;
5550
+ const message = `<${node.tagName}> element requires a ${naturalJoin(ancestor)} ancestor`;
5551
+ const context = {
5552
+ ancestor,
5553
+ child,
5554
+ };
5555
+ this.report(node, message, null, context);
5556
+ }
5557
+ }
5558
+
5307
5559
  class ElementRequiredAttributes extends Rule {
5308
5560
  documentation(context) {
5309
5561
  const docs = {
@@ -5381,52 +5633,6 @@ class ElementRequiredContent extends Rule {
5381
5633
  }
5382
5634
  }
5383
5635
 
5384
- const CACHE_KEY = Symbol(classifyNodeText.name);
5385
- var TextClassification;
5386
- (function (TextClassification) {
5387
- TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
5388
- TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
5389
- TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
5390
- })(TextClassification || (TextClassification = {}));
5391
- /**
5392
- * Checks text content of an element.
5393
- *
5394
- * Any text is considered including text from descendant elements. Whitespace is
5395
- * ignored.
5396
- *
5397
- * If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
5398
- */
5399
- function classifyNodeText(node) {
5400
- if (node.cacheExists(CACHE_KEY)) {
5401
- return node.cacheGet(CACHE_KEY);
5402
- }
5403
- const text = findTextNodes(node);
5404
- /* if any text is dynamic classify as dynamic */
5405
- if (text.some((cur) => cur.isDynamic)) {
5406
- return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
5407
- }
5408
- /* if any text has non-whitespace character classify as static */
5409
- if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
5410
- return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
5411
- }
5412
- /* default to empty */
5413
- return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
5414
- }
5415
- function findTextNodes(node) {
5416
- let text = [];
5417
- for (const child of node.childNodes) {
5418
- switch (child.nodeType) {
5419
- case NodeType.TEXT_NODE:
5420
- text.push(child);
5421
- break;
5422
- case NodeType.ELEMENT_NODE:
5423
- text = text.concat(findTextNodes(child));
5424
- break;
5425
- }
5426
- }
5427
- return text;
5428
- }
5429
-
5430
5636
  const selector = ["h1", "h2", "h3", "h4", "h5", "h6"].join(",");
5431
5637
  class EmptyHeading extends Rule {
5432
5638
  documentation() {
@@ -7607,24 +7813,6 @@ class TelNonBreaking extends Rule {
7607
7813
  }
7608
7814
  }
7609
7815
 
7610
- function hasAltText(image) {
7611
- const alt = image.getAttribute("alt");
7612
- /* missing or boolean */
7613
- if (alt === null || alt.value === null) {
7614
- return false;
7615
- }
7616
- return alt.isDynamic || alt.value.toString() !== "";
7617
- }
7618
-
7619
- function hasAriaLabel(node) {
7620
- const label = node.getAttribute("aria-label");
7621
- /* missing or boolean */
7622
- if (label === null || label.value === null) {
7623
- return false;
7624
- }
7625
- return label.isDynamic || label.value.toString() !== "";
7626
- }
7627
-
7628
7816
  /**
7629
7817
  * Check if attribute is present and non-empty or dynamic.
7630
7818
  */
@@ -9802,13 +9990,13 @@ class VoidStyle extends Rule {
9802
9990
  return;
9803
9991
  }
9804
9992
  if (this.shouldBeOmitted(node)) {
9805
- this.report(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
9993
+ this.reportError(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
9806
9994
  }
9807
9995
  if (this.shouldBeSelfClosed(node)) {
9808
- this.report(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
9996
+ this.reportError(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
9809
9997
  }
9810
9998
  }
9811
- report(node, message) {
9999
+ reportError(node, message) {
9812
10000
  const context = {
9813
10001
  style: this.style,
9814
10002
  tagName: node.tagName,
@@ -10120,6 +10308,7 @@ const bundledRules = {
10120
10308
  "element-permitted-content": ElementPermittedContent,
10121
10309
  "element-permitted-occurrences": ElementPermittedOccurrences,
10122
10310
  "element-permitted-order": ElementPermittedOrder,
10311
+ "element-required-ancestor": ElementRequiredAncestor,
10123
10312
  "element-required-attributes": ElementRequiredAttributes,
10124
10313
  "element-required-content": ElementRequiredContent,
10125
10314
  "empty-heading": EmptyHeading,
@@ -10227,6 +10416,7 @@ const config$1 = {
10227
10416
  "element-permitted-content": "error",
10228
10417
  "element-permitted-occurrences": "error",
10229
10418
  "element-permitted-order": "error",
10419
+ "element-required-ancestor": "error",
10230
10420
  "element-required-attributes": "error",
10231
10421
  "element-required-content": "error",
10232
10422
  "empty-heading": "error",
@@ -10285,6 +10475,7 @@ const config = {
10285
10475
  "element-permitted-content": "error",
10286
10476
  "element-permitted-occurrences": "error",
10287
10477
  "element-permitted-order": "error",
10478
+ "element-required-ancestor": "error",
10288
10479
  "element-required-attributes": "error",
10289
10480
  "element-required-content": "error",
10290
10481
  "multiple-labeled-controls": "error",
@@ -10364,6 +10555,7 @@ class ResolvedConfig {
10364
10555
  });
10365
10556
  }
10366
10557
  catch (err) {
10558
+ /* istanbul ignore next: only used as a fallback */
10367
10559
  const message = err instanceof Error ? err.message : String(err);
10368
10560
  throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
10369
10561
  }
@@ -10376,7 +10568,7 @@ class ResolvedConfig {
10376
10568
  * Wrapper around [[transformSource]] which reads a file before passing it
10377
10569
  * as-is to transformSource.
10378
10570
  *
10379
- * @param source - Filename to transform (according to configured
10571
+ * @param filename - Filename to transform (according to configured
10380
10572
  * transformations)
10381
10573
  * @returns A list of transformed sources ready for validation.
10382
10574
  */
@@ -10532,7 +10724,9 @@ class Config {
10532
10724
  var _a;
10533
10725
  const valid = validator(configData);
10534
10726
  if (!valid) {
10535
- throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema, (_a = validator.errors) !== null && _a !== void 0 ? _a : []);
10727
+ throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema,
10728
+ /* istanbul ignore next: will be set when a validation error has occurred */
10729
+ (_a = validator.errors) !== null && _a !== void 0 ? _a : []);
10536
10730
  }
10537
10731
  if (configData.rules) {
10538
10732
  const normalizedRules = Config.getRulesObject(configData.rules);
@@ -10641,6 +10835,7 @@ class Config {
10641
10835
  metaTable.loadFromObject(legacyRequire(entry));
10642
10836
  }
10643
10837
  catch (err) {
10838
+ /* istanbul ignore next: only used as a fallback */
10644
10839
  const message = err instanceof Error ? err.message : String(err);
10645
10840
  throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, ensureError(err));
10646
10841
  }
@@ -10662,6 +10857,7 @@ class Config {
10662
10857
  *
10663
10858
  * @internal primary purpose is unittests
10664
10859
  */
10860
+ /* istanbul ignore next: used for testing only */
10665
10861
  get() {
10666
10862
  const config = { ...this.config };
10667
10863
  if (config.elements) {
@@ -10684,6 +10880,7 @@ class Config {
10684
10880
  */
10685
10881
  getRules() {
10686
10882
  var _a;
10883
+ /* istanbul ignore next: only used as a fallback */
10687
10884
  return Config.getRulesObject((_a = this.config.rules) !== null && _a !== void 0 ? _a : {});
10688
10885
  }
10689
10886
  static getRulesObject(src) {
@@ -10718,6 +10915,7 @@ class Config {
10718
10915
  return plugin;
10719
10916
  }
10720
10917
  catch (err) {
10918
+ /* istanbul ignore next: only used as a fallback */
10721
10919
  const message = err instanceof Error ? err.message : String(err);
10722
10920
  throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, ensureError(err));
10723
10921
  }
@@ -11633,9 +11831,9 @@ class Reporter {
11633
11831
  if (!(location.filename in this.result)) {
11634
11832
  this.result[location.filename] = [];
11635
11833
  }
11636
- this.result[location.filename].push({
11834
+ const ruleUrl = (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url;
11835
+ const entry = {
11637
11836
  ruleId: rule.name,
11638
- ruleUrl: (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url,
11639
11837
  severity,
11640
11838
  message,
11641
11839
  offset: location.offset,
@@ -11645,8 +11843,14 @@ class Reporter {
11645
11843
  selector() {
11646
11844
  return node ? node.generateSelector() : null;
11647
11845
  },
11648
- context,
11649
- });
11846
+ };
11847
+ if (ruleUrl) {
11848
+ entry.ruleUrl = ruleUrl;
11849
+ }
11850
+ if (context) {
11851
+ entry.context = context;
11852
+ }
11853
+ this.result[location.filename].push(entry);
11650
11854
  }
11651
11855
  addManual(filename, message) {
11652
11856
  if (!(filename in this.result)) {
@@ -12060,7 +12264,7 @@ class Engine {
12060
12264
  offset: location.offset,
12061
12265
  line: location.line,
12062
12266
  column: location.column,
12063
- size: location.size || 0,
12267
+ size: location.size,
12064
12268
  selector: () => null,
12065
12269
  });
12066
12270
  }
@@ -12490,12 +12694,12 @@ class FileSystemConfigLoader extends ConfigLoader {
12490
12694
  * `null` if no configuration files are found.
12491
12695
  */
12492
12696
  fromFilename(filename) {
12493
- var _a;
12494
12697
  if (filename === "inline") {
12495
12698
  return null;
12496
12699
  }
12497
- if (this.cache.has(filename)) {
12498
- return (_a = this.cache.get(filename)) !== null && _a !== void 0 ? _a : null;
12700
+ const cache = this.cache.get(filename);
12701
+ if (cache) {
12702
+ return cache;
12499
12703
  }
12500
12704
  let found = false;
12501
12705
  let current = path__default["default"].resolve(path__default["default"].dirname(filename));