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/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
  }
@@ -2646,12 +2646,15 @@ class Validator {
2646
2646
  if (value === null || value === undefined) {
2647
2647
  return false;
2648
2648
  }
2649
+ const caseInsensitiveValue = value.toLowerCase();
2649
2650
  return rule.enum.some((entry) => {
2650
2651
  if (entry instanceof RegExp) {
2652
+ /* regular expressions are matched case-sensitive */
2651
2653
  return !!value.match(entry);
2652
2654
  }
2653
2655
  else {
2654
- return value === entry;
2656
+ /* strings matched case-insensitive */
2657
+ return caseInsensitiveValue === entry;
2655
2658
  }
2656
2659
  });
2657
2660
  }
@@ -3114,7 +3117,7 @@ var TRANSFORMER_API;
3114
3117
  /** @public */
3115
3118
  const name = "html-validate";
3116
3119
  /** @public */
3117
- const version = "7.1.0";
3120
+ const version = "7.2.0";
3118
3121
  /** @public */
3119
3122
  const homepage = "https://html-validate.org";
3120
3123
  /** @public */
@@ -3215,6 +3218,18 @@ function getSchemaValidator(ruleId, properties) {
3215
3218
  };
3216
3219
  return ajv$1.compile(schema);
3217
3220
  }
3221
+ function isErrorDescriptor(value) {
3222
+ return Boolean(value[0] && value[0].message);
3223
+ }
3224
+ function unpackErrorDescriptor(value) {
3225
+ if (isErrorDescriptor(value)) {
3226
+ return value[0];
3227
+ }
3228
+ else {
3229
+ const [node, message, location, context] = value;
3230
+ return { node, message, location, context };
3231
+ }
3232
+ }
3218
3233
  /**
3219
3234
  * @public
3220
3235
  */
@@ -3315,13 +3330,8 @@ class Rule {
3315
3330
  static schema() {
3316
3331
  return null;
3317
3332
  }
3318
- /**
3319
- * Report a new error.
3320
- *
3321
- * Rule must be enabled both globally and on the specific node for this to
3322
- * have any effect.
3323
- */
3324
- report(node, message, location, context) {
3333
+ report(...args) {
3334
+ const { node, message, location, context } = unpackErrorDescriptor(args);
3325
3335
  if (this.isEnabled() && (!node || node.ruleEnabled(this.name))) {
3326
3336
  const where = this.findLocation({ node, location, event: this.event });
3327
3337
  const interpolated = interpolate(message, context !== null && context !== void 0 ? context : {});
@@ -3449,11 +3459,11 @@ const mapping$1 = {
3449
3459
  script: "src",
3450
3460
  };
3451
3461
  const description = {
3452
- ["external" /* EXTERNAL */]: "External links are not allowed by current configuration.",
3453
- ["relative-base" /* RELATIVE_BASE */]: "Links relative to <base> are not allowed by current configuration.",
3454
- ["relative-path" /* RELATIVE_PATH */]: "Relative links are not allowed by current configuration.",
3455
- ["absolute" /* ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
3456
- ["anchor" /* ANCHOR */]: null,
3462
+ ["external" /* Style.EXTERNAL */]: "External links are not allowed by current configuration.",
3463
+ ["relative-base" /* Style.RELATIVE_BASE */]: "Links relative to <base> are not allowed by current configuration.",
3464
+ ["relative-path" /* Style.RELATIVE_PATH */]: "Relative links are not allowed by current configuration.",
3465
+ ["absolute" /* Style.ABSOLUTE */]: "Absolute links are not allowed by current configuration.",
3466
+ ["anchor" /* Style.ANCHOR */]: null,
3457
3467
  };
3458
3468
  function parseAllow(value) {
3459
3469
  if (typeof value === "boolean") {
@@ -3526,19 +3536,19 @@ class AllowedLinks extends Rule {
3526
3536
  const link = event.value.toString();
3527
3537
  const style = this.getStyle(link);
3528
3538
  switch (style) {
3529
- case "anchor" /* ANCHOR */:
3539
+ case "anchor" /* Style.ANCHOR */:
3530
3540
  /* anchor links are always allowed by this rule */
3531
3541
  break;
3532
- case "absolute" /* ABSOLUTE */:
3542
+ case "absolute" /* Style.ABSOLUTE */:
3533
3543
  this.handleAbsolute(link, event, style);
3534
3544
  break;
3535
- case "external" /* EXTERNAL */:
3545
+ case "external" /* Style.EXTERNAL */:
3536
3546
  this.handleExternal(link, event, style);
3537
3547
  break;
3538
- case "relative-base" /* RELATIVE_BASE */:
3548
+ case "relative-base" /* Style.RELATIVE_BASE */:
3539
3549
  this.handleRelativeBase(link, event, style);
3540
3550
  break;
3541
- case "relative-path" /* RELATIVE_PATH */:
3551
+ case "relative-path" /* Style.RELATIVE_PATH */:
3542
3552
  this.handleRelativePath(link, event, style);
3543
3553
  break;
3544
3554
  }
@@ -3556,21 +3566,21 @@ class AllowedLinks extends Rule {
3556
3566
  getStyle(value) {
3557
3567
  /* http://example.net or //example.net */
3558
3568
  if (value.match(/^([a-z]+:)?\/\//g)) {
3559
- return "external" /* EXTERNAL */;
3569
+ return "external" /* Style.EXTERNAL */;
3560
3570
  }
3561
3571
  switch (value[0]) {
3562
3572
  /* /foo/bar */
3563
3573
  case "/":
3564
- return "absolute" /* ABSOLUTE */;
3574
+ return "absolute" /* Style.ABSOLUTE */;
3565
3575
  /* ../foo/bar */
3566
3576
  case ".":
3567
- return "relative-path" /* RELATIVE_PATH */;
3577
+ return "relative-path" /* Style.RELATIVE_PATH */;
3568
3578
  /* #foo */
3569
3579
  case "#":
3570
- return "anchor" /* ANCHOR */;
3580
+ return "anchor" /* Style.ANCHOR */;
3571
3581
  /* foo/bar */
3572
3582
  default:
3573
- return "relative-base" /* RELATIVE_BASE */;
3583
+ return "relative-base" /* Style.RELATIVE_BASE */;
3574
3584
  }
3575
3585
  }
3576
3586
  handleAbsolute(target, event, style) {
@@ -3834,9 +3844,14 @@ class AttrCase extends Rule {
3834
3844
  return;
3835
3845
  }
3836
3846
  const letters = event.key.replace(/[^a-z]+/gi, "");
3837
- if (!this.style.match(letters)) {
3838
- this.report(event.target, `Attribute "${event.key}" should be ${this.style.name}`, event.keyLocation);
3847
+ if (this.style.match(letters)) {
3848
+ return;
3839
3849
  }
3850
+ this.report({
3851
+ node: event.target,
3852
+ message: `Attribute "${event.key}" should be ${this.style.name}`,
3853
+ location: event.keyLocation,
3854
+ });
3840
3855
  });
3841
3856
  }
3842
3857
  isIgnored(node) {
@@ -5136,6 +5151,11 @@ class ElementName extends Rule {
5136
5151
  }
5137
5152
  }
5138
5153
 
5154
+ var ErrorKind;
5155
+ (function (ErrorKind) {
5156
+ ErrorKind["CONTENT"] = "content";
5157
+ ErrorKind["DESCENDANT"] = "descendant";
5158
+ })(ErrorKind || (ErrorKind = {}));
5139
5159
  function getTransparentChildren(node, transparent) {
5140
5160
  if (typeof transparent === "boolean") {
5141
5161
  return node.childElements;
@@ -5149,10 +5169,28 @@ function getTransparentChildren(node, transparent) {
5149
5169
  });
5150
5170
  }
5151
5171
  }
5172
+ function getRuleDescription$1(context) {
5173
+ if (!context) {
5174
+ return [
5175
+ "Some elements has restrictions on what content is allowed.",
5176
+ "This can include both direct children or descendant elements.",
5177
+ ];
5178
+ }
5179
+ switch (context.kind) {
5180
+ case ErrorKind.CONTENT:
5181
+ return [
5182
+ `The \`${context.child}\` element is not permitted as content under the parent \`${context.parent}\` element.`,
5183
+ ];
5184
+ case ErrorKind.DESCENDANT:
5185
+ return [
5186
+ `The \`${context.child}\` element is not permitted as a descendant of the \`${context.ancestor}\` element.`,
5187
+ ];
5188
+ }
5189
+ }
5152
5190
  class ElementPermittedContent extends Rule {
5153
- documentation() {
5191
+ documentation(context) {
5154
5192
  return {
5155
- description: "Some elements has restrictions on what content is allowed. This can include both direct children or descendant elements.",
5193
+ description: getRuleDescription$1(context).join("\n"),
5156
5194
  url: ruleDocumentationUrl("@/rules/element-permitted-content.ts"),
5157
5195
  };
5158
5196
  }
@@ -5161,8 +5199,9 @@ class ElementPermittedContent extends Rule {
5161
5199
  const doc = event.document;
5162
5200
  doc.visitDepthFirst((node) => {
5163
5201
  const parent = node.parent;
5164
- /* dont verify root element, assume any element is allowed */
5165
- if (!parent || parent.isRootElement()) {
5202
+ /* istanbul ignore next: satisfy typescript but will visitDepthFirst()
5203
+ * will not yield nodes without a parent */
5204
+ if (!parent) {
5166
5205
  return;
5167
5206
  }
5168
5207
  /* Run each validation step, stop as soon as any errors are
@@ -5172,7 +5211,6 @@ class ElementPermittedContent extends Rule {
5172
5211
  [
5173
5212
  () => this.validatePermittedContent(node, parent),
5174
5213
  () => this.validatePermittedDescendant(node, parent),
5175
- () => this.validatePermittedAncestors(node),
5176
5214
  ].some((fn) => fn());
5177
5215
  });
5178
5216
  });
@@ -5189,7 +5227,14 @@ class ElementPermittedContent extends Rule {
5189
5227
  }
5190
5228
  validatePermittedContentImpl(cur, parent, rules) {
5191
5229
  if (!Validator.validatePermitted(cur, rules)) {
5192
- this.report(cur, `Element <${cur.tagName}> is not permitted as content in ${parent.annotatedName}`);
5230
+ const child = `<${cur.tagName}>`;
5231
+ const message = `${child} element is not permitted as content under ${parent.annotatedName}`;
5232
+ const context = {
5233
+ kind: ErrorKind.CONTENT,
5234
+ parent: parent.annotatedName,
5235
+ child,
5236
+ };
5237
+ this.report(cur, message, null, context);
5193
5238
  return true;
5194
5239
  }
5195
5240
  /* for transparent elements all/listed children must be validated against
@@ -5220,21 +5265,15 @@ class ElementPermittedContent extends Rule {
5220
5265
  if (Validator.validatePermitted(node, rules)) {
5221
5266
  continue;
5222
5267
  }
5223
- this.report(node, `Element <${node.tagName}> is not permitted as descendant of ${cur.annotatedName}`);
5224
- return true;
5225
- }
5226
- return false;
5227
- }
5228
- validatePermittedAncestors(node) {
5229
- if (!node.meta) {
5230
- return false;
5231
- }
5232
- const rules = node.meta.requiredAncestors;
5233
- if (!rules) {
5234
- return false;
5235
- }
5236
- if (!Validator.validateAncestors(node, rules)) {
5237
- this.report(node, `Element <${node.tagName}> requires an "${rules[0]}" ancestor`);
5268
+ const child = `<${node.tagName}>`;
5269
+ const ancestor = cur.annotatedName;
5270
+ const message = `${child} element is not permitted as a descendant of ${ancestor}`;
5271
+ const context = {
5272
+ kind: ErrorKind.DESCENDANT,
5273
+ ancestor,
5274
+ child,
5275
+ };
5276
+ this.report(node, message, null, context);
5238
5277
  return true;
5239
5278
  }
5240
5279
  return false;
@@ -5301,6 +5340,152 @@ class ElementPermittedOrder extends Rule {
5301
5340
  }
5302
5341
  }
5303
5342
 
5343
+ const CACHE_KEY = Symbol(classifyNodeText.name);
5344
+ var TextClassification;
5345
+ (function (TextClassification) {
5346
+ TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
5347
+ TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
5348
+ TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
5349
+ })(TextClassification || (TextClassification = {}));
5350
+ /**
5351
+ * Checks text content of an element.
5352
+ *
5353
+ * Any text is considered including text from descendant elements. Whitespace is
5354
+ * ignored.
5355
+ *
5356
+ * If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
5357
+ */
5358
+ function classifyNodeText(node) {
5359
+ if (node.cacheExists(CACHE_KEY)) {
5360
+ return node.cacheGet(CACHE_KEY);
5361
+ }
5362
+ const text = findTextNodes(node);
5363
+ /* if any text is dynamic classify as dynamic */
5364
+ if (text.some((cur) => cur.isDynamic)) {
5365
+ return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
5366
+ }
5367
+ /* if any text has non-whitespace character classify as static */
5368
+ if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
5369
+ return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
5370
+ }
5371
+ /* default to empty */
5372
+ return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
5373
+ }
5374
+ function findTextNodes(node) {
5375
+ let text = [];
5376
+ for (const child of node.childNodes) {
5377
+ switch (child.nodeType) {
5378
+ case NodeType.TEXT_NODE:
5379
+ text.push(child);
5380
+ break;
5381
+ case NodeType.ELEMENT_NODE:
5382
+ text = text.concat(findTextNodes(child));
5383
+ break;
5384
+ }
5385
+ }
5386
+ return text;
5387
+ }
5388
+
5389
+ function hasAltText(image) {
5390
+ const alt = image.getAttribute("alt");
5391
+ /* missing or boolean */
5392
+ if (alt === null || alt.value === null) {
5393
+ return false;
5394
+ }
5395
+ return alt.isDynamic || alt.value.toString() !== "";
5396
+ }
5397
+
5398
+ function hasAriaLabel(node) {
5399
+ const label = node.getAttribute("aria-label");
5400
+ /* missing or boolean */
5401
+ if (label === null || label.value === null) {
5402
+ return false;
5403
+ }
5404
+ return label.isDynamic || label.value.toString() !== "";
5405
+ }
5406
+
5407
+ /**
5408
+ * Joins a list of words into natural language.
5409
+ *
5410
+ * - `["foo"]` becomes `"foo"`
5411
+ * - `["foo", "bar"]` becomes `"foo or bar"`
5412
+ * - `["foo", "bar", "baz"]` becomes `"foo, bar or baz"`
5413
+ * - and so on...
5414
+ *
5415
+ * @internal
5416
+ * @param values - List of words to join
5417
+ * @param conjunction - Conjunction for the last element.
5418
+ * @returns String with the words naturally joined with a conjunction.
5419
+ */
5420
+ function naturalJoin(values, conjunction = "or") {
5421
+ switch (values.length) {
5422
+ case 0:
5423
+ return "";
5424
+ case 1:
5425
+ return values[0];
5426
+ case 2:
5427
+ return `${values[0]} ${conjunction} ${values[1]}`;
5428
+ default:
5429
+ return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.slice(-1)[0]}`;
5430
+ }
5431
+ }
5432
+
5433
+ function isTagnameOnly(value) {
5434
+ return Boolean(value.match(/^[a-zA-Z0-9-]+$/));
5435
+ }
5436
+ function getRuleDescription(context) {
5437
+ if (!context) {
5438
+ return [
5439
+ "Some elements has restrictions on what content is allowed.",
5440
+ "This can include both direct children or descendant elements.",
5441
+ ];
5442
+ }
5443
+ const escaped = context.ancestor.map((it) => `\`${it}\``);
5444
+ return [`The \`${context.child}\` element requires a ${naturalJoin(escaped)} ancestor.`];
5445
+ }
5446
+ class ElementRequiredAncestor extends Rule {
5447
+ documentation(context) {
5448
+ return {
5449
+ description: getRuleDescription(context).join("\n"),
5450
+ url: ruleDocumentationUrl("@/rules/element-required-ancestor.ts"),
5451
+ };
5452
+ }
5453
+ setup() {
5454
+ this.on("dom:ready", (event) => {
5455
+ const doc = event.document;
5456
+ doc.visitDepthFirst((node) => {
5457
+ const parent = node.parent;
5458
+ /* istanbul ignore next: satisfy typescript but will visitDepthFirst()
5459
+ * will not yield nodes without a parent */
5460
+ if (!parent) {
5461
+ return;
5462
+ }
5463
+ this.validateRequiredAncestors(node);
5464
+ });
5465
+ });
5466
+ }
5467
+ validateRequiredAncestors(node) {
5468
+ if (!node.meta) {
5469
+ return;
5470
+ }
5471
+ const rules = node.meta.requiredAncestors;
5472
+ if (!rules) {
5473
+ return;
5474
+ }
5475
+ if (Validator.validateAncestors(node, rules)) {
5476
+ return;
5477
+ }
5478
+ const ancestor = rules.map((it) => (isTagnameOnly(it) ? `<${it}>` : `"${it}"`));
5479
+ const child = `<${node.tagName}>`;
5480
+ const message = `<${node.tagName}> element requires a ${naturalJoin(ancestor)} ancestor`;
5481
+ const context = {
5482
+ ancestor,
5483
+ child,
5484
+ };
5485
+ this.report(node, message, null, context);
5486
+ }
5487
+ }
5488
+
5304
5489
  class ElementRequiredAttributes extends Rule {
5305
5490
  documentation(context) {
5306
5491
  const docs = {
@@ -5378,52 +5563,6 @@ class ElementRequiredContent extends Rule {
5378
5563
  }
5379
5564
  }
5380
5565
 
5381
- const CACHE_KEY = Symbol(classifyNodeText.name);
5382
- var TextClassification;
5383
- (function (TextClassification) {
5384
- TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
5385
- TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
5386
- TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
5387
- })(TextClassification || (TextClassification = {}));
5388
- /**
5389
- * Checks text content of an element.
5390
- *
5391
- * Any text is considered including text from descendant elements. Whitespace is
5392
- * ignored.
5393
- *
5394
- * If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
5395
- */
5396
- function classifyNodeText(node) {
5397
- if (node.cacheExists(CACHE_KEY)) {
5398
- return node.cacheGet(CACHE_KEY);
5399
- }
5400
- const text = findTextNodes(node);
5401
- /* if any text is dynamic classify as dynamic */
5402
- if (text.some((cur) => cur.isDynamic)) {
5403
- return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
5404
- }
5405
- /* if any text has non-whitespace character classify as static */
5406
- if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
5407
- return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
5408
- }
5409
- /* default to empty */
5410
- return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
5411
- }
5412
- function findTextNodes(node) {
5413
- let text = [];
5414
- for (const child of node.childNodes) {
5415
- switch (child.nodeType) {
5416
- case NodeType.TEXT_NODE:
5417
- text.push(child);
5418
- break;
5419
- case NodeType.ELEMENT_NODE:
5420
- text = text.concat(findTextNodes(child));
5421
- break;
5422
- }
5423
- }
5424
- return text;
5425
- }
5426
-
5427
5566
  const selector = ["h1", "h2", "h3", "h4", "h5", "h6"].join(",");
5428
5567
  class EmptyHeading extends Rule {
5429
5568
  documentation() {
@@ -7604,24 +7743,6 @@ class TelNonBreaking extends Rule {
7604
7743
  }
7605
7744
  }
7606
7745
 
7607
- function hasAltText(image) {
7608
- const alt = image.getAttribute("alt");
7609
- /* missing or boolean */
7610
- if (alt === null || alt.value === null) {
7611
- return false;
7612
- }
7613
- return alt.isDynamic || alt.value.toString() !== "";
7614
- }
7615
-
7616
- function hasAriaLabel(node) {
7617
- const label = node.getAttribute("aria-label");
7618
- /* missing or boolean */
7619
- if (label === null || label.value === null) {
7620
- return false;
7621
- }
7622
- return label.isDynamic || label.value.toString() !== "";
7623
- }
7624
-
7625
7746
  /**
7626
7747
  * Check if attribute is present and non-empty or dynamic.
7627
7748
  */
@@ -9799,13 +9920,13 @@ class VoidStyle extends Rule {
9799
9920
  return;
9800
9921
  }
9801
9922
  if (this.shouldBeOmitted(node)) {
9802
- this.report(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
9923
+ this.reportError(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
9803
9924
  }
9804
9925
  if (this.shouldBeSelfClosed(node)) {
9805
- this.report(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
9926
+ this.reportError(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
9806
9927
  }
9807
9928
  }
9808
- report(node, message) {
9929
+ reportError(node, message) {
9809
9930
  const context = {
9810
9931
  style: this.style,
9811
9932
  tagName: node.tagName,
@@ -10117,6 +10238,7 @@ const bundledRules = {
10117
10238
  "element-permitted-content": ElementPermittedContent,
10118
10239
  "element-permitted-occurrences": ElementPermittedOccurrences,
10119
10240
  "element-permitted-order": ElementPermittedOrder,
10241
+ "element-required-ancestor": ElementRequiredAncestor,
10120
10242
  "element-required-attributes": ElementRequiredAttributes,
10121
10243
  "element-required-content": ElementRequiredContent,
10122
10244
  "empty-heading": EmptyHeading,
@@ -10224,6 +10346,7 @@ const config$1 = {
10224
10346
  "element-permitted-content": "error",
10225
10347
  "element-permitted-occurrences": "error",
10226
10348
  "element-permitted-order": "error",
10349
+ "element-required-ancestor": "error",
10227
10350
  "element-required-attributes": "error",
10228
10351
  "element-required-content": "error",
10229
10352
  "empty-heading": "error",
@@ -10282,6 +10405,7 @@ const config = {
10282
10405
  "element-permitted-content": "error",
10283
10406
  "element-permitted-occurrences": "error",
10284
10407
  "element-permitted-order": "error",
10408
+ "element-required-ancestor": "error",
10285
10409
  "element-required-attributes": "error",
10286
10410
  "element-required-content": "error",
10287
10411
  "multiple-labeled-controls": "error",
@@ -10361,6 +10485,7 @@ class ResolvedConfig {
10361
10485
  });
10362
10486
  }
10363
10487
  catch (err) {
10488
+ /* istanbul ignore next: only used as a fallback */
10364
10489
  const message = err instanceof Error ? err.message : String(err);
10365
10490
  throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
10366
10491
  }
@@ -10373,7 +10498,7 @@ class ResolvedConfig {
10373
10498
  * Wrapper around [[transformSource]] which reads a file before passing it
10374
10499
  * as-is to transformSource.
10375
10500
  *
10376
- * @param source - Filename to transform (according to configured
10501
+ * @param filename - Filename to transform (according to configured
10377
10502
  * transformations)
10378
10503
  * @returns A list of transformed sources ready for validation.
10379
10504
  */
@@ -10529,7 +10654,9 @@ class Config {
10529
10654
  var _a;
10530
10655
  const valid = validator(configData);
10531
10656
  if (!valid) {
10532
- throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema, (_a = validator.errors) !== null && _a !== void 0 ? _a : []);
10657
+ throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema,
10658
+ /* istanbul ignore next: will be set when a validation error has occurred */
10659
+ (_a = validator.errors) !== null && _a !== void 0 ? _a : []);
10533
10660
  }
10534
10661
  if (configData.rules) {
10535
10662
  const normalizedRules = Config.getRulesObject(configData.rules);
@@ -10638,6 +10765,7 @@ class Config {
10638
10765
  metaTable.loadFromObject(legacyRequire(entry));
10639
10766
  }
10640
10767
  catch (err) {
10768
+ /* istanbul ignore next: only used as a fallback */
10641
10769
  const message = err instanceof Error ? err.message : String(err);
10642
10770
  throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, ensureError(err));
10643
10771
  }
@@ -10659,6 +10787,7 @@ class Config {
10659
10787
  *
10660
10788
  * @internal primary purpose is unittests
10661
10789
  */
10790
+ /* istanbul ignore next: used for testing only */
10662
10791
  get() {
10663
10792
  const config = { ...this.config };
10664
10793
  if (config.elements) {
@@ -10681,6 +10810,7 @@ class Config {
10681
10810
  */
10682
10811
  getRules() {
10683
10812
  var _a;
10813
+ /* istanbul ignore next: only used as a fallback */
10684
10814
  return Config.getRulesObject((_a = this.config.rules) !== null && _a !== void 0 ? _a : {});
10685
10815
  }
10686
10816
  static getRulesObject(src) {
@@ -10715,6 +10845,7 @@ class Config {
10715
10845
  return plugin;
10716
10846
  }
10717
10847
  catch (err) {
10848
+ /* istanbul ignore next: only used as a fallback */
10718
10849
  const message = err instanceof Error ? err.message : String(err);
10719
10850
  throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, ensureError(err));
10720
10851
  }
@@ -11630,9 +11761,9 @@ class Reporter {
11630
11761
  if (!(location.filename in this.result)) {
11631
11762
  this.result[location.filename] = [];
11632
11763
  }
11633
- this.result[location.filename].push({
11764
+ const ruleUrl = (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url;
11765
+ const entry = {
11634
11766
  ruleId: rule.name,
11635
- ruleUrl: (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url,
11636
11767
  severity,
11637
11768
  message,
11638
11769
  offset: location.offset,
@@ -11642,8 +11773,14 @@ class Reporter {
11642
11773
  selector() {
11643
11774
  return node ? node.generateSelector() : null;
11644
11775
  },
11645
- context,
11646
- });
11776
+ };
11777
+ if (ruleUrl) {
11778
+ entry.ruleUrl = ruleUrl;
11779
+ }
11780
+ if (context) {
11781
+ entry.context = context;
11782
+ }
11783
+ this.result[location.filename].push(entry);
11647
11784
  }
11648
11785
  addManual(filename, message) {
11649
11786
  if (!(filename in this.result)) {
@@ -12057,7 +12194,7 @@ class Engine {
12057
12194
  offset: location.offset,
12058
12195
  line: location.line,
12059
12196
  column: location.column,
12060
- size: location.size || 0,
12197
+ size: location.size,
12061
12198
  selector: () => null,
12062
12199
  });
12063
12200
  }
@@ -12487,12 +12624,12 @@ class FileSystemConfigLoader extends ConfigLoader {
12487
12624
  * `null` if no configuration files are found.
12488
12625
  */
12489
12626
  fromFilename(filename) {
12490
- var _a;
12491
12627
  if (filename === "inline") {
12492
12628
  return null;
12493
12629
  }
12494
- if (this.cache.has(filename)) {
12495
- return (_a = this.cache.get(filename)) !== null && _a !== void 0 ? _a : null;
12630
+ const cache = this.cache.get(filename);
12631
+ if (cache) {
12632
+ return cache;
12496
12633
  }
12497
12634
  let found = false;
12498
12635
  let current = path__default["default"].resolve(path__default["default"].dirname(filename));