html-validate 8.7.3 → 8.8.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.
Files changed (73) hide show
  1. package/dist/cjs/browser.js +7 -21
  2. package/dist/cjs/browser.js.map +1 -1
  3. package/dist/cjs/cli.js +468 -451
  4. package/dist/cjs/cli.js.map +1 -1
  5. package/dist/cjs/core-browser.js +9 -20
  6. package/dist/cjs/core-browser.js.map +1 -1
  7. package/dist/cjs/core-nodejs.js +203 -297
  8. package/dist/cjs/core-nodejs.js.map +1 -1
  9. package/dist/cjs/core.js +9511 -10450
  10. package/dist/cjs/core.js.map +1 -1
  11. package/dist/cjs/elements.js +2293 -2296
  12. package/dist/cjs/elements.js.map +1 -1
  13. package/dist/cjs/html-validate.js +148 -169
  14. package/dist/cjs/html-validate.js.map +1 -1
  15. package/dist/cjs/html5.js.map +1 -1
  16. package/dist/cjs/index.js +8 -21
  17. package/dist/cjs/index.js.map +1 -1
  18. package/dist/cjs/jest-diff.js +26 -36
  19. package/dist/cjs/jest-diff.js.map +1 -1
  20. package/dist/cjs/jest.js +8 -8
  21. package/dist/cjs/jest.js.map +1 -1
  22. package/dist/cjs/matcher-utils.js +12 -28
  23. package/dist/cjs/matcher-utils.js.map +1 -1
  24. package/dist/cjs/matchers-jestonly.js +38 -47
  25. package/dist/cjs/matchers-jestonly.js.map +1 -1
  26. package/dist/cjs/matchers.js +196 -196
  27. package/dist/cjs/matchers.js.map +1 -1
  28. package/dist/cjs/meta-helper.js +40 -60
  29. package/dist/cjs/meta-helper.js.map +1 -1
  30. package/dist/cjs/test-utils.js +26 -47
  31. package/dist/cjs/test-utils.js.map +1 -1
  32. package/dist/cjs/tsdoc-metadata.json +1 -1
  33. package/dist/cjs/utils/natural-join.js +10 -23
  34. package/dist/cjs/utils/natural-join.js.map +1 -1
  35. package/dist/cjs/vitest.js +6 -6
  36. package/dist/cjs/vitest.js.map +1 -1
  37. package/dist/es/browser.js +2 -2
  38. package/dist/es/cli.js +467 -454
  39. package/dist/es/cli.js.map +1 -1
  40. package/dist/es/core-browser.js +10 -21
  41. package/dist/es/core-browser.js.map +1 -1
  42. package/dist/es/core-nodejs.js +204 -299
  43. package/dist/es/core-nodejs.js.map +1 -1
  44. package/dist/es/core.js +9506 -10451
  45. package/dist/es/core.js.map +1 -1
  46. package/dist/es/elements.js +2293 -2296
  47. package/dist/es/elements.js.map +1 -1
  48. package/dist/es/html-validate.js +150 -171
  49. package/dist/es/html-validate.js.map +1 -1
  50. package/dist/es/html5.js.map +1 -1
  51. package/dist/es/index.js +3 -3
  52. package/dist/es/jest-diff.js +11 -21
  53. package/dist/es/jest-diff.js.map +1 -1
  54. package/dist/es/jest.js +8 -8
  55. package/dist/es/jest.js.map +1 -1
  56. package/dist/es/matcher-utils.js +12 -28
  57. package/dist/es/matcher-utils.js.map +1 -1
  58. package/dist/es/matchers-jestonly.js +39 -48
  59. package/dist/es/matchers-jestonly.js.map +1 -1
  60. package/dist/es/matchers.js +196 -196
  61. package/dist/es/matchers.js.map +1 -1
  62. package/dist/es/meta-helper.js +40 -60
  63. package/dist/es/meta-helper.js.map +1 -1
  64. package/dist/es/test-utils.js +26 -47
  65. package/dist/es/test-utils.js.map +1 -1
  66. package/dist/es/utils/natural-join.js +10 -23
  67. package/dist/es/utils/natural-join.js.map +1 -1
  68. package/dist/es/vitest.js +6 -6
  69. package/dist/es/vitest.js.map +1 -1
  70. package/dist/tsdoc-metadata.json +1 -1
  71. package/dist/types/browser.d.ts +62 -30
  72. package/dist/types/index.d.ts +89 -32
  73. package/package.json +22 -18
package/dist/cjs/cli.js CHANGED
@@ -7,6 +7,8 @@ var fs = require('fs');
7
7
  var glob = require('glob');
8
8
  var prompts = require('prompts');
9
9
  require('./meta-helper.js');
10
+ var fs$1 = require('node:fs');
11
+ var betterAjvErrors = require('@sidvind/better-ajv-errors');
10
12
  var kleur = require('kleur');
11
13
 
12
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -14,531 +16,546 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
16
  var path__default = /*#__PURE__*/_interopDefault(path);
15
17
  var fs__default = /*#__PURE__*/_interopDefault(fs);
16
18
  var prompts__default = /*#__PURE__*/_interopDefault(prompts);
19
+ var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
20
+ var betterAjvErrors__default = /*#__PURE__*/_interopDefault(betterAjvErrors);
17
21
  var kleur__default = /*#__PURE__*/_interopDefault(kleur);
18
22
 
19
23
  const DEFAULT_EXTENSIONS = ["html"];
20
24
  function isDirectory(filename) {
21
- const st = fs__default.default.statSync(filename);
22
- return st.isDirectory();
25
+ const st = fs__default.default.statSync(filename);
26
+ return st.isDirectory();
23
27
  }
24
28
  function join(stem, filename) {
25
- if (path__default.default.isAbsolute(filename)) {
26
- return path__default.default.normalize(filename);
27
- }
28
- else {
29
- return path__default.default.normalize(path__default.default.join(stem, filename));
30
- }
29
+ if (path__default.default.isAbsolute(filename)) {
30
+ return path__default.default.normalize(filename);
31
+ } else {
32
+ return path__default.default.normalize(path__default.default.join(stem, filename));
33
+ }
31
34
  }
32
35
  function directoryPattern(extensions) {
33
- switch (extensions.length) {
34
- case 0:
35
- return "**/*";
36
- case 1:
37
- return `**/*.${extensions[0]}`;
38
- default:
39
- return `**/*.{${extensions.join(",")}}`;
40
- }
36
+ switch (extensions.length) {
37
+ case 0:
38
+ return "**/*";
39
+ case 1:
40
+ return `**/*.${extensions[0]}`;
41
+ default:
42
+ return `**/*.{${extensions.join(",")}}`;
43
+ }
41
44
  }
42
- /**
43
- * Takes a number of file patterns (globs) and returns array of expanded
44
- * filenames.
45
- */
46
45
  function expandFiles(patterns, options) {
47
- var _a, _b;
48
- const cwd = (_a = options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
49
- const extensions = (_b = options.extensions) !== null && _b !== void 0 ? _b : DEFAULT_EXTENSIONS;
50
- const files = patterns.reduce((result, pattern) => {
51
- /* process - as standard input */
52
- if (pattern === "-") {
53
- result.push("/dev/stdin");
54
- return result;
55
- }
56
- for (const filename of glob.globSync(pattern, { cwd })) {
57
- /* if file is a directory recursively expand files from it */
58
- const fullpath = join(cwd, filename);
59
- if (isDirectory(fullpath)) {
60
- const dir = expandFiles([directoryPattern(extensions)], { ...options, cwd: fullpath });
61
- result = result.concat(dir.map((cur) => join(filename, cur)));
62
- continue;
63
- }
64
- result.push(fullpath);
65
- }
66
- return result.sort((a, b) => {
67
- const pa = a.split("/").length;
68
- const pb = b.split("/").length;
69
- if (pa !== pb) {
70
- return pa - pb;
71
- }
72
- else {
73
- return a > b ? 1 : -1;
74
- }
75
- });
76
- }, []);
77
- /* only return unique matches */
78
- return Array.from(new Set(files));
46
+ const cwd = options.cwd ?? process.cwd();
47
+ const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
48
+ const files = patterns.reduce((result, pattern) => {
49
+ if (pattern === "-") {
50
+ result.push("/dev/stdin");
51
+ return result;
52
+ }
53
+ for (const filename of glob.globSync(pattern, { cwd })) {
54
+ const fullpath = join(cwd, filename);
55
+ if (isDirectory(fullpath)) {
56
+ const dir = expandFiles([directoryPattern(extensions)], { ...options, cwd: fullpath });
57
+ result = result.concat(dir.map((cur) => join(filename, cur)));
58
+ continue;
59
+ }
60
+ result.push(fullpath);
61
+ }
62
+ return result.sort((a, b) => {
63
+ const pa = a.split("/").length;
64
+ const pb = b.split("/").length;
65
+ if (pa !== pb) {
66
+ return pa - pb;
67
+ } else {
68
+ return a > b ? 1 : -1;
69
+ }
70
+ });
71
+ }, []);
72
+ return Array.from(new Set(files));
79
73
  }
80
74
 
81
75
  function wrap(formatter, dst) {
82
- return (results) => {
83
- const output = formatter(results);
84
- if (dst) {
85
- const dir = path__default.default.dirname(dst);
86
- if (!fs__default.default.existsSync(dir)) {
87
- fs__default.default.mkdirSync(dir, { recursive: true });
88
- }
89
- fs__default.default.writeFileSync(dst, output, "utf-8");
90
- return "";
91
- }
92
- else {
93
- return output;
94
- }
95
- };
76
+ return (results) => {
77
+ const output = formatter(results);
78
+ if (dst) {
79
+ const dir = path__default.default.dirname(dst);
80
+ if (!fs__default.default.existsSync(dir)) {
81
+ fs__default.default.mkdirSync(dir, { recursive: true });
82
+ }
83
+ fs__default.default.writeFileSync(dst, output, "utf-8");
84
+ return "";
85
+ } else {
86
+ return output;
87
+ }
88
+ };
96
89
  }
97
90
  function loadFormatter(name) {
98
- const fn = core.getFormatter(name);
99
- if (fn) {
100
- return fn;
101
- }
102
- try {
103
- return coreNodejs.legacyRequire(name);
104
- }
105
- catch (error) {
106
- throw new core.UserError(`No formatter named "${name}"`, core.ensureError(error));
107
- }
91
+ const fn = core.getFormatter(name);
92
+ if (fn) {
93
+ return fn;
94
+ }
95
+ try {
96
+ return coreNodejs.legacyRequire(name);
97
+ } catch (error) {
98
+ throw new core.UserError(`No formatter named "${name}"`, core.ensureError(error));
99
+ }
108
100
  }
109
101
  function getFormatter(formatters) {
110
- const fn = formatters.split(",").map((cur) => {
111
- const [name, dst] = cur.split("=", 2);
112
- const fn = loadFormatter(name);
113
- return wrap(fn, dst);
114
- });
115
- return (report) => {
116
- return fn
117
- .map((formatter) => formatter(report.results))
118
- .filter(Boolean)
119
- .join("\n");
120
- };
102
+ const fn = formatters.split(",").map((cur) => {
103
+ const [name, dst] = cur.split("=", 2);
104
+ const fn2 = loadFormatter(name);
105
+ return wrap(fn2, dst);
106
+ });
107
+ return (report) => {
108
+ return fn.map((formatter) => formatter(report.results)).filter(Boolean).join("\n");
109
+ };
121
110
  }
122
111
 
123
112
  class IsIgnored {
124
- constructor() {
125
- this.cacheIgnore = new Map();
126
- }
127
- /**
128
- * Searches ".htmlvalidateignore" files from filesystem and returns `true` if
129
- * one of them contains a pattern matching given filename.
130
- */
131
- isIgnored(filename) {
132
- return this.match(filename);
133
- }
134
- /**
135
- * Clear cache
136
- */
137
- clearCache() {
138
- this.cacheIgnore.clear();
139
- }
140
- match(target) {
141
- let current = path__default.default.dirname(target);
142
- // eslint-disable-next-line no-constant-condition -- breaks out when filesystem is traversed
143
- while (true) {
144
- const relative = path__default.default.relative(current, target);
145
- const filename = path__default.default.join(current, ".htmlvalidateignore");
146
- /* test filename (relative to the ignore file) against the patterns */
147
- const ig = this.parseFile(filename);
148
- if (ig && ig.ignores(relative)) {
149
- return true;
150
- }
151
- /* get the parent directory */
152
- const child = current;
153
- current = path__default.default.dirname(current);
154
- /* stop if this is the root directory */
155
- if (current === child) {
156
- break;
157
- }
158
- }
159
- return false;
160
- }
161
- parseFile(filename) {
162
- if (this.cacheIgnore.has(filename)) {
163
- return this.cacheIgnore.get(filename);
164
- }
165
- if (!fs__default.default.existsSync(filename)) {
166
- this.cacheIgnore.set(filename, undefined);
167
- return undefined;
168
- }
169
- const content = fs__default.default.readFileSync(filename, "utf-8");
170
- const ig = core.ignore().add(content);
171
- this.cacheIgnore.set(filename, ig);
172
- return ig;
173
- }
113
+ constructor() {
114
+ this.cacheIgnore = /* @__PURE__ */ new Map();
115
+ }
116
+ /**
117
+ * Searches ".htmlvalidateignore" files from filesystem and returns `true` if
118
+ * one of them contains a pattern matching given filename.
119
+ */
120
+ isIgnored(filename) {
121
+ return this.match(filename);
122
+ }
123
+ /**
124
+ * Clear cache
125
+ */
126
+ clearCache() {
127
+ this.cacheIgnore.clear();
128
+ }
129
+ match(target) {
130
+ let current = path__default.default.dirname(target);
131
+ while (true) {
132
+ const relative = path__default.default.relative(current, target);
133
+ const filename = path__default.default.join(current, ".htmlvalidateignore");
134
+ const ig = this.parseFile(filename);
135
+ if (ig && ig.ignores(relative)) {
136
+ return true;
137
+ }
138
+ const child = current;
139
+ current = path__default.default.dirname(current);
140
+ if (current === child) {
141
+ break;
142
+ }
143
+ }
144
+ return false;
145
+ }
146
+ parseFile(filename) {
147
+ if (this.cacheIgnore.has(filename)) {
148
+ return this.cacheIgnore.get(filename);
149
+ }
150
+ if (!fs__default.default.existsSync(filename)) {
151
+ this.cacheIgnore.set(filename, void 0);
152
+ return void 0;
153
+ }
154
+ const content = fs__default.default.readFileSync(filename, "utf-8");
155
+ const ig = core.ignore().add(content);
156
+ this.cacheIgnore.set(filename, ig);
157
+ return ig;
158
+ }
174
159
  }
175
160
 
176
- var Frameworks;
177
- (function (Frameworks) {
178
- Frameworks["angularjs"] = "AngularJS";
179
- Frameworks["vuejs"] = "Vue.js";
180
- Frameworks["markdown"] = "Markdown";
181
- })(Frameworks || (Frameworks = {}));
182
161
  const frameworkConfig = {
183
- [Frameworks.angularjs]: {
184
- transform: {
185
- "^.*\\.js$": "html-validate-angular/js",
186
- "^.*\\.html$": "html-validate-angular/html",
187
- },
188
- },
189
- [Frameworks.vuejs]: {
190
- plugins: ["html-validate-vue"],
191
- extends: ["html-validate-vue:recommended"],
192
- transform: {
193
- "^.*\\.vue$": "html-validate-vue",
194
- },
195
- },
196
- [Frameworks.markdown]: {
197
- transform: {
198
- "^.*\\.md$": "html-validate-markdown",
199
- },
200
- },
162
+ ["AngularJS" /* angularjs */]: {
163
+ transform: {
164
+ "^.*\\.js$": "html-validate-angular/js",
165
+ "^.*\\.html$": "html-validate-angular/html"
166
+ }
167
+ },
168
+ ["Vue.js" /* vuejs */]: {
169
+ plugins: ["html-validate-vue"],
170
+ extends: ["html-validate-vue:recommended"],
171
+ transform: {
172
+ "^.*\\.vue$": "html-validate-vue"
173
+ }
174
+ },
175
+ ["Markdown" /* markdown */]: {
176
+ transform: {
177
+ "^.*\\.md$": "html-validate-markdown"
178
+ }
179
+ }
201
180
  };
202
181
  function addFrameworks(src, frameworks) {
203
- let config = src;
204
- for (const framework of frameworks) {
205
- config = core.deepmerge(config, frameworkConfig[framework]);
206
- }
207
- return config;
182
+ let config = src;
183
+ for (const framework of frameworks) {
184
+ config = core.deepmerge(config, frameworkConfig[framework]);
185
+ }
186
+ return config;
208
187
  }
209
188
  function writeConfig(dst, config) {
210
- return new Promise((resolve, reject) => {
211
- fs__default.default.writeFile(dst, JSON.stringify(config, null, 2), (err) => {
212
- if (err)
213
- reject(err);
214
- resolve();
215
- });
189
+ return new Promise((resolve, reject) => {
190
+ fs__default.default.writeFile(dst, JSON.stringify(config, null, 2), (err) => {
191
+ if (err)
192
+ reject(err);
193
+ resolve();
216
194
  });
195
+ });
217
196
  }
218
197
  async function init$1(cwd) {
219
- const filename = `${cwd}/.htmlvalidate.json`;
220
- const exists = fs__default.default.existsSync(filename);
221
- const initialConfig = {
222
- elements: ["html5"],
223
- extends: ["html-validate:recommended"],
224
- };
225
- /* confirm overwrite */
226
- if (exists) {
227
- const result = await prompts__default.default({
228
- name: "overwrite",
229
- type: "confirm",
230
- message: "A .htmlvalidate.json file already exists, do you want to overwrite it?",
231
- });
232
- if (!result.overwrite) {
233
- return Promise.reject();
234
- }
235
- }
236
- const questions = [
237
- {
238
- name: "frameworks",
239
- type: "multiselect",
240
- choices: [
241
- { title: Frameworks.angularjs, value: Frameworks.angularjs },
242
- { title: Frameworks.vuejs, value: Frameworks.vuejs },
243
- { title: Frameworks.markdown, value: Frameworks.markdown },
244
- ],
245
- message: "Support additional frameworks?",
246
- },
247
- ];
248
- /* prompt user for questions */
249
- const answers = await prompts__default.default(questions);
250
- /* write configuration to file */
251
- let config = initialConfig;
252
- config = addFrameworks(config, answers.frameworks);
253
- await writeConfig(filename, config);
254
- return {
255
- filename,
256
- };
198
+ const filename = `${cwd}/.htmlvalidate.json`;
199
+ const exists = fs__default.default.existsSync(filename);
200
+ const initialConfig = {
201
+ elements: ["html5"],
202
+ extends: ["html-validate:recommended"]
203
+ };
204
+ if (exists) {
205
+ const result = await prompts__default.default({
206
+ name: "overwrite",
207
+ type: "confirm",
208
+ message: "A .htmlvalidate.json file already exists, do you want to overwrite it?"
209
+ });
210
+ if (!result.overwrite) {
211
+ return Promise.reject();
212
+ }
213
+ }
214
+ const questions = [
215
+ {
216
+ name: "frameworks",
217
+ type: "multiselect",
218
+ choices: [
219
+ { title: "AngularJS" /* angularjs */, value: "AngularJS" /* angularjs */ },
220
+ { title: "Vue.js" /* vuejs */, value: "Vue.js" /* vuejs */ },
221
+ { title: "Markdown" /* markdown */, value: "Markdown" /* markdown */ }
222
+ ],
223
+ message: "Support additional frameworks?"
224
+ }
225
+ ];
226
+ const answers = await prompts__default.default(questions);
227
+ let config = initialConfig;
228
+ config = addFrameworks(config, answers.frameworks);
229
+ await writeConfig(filename, config);
230
+ return {
231
+ filename
232
+ };
257
233
  }
258
234
 
259
235
  const defaultConfig = {
260
- extends: ["html-validate:recommended"],
236
+ extends: ["html-validate:recommended"]
261
237
  };
262
238
  function getBaseConfig(filename) {
263
- if (filename) {
264
- const resolver = coreNodejs.nodejsResolver();
265
- const configData = resolver.resolveConfig(path__default.default.resolve(filename), { cache: false });
266
- if (!configData) {
267
- throw new core.UserError(`Failed to read configuration from "${filename}"`);
268
- }
269
- return configData;
270
- }
271
- else {
272
- return defaultConfig;
273
- }
239
+ if (filename) {
240
+ const resolver = coreNodejs.cjsResolver();
241
+ const configData = resolver.resolveConfig(path__default.default.resolve(filename), { cache: false });
242
+ if (!configData) {
243
+ throw new core.UserError(`Failed to read configuration from "${filename}"`);
244
+ }
245
+ return configData;
246
+ } else {
247
+ return defaultConfig;
248
+ }
274
249
  }
275
- /**
276
- * @public
277
- */
278
250
  class CLI {
279
- /**
280
- * Create new CLI helper.
281
- *
282
- * Can be used to create tooling with similar properties to bundled CLI
283
- * script.
284
- */
285
- constructor(options) {
286
- this.options = options !== null && options !== void 0 ? options : {};
287
- this.config = this.resolveConfig();
288
- this.loader = new coreNodejs.FileSystemConfigLoader(this.config);
289
- this.ignored = new IsIgnored();
290
- }
291
- /**
292
- * Returns list of files matching patterns and are not ignored. Filenames will
293
- * have absolute paths.
294
- *
295
- * @public
296
- */
297
- expandFiles(patterns, options = {}) {
298
- return expandFiles(patterns, options).filter((filename) => !this.isIgnored(filename));
299
- }
300
- getFormatter(formatters) {
301
- return getFormatter(formatters);
302
- }
303
- /**
304
- * Initialize project with a new configuration.
305
- *
306
- * A new `.htmlvalidate.json` file will be placed in the path provided by
307
- * `cwd`.
308
- */
309
- init(cwd) {
310
- return init$1(cwd);
311
- }
312
- /**
313
- * Searches ".htmlvalidateignore" files from filesystem and returns `true` if
314
- * one of them contains a pattern matching given filename.
315
- */
316
- isIgnored(filename) {
317
- return this.ignored.isIgnored(filename);
318
- }
319
- /**
320
- * Clear cache.
321
- *
322
- * Previously fetched [[HtmlValidate]] instances must either be fetched again
323
- * or call [[HtmlValidate.flushConfigCache]].
324
- */
325
- /* istanbul ignore next: each method is tested separately */
326
- clearCache() {
327
- this.loader.flushCache();
328
- this.ignored.clearCache();
329
- }
330
- /**
331
- * Get HtmlValidate instance with configuration based on options passed to the
332
- * constructor.
333
- *
334
- * @internal
335
- */
336
- getLoader() {
337
- return this.loader;
338
- }
339
- /**
340
- * Get HtmlValidate instance with configuration based on options passed to the
341
- * constructor.
342
- *
343
- * @public
344
- */
345
- getValidator() {
346
- const loader = this.getLoader();
347
- return new core.HtmlValidate(loader);
348
- }
349
- /**
350
- * @internal
351
- */
352
- getConfig() {
353
- return this.config;
354
- }
355
- resolveConfig() {
356
- const { options } = this;
357
- const config = getBaseConfig(options.configFile);
358
- if (options.rules) {
359
- const rules = Array.isArray(options.rules) ? options.rules : [options.rules];
360
- try {
361
- const severityMap = { off: 0, warn: 1, error: 2 };
362
- const ruleConfig = rules.reduce((parsedRules, rule) => {
363
- var _a;
364
- const [ruleName, ruleSeverity] = rule.trim().split(":");
365
- const severityValue = (_a = severityMap[ruleSeverity]) !== null && _a !== void 0 ? _a : parseInt(ruleSeverity, 10);
366
- if (!Object.values(severityMap).includes(severityValue)) {
367
- throw new Error(`Invalid severity value for rule "${ruleName}": ${ruleSeverity}`);
368
- }
369
- parsedRules[ruleName] = severityValue;
370
- return parsedRules;
371
- }, {});
372
- config.extends = [];
373
- config.rules = ruleConfig;
374
- }
375
- catch (err) /* istanbul ignore next */ {
376
- const message = err instanceof Error ? err.message : String(err);
377
- throw new core.UserError(`Error while parsing --rule option "{${rules.join(",")}": ${message}.\n`);
378
- }
379
- }
380
- return config;
251
+ /**
252
+ * Create new CLI helper.
253
+ *
254
+ * Can be used to create tooling with similar properties to bundled CLI
255
+ * script.
256
+ */
257
+ constructor(options) {
258
+ this.options = options ?? {};
259
+ this.config = null;
260
+ this.loader = null;
261
+ this.ignored = new IsIgnored();
262
+ }
263
+ /**
264
+ * Returns list of files matching patterns and are not ignored. Filenames will
265
+ * have absolute paths.
266
+ *
267
+ * @public
268
+ */
269
+ expandFiles(patterns, options = {}) {
270
+ return expandFiles(patterns, options).filter((filename) => !this.isIgnored(filename));
271
+ }
272
+ getFormatter(formatters) {
273
+ return getFormatter(formatters);
274
+ }
275
+ /**
276
+ * Initialize project with a new configuration.
277
+ *
278
+ * A new `.htmlvalidate.json` file will be placed in the path provided by
279
+ * `cwd`.
280
+ */
281
+ init(cwd) {
282
+ return init$1(cwd);
283
+ }
284
+ /**
285
+ * Searches ".htmlvalidateignore" files from filesystem and returns `true` if
286
+ * one of them contains a pattern matching given filename.
287
+ */
288
+ isIgnored(filename) {
289
+ return this.ignored.isIgnored(filename);
290
+ }
291
+ /**
292
+ * Clear cache.
293
+ *
294
+ * Previously fetched [[HtmlValidate]] instances must either be fetched again
295
+ * or call [[HtmlValidate.flushConfigCache]].
296
+ */
297
+ /* istanbul ignore next: each method is tested separately */
298
+ clearCache() {
299
+ if (this.loader) {
300
+ this.loader.flushCache();
301
+ }
302
+ this.ignored.clearCache();
303
+ }
304
+ /**
305
+ * Get HtmlValidate instance with configuration based on options passed to the
306
+ * constructor.
307
+ *
308
+ * @internal
309
+ */
310
+ getLoader() {
311
+ if (!this.loader) {
312
+ this.loader = new coreNodejs.FileSystemConfigLoader(this.getConfig());
313
+ }
314
+ return this.loader;
315
+ }
316
+ /**
317
+ * Get HtmlValidate instance with configuration based on options passed to the
318
+ * constructor.
319
+ *
320
+ * @public
321
+ */
322
+ getValidator() {
323
+ const loader = this.getLoader();
324
+ return new core.HtmlValidate(loader);
325
+ }
326
+ /**
327
+ * @internal
328
+ */
329
+ getConfig() {
330
+ if (!this.config) {
331
+ this.config = this.resolveConfig();
332
+ }
333
+ return this.config;
334
+ }
335
+ resolveConfig() {
336
+ const { options } = this;
337
+ const config = getBaseConfig(options.configFile);
338
+ if (options.rules) {
339
+ const rules = Array.isArray(options.rules) ? options.rules : [options.rules];
340
+ try {
341
+ const severityMap = { off: 0, warn: 1, error: 2 };
342
+ const ruleConfig = rules.reduce((parsedRules, rule) => {
343
+ const [ruleName, ruleSeverity] = rule.trim().split(":");
344
+ const severityValue = severityMap[ruleSeverity] ?? parseInt(ruleSeverity, 10);
345
+ if (!Object.values(severityMap).includes(severityValue)) {
346
+ throw new Error(`Invalid severity value for rule "${ruleName}": ${ruleSeverity}`);
347
+ }
348
+ parsedRules[ruleName] = severityValue;
349
+ return parsedRules;
350
+ }, {});
351
+ config.extends = [];
352
+ config.rules = ruleConfig;
353
+ } catch (err) {
354
+ const message = err instanceof Error ? err.message : String(err);
355
+ throw new core.UserError(
356
+ `Error while parsing --rule option "{${rules.join(",")}": ${message}.
357
+ `
358
+ );
359
+ }
381
360
  }
361
+ return config;
362
+ }
363
+ }
364
+
365
+ function prettyError(err) {
366
+ let json;
367
+ if (err.filename && fs__default$1.default.existsSync(err.filename)) {
368
+ json = fs__default$1.default.readFileSync(err.filename, "utf-8");
369
+ }
370
+ return betterAjvErrors__default.default(err.schema, err.obj, err.errors, {
371
+ format: "cli",
372
+ indent: 2,
373
+ json
374
+ });
375
+ }
376
+ function handleSchemaValidationError(console, err) {
377
+ if (err.filename) {
378
+ const filename = path__default.default.relative(process.cwd(), err.filename);
379
+ console.error(kleur__default.default.red(`A configuration error was found in "${filename}":`));
380
+ } else {
381
+ console.error(kleur__default.default.red(`A configuration error was found:`));
382
+ }
383
+ console.group();
384
+ {
385
+ console.error(prettyError(err));
386
+ }
387
+ console.groupEnd();
382
388
  }
383
389
 
384
- /**
385
- * @internal
386
- */
387
- exports.Mode = void 0;
388
- (function (Mode) {
389
- Mode[Mode["LINT"] = 0] = "LINT";
390
- Mode[Mode["INIT"] = 1] = "INIT";
391
- Mode[Mode["DUMP_EVENTS"] = 2] = "DUMP_EVENTS";
392
- Mode[Mode["DUMP_TOKENS"] = 3] = "DUMP_TOKENS";
393
- Mode[Mode["DUMP_TREE"] = 4] = "DUMP_TREE";
394
- Mode[Mode["DUMP_SOURCE"] = 5] = "DUMP_SOURCE";
395
- Mode[Mode["PRINT_CONFIG"] = 6] = "PRINT_CONFIG";
396
- })(exports.Mode || (exports.Mode = {}));
390
+ var Mode = /* @__PURE__ */ ((Mode2) => {
391
+ Mode2[Mode2["LINT"] = 0] = "LINT";
392
+ Mode2[Mode2["INIT"] = 1] = "INIT";
393
+ Mode2[Mode2["DUMP_EVENTS"] = 2] = "DUMP_EVENTS";
394
+ Mode2[Mode2["DUMP_TOKENS"] = 3] = "DUMP_TOKENS";
395
+ Mode2[Mode2["DUMP_TREE"] = 4] = "DUMP_TREE";
396
+ Mode2[Mode2["DUMP_SOURCE"] = 5] = "DUMP_SOURCE";
397
+ Mode2[Mode2["PRINT_CONFIG"] = 6] = "PRINT_CONFIG";
398
+ return Mode2;
399
+ })(Mode || {});
397
400
  function modeToFlag(mode) {
398
- switch (mode) {
399
- case exports.Mode.LINT:
400
- return null;
401
- case exports.Mode.INIT:
402
- return "--init";
403
- case exports.Mode.DUMP_EVENTS:
404
- return "--dump-events";
405
- case exports.Mode.DUMP_TOKENS:
406
- return "--dump-tokens";
407
- case exports.Mode.DUMP_TREE:
408
- return "--dump-tree";
409
- case exports.Mode.DUMP_SOURCE:
410
- return "--dump-source";
411
- case exports.Mode.PRINT_CONFIG:
412
- return "--print-config";
413
- }
401
+ switch (mode) {
402
+ case 0 /* LINT */:
403
+ return null;
404
+ case 1 /* INIT */:
405
+ return "--init";
406
+ case 2 /* DUMP_EVENTS */:
407
+ return "--dump-events";
408
+ case 3 /* DUMP_TOKENS */:
409
+ return "--dump-tokens";
410
+ case 4 /* DUMP_TREE */:
411
+ return "--dump-tree";
412
+ case 5 /* DUMP_SOURCE */:
413
+ return "--dump-source";
414
+ case 6 /* PRINT_CONFIG */:
415
+ return "--print-config";
416
+ }
414
417
  }
415
418
 
416
419
  function renameStdin(report, filename) {
417
- const stdin = report.results.find((cur) => cur.filePath === "/dev/stdin");
418
- if (stdin) {
419
- stdin.filePath = filename;
420
- }
420
+ const stdin = report.results.find((cur) => cur.filePath === "/dev/stdin");
421
+ if (stdin) {
422
+ stdin.filePath = filename;
423
+ }
421
424
  }
422
- function lint(htmlvalidate, output, files, options) {
423
- const reports = files.map((filename) => {
424
- try {
425
- return htmlvalidate.validateFileSync(filename);
426
- }
427
- catch (err) {
428
- const message = kleur__default.default.red(`Validator crashed when parsing "${filename}"`);
429
- output.write(`${message}\n`);
430
- throw err;
431
- }
432
- });
433
- const merged = core.Reporter.merge(reports);
434
- /* rename stdin if an explicit filename was passed */
435
- if (options.stdinFilename) {
436
- renameStdin(merged, options.stdinFilename);
437
- }
438
- output.write(options.formatter(merged));
439
- if (options.maxWarnings >= 0 && merged.warningCount > options.maxWarnings) {
440
- output.write(`\nhtml-validate found too many warnings (maximum: ${options.maxWarnings}).\n`);
441
- return Promise.resolve(false);
442
- }
443
- return Promise.resolve(merged.valid);
425
+ async function lint(htmlvalidate, output, files, options) {
426
+ const reports = files.map(async (filename) => {
427
+ try {
428
+ return await htmlvalidate.validateFile(filename);
429
+ } catch (err) {
430
+ const message = kleur__default.default.red(`Validator crashed when parsing "${filename}"`);
431
+ output.write(`${message}
432
+ `);
433
+ throw err;
434
+ }
435
+ });
436
+ const merged = await core.Reporter.merge(reports);
437
+ if (options.stdinFilename) {
438
+ renameStdin(merged, options.stdinFilename);
439
+ }
440
+ output.write(options.formatter(merged));
441
+ if (options.maxWarnings >= 0 && merged.warningCount > options.maxWarnings) {
442
+ output.write(`
443
+ html-validate found too many warnings (maximum: ${options.maxWarnings}).
444
+ `);
445
+ return false;
446
+ }
447
+ return merged.valid;
444
448
  }
445
449
 
446
450
  async function init(cli, output, options) {
447
- const result = await cli.init(options.cwd);
448
- output.write(`Configuration written to "${result.filename}"\n`);
449
- return true;
451
+ const result = await cli.init(options.cwd);
452
+ output.write(`Configuration written to "${result.filename}"
453
+ `);
454
+ return true;
450
455
  }
451
456
 
452
457
  async function printConfig(htmlvalidate, output, files) {
453
- if (files.length > 1) {
454
- output.write(`\`--print-config\` expected a single filename but got multiple:\n\n`);
455
- for (const filename of files) {
456
- output.write(` - ${filename}\n`);
457
- }
458
- output.write("\n");
459
- return false;
458
+ if (files.length > 1) {
459
+ output.write(`\`--print-config\` expected a single filename but got multiple:
460
+
461
+ `);
462
+ for (const filename of files) {
463
+ output.write(` - ${filename}
464
+ `);
460
465
  }
461
- const config = await htmlvalidate.getConfigFor(files[0]);
462
- const json = JSON.stringify(config.getConfigData(), null, 2);
463
- output.write(`${json}\n`);
464
- return true;
466
+ output.write("\n");
467
+ return false;
468
+ }
469
+ const config = await htmlvalidate.getConfigFor(files[0]);
470
+ const json = JSON.stringify(config.getConfigData(), null, 2);
471
+ output.write(`${json}
472
+ `);
473
+ return true;
465
474
  }
466
475
 
467
476
  const jsonIgnored = [
468
- "annotation",
469
- "blockedRules",
470
- "cache",
471
- "closed",
472
- "depth",
473
- "disabledRules",
474
- "nodeType",
475
- "unique",
476
- "voidElement",
477
+ "annotation",
478
+ "blockedRules",
479
+ "cache",
480
+ "closed",
481
+ "depth",
482
+ "disabledRules",
483
+ "nodeType",
484
+ "unique",
485
+ "voidElement"
477
486
  ];
478
487
  const jsonFiltered = [
479
- "childNodes",
480
- "children",
481
- "data",
482
- "meta",
483
- "metaElement",
484
- "originalData",
485
- "parent",
488
+ "childNodes",
489
+ "children",
490
+ "data",
491
+ "meta",
492
+ "metaElement",
493
+ "originalData",
494
+ "parent"
486
495
  ];
487
496
  function isLocation(key, value) {
488
- return Boolean(value && (key === "location" || key.endsWith("Location")));
497
+ return Boolean(value && (key === "location" || key.endsWith("Location")));
489
498
  }
490
499
  function isIgnored(key) {
491
- return Boolean(key.startsWith("_") || jsonIgnored.includes(key));
500
+ return Boolean(key.startsWith("_") || jsonIgnored.includes(key));
492
501
  }
493
502
  function isFiltered(key, value) {
494
- return Boolean(value && jsonFiltered.includes(key));
503
+ return Boolean(value && jsonFiltered.includes(key));
495
504
  }
496
505
  function eventReplacer(key, value) {
497
- if (isLocation(key, value)) {
498
- return `${value.filename}:${value.line}:${value.column}`;
499
- }
500
- if (isIgnored(key)) {
501
- return undefined;
502
- }
503
- if (isFiltered(key, value)) {
504
- return "[truncated]";
505
- }
506
- return value;
506
+ if (isLocation(key, value)) {
507
+ return `${value.filename}:${value.line}:${value.column}`;
508
+ }
509
+ if (isIgnored(key)) {
510
+ return void 0;
511
+ }
512
+ if (isFiltered(key, value)) {
513
+ return "[truncated]";
514
+ }
515
+ return value;
507
516
  }
508
517
  function eventFormatter(entry) {
509
- const strdata = JSON.stringify(entry.data, eventReplacer, 2);
510
- return `${entry.event}: ${strdata}`;
518
+ const strdata = JSON.stringify(entry.data, eventReplacer, 2);
519
+ return `${entry.event}: ${strdata}`;
511
520
  }
512
521
 
513
522
  function dump(htmlvalidate, output, files, mode) {
514
- let lines = [];
515
- switch (mode) {
516
- case exports.Mode.DUMP_EVENTS:
517
- lines = files.map((filename) => htmlvalidate.dumpEvents(filename).map(eventFormatter));
518
- break;
519
- case exports.Mode.DUMP_TOKENS:
520
- lines = files.map((filename) => htmlvalidate.dumpTokens(filename).map((entry) => {
521
- const data = JSON.stringify(entry.data);
522
- return `TOKEN: ${entry.token}\n Data: ${data}\n Location: ${entry.location}`;
523
- }));
524
- break;
525
- case exports.Mode.DUMP_TREE:
526
- lines = files.map((filename) => htmlvalidate.dumpTree(filename));
527
- break;
528
- case exports.Mode.DUMP_SOURCE:
529
- lines = files.map((filename) => htmlvalidate.dumpSource(filename));
530
- break;
531
- default:
532
- throw new Error(`Unknown mode "${mode}"`);
533
- }
534
- const flat = lines.reduce((s, c) => s.concat(c), []);
535
- output.write(flat.join("\n"));
536
- output.write("\n");
537
- return Promise.resolve(true);
523
+ let lines = [];
524
+ switch (mode) {
525
+ case Mode.DUMP_EVENTS:
526
+ lines = files.map(
527
+ (filename) => htmlvalidate.dumpEvents(filename).map(eventFormatter)
528
+ );
529
+ break;
530
+ case Mode.DUMP_TOKENS:
531
+ lines = files.map(
532
+ (filename) => htmlvalidate.dumpTokens(filename).map((entry) => {
533
+ const data = JSON.stringify(entry.data);
534
+ return `TOKEN: ${entry.token}
535
+ Data: ${data}
536
+ Location: ${entry.location}`;
537
+ })
538
+ );
539
+ break;
540
+ case Mode.DUMP_TREE:
541
+ lines = files.map((filename) => htmlvalidate.dumpTree(filename));
542
+ break;
543
+ case Mode.DUMP_SOURCE:
544
+ lines = files.map((filename) => htmlvalidate.dumpSource(filename));
545
+ break;
546
+ default:
547
+ throw new Error(`Unknown mode "${mode}"`);
548
+ }
549
+ const flat = lines.reduce((s, c) => s.concat(c), []);
550
+ output.write(flat.join("\n"));
551
+ output.write("\n");
552
+ return Promise.resolve(true);
538
553
  }
539
554
 
540
555
  exports.CLI = CLI;
556
+ exports.Mode = Mode;
541
557
  exports.dump = dump;
558
+ exports.handleSchemaValidationError = handleSchemaValidationError;
542
559
  exports.init = init;
543
560
  exports.lint = lint;
544
561
  exports.modeToFlag = modeToFlag;