html-validate 7.18.1 → 8.0.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
@@ -1,15 +1,15 @@
1
- import fs from 'fs';
2
- import * as espree from 'espree';
3
- import * as walk from 'acorn-walk';
4
1
  import path from 'path';
5
2
  import semver from 'semver';
6
3
  import kleur from 'kleur';
4
+ import fs$1 from 'node:fs';
5
+ import path$1 from 'node:path';
7
6
  import Ajv from 'ajv';
8
7
  import deepmerge from 'deepmerge';
9
8
  import { e as entities$1, h as html5, b as bundledElements } from './elements.js';
10
- import { createRequire } from 'module';
11
9
  import { i as isKeywordIgnored, C as CaseStyle, n as naturalJoin, c as classifyNodeText, T as TextClassification, h as hasAltText, p as partition, a as isHTMLHidden, b as isAriaHidden, d as hasAccessibleName, k as keywordPatternMatcher, e as inAccessibilityTree, f as hasAriaLabel } from './rules-helper.js';
10
+ import fs from 'fs';
12
11
  import betterAjvErrors from '@sidvind/better-ajv-errors';
12
+ import { createRequire } from 'module';
13
13
  import { codeFrameColumns } from '@babel/code-frame';
14
14
  import { stylish as stylish$2 } from '@html-validate/stylish';
15
15
 
@@ -3157,232 +3157,11 @@ var configurationSchema = {
3157
3157
  properties: properties
3158
3158
  };
3159
3159
 
3160
- /* eslint-disable @typescript-eslint/no-non-null-assertion -- declarations say
3161
- * location fields are optional but they are always present when `{loc: true}` */
3162
- function joinTemplateLiteral(nodes) {
3163
- let offset = nodes[0].start + 1;
3164
- let output = "";
3165
- for (const node of nodes) {
3166
- output += " ".repeat(node.start + 1 - offset);
3167
- output += node.value.raw;
3168
- offset = node.end - 2;
3169
- }
3170
- return output;
3171
- }
3172
- /**
3173
- * Compute source offset from line and column and the given markup.
3174
- *
3175
- * @param position - Line and column.
3176
- * @param data - Source markup.
3177
- * @returns The byte offset into the markup which line and column corresponds to.
3178
- */
3179
- function computeOffset(position, data) {
3180
- let line = position.line;
3181
- let column = position.column + 1;
3182
- for (let i = 0; i < data.length; i++) {
3183
- if (line > 1) {
3184
- /* not yet on the correct line */
3185
- if (data[i] === "\n") {
3186
- line--;
3187
- }
3188
- }
3189
- else if (column > 1) {
3190
- /* not yet on the correct column */
3191
- column--;
3192
- }
3193
- else {
3194
- /* line/column found, return current position */
3195
- return i;
3196
- }
3197
- }
3198
- /* istanbul ignore next: should never reach this line unless espree passes bad
3199
- * positions, no sane way to test */
3200
- throw new Error("Failed to compute location offset from position");
3201
- }
3202
- function extractLiteral(node, filename, data) {
3203
- switch (node.type) {
3204
- /* ignored nodes */
3205
- case "FunctionExpression":
3206
- case "Identifier":
3207
- return null;
3208
- case "Literal":
3209
- if (typeof node.value !== "string") {
3210
- return null;
3211
- }
3212
- return {
3213
- data: node.value.toString(),
3214
- filename,
3215
- line: node.loc.start.line,
3216
- column: node.loc.start.column + 1,
3217
- offset: computeOffset(node.loc.start, data) + 1,
3218
- };
3219
- case "TemplateLiteral":
3220
- return {
3221
- data: joinTemplateLiteral(node.quasis),
3222
- filename,
3223
- line: node.loc.start.line,
3224
- column: node.loc.start.column + 1,
3225
- offset: computeOffset(node.loc.start, data) + 1,
3226
- };
3227
- case "TaggedTemplateExpression":
3228
- return {
3229
- data: joinTemplateLiteral(node.quasi.quasis),
3230
- filename,
3231
- line: node.quasi.loc.start.line,
3232
- column: node.quasi.loc.start.column + 1,
3233
- offset: computeOffset(node.quasi.loc.start, data) + 1,
3234
- };
3235
- case "ArrowFunctionExpression": {
3236
- const whitelist = ["Literal", "TemplateLiteral"];
3237
- if (whitelist.includes(node.body.type)) {
3238
- return extractLiteral(node.body, filename, data);
3239
- }
3240
- else {
3241
- return null;
3242
- }
3243
- }
3244
- /* istanbul ignore next: this only provides a better error, all currently known nodes are tested */
3245
- default: {
3246
- const loc = node.loc.start;
3247
- const context = `${filename}:${loc.line}:${loc.column}`;
3248
- throw new Error(`Unhandled node type "${node.type}" at "${context}" in extractLiteral`);
3249
- }
3250
- }
3251
- }
3252
- function compareKey(node, key, filename) {
3253
- switch (node.type) {
3254
- case "Identifier":
3255
- return node.name === key;
3256
- case "Literal":
3257
- return node.value === key;
3258
- /* istanbul ignore next: this only provides a better error, all currently known nodes are tested */
3259
- default: {
3260
- const loc = node.loc.start;
3261
- const context = `${filename}:${loc.line}:${loc.column}`;
3262
- throw new Error(`Unhandled node type "${node.type}" at "${context}" in compareKey`);
3263
- }
3264
- }
3265
- }
3266
- /**
3267
- * @public
3268
- */
3269
- class TemplateExtractor {
3270
- constructor(ast, filename, data) {
3271
- this.ast = ast;
3272
- this.filename = filename;
3273
- this.data = data;
3274
- }
3275
- static fromFilename(filename) {
3276
- const source = fs.readFileSync(filename, "utf-8");
3277
- const ast = espree.parse(source, {
3278
- ecmaVersion: 2017,
3279
- sourceType: "module",
3280
- loc: true,
3281
- });
3282
- return new TemplateExtractor(ast, filename, source);
3283
- }
3284
- /**
3285
- * Create a new [[TemplateExtractor]] from javascript source code.
3286
- *
3287
- * `Source` offsets will be relative to the string, i.e. offset 0 is the first
3288
- * character of the string. If the string is only a subset of a larger string
3289
- * the offsets must be adjusted manually.
3290
- *
3291
- * @param source - Source code.
3292
- * @param filename - Optional filename to set in the resulting
3293
- * `Source`. Defauls to `"inline"`.
3294
- */
3295
- static fromString(source, filename) {
3296
- const ast = espree.parse(source, {
3297
- ecmaVersion: 2017,
3298
- sourceType: "module",
3299
- loc: true,
3300
- });
3301
- return new TemplateExtractor(ast, filename || "inline", source);
3302
- }
3303
- /**
3304
- * Convenience function to create a [[Source]] instance from an existing file.
3305
- *
3306
- * @param filename - Filename with javascript source code. The file must exist
3307
- * and be readable by the user.
3308
- * @returns An array of Source's suitable for passing to [[Engine]] linting
3309
- * functions.
3310
- */
3311
- static createSource(filename) {
3312
- const data = fs.readFileSync(filename, "utf-8");
3313
- return [
3314
- {
3315
- column: 1,
3316
- data,
3317
- filename,
3318
- line: 1,
3319
- offset: 0,
3320
- },
3321
- ];
3322
- }
3323
- /**
3324
- * Extract object properties.
3325
- *
3326
- * Given a key `"template"` this method finds all objects literals with a
3327
- * `"template"` property and creates a [[Source]] instance with proper offsets
3328
- * with the value of the property. For instance:
3329
- *
3330
- * ```
3331
- * const myObj = {
3332
- * foo: 'bar',
3333
- * };
3334
- * ```
3335
- *
3336
- * The above snippet would yield a `Source` with the content `bar`.
3337
- *
3338
- */
3339
- extractObjectProperty(key) {
3340
- const result = [];
3341
- const { filename, data } = this;
3342
- const node = this.ast;
3343
- walk.simple(node, {
3344
- Property(node) {
3345
- if (compareKey(node.key, key, filename)) {
3346
- const source = extractLiteral(node.value, filename, data);
3347
- if (source) {
3348
- source.filename = filename;
3349
- result.push(source);
3350
- }
3351
- }
3352
- },
3353
- });
3354
- return result;
3355
- }
3356
- }
3357
-
3358
3160
  var TRANSFORMER_API;
3359
3161
  (function (TRANSFORMER_API) {
3360
3162
  TRANSFORMER_API[TRANSFORMER_API["VERSION"] = 1] = "VERSION";
3361
3163
  })(TRANSFORMER_API || (TRANSFORMER_API = {}));
3362
3164
 
3363
- /**
3364
- * Similar to `require(..)` but removes the cached copy first.
3365
- */
3366
- function requireUncached(require, moduleId) {
3367
- const filename = require.resolve(moduleId);
3368
- /* remove references from the parent module to prevent memory leak */
3369
- const m = require.cache[filename];
3370
- if (m && m.parent) {
3371
- const { parent } = m;
3372
- for (let i = parent.children.length - 1; i >= 0; i--) {
3373
- if (parent.children[i].id === filename) {
3374
- parent.children.splice(i, 1);
3375
- }
3376
- }
3377
- }
3378
- /* remove old module from cache */
3379
- delete require.cache[filename];
3380
- /* eslint-disable-next-line import/no-dynamic-require, security/detect-non-literal-require -- as expected but should be moved to upcoming resolver class */
3381
- return require(filename);
3382
- }
3383
-
3384
- const legacyRequire = createRequire(import.meta.url);
3385
-
3386
3165
  /**
3387
3166
  * @public
3388
3167
  */
@@ -3400,11 +3179,6 @@ function parseSeverity(value) {
3400
3179
  case 0:
3401
3180
  case "off":
3402
3181
  return Severity.DISABLED;
3403
- /* istanbul ignore next: deprecated code which will be removed later */
3404
- case "disable":
3405
- // eslint-disable-next-line no-console -- expected to log
3406
- console.warn(`Deprecated alias "disabled" will be removed, replace with severity "off"`);
3407
- return Severity.DISABLED;
3408
3182
  case 1:
3409
3183
  case "warn":
3410
3184
  return Severity.WARN;
@@ -3739,6 +3513,8 @@ class Rule {
3739
3513
  * Called when requesting additional documentation for a rule. Some rules
3740
3514
  * provide additional context to provide context-aware suggestions.
3741
3515
  *
3516
+ * @public
3517
+ * @virtual
3742
3518
  * @param context - Error context given by a reported error.
3743
3519
  * @returns Rule documentation and url with additional details or `null` if no
3744
3520
  * additional documentation is available.
@@ -3749,15 +3525,15 @@ class Rule {
3749
3525
  }
3750
3526
  }
3751
3527
 
3752
- var Style$2;
3528
+ var Style$1;
3753
3529
  (function (Style) {
3754
3530
  Style["EXTERNAL"] = "external";
3755
3531
  Style["RELATIVE_BASE"] = "relative-base";
3756
3532
  Style["RELATIVE_PATH"] = "relative-path";
3757
3533
  Style["ABSOLUTE"] = "absolute";
3758
3534
  Style["ANCHOR"] = "anchor";
3759
- })(Style$2 || (Style$2 = {}));
3760
- const defaults$w = {
3535
+ })(Style$1 || (Style$1 = {}));
3536
+ const defaults$v = {
3761
3537
  allowExternal: true,
3762
3538
  allowRelative: true,
3763
3539
  allowAbsolute: true,
@@ -3770,11 +3546,11 @@ const mapping$1 = {
3770
3546
  script: "src",
3771
3547
  };
3772
3548
  const description = {
3773
- [Style$2.EXTERNAL]: "External links are not allowed by current configuration.",
3774
- [Style$2.RELATIVE_BASE]: "Links relative to <base> are not allowed by current configuration.",
3775
- [Style$2.RELATIVE_PATH]: "Relative links are not allowed by current configuration.",
3776
- [Style$2.ABSOLUTE]: "Absolute links are not allowed by current configuration.",
3777
- [Style$2.ANCHOR]: null,
3549
+ [Style$1.EXTERNAL]: "External links are not allowed by current configuration.",
3550
+ [Style$1.RELATIVE_BASE]: "Links relative to <base> are not allowed by current configuration.",
3551
+ [Style$1.RELATIVE_PATH]: "Relative links are not allowed by current configuration.",
3552
+ [Style$1.ABSOLUTE]: "Absolute links are not allowed by current configuration.",
3553
+ [Style$1.ANCHOR]: null,
3778
3554
  };
3779
3555
  function parseAllow(value) {
3780
3556
  if (typeof value === "boolean") {
@@ -3801,7 +3577,7 @@ function matchList(value, list) {
3801
3577
  }
3802
3578
  class AllowedLinks extends Rule {
3803
3579
  constructor(options) {
3804
- super({ ...defaults$w, ...options });
3580
+ super({ ...defaults$v, ...options });
3805
3581
  this.allowExternal = parseAllow(this.options.allowExternal);
3806
3582
  this.allowRelative = parseAllow(this.options.allowRelative);
3807
3583
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -3847,19 +3623,19 @@ class AllowedLinks extends Rule {
3847
3623
  const link = event.value.toString();
3848
3624
  const style = this.getStyle(link);
3849
3625
  switch (style) {
3850
- case Style$2.ANCHOR:
3626
+ case Style$1.ANCHOR:
3851
3627
  /* anchor links are always allowed by this rule */
3852
3628
  break;
3853
- case Style$2.ABSOLUTE:
3629
+ case Style$1.ABSOLUTE:
3854
3630
  this.handleAbsolute(link, event, style);
3855
3631
  break;
3856
- case Style$2.EXTERNAL:
3632
+ case Style$1.EXTERNAL:
3857
3633
  this.handleExternal(link, event, style);
3858
3634
  break;
3859
- case Style$2.RELATIVE_BASE:
3635
+ case Style$1.RELATIVE_BASE:
3860
3636
  this.handleRelativeBase(link, event, style);
3861
3637
  break;
3862
- case Style$2.RELATIVE_PATH:
3638
+ case Style$1.RELATIVE_PATH:
3863
3639
  this.handleRelativePath(link, event, style);
3864
3640
  break;
3865
3641
  }
@@ -3877,21 +3653,21 @@ class AllowedLinks extends Rule {
3877
3653
  getStyle(value) {
3878
3654
  /* http://example.net or //example.net */
3879
3655
  if (value.match(/^([a-z]+:)?\/\//g)) {
3880
- return Style$2.EXTERNAL;
3656
+ return Style$1.EXTERNAL;
3881
3657
  }
3882
3658
  switch (value[0]) {
3883
3659
  /* /foo/bar */
3884
3660
  case "/":
3885
- return Style$2.ABSOLUTE;
3661
+ return Style$1.ABSOLUTE;
3886
3662
  /* ../foo/bar */
3887
3663
  case ".":
3888
- return Style$2.RELATIVE_PATH;
3664
+ return Style$1.RELATIVE_PATH;
3889
3665
  /* #foo */
3890
3666
  case "#":
3891
- return Style$2.ANCHOR;
3667
+ return Style$1.ANCHOR;
3892
3668
  /* foo/bar */
3893
3669
  default:
3894
- return Style$2.RELATIVE_BASE;
3670
+ return Style$1.RELATIVE_BASE;
3895
3671
  }
3896
3672
  }
3897
3673
  handleAbsolute(target, event, style) {
@@ -3949,7 +3725,7 @@ var RuleContext$1;
3949
3725
  RuleContext["MISSING_ALT"] = "missing-alt";
3950
3726
  RuleContext["MISSING_HREF"] = "missing-href";
3951
3727
  })(RuleContext$1 || (RuleContext$1 = {}));
3952
- const defaults$v = {
3728
+ const defaults$u = {
3953
3729
  accessible: true,
3954
3730
  };
3955
3731
  function findByTarget(target, siblings) {
@@ -3987,7 +3763,7 @@ function getDescription$1(context) {
3987
3763
  }
3988
3764
  class AreaAlt extends Rule {
3989
3765
  constructor(options) {
3990
- super({ ...defaults$v, ...options });
3766
+ super({ ...defaults$u, ...options });
3991
3767
  }
3992
3768
  static schema() {
3993
3769
  return {
@@ -4167,13 +3943,13 @@ class ConfigError extends UserError {
4167
3943
  }
4168
3944
  }
4169
3945
 
4170
- const defaults$u = {
3946
+ const defaults$t = {
4171
3947
  style: "lowercase",
4172
3948
  ignoreForeign: true,
4173
3949
  };
4174
3950
  class AttrCase extends Rule {
4175
3951
  constructor(options) {
4176
- super({ ...defaults$u, ...options });
3952
+ super({ ...defaults$t, ...options });
4177
3953
  this.style = new CaseStyle(this.options.style, "attr-case");
4178
3954
  }
4179
3955
  static schema() {
@@ -4521,7 +4297,7 @@ class AttrDelimiter extends Rule {
4521
4297
  }
4522
4298
 
4523
4299
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4524
- const defaults$t = {
4300
+ const defaults$s = {
4525
4301
  pattern: DEFAULT_PATTERN,
4526
4302
  ignoreForeign: true,
4527
4303
  };
@@ -4558,7 +4334,7 @@ function generateDescription(name, pattern) {
4558
4334
  }
4559
4335
  class AttrPattern extends Rule {
4560
4336
  constructor(options) {
4561
- super({ ...defaults$t, ...options });
4337
+ super({ ...defaults$s, ...options });
4562
4338
  this.pattern = generateRegexp(this.options.pattern);
4563
4339
  }
4564
4340
  static schema() {
@@ -4619,7 +4395,7 @@ var QuoteStyle;
4619
4395
  QuoteStyle["AUTO_QUOTE"] = "auto";
4620
4396
  QuoteStyle["ANY_QUOTE"] = "any";
4621
4397
  })(QuoteStyle || (QuoteStyle = {}));
4622
- const defaults$s = {
4398
+ const defaults$r = {
4623
4399
  style: "auto",
4624
4400
  unquoted: false,
4625
4401
  };
@@ -4686,8 +4462,8 @@ class AttrQuotes extends Rule {
4686
4462
  };
4687
4463
  }
4688
4464
  constructor(options) {
4689
- super({ ...defaults$s, ...options });
4690
- this.style = parseStyle$4(this.options.style);
4465
+ super({ ...defaults$r, ...options });
4466
+ this.style = parseStyle$3(this.options.style);
4691
4467
  }
4692
4468
  setup() {
4693
4469
  this.on("attr", (event) => {
@@ -4734,7 +4510,7 @@ class AttrQuotes extends Rule {
4734
4510
  }
4735
4511
  }
4736
4512
  }
4737
- function parseStyle$4(style) {
4513
+ function parseStyle$3(style) {
4738
4514
  switch (style.toLowerCase()) {
4739
4515
  case "auto":
4740
4516
  return QuoteStyle.AUTO_QUOTE;
@@ -4856,13 +4632,13 @@ class AttributeAllowedValues extends Rule {
4856
4632
  }
4857
4633
  }
4858
4634
 
4859
- const defaults$r = {
4635
+ const defaults$q = {
4860
4636
  style: "omit",
4861
4637
  };
4862
4638
  class AttributeBooleanStyle extends Rule {
4863
4639
  constructor(options) {
4864
- super({ ...defaults$r, ...options });
4865
- this.hasInvalidStyle = parseStyle$3(this.options.style);
4640
+ super({ ...defaults$q, ...options });
4641
+ this.hasInvalidStyle = parseStyle$2(this.options.style);
4866
4642
  }
4867
4643
  static schema() {
4868
4644
  return {
@@ -4910,7 +4686,7 @@ class AttributeBooleanStyle extends Rule {
4910
4686
  return Boolean((_a = rules[attr.key]) === null || _a === void 0 ? void 0 : _a.boolean);
4911
4687
  }
4912
4688
  }
4913
- function parseStyle$3(style) {
4689
+ function parseStyle$2(style) {
4914
4690
  switch (style.toLowerCase()) {
4915
4691
  case "omit":
4916
4692
  return (attr) => attr.value !== null;
@@ -4937,13 +4713,13 @@ function reportMessage$1(attr, style) {
4937
4713
  return "";
4938
4714
  }
4939
4715
 
4940
- const defaults$q = {
4716
+ const defaults$p = {
4941
4717
  style: "omit",
4942
4718
  };
4943
4719
  class AttributeEmptyStyle extends Rule {
4944
4720
  constructor(options) {
4945
- super({ ...defaults$q, ...options });
4946
- this.hasInvalidStyle = parseStyle$2(this.options.style);
4721
+ super({ ...defaults$p, ...options });
4722
+ this.hasInvalidStyle = parseStyle$1(this.options.style);
4947
4723
  }
4948
4724
  static schema() {
4949
4725
  return {
@@ -5001,7 +4777,7 @@ function isEmptyValue(attr) {
5001
4777
  }
5002
4778
  return attr.value === null || attr.value === "";
5003
4779
  }
5004
- function parseStyle$2(style) {
4780
+ function parseStyle$1(style) {
5005
4781
  switch (style.toLowerCase()) {
5006
4782
  case "omit":
5007
4783
  return (attr) => attr.value !== null;
@@ -5098,12 +4874,12 @@ function describePattern(pattern) {
5098
4874
  }
5099
4875
  }
5100
4876
 
5101
- const defaults$p = {
4877
+ const defaults$o = {
5102
4878
  pattern: "kebabcase",
5103
4879
  };
5104
4880
  class ClassPattern extends Rule {
5105
4881
  constructor(options) {
5106
- super({ ...defaults$p, ...options });
4882
+ super({ ...defaults$o, ...options });
5107
4883
  this.pattern = parsePattern(this.options.pattern);
5108
4884
  }
5109
4885
  static schema() {
@@ -5212,13 +4988,13 @@ class CloseOrder extends Rule {
5212
4988
  }
5213
4989
  }
5214
4990
 
5215
- const defaults$o = {
4991
+ const defaults$n = {
5216
4992
  include: null,
5217
4993
  exclude: null,
5218
4994
  };
5219
4995
  class Deprecated extends Rule {
5220
4996
  constructor(options) {
5221
- super({ ...defaults$o, ...options });
4997
+ super({ ...defaults$n, ...options });
5222
4998
  }
5223
4999
  static schema() {
5224
5000
  return {
@@ -5381,12 +5157,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
5381
5157
  }
5382
5158
  };
5383
5159
 
5384
- const defaults$n = {
5160
+ const defaults$m = {
5385
5161
  style: "uppercase",
5386
5162
  };
5387
5163
  class DoctypeStyle extends Rule {
5388
5164
  constructor(options) {
5389
- super({ ...defaults$n, ...options });
5165
+ super({ ...defaults$m, ...options });
5390
5166
  }
5391
5167
  static schema() {
5392
5168
  return {
@@ -5418,12 +5194,12 @@ class DoctypeStyle extends Rule {
5418
5194
  }
5419
5195
  }
5420
5196
 
5421
- const defaults$m = {
5197
+ const defaults$l = {
5422
5198
  style: "lowercase",
5423
5199
  };
5424
5200
  class ElementCase extends Rule {
5425
5201
  constructor(options) {
5426
- super({ ...defaults$m, ...options });
5202
+ super({ ...defaults$l, ...options });
5427
5203
  this.style = new CaseStyle(this.options.style, "element-case");
5428
5204
  }
5429
5205
  static schema() {
@@ -5489,14 +5265,14 @@ class ElementCase extends Rule {
5489
5265
  }
5490
5266
  }
5491
5267
 
5492
- const defaults$l = {
5268
+ const defaults$k = {
5493
5269
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
5494
5270
  whitelist: [],
5495
5271
  blacklist: [],
5496
5272
  };
5497
5273
  class ElementName extends Rule {
5498
5274
  constructor(options) {
5499
- super({ ...defaults$l, ...options });
5275
+ super({ ...defaults$k, ...options });
5500
5276
  /* eslint-disable-next-line security/detect-non-literal-regexp -- expected to be a regexp */
5501
5277
  this.pattern = new RegExp(this.options.pattern);
5502
5278
  }
@@ -5537,7 +5313,7 @@ class ElementName extends Rule {
5537
5313
  ...context.blacklist.map((cur) => `- ${cur}`),
5538
5314
  ];
5539
5315
  }
5540
- if (context.pattern !== defaults$l.pattern) {
5316
+ if (context.pattern !== defaults$k.pattern) {
5541
5317
  return [
5542
5318
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
5543
5319
  "",
@@ -6081,7 +5857,7 @@ class EmptyTitle extends Rule {
6081
5857
  }
6082
5858
  }
6083
5859
 
6084
- const defaults$k = {
5860
+ const defaults$j = {
6085
5861
  allowArrayBrackets: true,
6086
5862
  shared: ["radio", "button", "reset", "submit"],
6087
5863
  };
@@ -6114,7 +5890,7 @@ function getDocumentation(context) {
6114
5890
  }
6115
5891
  class FormDupName extends Rule {
6116
5892
  constructor(options) {
6117
- super({ ...defaults$k, ...options });
5893
+ super({ ...defaults$j, ...options });
6118
5894
  }
6119
5895
  static schema() {
6120
5896
  return {
@@ -6274,7 +6050,7 @@ class FormDupName extends Rule {
6274
6050
  }
6275
6051
  }
6276
6052
 
6277
- const defaults$j = {
6053
+ const defaults$i = {
6278
6054
  allowMultipleH1: false,
6279
6055
  minInitialRank: "h1",
6280
6056
  sectioningRoots: ["dialog", '[role="dialog"]', '[role="alertdialog"]'],
@@ -6305,7 +6081,7 @@ function parseMaxInitial(value) {
6305
6081
  }
6306
6082
  class HeadingLevel extends Rule {
6307
6083
  constructor(options) {
6308
- super({ ...defaults$j, ...options });
6084
+ super({ ...defaults$i, ...options });
6309
6085
  this.stack = [];
6310
6086
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
6311
6087
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -6463,12 +6239,12 @@ class HeadingLevel extends Rule {
6463
6239
  }
6464
6240
  }
6465
6241
 
6466
- const defaults$i = {
6242
+ const defaults$h = {
6467
6243
  pattern: "kebabcase",
6468
6244
  };
6469
6245
  class IdPattern extends Rule {
6470
6246
  constructor(options) {
6471
- super({ ...defaults$i, ...options });
6247
+ super({ ...defaults$h, ...options });
6472
6248
  this.pattern = parsePattern(this.options.pattern);
6473
6249
  }
6474
6250
  static schema() {
@@ -6750,12 +6526,12 @@ function findLabelByParent(el) {
6750
6526
  return [];
6751
6527
  }
6752
6528
 
6753
- const defaults$h = {
6529
+ const defaults$g = {
6754
6530
  maxlength: 70,
6755
6531
  };
6756
6532
  class LongTitle extends Rule {
6757
6533
  constructor(options) {
6758
- super({ ...defaults$h, ...options });
6534
+ super({ ...defaults$g, ...options });
6759
6535
  this.maxlength = this.options.maxlength;
6760
6536
  }
6761
6537
  static schema() {
@@ -6981,13 +6757,13 @@ class MultipleLabeledControls extends Rule {
6981
6757
  }
6982
6758
  }
6983
6759
 
6984
- const defaults$g = {
6760
+ const defaults$f = {
6985
6761
  include: null,
6986
6762
  exclude: null,
6987
6763
  };
6988
6764
  class NoAutoplay extends Rule {
6989
6765
  constructor(options) {
6990
- super({ ...defaults$g, ...options });
6766
+ super({ ...defaults$f, ...options });
6991
6767
  }
6992
6768
  documentation(context) {
6993
6769
  const tagName = context ? ` on <${context.tagName}>` : "";
@@ -7228,14 +7004,14 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
7228
7004
  }
7229
7005
  }
7230
7006
 
7231
- const defaults$f = {
7007
+ const defaults$e = {
7232
7008
  include: null,
7233
7009
  exclude: null,
7234
7010
  allowedProperties: ["display"],
7235
7011
  };
7236
7012
  class NoInlineStyle extends Rule {
7237
7013
  constructor(options) {
7238
- super({ ...defaults$f, ...options });
7014
+ super({ ...defaults$e, ...options });
7239
7015
  }
7240
7016
  static schema() {
7241
7017
  return {
@@ -7437,7 +7213,7 @@ class NoMultipleMain extends Rule {
7437
7213
  }
7438
7214
  }
7439
7215
 
7440
- const defaults$e = {
7216
+ const defaults$d = {
7441
7217
  relaxed: false,
7442
7218
  };
7443
7219
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
@@ -7454,7 +7230,7 @@ const replacementTable = {
7454
7230
  };
7455
7231
  class NoRawCharacters extends Rule {
7456
7232
  constructor(options) {
7457
- super({ ...defaults$e, ...options });
7233
+ super({ ...defaults$d, ...options });
7458
7234
  this.relaxed = this.options.relaxed;
7459
7235
  }
7460
7236
  static schema() {
@@ -7632,13 +7408,13 @@ class NoRedundantRole extends Rule {
7632
7408
  }
7633
7409
 
7634
7410
  const xmlns = /^(.+):.+$/;
7635
- const defaults$d = {
7411
+ const defaults$c = {
7636
7412
  ignoreForeign: true,
7637
7413
  ignoreXML: true,
7638
7414
  };
7639
7415
  class NoSelfClosing extends Rule {
7640
7416
  constructor(options) {
7641
- super({ ...defaults$d, ...options });
7417
+ super({ ...defaults$c, ...options });
7642
7418
  }
7643
7419
  static schema() {
7644
7420
  return {
@@ -7727,13 +7503,13 @@ class NoTrailingWhitespace extends Rule {
7727
7503
  }
7728
7504
  }
7729
7505
 
7730
- const defaults$c = {
7506
+ const defaults$b = {
7731
7507
  include: null,
7732
7508
  exclude: null,
7733
7509
  };
7734
7510
  class NoUnknownElements extends Rule {
7735
7511
  constructor(options) {
7736
- super({ ...defaults$c, ...options });
7512
+ super({ ...defaults$b, ...options });
7737
7513
  }
7738
7514
  static schema() {
7739
7515
  return {
@@ -7845,13 +7621,13 @@ const replacement = {
7845
7621
  reset: '<button type="reset">',
7846
7622
  image: '<button type="button">',
7847
7623
  };
7848
- const defaults$b = {
7624
+ const defaults$a = {
7849
7625
  include: null,
7850
7626
  exclude: null,
7851
7627
  };
7852
7628
  class PreferButton extends Rule {
7853
7629
  constructor(options) {
7854
- super({ ...defaults$b, ...options });
7630
+ super({ ...defaults$a, ...options });
7855
7631
  }
7856
7632
  static schema() {
7857
7633
  return {
@@ -7926,7 +7702,7 @@ class PreferButton extends Rule {
7926
7702
  }
7927
7703
  }
7928
7704
 
7929
- const defaults$a = {
7705
+ const defaults$9 = {
7930
7706
  mapping: {
7931
7707
  article: "article",
7932
7708
  banner: "header",
@@ -7956,7 +7732,7 @@ const defaults$a = {
7956
7732
  };
7957
7733
  class PreferNativeElement extends Rule {
7958
7734
  constructor(options) {
7959
- super({ ...defaults$a, ...options });
7735
+ super({ ...defaults$9, ...options });
7960
7736
  }
7961
7737
  static schema() {
7962
7738
  return {
@@ -8076,12 +7852,12 @@ class PreferTbody extends Rule {
8076
7852
  }
8077
7853
  }
8078
7854
 
8079
- const defaults$9 = {
7855
+ const defaults$8 = {
8080
7856
  tags: ["script", "style"],
8081
7857
  };
8082
7858
  class RequireCSPNonce extends Rule {
8083
7859
  constructor(options) {
8084
- super({ ...defaults$9, ...options });
7860
+ super({ ...defaults$8, ...options });
8085
7861
  }
8086
7862
  static schema() {
8087
7863
  return {
@@ -8132,7 +7908,7 @@ class RequireCSPNonce extends Rule {
8132
7908
  }
8133
7909
  }
8134
7910
 
8135
- const defaults$8 = {
7911
+ const defaults$7 = {
8136
7912
  target: "all",
8137
7913
  include: null,
8138
7914
  exclude: null,
@@ -8144,7 +7920,7 @@ const supportSri = {
8144
7920
  };
8145
7921
  class RequireSri extends Rule {
8146
7922
  constructor(options) {
8147
- super({ ...defaults$8, ...options });
7923
+ super({ ...defaults$7, ...options });
8148
7924
  this.target = this.options.target;
8149
7925
  }
8150
7926
  static schema() {
@@ -8306,7 +8082,7 @@ class SvgFocusable extends Rule {
8306
8082
  }
8307
8083
  }
8308
8084
 
8309
- const defaults$7 = {
8085
+ const defaults$6 = {
8310
8086
  characters: [
8311
8087
  { pattern: " ", replacement: "&nbsp;", description: "non-breaking space" },
8312
8088
  { pattern: "-", replacement: "&#8209;", description: "non-breaking hyphen" },
@@ -8345,7 +8121,7 @@ function matchAll(text, regexp) {
8345
8121
  }
8346
8122
  class TelNonBreaking extends Rule {
8347
8123
  constructor(options) {
8348
- super({ ...defaults$7, ...options });
8124
+ super({ ...defaults$6, ...options });
8349
8125
  this.regex = constructRegex(this.options.characters);
8350
8126
  }
8351
8127
  static schema() {
@@ -8633,7 +8409,7 @@ class TextContent extends Rule {
8633
8409
  }
8634
8410
  }
8635
8411
 
8636
- const defaults$6 = {
8412
+ const defaults$5 = {
8637
8413
  ignoreCase: false,
8638
8414
  requireSemicolon: true,
8639
8415
  };
@@ -8675,7 +8451,7 @@ function getDescription(context, options) {
8675
8451
  }
8676
8452
  class UnknownCharReference extends Rule {
8677
8453
  constructor(options) {
8678
- super({ ...defaults$6, ...options });
8454
+ super({ ...defaults$5, ...options });
8679
8455
  }
8680
8456
  static schema() {
8681
8457
  return {
@@ -8792,12 +8568,12 @@ var RuleContext;
8792
8568
  RuleContext[RuleContext["LEADING_CHARACTER"] = 3] = "LEADING_CHARACTER";
8793
8569
  RuleContext[RuleContext["DISALLOWED_CHARACTER"] = 4] = "DISALLOWED_CHARACTER";
8794
8570
  })(RuleContext || (RuleContext = {}));
8795
- const defaults$5 = {
8571
+ const defaults$4 = {
8796
8572
  relaxed: false,
8797
8573
  };
8798
8574
  class ValidID extends Rule {
8799
8575
  constructor(options) {
8800
- super({ ...defaults$5, ...options });
8576
+ super({ ...defaults$4, ...options });
8801
8577
  }
8802
8578
  static schema() {
8803
8579
  return {
@@ -8874,119 +8650,39 @@ class ValidID extends Rule {
8874
8650
  }
8875
8651
  }
8876
8652
 
8877
- var Style$1;
8653
+ class VoidContent extends Rule {
8654
+ documentation(tagName) {
8655
+ const doc = {
8656
+ description: "HTML void elements cannot have any content and must not have content or end tag.",
8657
+ url: "https://html-validate.org/rules/void-content.html",
8658
+ };
8659
+ if (tagName) {
8660
+ doc.description = `<${tagName}> is a void element and must not have content or end tag.`;
8661
+ }
8662
+ return doc;
8663
+ }
8664
+ setup() {
8665
+ this.on("tag:end", (event) => {
8666
+ const node = event.target; // The current element being closed.
8667
+ if (!node) {
8668
+ return;
8669
+ }
8670
+ if (!node.voidElement) {
8671
+ return;
8672
+ }
8673
+ if (node.closed === NodeClosed.EndTag) {
8674
+ this.report(null, `End tag for <${node.tagName}> must be omitted`, node.location, node.tagName);
8675
+ }
8676
+ });
8677
+ }
8678
+ }
8679
+
8680
+ var Style;
8878
8681
  (function (Style) {
8879
- Style[Style["Any"] = 0] = "Any";
8880
8682
  Style[Style["AlwaysOmit"] = 1] = "AlwaysOmit";
8881
8683
  Style[Style["AlwaysSelfclose"] = 2] = "AlwaysSelfclose";
8882
- })(Style$1 || (Style$1 = {}));
8883
- const defaults$4 = {
8884
- style: "omit",
8885
- };
8886
- class Void extends Rule {
8887
- get deprecated() {
8888
- return true;
8889
- }
8890
- static schema() {
8891
- return {
8892
- style: {
8893
- enum: ["any", "omit", "selfclose", "selfclosing"],
8894
- type: "string",
8895
- },
8896
- };
8897
- }
8898
- documentation() {
8899
- return {
8900
- description: "HTML void elements cannot have any content and must not have an end tag.",
8901
- url: "https://html-validate.org/rules/void.html",
8902
- };
8903
- }
8904
- constructor(options) {
8905
- super({ ...defaults$4, ...options });
8906
- this.style = parseStyle$1(this.options.style);
8907
- }
8908
- setup() {
8909
- this.on("tag:end", (event) => {
8910
- const current = event.target; // The current element being closed
8911
- const active = event.previous; // The current active element (that is, the current element on the stack)
8912
- if (current && current.meta) {
8913
- this.validateCurrent(current);
8914
- }
8915
- if (active && active.meta) {
8916
- this.validateActive(active, active.meta);
8917
- }
8918
- });
8919
- }
8920
- validateCurrent(node) {
8921
- if (node.voidElement && node.closed === NodeClosed.EndTag) {
8922
- this.report(null, `End tag for <${node.tagName}> must be omitted`, node.location);
8923
- }
8924
- }
8925
- validateActive(node, meta) {
8926
- /* ignore foreign elements, they may or may not be self-closed and both are
8927
- * valid */
8928
- if (meta.foreign) {
8929
- return;
8930
- }
8931
- const selfOrOmitted = node.closed === NodeClosed.VoidOmitted || node.closed === NodeClosed.VoidSelfClosed;
8932
- if (node.voidElement) {
8933
- if (this.style === Style$1.AlwaysOmit && node.closed === NodeClosed.VoidSelfClosed) {
8934
- this.report(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
8935
- }
8936
- if (this.style === Style$1.AlwaysSelfclose && node.closed === NodeClosed.VoidOmitted) {
8937
- this.report(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
8938
- }
8939
- }
8940
- if (selfOrOmitted && node.voidElement === false) {
8941
- this.report(node, `End tag for <${node.tagName}> must not be omitted`);
8942
- }
8943
- }
8944
- }
8945
- function parseStyle$1(name) {
8946
- switch (name) {
8947
- case "any":
8948
- return Style$1.Any;
8949
- case "omit":
8950
- return Style$1.AlwaysOmit;
8951
- case "selfclose":
8952
- case "selfclosing":
8953
- return Style$1.AlwaysSelfclose;
8954
- }
8955
- }
8956
-
8957
- class VoidContent extends Rule {
8958
- documentation(tagName) {
8959
- const doc = {
8960
- description: "HTML void elements cannot have any content and must not have content or end tag.",
8961
- url: "https://html-validate.org/rules/void-content.html",
8962
- };
8963
- if (tagName) {
8964
- doc.description = `<${tagName}> is a void element and must not have content or end tag.`;
8965
- }
8966
- return doc;
8967
- }
8968
- setup() {
8969
- this.on("tag:end", (event) => {
8970
- const node = event.target; // The current element being closed.
8971
- if (!node) {
8972
- return;
8973
- }
8974
- if (!node.voidElement) {
8975
- return;
8976
- }
8977
- if (node.closed === NodeClosed.EndTag) {
8978
- this.report(null, `End tag for <${node.tagName}> must be omitted`, node.location, node.tagName);
8979
- }
8980
- });
8981
- }
8982
- }
8983
-
8984
- var Style;
8985
- (function (Style) {
8986
- Style[Style["AlwaysOmit"] = 1] = "AlwaysOmit";
8987
- Style[Style["AlwaysSelfclose"] = 2] = "AlwaysSelfclose";
8988
- })(Style || (Style = {}));
8989
- const defaults$3 = {
8684
+ })(Style || (Style = {}));
8685
+ const defaults$3 = {
8990
8686
  style: "omit",
8991
8687
  };
8992
8688
  class VoidStyle extends Rule {
@@ -9455,7 +9151,6 @@ const bundledRules = {
9455
9151
  "text-content": TextContent,
9456
9152
  "unrecognized-char-ref": UnknownCharReference,
9457
9153
  "valid-id": ValidID,
9458
- void: Void,
9459
9154
  "void-content": VoidContent,
9460
9155
  "void-style": VoidStyle,
9461
9156
  ...WCAG,
@@ -9748,7 +9443,112 @@ class ResolvedConfig {
9748
9443
  }
9749
9444
  }
9750
9445
 
9751
- let rootDirCache = null;
9446
+ function haveResolver(key, value) {
9447
+ return key in value;
9448
+ }
9449
+ function haveConfigResolver(value) {
9450
+ return haveResolver("resolveConfig", value);
9451
+ }
9452
+ function haveElementsResolver(value) {
9453
+ return haveResolver("resolveElements", value);
9454
+ }
9455
+ function havePluginResolver(value) {
9456
+ return haveResolver("resolvePlugin", value);
9457
+ }
9458
+ function haveTransformerResolver(value) {
9459
+ return haveResolver("resolveTransformer", value);
9460
+ }
9461
+ /**
9462
+ * @internal
9463
+ */
9464
+ function resolveConfig(resolvers, id, options) {
9465
+ for (const resolver of resolvers.filter(haveConfigResolver)) {
9466
+ const config = resolver.resolveConfig(id, options);
9467
+ if (config) {
9468
+ return config;
9469
+ }
9470
+ }
9471
+ throw new UserError(`Failed to load configuration from "${id}"`);
9472
+ }
9473
+ /**
9474
+ * @internal
9475
+ */
9476
+ function resolveElements(resolvers, id, options) {
9477
+ for (const resolver of resolvers.filter(haveElementsResolver)) {
9478
+ const elements = resolver.resolveElements(id, options);
9479
+ if (elements) {
9480
+ return elements;
9481
+ }
9482
+ }
9483
+ throw new UserError(`Failed to load elements from "${id}"`);
9484
+ }
9485
+ /**
9486
+ * @internal
9487
+ */
9488
+ function resolvePlugin(resolvers, id, options) {
9489
+ for (const resolver of resolvers.filter(havePluginResolver)) {
9490
+ const plugin = resolver.resolvePlugin(id, options);
9491
+ if (plugin) {
9492
+ return plugin;
9493
+ }
9494
+ }
9495
+ throw new UserError(`Failed to load plugin from "${id}"`);
9496
+ }
9497
+ /**
9498
+ * @internal
9499
+ */
9500
+ function resolveTransformer(resolvers, id, options) {
9501
+ for (const resolver of resolvers.filter(haveTransformerResolver)) {
9502
+ const transformer = resolver.resolveTransformer(id, options);
9503
+ if (transformer) {
9504
+ return transformer;
9505
+ }
9506
+ }
9507
+ throw new UserError(`Failed to load transformer from "${id}"`);
9508
+ }
9509
+
9510
+ /**
9511
+ * Create a new resolver for static content, i.e. plugins or transformers known
9512
+ * at compile time.
9513
+ *
9514
+ * @public
9515
+ * @since 8.0.0
9516
+ */
9517
+ function staticResolver(map = {}) {
9518
+ const { elements = {}, configs = {}, plugins = {}, transformers = {} } = map;
9519
+ return {
9520
+ name: "static-qresolver",
9521
+ addElements(id, value) {
9522
+ elements[id] = value;
9523
+ },
9524
+ addConfig(id, value) {
9525
+ configs[id] = value;
9526
+ },
9527
+ addPlugin(id, value) {
9528
+ plugins[id] = value;
9529
+ },
9530
+ addTransformer(id, value) {
9531
+ transformers[id] = value;
9532
+ },
9533
+ resolveElements(id) {
9534
+ var _a;
9535
+ return (_a = elements[id]) !== null && _a !== void 0 ? _a : null;
9536
+ },
9537
+ resolveConfig(id) {
9538
+ var _a;
9539
+ return (_a = configs[id]) !== null && _a !== void 0 ? _a : null;
9540
+ },
9541
+ resolvePlugin(id) {
9542
+ var _a;
9543
+ return (_a = plugins[id]) !== null && _a !== void 0 ? _a : null;
9544
+ },
9545
+ resolveTransformer(id) {
9546
+ var _a;
9547
+ return (_a = transformers[id]) !== null && _a !== void 0 ? _a : null;
9548
+ },
9549
+ };
9550
+ }
9551
+
9752
9552
  const ajv = new Ajv({ strict: true, strictTuples: true, strictTypes: true });
9753
9553
  ajv.addMetaSchema(ajvSchemaDraft);
9754
9554
  const validator = ajv.compile(configurationSchema);
@@ -9771,30 +9571,13 @@ function mergeInternal(base, rhs) {
9771
9571
  }
9772
9572
  return dst;
9773
9573
  }
9774
- /**
9775
- * @internal
9776
- */
9777
- function configDataFromFile(filename) {
9778
- let json;
9779
- try {
9780
- /* load using require as it can process both js and json */
9781
- /* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- technical debt, should be refactored into something more typesafe */
9782
- json = requireUncached(legacyRequire, filename);
9783
- }
9784
- catch (err) {
9785
- throw new ConfigError(`Failed to read configuration from "${filename}"`, ensureError(err));
9786
- }
9787
- /* expand any relative paths */
9788
- for (const key of ["extends", "elements", "plugins"]) {
9789
- /* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- technical debt, should be refactored into something more typesafe */
9790
- const value = json[key];
9791
- if (!value)
9792
- continue;
9793
- json[key] = value.map((ref) => {
9794
- return Config.expandRelative(ref, path.dirname(filename));
9795
- });
9574
+ function toArray(value) {
9575
+ if (Array.isArray(value)) {
9576
+ return value;
9577
+ }
9578
+ else {
9579
+ return [value];
9796
9580
  }
9797
- return json;
9798
9581
  }
9799
9582
  /**
9800
9583
  * Configuration holder.
@@ -9808,7 +9591,7 @@ class Config {
9808
9591
  * Create a new blank configuration. See also `Config.defaultConfig()`.
9809
9592
  */
9810
9593
  static empty() {
9811
- return new Config({
9594
+ return new Config([], {
9812
9595
  extends: [],
9813
9596
  rules: {},
9814
9597
  plugins: [],
@@ -9818,9 +9601,9 @@ class Config {
9818
9601
  /**
9819
9602
  * Create configuration from object.
9820
9603
  */
9821
- static fromObject(options, filename = null) {
9604
+ static fromObject(resolvers, options, filename = null) {
9822
9605
  Config.validate(options, filename);
9823
- return new Config(options);
9606
+ return new Config(resolvers, options);
9824
9607
  }
9825
9608
  /**
9826
9609
  * Read configuration from filename.
@@ -9831,9 +9614,9 @@ class Config {
9831
9614
  * @internal
9832
9615
  * @param filename - The file to read from
9833
9616
  */
9834
- static fromFile(filename) {
9835
- const configdata = configDataFromFile(filename);
9836
- return Config.fromObject(configdata, filename);
9617
+ static fromFile(resolvers, filename) {
9618
+ const configData = resolveConfig(toArray(resolvers), filename, { cache: false });
9619
+ return Config.fromObject(resolvers, configData, filename);
9837
9620
  }
9838
9621
  /**
9839
9622
  * Validate configuration data.
@@ -9863,12 +9646,12 @@ class Config {
9863
9646
  * Load a default configuration object.
9864
9647
  */
9865
9648
  static defaultConfig() {
9866
- return new Config(defaultConfig);
9649
+ return new Config([], defaultConfig);
9867
9650
  }
9868
9651
  /**
9869
9652
  * @internal
9870
9653
  */
9871
- constructor(options) {
9654
+ constructor(resolvers, options) {
9872
9655
  var _a;
9873
9656
  this.transformers = [];
9874
9657
  const initial = {
@@ -9877,10 +9660,10 @@ class Config {
9877
9660
  rules: {},
9878
9661
  transform: {},
9879
9662
  };
9880
- this.config = mergeInternal(initial, options || {});
9663
+ this.config = mergeInternal(initial, options);
9881
9664
  this.metaTable = null;
9882
- this.rootDir = this.findRootDir();
9883
9665
  this.initialized = false;
9666
+ this.resolvers = toArray(resolvers);
9884
9667
  /* load plugins */
9885
9668
  this.plugins = this.loadPlugins(this.config.plugins || []);
9886
9669
  this.configurations = this.loadConfigurations(this.plugins);
@@ -9926,8 +9709,8 @@ class Config {
9926
9709
  * @public
9927
9710
  * @param rhs - Configuration to merge with this one.
9928
9711
  */
9929
- merge(rhs) {
9930
- return new Config(mergeInternal(this.config, rhs.config));
9712
+ merge(resolvers, rhs) {
9713
+ return new Config(resolvers, mergeInternal(this.config, rhs.config));
9931
9714
  }
9932
9715
  extendConfig(entries) {
9933
9716
  if (entries.length === 0) {
@@ -9941,7 +9724,7 @@ class Config {
9941
9724
  extended = this.configurations.get(entry);
9942
9725
  }
9943
9726
  else {
9944
- extended = Config.fromFile(entry).config;
9727
+ extended = Config.fromFile(this.resolvers, entry).config;
9945
9728
  }
9946
9729
  base = mergeInternal(base, extended);
9947
9730
  }
@@ -9978,30 +9761,20 @@ class Config {
9978
9761
  metaTable.loadFromObject(bundled);
9979
9762
  continue;
9980
9763
  }
9981
- /* assume it is loadable with require() */
9982
- const id = entry.replace("<rootDir>", this.rootDir);
9764
+ /* load with resolver */
9983
9765
  try {
9984
- const data = legacyRequire(id);
9985
- metaTable.loadFromObject(data, id);
9766
+ const data = resolveElements(this.resolvers, entry, { cache: false });
9767
+ metaTable.loadFromObject(data, entry);
9986
9768
  }
9987
9769
  catch (err) {
9988
9770
  /* istanbul ignore next: only used as a fallback */
9989
9771
  const message = err instanceof Error ? err.message : String(err);
9990
- throw new ConfigError(`Failed to load elements from "${id}": ${message}`, ensureError(err));
9772
+ throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, ensureError(err));
9991
9773
  }
9992
9774
  }
9993
9775
  metaTable.init();
9994
9776
  return (this.metaTable = metaTable);
9995
9777
  }
9996
- /**
9997
- * @internal exposed for testing only
9998
- */
9999
- static expandRelative(src, currentPath) {
10000
- if (src[0] === ".") {
10001
- return path.normalize(`${currentPath}/${src}`);
10002
- }
10003
- return src;
10004
- }
10005
9778
  /**
10006
9779
  * Get a copy of internal configuration data.
10007
9780
  *
@@ -10053,7 +9826,7 @@ class Config {
10053
9826
  return plugin;
10054
9827
  }
10055
9828
  try {
10056
- const plugin = legacyRequire(moduleName.replace("<rootDir>", this.rootDir));
9829
+ const plugin = resolvePlugin(this.resolvers, moduleName, { cache: true });
10057
9830
  plugin.name = plugin.name || moduleName;
10058
9831
  plugin.originalName = moduleName;
10059
9832
  return plugin;
@@ -10226,57 +9999,7 @@ class Config {
10226
9999
  return plugin.transformer;
10227
10000
  }
10228
10001
  getTransformerFromModule(name) {
10229
- /* expand <rootDir> */
10230
- const moduleName = name.replace("<rootDir>", this.rootDir);
10231
- /* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- technical debt, the code kinda does the right thing but it should be reflected in the typing too */
10232
- const fn = legacyRequire(moduleName);
10233
- /* sanity check */
10234
- if (typeof fn !== "function") {
10235
- /* this is not a proper transformer, is it a plugin exposing a transformer? */
10236
- if (fn.transformer) {
10237
- throw new ConfigError(`Module is not a valid transformer. This looks like a plugin, did you forget to load the plugin first?`);
10238
- }
10239
- throw new ConfigError(`Module is not a valid transformer.`);
10240
- }
10241
- return fn;
10242
- }
10243
- /**
10244
- * @internal
10245
- */
10246
- get rootDirCache() {
10247
- /* return global instance */
10248
- return rootDirCache;
10249
- }
10250
- set rootDirCache(value) {
10251
- /* set global instance */
10252
- rootDirCache = value;
10253
- }
10254
- /**
10255
- * @internal
10256
- */
10257
- findRootDir() {
10258
- const cache = this.rootDirCache;
10259
- if (cache !== null) {
10260
- return cache;
10261
- }
10262
- /* try to locate package.json */
10263
- let current = process.cwd();
10264
- // eslint-disable-next-line no-constant-condition -- break outs when filesystem is traversed
10265
- while (true) {
10266
- const search = path.join(current, "package.json");
10267
- if (fs.existsSync(search)) {
10268
- return (this.rootDirCache = current);
10269
- }
10270
- /* get the parent directory */
10271
- const child = current;
10272
- current = path.dirname(current);
10273
- /* stop if this is the root directory */
10274
- if (current === child) {
10275
- break;
10276
- }
10277
- }
10278
- /* default to working directory if no package.json is found */
10279
- return (this.rootDirCache = process.cwd());
10002
+ return resolveTransformer(this.resolvers, name, { cache: true });
10280
10003
  }
10281
10004
  }
10282
10005
 
@@ -10289,19 +10012,25 @@ class Config {
10289
10012
  * @public
10290
10013
  */
10291
10014
  class ConfigLoader {
10292
- constructor(config, configFactory = Config) {
10293
- const defaults = configFactory.empty();
10294
- this.configFactory = configFactory;
10295
- this.globalConfig = defaults.merge(config ? this.loadFromObject(config) : this.defaultConfig());
10015
+ constructor(resolvers, config) {
10016
+ const defaults = Config.empty();
10017
+ this.resolvers = resolvers;
10018
+ this.globalConfig = defaults.merge(this.resolvers, config ? this.loadFromObject(config) : this.defaultConfig());
10019
+ }
10020
+ /**
10021
+ * @internal For testing only
10022
+ */
10023
+ _getGlobalConfig() {
10024
+ return this.globalConfig.get();
10296
10025
  }
10297
10026
  empty() {
10298
- return this.configFactory.empty();
10027
+ return Config.empty();
10299
10028
  }
10300
10029
  loadFromObject(options, filename) {
10301
- return this.configFactory.fromObject(options, filename);
10030
+ return Config.fromObject(this.resolvers, options, filename);
10302
10031
  }
10303
10032
  loadFromFile(filename) {
10304
- return this.configFactory.fromFile(filename);
10033
+ return Config.fromFile(this.resolvers, filename);
10305
10034
  }
10306
10035
  }
10307
10036
 
@@ -11289,7 +11018,7 @@ class Engine {
11289
11018
  /**
11290
11019
  * Get rule documentation.
11291
11020
  */
11292
- getRuleDocumentation(ruleId, context) {
11021
+ getRuleDocumentation({ ruleId, context, }) {
11293
11022
  const rules = this.config.getRules();
11294
11023
  const ruleData = rules.get(ruleId);
11295
11024
  if (ruleData) {
@@ -11526,6 +11255,10 @@ class Engine {
11526
11255
  }
11527
11256
  }
11528
11257
 
11258
+ const defaultResolvers$1 = [];
11259
+ function hasResolver$1(value) {
11260
+ return Array.isArray(value[0]);
11261
+ }
11529
11262
  /**
11530
11263
  * The static configuration loader does not do any per-handle lookup. Only the
11531
11264
  * global or per-call configuration is used.
@@ -11536,13 +11269,23 @@ class Engine {
11536
11269
  * @public
11537
11270
  */
11538
11271
  class StaticConfigLoader extends ConfigLoader {
11272
+ constructor(...args) {
11273
+ if (hasResolver$1(args)) {
11274
+ const [resolvers, config] = args;
11275
+ super(resolvers, config);
11276
+ }
11277
+ else {
11278
+ const [config] = args;
11279
+ super(defaultResolvers$1, config);
11280
+ }
11281
+ }
11539
11282
  getConfigFor(_handle, configOverride) {
11540
11283
  const override = this.loadFromObject(configOverride || {});
11541
11284
  if (override.isRootFound()) {
11542
11285
  override.init();
11543
11286
  return override.resolve();
11544
11287
  }
11545
- const merged = this.globalConfig.merge(override);
11288
+ const merged = this.globalConfig.merge(this.resolvers, override);
11546
11289
  merged.init();
11547
11290
  return merged.resolve();
11548
11291
  }
@@ -11595,6 +11338,33 @@ class HtmlValidate {
11595
11338
  };
11596
11339
  return this.validateSource(source, options);
11597
11340
  }
11341
+ validateStringSync(str, arg1, arg2, arg3) {
11342
+ const filename = typeof arg1 === "string" ? arg1 : "inline";
11343
+ const options = isConfigData(arg1) ? arg1 : isConfigData(arg2) ? arg2 : undefined;
11344
+ const hooks = isSourceHooks(arg1) ? arg1 : isSourceHooks(arg2) ? arg2 : arg3;
11345
+ const source = {
11346
+ data: str,
11347
+ filename,
11348
+ line: 1,
11349
+ column: 1,
11350
+ offset: 0,
11351
+ hooks,
11352
+ };
11353
+ return this.validateSourceSync(source, options);
11354
+ }
11355
+ /**
11356
+ * Parse and validate HTML from [[Source]].
11357
+ *
11358
+ * @public
11359
+ * @param input - Source to parse.
11360
+ * @returns Report output.
11361
+ */
11362
+ async validateSource(input, configOverride) {
11363
+ const config = await this.getConfigFor(input.filename, configOverride);
11364
+ const source = config.transformSource(input);
11365
+ const engine = new Engine(config, Parser);
11366
+ return engine.lint(source);
11367
+ }
11598
11368
  /**
11599
11369
  * Parse and validate HTML from [[Source]].
11600
11370
  *
@@ -11602,8 +11372,8 @@ class HtmlValidate {
11602
11372
  * @param input - Source to parse.
11603
11373
  * @returns Report output.
11604
11374
  */
11605
- validateSource(input, configOverride) {
11606
- const config = this.getConfigFor(input.filename, configOverride);
11375
+ validateSourceSync(input, configOverride) {
11376
+ const config = this.getConfigForSync(input.filename, configOverride);
11607
11377
  const source = config.transformSource(input);
11608
11378
  const engine = new Engine(config, Parser);
11609
11379
  return engine.lint(source);
@@ -11615,8 +11385,21 @@ class HtmlValidate {
11615
11385
  * @param filename - Filename to read and parse.
11616
11386
  * @returns Report output.
11617
11387
  */
11618
- validateFile(filename) {
11619
- const config = this.getConfigFor(filename);
11388
+ async validateFile(filename) {
11389
+ const config = await this.getConfigFor(filename);
11390
+ const source = config.transformFilename(filename);
11391
+ const engine = new Engine(config, Parser);
11392
+ return Promise.resolve(engine.lint(source));
11393
+ }
11394
+ /**
11395
+ * Parse and validate HTML from file.
11396
+ *
11397
+ * @public
11398
+ * @param filename - Filename to read and parse.
11399
+ * @returns Report output.
11400
+ */
11401
+ validateFileSync(filename) {
11402
+ const config = this.getConfigForSync(filename);
11620
11403
  const source = config.transformFilename(filename);
11621
11404
  const engine = new Engine(config, Parser);
11622
11405
  return engine.lint(source);
@@ -11628,8 +11411,38 @@ class HtmlValidate {
11628
11411
  * @param filenames - Filenames to read and parse.
11629
11412
  * @returns Report output.
11630
11413
  */
11631
- validateMultipleFiles(filenames) {
11632
- return Reporter.merge(filenames.map((filename) => this.validateFile(filename)));
11414
+ async validateMultipleFiles(filenames) {
11415
+ const report = Reporter.merge(filenames.map((filename) => this.validateFileSync(filename)));
11416
+ return Promise.resolve(report);
11417
+ }
11418
+ /**
11419
+ * Parse and validate HTML from multiple files. Result is merged together to a
11420
+ * single report.
11421
+ *
11422
+ * @param filenames - Filenames to read and parse.
11423
+ * @returns Report output.
11424
+ */
11425
+ validateMultipleFilesSync(filenames) {
11426
+ return Reporter.merge(filenames.map((filename) => this.validateFileSync(filename)));
11427
+ }
11428
+ /**
11429
+ * Returns true if the given filename can be validated.
11430
+ *
11431
+ * A file is considered to be validatable if the extension is `.html` or if a
11432
+ * transformer matches the filename.
11433
+ *
11434
+ * This is mostly useful for tooling to determine whenever to validate the
11435
+ * file or not. CLI tools will run on all the given files anyway.
11436
+ */
11437
+ async canValidate(filename) {
11438
+ /* .html is always supported */
11439
+ const extension = path.extname(filename).toLowerCase();
11440
+ if (extension === ".html") {
11441
+ return true;
11442
+ }
11443
+ /* test if there is a matching transformer */
11444
+ const config = await this.getConfigFor(filename);
11445
+ return config.canTransform(filename);
11633
11446
  }
11634
11447
  /**
11635
11448
  * Returns true if the given filename can be validated.
@@ -11640,14 +11453,14 @@ class HtmlValidate {
11640
11453
  * This is mostly useful for tooling to determine whenever to validate the
11641
11454
  * file or not. CLI tools will run on all the given files anyway.
11642
11455
  */
11643
- canValidate(filename) {
11456
+ canValidateSync(filename) {
11644
11457
  /* .html is always supported */
11645
11458
  const extension = path.extname(filename).toLowerCase();
11646
11459
  if (extension === ".html") {
11647
11460
  return true;
11648
11461
  }
11649
11462
  /* test if there is a matching transformer */
11650
- const config = this.getConfigFor(filename);
11463
+ const config = this.getConfigForSync(filename);
11651
11464
  return config.canTransform(filename);
11652
11465
  }
11653
11466
  /**
@@ -11660,7 +11473,7 @@ class HtmlValidate {
11660
11473
  * @param filename - Filename to tokenize.
11661
11474
  */
11662
11475
  dumpTokens(filename) {
11663
- const config = this.getConfigFor(filename);
11476
+ const config = this.getConfigForSync(filename);
11664
11477
  const source = config.transformFilename(filename);
11665
11478
  const engine = new Engine(config, Parser);
11666
11479
  return engine.dumpTokens(source);
@@ -11675,7 +11488,7 @@ class HtmlValidate {
11675
11488
  * @param filename - Filename to dump events from.
11676
11489
  */
11677
11490
  dumpEvents(filename) {
11678
- const config = this.getConfigFor(filename);
11491
+ const config = this.getConfigForSync(filename);
11679
11492
  const source = config.transformFilename(filename);
11680
11493
  const engine = new Engine(config, Parser);
11681
11494
  return engine.dumpEvents(source);
@@ -11690,7 +11503,7 @@ class HtmlValidate {
11690
11503
  * @param filename - Filename to dump DOM tree from.
11691
11504
  */
11692
11505
  dumpTree(filename) {
11693
- const config = this.getConfigFor(filename);
11506
+ const config = this.getConfigForSync(filename);
11694
11507
  const source = config.transformFilename(filename);
11695
11508
  const engine = new Engine(config, Parser);
11696
11509
  return engine.dumpTree(source);
@@ -11705,7 +11518,7 @@ class HtmlValidate {
11705
11518
  * @param filename - Filename to dump source from.
11706
11519
  */
11707
11520
  dumpSource(filename) {
11708
- const config = this.getConfigFor(filename);
11521
+ const config = this.getConfigForSync(filename);
11709
11522
  const sources = config.transformFilename(filename);
11710
11523
  return sources.reduce((result, source) => {
11711
11524
  result.push(`Source ${source.filename}@${source.line}:${source.column} (offset: ${source.offset})`);
@@ -11741,37 +11554,95 @@ class HtmlValidate {
11741
11554
  * handled by html-validate but the path will be used when resolving
11742
11555
  * configuration. As a rule-of-thumb, set it to the elements json file.
11743
11556
  */
11744
- getElementsSchema(filename) {
11745
- const config = this.getConfigFor(filename !== null && filename !== void 0 ? filename : "inline");
11557
+ async getElementsSchema(filename) {
11558
+ const config = await this.getConfigFor(filename !== null && filename !== void 0 ? filename : "inline");
11746
11559
  const metaTable = config.getMetaTable();
11747
11560
  return metaTable.getJSONSchema();
11748
11561
  }
11562
+ /**
11563
+ * Get effective metadata element schema.
11564
+ *
11565
+ * If a filename is given the configured plugins can extend the
11566
+ * schema. Filename must not be an existing file or a filetype normally
11567
+ * handled by html-validate but the path will be used when resolving
11568
+ * configuration. As a rule-of-thumb, set it to the elements json file.
11569
+ */
11570
+ getElementsSchemaSync(filename) {
11571
+ const config = this.getConfigForSync(filename !== null && filename !== void 0 ? filename : "inline");
11572
+ const metaTable = config.getMetaTable();
11573
+ return metaTable.getJSONSchema();
11574
+ }
11575
+ async getContextualDocumentation(message, filenameOrConfig = "inline") {
11576
+ const config = typeof filenameOrConfig === "string"
11577
+ ? await this.getConfigFor(filenameOrConfig)
11578
+ : await filenameOrConfig;
11579
+ const engine = new Engine(config, Parser);
11580
+ return engine.getRuleDocumentation(message);
11581
+ }
11582
+ getContextualDocumentationSync(message, filenameOrConfig = "inline") {
11583
+ const config = typeof filenameOrConfig === "string"
11584
+ ? this.getConfigForSync(filenameOrConfig)
11585
+ : filenameOrConfig;
11586
+ const engine = new Engine(config, Parser);
11587
+ return engine.getRuleDocumentation(message);
11588
+ }
11749
11589
  /**
11750
11590
  * Get contextual documentation for the given rule.
11751
11591
  *
11752
11592
  * Typical usage:
11753
11593
  *
11754
11594
  * ```js
11755
- * const report = htmlvalidate.validateFile("my-file.html");
11595
+ * const report = await htmlvalidate.validateFile("my-file.html");
11756
11596
  * for (const result of report.results){
11757
- * const config = htmlvalidate.getConfigFor(result.filePath);
11597
+ * const config = await htmlvalidate.getConfigFor(result.filePath);
11758
11598
  * for (const message of result.messages){
11759
- * const documentation = htmlvalidate.getRuleDocumentation(message.ruleId, config, message.context);
11599
+ * const documentation = await htmlvalidate.getRuleDocumentation(message.ruleId, config, message.context);
11760
11600
  * // do something with documentation
11761
11601
  * }
11762
11602
  * }
11763
11603
  * ```
11764
11604
  *
11605
+ * @public
11606
+ * @deprecated Deprecated since 8.0.0, use [[getContextualDocumentation]] instead.
11765
11607
  * @param ruleId - Rule to get documentation for.
11766
11608
  * @param config - If set it provides more accurate description by using the
11767
11609
  * correct configuration for the file.
11768
11610
  * @param context - If set to `Message.context` some rules can provide
11769
11611
  * contextual details and suggestions.
11770
11612
  */
11771
- getRuleDocumentation(ruleId, config = null, context = null) {
11613
+ async getRuleDocumentation(ruleId, config = null, context = null) {
11772
11614
  const c = config || this.getConfigFor("inline");
11615
+ const engine = new Engine(await c, Parser);
11616
+ return engine.getRuleDocumentation({ ruleId, context });
11617
+ }
11618
+ /**
11619
+ * Get contextual documentation for the given rule.
11620
+ *
11621
+ * Typical usage:
11622
+ *
11623
+ * ```js
11624
+ * const report = htmlvalidate.validateFileSync("my-file.html");
11625
+ * for (const result of report.results){
11626
+ * const config = htmlvalidate.getConfigForSync(result.filePath);
11627
+ * for (const message of result.messages){
11628
+ * const documentation = htmlvalidate.getRuleDocumentationSync(message.ruleId, config, message.context);
11629
+ * // do something with documentation
11630
+ * }
11631
+ * }
11632
+ * ```
11633
+ *
11634
+ * @public
11635
+ * @deprecated Deprecated since 8.0.0, use [[getContextualDocumentationSync]] instead.
11636
+ * @param ruleId - Rule to get documentation for.
11637
+ * @param config - If set it provides more accurate description by using the
11638
+ * correct configuration for the file.
11639
+ * @param context - If set to `Message.context` some rules can provide
11640
+ * contextual details and suggestions.
11641
+ */
11642
+ getRuleDocumentationSync(ruleId, config = null, context = null) {
11643
+ const c = config || this.getConfigForSync("inline");
11773
11644
  const engine = new Engine(c, Parser);
11774
- return engine.getRuleDocumentation(ruleId, context);
11645
+ return engine.getRuleDocumentation({ ruleId, context });
11775
11646
  }
11776
11647
  /**
11777
11648
  * Create a parser configured for given filename.
@@ -11779,8 +11650,8 @@ class HtmlValidate {
11779
11650
  * @internal
11780
11651
  * @param source - Source to use.
11781
11652
  */
11782
- getParserFor(source) {
11783
- const config = this.getConfigFor(source.filename);
11653
+ async getParserFor(source) {
11654
+ const config = await this.getConfigFor(source.filename);
11784
11655
  return new Parser(config);
11785
11656
  }
11786
11657
  /**
@@ -11794,11 +11665,19 @@ class HtmlValidate {
11794
11665
  */
11795
11666
  getConfigFor(filename, configOverride) {
11796
11667
  const config = this.configLoader.getConfigFor(filename, configOverride);
11797
- /* for backwards compatibility only */
11798
- if (config instanceof Config) {
11799
- return config.resolve();
11800
- }
11801
- return config;
11668
+ return Promise.resolve(config);
11669
+ }
11670
+ /**
11671
+ * Get configuration for given filename.
11672
+ *
11673
+ * See [[FileSystemConfigLoader]] for details.
11674
+ *
11675
+ * @public
11676
+ * @param filename - Filename to get configuration for.
11677
+ * @param configOverride - Configuration to apply last.
11678
+ */
11679
+ getConfigForSync(filename, configOverride) {
11680
+ return this.configLoader.getConfigFor(filename, configOverride);
11802
11681
  }
11803
11682
  /**
11804
11683
  * Flush configuration cache. Clears full cache unless a filename is given.
@@ -11817,7 +11696,7 @@ class HtmlValidate {
11817
11696
  /** @public */
11818
11697
  const name = "html-validate";
11819
11698
  /** @public */
11820
- const version = "7.18.1";
11699
+ const version = "8.0.0";
11821
11700
  /** @public */
11822
11701
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11823
11702
 
@@ -11843,6 +11722,7 @@ const defaults$1 = {
11843
11722
  * option is used a warning is displayed on the console.
11844
11723
  *
11845
11724
  * @public
11725
+ * @since v5.0.0
11846
11726
  * @param name - Name of plugin
11847
11727
  * @param declared - What library versions the plugin support (e.g. declared peerDependencies)
11848
11728
  * @returns - `true` if version is compatible
@@ -11863,6 +11743,27 @@ function compatibilityCheck(name, declared, options) {
11863
11743
  return false;
11864
11744
  }
11865
11745
 
11746
+ /**
11747
+ * Similar to `require(..)` but removes the cached copy first.
11748
+ */
11749
+ function requireUncached(require, moduleId) {
11750
+ const filename = require.resolve(moduleId);
11751
+ /* remove references from the parent module to prevent memory leak */
11752
+ const m = require.cache[filename];
11753
+ if (m && m.parent) {
11754
+ const { parent } = m;
11755
+ for (let i = parent.children.length - 1; i >= 0; i--) {
11756
+ if (parent.children[i].id === filename) {
11757
+ parent.children.splice(i, 1);
11758
+ }
11759
+ }
11760
+ }
11761
+ /* remove old module from cache */
11762
+ delete require.cache[filename];
11763
+ /* eslint-disable-next-line import/no-dynamic-require, security/detect-non-literal-require -- as expected but should be moved to upcoming resolver class */
11764
+ return require(filename);
11765
+ }
11766
+
11866
11767
  const ruleIds = new Set(Object.keys(rules));
11867
11768
  /**
11868
11769
  * Returns true if given ruleId is an existing builtin rule. It does not handle
@@ -11879,14 +11780,149 @@ function ruleExists(ruleId) {
11879
11780
  return ruleIds.has(ruleId);
11880
11781
  }
11881
11782
 
11783
+ const legacyRequire = createRequire(import.meta.url);
11784
+
11785
+ let cachedRootDir = null;
11786
+ /**
11787
+ * @internal
11788
+ */
11789
+ function determineRootDirImpl(intial, fs) {
11790
+ /* try to locate package.json */
11791
+ let current = intial;
11792
+ // eslint-disable-next-line no-constant-condition -- break outs when filesystem is traversed
11793
+ while (true) {
11794
+ const search = path$1.join(current, "package.json");
11795
+ if (fs.existsSync(search)) {
11796
+ return current;
11797
+ }
11798
+ /* get the parent directory */
11799
+ const child = current;
11800
+ current = path$1.dirname(current);
11801
+ /* stop if this is the root directory */
11802
+ if (current === child) {
11803
+ break;
11804
+ }
11805
+ }
11806
+ /* default to working directory if no package.json is found */
11807
+ return intial;
11808
+ }
11809
+ /**
11810
+ * Try to determine root directory based on the location of the closest
11811
+ * `package.json`. Fallbacks on `process.cwd()` if no package.json was found.
11812
+ *
11813
+ * @internal
11814
+ */
11815
+ /* istanbul ignore next: cached version of determineRootDirImpl, no need to test */
11816
+ function determineRootDir() {
11817
+ if (cachedRootDir === null) {
11818
+ cachedRootDir = determineRootDirImpl(process.cwd(), fs$1);
11819
+ }
11820
+ return cachedRootDir;
11821
+ }
11822
+
11823
+ /**
11824
+ * @internal
11825
+ */
11826
+ function expandRelativePath(value, { cwd }) {
11827
+ if (typeof value === "string" && value[0] === ".") {
11828
+ return path$1.normalize(path$1.join(cwd, value));
11829
+ }
11830
+ else {
11831
+ return value;
11832
+ }
11833
+ }
11834
+
11835
+ function isRequireError(error) {
11836
+ return Boolean(error && typeof error === "object" && "code" in error);
11837
+ }
11838
+ function isTransformer(value) {
11839
+ return typeof value === "function";
11840
+ }
11841
+ /**
11842
+ * Create a new resolver for NodeJS packages using `require(..)`.
11843
+ *
11844
+ * If the module name contains `<rootDir>` (e.g. `<rootDir/foo`) it will be
11845
+ * expanded relative to the root directory either explicitly set by the
11846
+ * `rootDir` parameter or determined automatically by the closest `package.json`
11847
+ * file (starting at the current working directory).
11848
+ *
11849
+ * @public
11850
+ * @since 8.0.0
11851
+ */
11852
+ function nodejsResolver(options = {}) {
11853
+ var _a;
11854
+ const rootDir = (_a = options.rootDir) !== null && _a !== void 0 ? _a : determineRootDir();
11855
+ function internalRequire(id, { cache }) {
11856
+ const moduleName = id.replace("<rootDir>", rootDir);
11857
+ try {
11858
+ /* istanbul ignore else: the tests only runs the cached versions to get
11859
+ * unmodified access to `require`, the implementation of `requireUncached`
11860
+ * is assumed to be tested elsewhere */
11861
+ if (cache) {
11862
+ return legacyRequire(moduleName);
11863
+ }
11864
+ else {
11865
+ return requireUncached(legacyRequire, moduleName);
11866
+ }
11867
+ }
11868
+ catch (err) {
11869
+ if (isRequireError(err) && err.code === "MODULE_NOT_FOUND") {
11870
+ return null;
11871
+ }
11872
+ throw err;
11873
+ }
11874
+ }
11875
+ return {
11876
+ name: "nodejs-resolver",
11877
+ resolveElements(id, options) {
11878
+ return internalRequire(id, options);
11879
+ },
11880
+ resolveConfig(id, options) {
11881
+ var _a, _b, _c;
11882
+ const configData = internalRequire(id, options);
11883
+ if (!configData) {
11884
+ return null;
11885
+ }
11886
+ /* expand any relative paths */
11887
+ const cwd = path$1.dirname(id);
11888
+ const expand = (value) => expandRelativePath(value, { cwd });
11889
+ configData.elements = (_a = configData.elements) === null || _a === void 0 ? void 0 : _a.map(expand);
11890
+ configData.extends = (_b = configData.extends) === null || _b === void 0 ? void 0 : _b.map(expand);
11891
+ configData.plugins = (_c = configData.plugins) === null || _c === void 0 ? void 0 : _c.map(expand);
11892
+ return configData;
11893
+ },
11894
+ resolvePlugin(id, options) {
11895
+ return internalRequire(id, options);
11896
+ },
11897
+ resolveTransformer(id, options) {
11898
+ const mod = internalRequire(id, options);
11899
+ if (!mod) {
11900
+ return null;
11901
+ }
11902
+ if (isTransformer(mod)) {
11903
+ return mod;
11904
+ }
11905
+ /* this is not a proper transformer, is it a plugin exposing a transformer? */
11906
+ if (mod.transformer) {
11907
+ throw new ConfigError(`Module "${id}" is not a valid transformer. This looks like a plugin, did you forget to load the plugin first?`);
11908
+ }
11909
+ throw new ConfigError(`Module "${id}" is not a valid transformer.`);
11910
+ },
11911
+ };
11912
+ }
11913
+
11882
11914
  /**
11883
11915
  * @internal
11884
11916
  */
11885
- function findConfigurationFiles(directory) {
11917
+ function findConfigurationFiles(fs, directory) {
11886
11918
  return ["json", "cjs", "js"]
11887
- .map((extension) => path.join(directory, `.htmlvalidate.${extension}`))
11919
+ .map((extension) => path$1.join(directory, `.htmlvalidate.${extension}`))
11888
11920
  .filter((filePath) => fs.existsSync(filePath));
11889
11921
  }
11922
+ const defaultResolvers = [nodejsResolver()];
11923
+ function hasResolver(value) {
11924
+ return Array.isArray(value[0]);
11925
+ }
11890
11926
  /**
11891
11927
  * Loads configuration by traversing filesystem.
11892
11928
  *
@@ -11915,12 +11951,20 @@ function findConfigurationFiles(directory) {
11915
11951
  * @public
11916
11952
  */
11917
11953
  class FileSystemConfigLoader extends ConfigLoader {
11918
- /**
11919
- * @param config - Global configuration
11920
- * @param configFactory - Optional configuration factory
11921
- */
11922
- constructor(config, configFactory = Config) {
11923
- super(config, configFactory);
11954
+ constructor(...args) {
11955
+ var _a, _b;
11956
+ if (hasResolver(args)) {
11957
+ /* istanbul ignore next */
11958
+ const [resolvers, config, options = {}] = args;
11959
+ super(resolvers, config);
11960
+ this.fs = /* istanbul ignore next */ (_a = options.fs) !== null && _a !== void 0 ? _a : fs$1;
11961
+ }
11962
+ else {
11963
+ /* istanbul ignore next */
11964
+ const [config, options = {}] = args;
11965
+ super(defaultResolvers, config);
11966
+ this.fs = /* istanbul ignore next */ (_b = options.fs) !== null && _b !== void 0 ? _b : fs$1;
11967
+ }
11924
11968
  this.cache = new Map();
11925
11969
  }
11926
11970
  /**
@@ -11940,12 +11984,14 @@ class FileSystemConfigLoader extends ConfigLoader {
11940
11984
  /* special case when the global configuration is marked as root, should not
11941
11985
  * try to load and more configuration files */
11942
11986
  if (this.globalConfig.isRootFound()) {
11943
- const merged = this.globalConfig.merge(override);
11987
+ const merged = this.globalConfig.merge(this.resolvers, override);
11944
11988
  merged.init();
11945
11989
  return merged.resolve();
11946
11990
  }
11947
11991
  const config = this.fromFilename(filename);
11948
- const merged = config ? config.merge(override) : this.globalConfig.merge(override);
11992
+ const merged = config
11993
+ ? config.merge(this.resolvers, override)
11994
+ : this.globalConfig.merge(this.resolvers, override);
11949
11995
  merged.init();
11950
11996
  return merged.resolve();
11951
11997
  }
@@ -11977,15 +12023,15 @@ class FileSystemConfigLoader extends ConfigLoader {
11977
12023
  return cache;
11978
12024
  }
11979
12025
  let found = false;
11980
- let current = path.resolve(path.dirname(filename));
12026
+ let current = path$1.resolve(path$1.dirname(filename));
11981
12027
  let config = this.empty();
11982
12028
  // eslint-disable-next-line no-constant-condition -- it will break out when filesystem is traversed
11983
12029
  while (true) {
11984
12030
  /* search configuration files in current directory */
11985
- for (const configFile of findConfigurationFiles(current)) {
12031
+ for (const configFile of findConfigurationFiles(this.fs, current)) {
11986
12032
  const local = this.loadFromFile(configFile);
11987
12033
  found = true;
11988
- config = local.merge(config);
12034
+ config = local.merge(this.resolvers, config);
11989
12035
  }
11990
12036
  /* stop if a configuration with "root" is set to true */
11991
12037
  if (config.isRootFound()) {
@@ -11993,7 +12039,7 @@ class FileSystemConfigLoader extends ConfigLoader {
11993
12039
  }
11994
12040
  /* get the parent directory */
11995
12041
  const child = current;
11996
- current = path.dirname(current);
12042
+ current = path$1.dirname(current);
11997
12043
  /* stop if this is the root directory */
11998
12044
  if (current === child) {
11999
12045
  break;
@@ -12007,8 +12053,14 @@ class FileSystemConfigLoader extends ConfigLoader {
12007
12053
  this.cache.set(filename, config);
12008
12054
  return config;
12009
12055
  }
12056
+ /**
12057
+ * @internal For testing only
12058
+ */
12059
+ _getInternalCache() {
12060
+ return this.cache;
12061
+ }
12010
12062
  defaultConfig() {
12011
- return this.configFactory.defaultConfig();
12063
+ return Config.defaultConfig();
12012
12064
  }
12013
12065
  }
12014
12066
 
@@ -12263,5 +12315,5 @@ function getFormatter(name) {
12263
12315
  return (_a = availableFormatters[name]) !== null && _a !== void 0 ? _a : null;
12264
12316
  }
12265
12317
 
12266
- export { Attribute as A, isTextNode as B, Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, isElementNode as G, HtmlValidate as H, generateIdSelector as I, name as J, bugs as K, MetaTable as M, NodeClosed as N, Presets as P, ResolvedConfig as R, Severity as S, TextNode as T, UserError as U, Validator as V, WrappedError as W, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, DOMTokenList as d, HtmlElement as e, DOMNode as f, DOMTree as g, NodeType as h, SchemaValidationError as i, NestedError as j, TextContent$1 as k, MetaCopyableProperty as l, Rule as m, Reporter as n, TemplateExtractor as o, definePlugin as p, Parser as q, ruleExists as r, sliceLocation as s, getFormatter as t, legacyRequire as u, version as v, ensureError as w, configDataFromFile as x, compatibilityCheck as y, codeframe as z };
12318
+ export { Attribute as A, isTextNode as B, Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, isElementNode as G, HtmlValidate as H, generateIdSelector as I, name as J, bugs as K, MetaTable as M, NodeClosed as N, Presets as P, ResolvedConfig as R, Severity as S, TextNode as T, UserError as U, Validator as V, WrappedError as W, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, DOMTokenList as d, HtmlElement as e, DOMNode as f, DOMTree as g, NodeType as h, SchemaValidationError as i, NestedError as j, TextContent$1 as k, MetaCopyableProperty as l, Rule as m, sliceLocation as n, Reporter as o, definePlugin as p, Parser as q, ruleExists as r, staticResolver as s, getFormatter as t, legacyRequire as u, version as v, ensureError as w, nodejsResolver as x, compatibilityCheck as y, codeframe as z };
12267
12319
  //# sourceMappingURL=core.js.map