metalint 0.14.1 → 0.16.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 (90) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +39 -8
  3. package/package.json +44 -36
  4. package/src/bin/argv.js +6 -4
  5. package/src/bin/index.js +10 -105
  6. package/src/core/configuration/flatten.js +56 -54
  7. package/src/core/configuration/normalize.js +18 -16
  8. package/src/core/configuration/override.js +1 -1
  9. package/src/core/formatter/checkstyle.js +2 -2
  10. package/src/core/formatter/console.js +10 -10
  11. package/src/core/formatter/csv.js +2 -2
  12. package/src/core/formatter/formatter.js +7 -20
  13. package/src/core/formatter/github.js +2 -2
  14. package/src/core/formatter/json.js +2 -2
  15. package/src/core/formatter/unix.js +2 -2
  16. package/src/core/index.js +194 -111
  17. package/src/core/results.js +106 -0
  18. package/src/core/wrapper/addons-linter.js +19 -20
  19. package/src/core/wrapper/ajv.js +123 -0
  20. package/src/core/wrapper/coffeelint__cli.js +16 -14
  21. package/src/core/wrapper/csslint.js +17 -20
  22. package/src/core/wrapper/depcheck.js +149 -0
  23. package/src/core/wrapper/doiuse.js +19 -17
  24. package/src/core/wrapper/eslint.js +17 -17
  25. package/src/core/wrapper/htmlhint.js +16 -20
  26. package/src/core/wrapper/htmllint.js +17 -20
  27. package/src/core/wrapper/jshint.js +16 -20
  28. package/src/core/wrapper/jsonlint-mod.js +15 -13
  29. package/src/core/wrapper/markdownlint.js +15 -14
  30. package/src/core/wrapper/markuplint.js +19 -14
  31. package/src/core/wrapper/npm-check-updates.js +15 -14
  32. package/src/core/wrapper/npm-package-json-lint.js +15 -14
  33. package/src/core/wrapper/prantlf__jsonlint.js +15 -14
  34. package/src/core/wrapper/prettier.js +15 -14
  35. package/src/core/wrapper/publint.js +137 -0
  36. package/src/core/wrapper/purgecss.js +15 -14
  37. package/src/core/wrapper/sort-package-json.js +15 -14
  38. package/src/core/wrapper/standard.js +22 -14
  39. package/src/core/wrapper/stylelint.js +16 -20
  40. package/src/core/wrapper/svglint.js +94 -0
  41. package/src/core/wrapper/wrapper.js +7 -20
  42. package/src/core/wrapper/yaml-lint.js +15 -14
  43. package/src/types/config.d.ts +6 -0
  44. package/src/types/configuration/flattened.d.ts +38 -0
  45. package/src/types/configuration/normalized.d.ts +44 -0
  46. package/src/types/configuration/partial.d.ts +54 -0
  47. package/src/types/level.d.ts +9 -0
  48. package/src/types/location.d.ts +12 -0
  49. package/src/types/notice.d.ts +26 -0
  50. package/src/types/severity.d.ts +9 -0
  51. package/src/types/typeofformatter.d.ts +13 -0
  52. package/src/types/typeofwrapper.d.ts +20 -0
  53. package/types/configuration/flatten.d.ts +26 -26
  54. package/types/configuration/normalize.d.ts +12 -12
  55. package/types/configuration/override.d.ts +1 -1
  56. package/types/formatter/checkstyle.d.ts +2 -2
  57. package/types/formatter/console.d.ts +4 -4
  58. package/types/formatter/csv.d.ts +2 -2
  59. package/types/formatter/formatter.d.ts +2 -2
  60. package/types/formatter/github.d.ts +4 -4
  61. package/types/formatter/json.d.ts +4 -4
  62. package/types/formatter/unix.d.ts +4 -4
  63. package/types/index.d.ts +82 -16
  64. package/types/results.d.ts +27 -0
  65. package/types/wrapper/addons-linter.d.ts +17 -16
  66. package/types/wrapper/ajv.d.ts +37 -0
  67. package/types/wrapper/coffeelint__cli.d.ts +17 -16
  68. package/types/wrapper/csslint.d.ts +17 -16
  69. package/types/wrapper/depcheck.d.ts +37 -0
  70. package/types/wrapper/doiuse.d.ts +19 -16
  71. package/types/wrapper/eslint.d.ts +17 -16
  72. package/types/wrapper/htmlhint.d.ts +17 -16
  73. package/types/wrapper/htmllint.d.ts +17 -16
  74. package/types/wrapper/jshint.d.ts +17 -16
  75. package/types/wrapper/jsonlint-mod.d.ts +17 -16
  76. package/types/wrapper/markdownlint.d.ts +17 -16
  77. package/types/wrapper/markuplint.d.ts +17 -16
  78. package/types/wrapper/npm-check-updates.d.ts +17 -16
  79. package/types/wrapper/npm-package-json-lint.d.ts +17 -16
  80. package/types/wrapper/prantlf__jsonlint.d.ts +17 -16
  81. package/types/wrapper/prettier.d.ts +17 -16
  82. package/types/wrapper/publint.d.ts +34 -0
  83. package/types/wrapper/purgecss.d.ts +17 -16
  84. package/types/wrapper/sort-package-json.d.ts +17 -16
  85. package/types/wrapper/standard.d.ts +17 -16
  86. package/types/wrapper/stylelint.d.ts +17 -16
  87. package/types/wrapper/svglint.d.ts +37 -0
  88. package/types/wrapper/wrapper.d.ts +2 -2
  89. package/types/wrapper/yaml-lint.d.ts +17 -16
  90. package/src/type/index.d.ts +0 -149
@@ -11,24 +11,26 @@ import Levels from "../levels.js";
11
11
  import Wrapper, { WRAPPERS } from "../wrapper/wrapper.js";
12
12
 
13
13
  /**
14
- * @typedef {import("../../type/index.d.ts").Level} Level
15
- * @typedef {import("../../type/index.d.ts").NormalizedConfig} NormalizedConfig
16
- * @typedef {import("../../type/index.d.ts").NormalizedConfigChecker} NormalizedConfigChecker
17
- * @typedef {import("../../type/index.d.ts").NormalizedConfigLinter} NormalizedConfigLinter
18
- * @typedef {import("../../type/index.d.ts").NormalizedConfigOverride} NormalizedConfigOverride
19
- * @typedef {import("../../type/index.d.ts").NormalizedConfigReporter} NormalizedConfigReporter
14
+ * @typedef {import("../../types/configuration/normalized.d.ts").NormalizedConfig} NormalizedConfig
15
+ * @typedef {import("../../types/configuration/normalized.d.ts").NormalizedConfigChecker} NormalizedConfigChecker
16
+ * @typedef {import("../../types/configuration/normalized.d.ts").NormalizedConfigLinter} NormalizedConfigLinter
17
+ * @typedef {import("../../types/configuration/normalized.d.ts").NormalizedConfigOverride} NormalizedConfigOverride
18
+ * @typedef {import("../../types/configuration/normalized.d.ts").NormalizedConfigReporter} NormalizedConfigReporter
19
+ * @typedef {import("../../types/level.d.ts").default} Level
20
+ * @typedef {import("../../types/typeofformatter.d.ts").default} TypeofFormatter
21
+ * @typedef {import("../../types/typeofwrapper.d.ts").default} TypeofWrapper
20
22
  */
21
23
 
22
24
  /**
23
25
  * Lit un fichier JavaScript exportant un objet JSON.
24
26
  *
25
27
  * @param {string} file L'adresse du fichier qui sera lu.
26
- * @returns {Promise<Record<string, any>>} L'objet JSON récupéré.
28
+ * @returns {Promise<Record<string, unknown>>} L'objet JSON récupéré.
27
29
  */
28
30
  const read = async function (file) {
29
31
  try {
30
32
  // eslint-disable-next-line no-unsanitized/method
31
- const module = await import(pathToFileURL(file));
33
+ const module = await import(pathToFileURL(file).href);
32
34
  return module.default;
33
35
  } catch (err) {
34
36
  throw new Error(`Cannot import '${file}'.`, { cause: err });
@@ -138,7 +140,7 @@ export const normalizeLevel = function (partial) {
138
140
  * Normalise une propriété <code>"formatter"</code>.
139
141
  *
140
142
  * @param {*} partial La valeur d'une propriété <code>"formatter"</code>.
141
- * @returns {Promise<typeof Formatter>} La valeur normalisée.
143
+ * @returns {Promise<TypeofFormatter>} La valeur normalisée.
142
144
  * @throws {Error} Si le <code>"formatter"</code> est invalide.
143
145
  * @throws {TypeError} Si le <code>"formatter"</code> n'a pas le bon type.
144
146
  */
@@ -178,7 +180,7 @@ export const normalizeFormatter = async function (partial) {
178
180
  * @param {Object} context Le context de la propriété.
179
181
  * @param {string} context.dir Le répertoire où se trouve le fichier
180
182
  * <code>metalint.config.js</code>.
181
- * @returns {Promise<Record<string, any>>} La valeur normalisée.
183
+ * @returns {Promise<Record<string, unknown>>} La valeur normalisée.
182
184
  * @throws {TypeError} Si l'<code>"options"</code> n'a pas le bon type.
183
185
  */
184
186
  export const normalizeOption = async function (partial, { dir }) {
@@ -202,7 +204,7 @@ export const normalizeOption = async function (partial, { dir }) {
202
204
  * @param {Object} context Le context de la propriété.
203
205
  * @param {string} context.dir Le répertoire où se trouve le fichier
204
206
  * <code>metalint.config.js</code>.
205
- * @returns {Promise<Record<string, any>[]>} La valeur normalisée.
207
+ * @returns {Promise<Record<string, unknown>[]>} La valeur normalisée.
206
208
  * @throws {TypeError} Si l'<code>"options"</code> n'a pas le bon type.
207
209
  */
208
210
  export const normalizeOptions = async function (partials, { dir }) {
@@ -279,7 +281,7 @@ export const normalizeReporters = async function (partials, { dir }) {
279
281
  * Normalise une propriété <code>"wrapper"</code>.
280
282
  *
281
283
  * @param {*} partial La valeur d'une propriété <code>"wrapper"</code>.
282
- * @returns {Promise<typeof Wrapper>} La valeur normalisée.
284
+ * @returns {Promise<TypeofWrapper>} La valeur normalisée.
283
285
  * @throws {TypeError} Si le <code>"reporters"</code> n'a pas le bon type.
284
286
  */
285
287
  export const normalizeWrapper = async function (partial) {
@@ -358,7 +360,7 @@ export const normalizeLinter = async function (partial, { dir }) {
358
360
  export const normalizeLinters = async function (partials, { dir }) {
359
361
  let normalizeds;
360
362
  if (undefined === partials) {
361
- normalizeds = [];
363
+ normalizeds = /** @type {NormalizedConfigLinter[]} */ ([]);
362
364
  } else if (Array.isArray(partials)) {
363
365
  normalizeds = await Promise.all(
364
366
  partials.map((p) => normalizeLinter(p, { dir })),
@@ -411,7 +413,7 @@ export const normalizeOverride = async function (partial, { dir }) {
411
413
  export const normalizeOverrides = async function (partials, { dir }) {
412
414
  let normalizeds;
413
415
  if (undefined === partials) {
414
- normalizeds = [];
416
+ normalizeds = /** @type {NormalizedConfigOverride[]} */ ([]);
415
417
  } else if (Array.isArray(partials)) {
416
418
  normalizeds = await Promise.all(
417
419
  partials.map((p) => normalizeOverride(p, { dir })),
@@ -465,7 +467,7 @@ export const normalizeChecker = async function (partial, { dir }) {
465
467
  export const normalizeCheckers = async function (partials, { dir }) {
466
468
  let normalizeds;
467
469
  if (undefined === partials) {
468
- normalizeds = [];
470
+ normalizeds = /** @type {NormalizedConfigChecker[]} */ ([]);
469
471
  } else if (Array.isArray(partials)) {
470
472
  normalizeds = await Promise.all(
471
473
  partials.map((p) => normalizeChecker(p, { dir })),
@@ -495,7 +497,7 @@ export const normalize = async function (partial, { dir }) {
495
497
  if ("object" === typeof partial) {
496
498
  normalized = {
497
499
  patterns: normalizePatterns(partial.patterns, { auto: [] }),
498
- fix: normalizeFix(partial.fix),
500
+ fix: normalizeFix(partial.fix) ?? false,
499
501
  level: normalizeLevel(partial.level),
500
502
  reporters: await normalizeReporters(partial.reporters, { dir }),
501
503
  checkers: await normalizeCheckers(partial.checkers, { dir }),
@@ -7,7 +7,7 @@
7
7
  import { flattenFix, flattenLevel, flattenOptions } from "./flatten.js";
8
8
 
9
9
  /**
10
- * @typedef {import("../../type/index.d.ts").FlattenedConfigLinter} FlattenedConfigLinter
10
+ * @typedef {import("../../types/configuration/flattened.js").FlattenedConfigLinter} FlattenedConfigLinter
11
11
  */
12
12
 
13
13
  /**
@@ -10,8 +10,8 @@ import Formatter from "./formatter.js";
10
10
 
11
11
  /**
12
12
  * @typedef {import("node:stream").Writable} Writable
13
- * @typedef {import("../../type/index.js").Level} Level
14
- * @typedef {import("../../type/index.js").Notice} Notice
13
+ * @typedef {import("../../types/level.d.ts").default} Level
14
+ * @typedef {import("../../types/notice.d.ts").default} Notice
15
15
  */
16
16
 
17
17
  /**
@@ -12,10 +12,10 @@ import Formatter from "./formatter.js";
12
12
 
13
13
  /**
14
14
  * @typedef {import("node:stream").Writable} Writable
15
- * @typedef {import("../../type/index.js").Level} Level
16
- * @typedef {import("../../type/index.js").Location} Location
17
- * @typedef {import("../../type/index.js").Notice} Notice
18
- * @typedef {import("../../type/index.js").Severity} Severity
15
+ * @typedef {import("../../types/level.d.ts").default} Level
16
+ * @typedef {import("../../types/location.d.ts").default} Location
17
+ * @typedef {import("../../types/notice.d.ts").default} Notice
18
+ * @typedef {import("../../types/severity.d.ts").default} Severity
19
19
  */
20
20
 
21
21
  /**
@@ -97,7 +97,7 @@ const printCodeSource = function (locations, content, writer) {
97
97
  if (characters.length === i) {
98
98
  characters[i] = {
99
99
  line: location.line,
100
- columns: [],
100
+ columns: /** @type {number[]} */ ([]),
101
101
  };
102
102
  if (undefined !== location.column) {
103
103
  characters[i].columns.push(location.column);
@@ -112,14 +112,14 @@ const printCodeSource = function (locations, content, writer) {
112
112
  printCodeSourceLine(line - 1, content, false, writer);
113
113
  printCodeSourceLine(line, content, true, writer);
114
114
  if (0 !== columns.length) {
115
- let dashs = "-".repeat(6 + columns.at(-1));
115
+ let dashes = "-".repeat(6 + columns.at(-1));
116
116
  for (const column of columns) {
117
- dashs =
118
- dashs.slice(0, 6 + column) +
117
+ dashes =
118
+ dashes.slice(0, 6 + column) +
119
119
  "^" +
120
- dashs.slice(6 + column + 1);
120
+ dashes.slice(6 + column + 1);
121
121
  }
122
- print(writer, dashs + "\n");
122
+ print(writer, dashes + "\n");
123
123
  }
124
124
  printCodeSourceLine(line + 1, content, false, writer);
125
125
  printCodeSourceLine(line + 2, content, false, writer);
@@ -9,8 +9,8 @@ import Formatter from "./formatter.js";
9
9
 
10
10
  /**
11
11
  * @typedef {import("node:stream").Writable} Writable
12
- * @typedef {import("../../type/index.js").Level} Level
13
- * @typedef {import("../../type/index.js").Notice} Notice
12
+ * @typedef {import("../../types/level.d.ts").default} Level
13
+ * @typedef {import("../../types/notice.d.ts").default} Notice
14
14
  */
15
15
 
16
16
  /**
@@ -8,24 +8,10 @@ import fs from "node:fs/promises";
8
8
  import { fileURLToPath } from "node:url";
9
9
 
10
10
  /**
11
- * @typedef {import("../../type/index.js").Level} Level
12
- * @typedef {import("../../type/index.js").Notice} Notice
11
+ * @typedef {import("../../types/level.d.ts").default} Level
12
+ * @typedef {import("../../types/notice.d.ts").default} Notice
13
13
  */
14
14
 
15
- if (undefined === import.meta.resolve) {
16
- /**
17
- * Résous un chemin relatif à partir du module.
18
- *
19
- * @param {string} specifier Le chemin relatif vers un fichier ou un
20
- * répertoire.
21
- * @returns {string} L'URL absolue vers le fichier ou le répertoire.
22
- * @see https://nodejs.org/api/esm.html#importmetaresolvespecifier-parent
23
- */
24
- import.meta.resolve = (specifier) => {
25
- return new URL(specifier, import.meta.url).href;
26
- };
27
- }
28
-
29
15
  /**
30
16
  * La liste des fichiers JavaScript des formateurs.
31
17
  *
@@ -39,10 +25,11 @@ const scripts = await fs.readdir(fileURLToPath(import.meta.resolve("./")));
39
25
  * @constant {string[]} FORMATTERS
40
26
  */
41
27
  export const FORMATTERS = scripts
42
- // Enlever l'extension du fichier.
43
- .map((f) => f.slice(0, -3))
44
- // Enlever ce fichier qui n'est pas un vrai formateur.
45
- .filter((f) => "formatter" !== f);
28
+ // Garder seulement les fichiers JavaScript et enlever le fichier
29
+ // "formatter.js" qui n'est pas un vrai formateur.
30
+ .filter((f) => f.endsWith(".js") && "formatter.js" !== f)
31
+ // Enlever l'extension des fichiers.
32
+ .map((f) => f.slice(0, -3));
46
33
 
47
34
  /**
48
35
  * Le formateur parent.
@@ -10,8 +10,8 @@ import Formatter from "./formatter.js";
10
10
 
11
11
  /**
12
12
  * @typedef {import("node:stream").Writable} Writable
13
- * @typedef {import("../../type/index.js").Level} Level
14
- * @typedef {import("../../type/index.js").Notice} Notice
13
+ * @typedef {import("../../types/level.d.ts").default} Level
14
+ * @typedef {import("../../types/notice.d.ts").default} Notice
15
15
  */
16
16
 
17
17
  /**
@@ -9,8 +9,8 @@ import Formatter from "./formatter.js";
9
9
 
10
10
  /**
11
11
  * @typedef {import("node:stream").Writable} Writable
12
- * @typedef {import("../../type/index.js").Level} Level
13
- * @typedef {import("../../type/index.js").Notice} Notice
12
+ * @typedef {import("../../types/level.d.ts").default} Level
13
+ * @typedef {import("../../types/notice.d.ts").default} Notice
14
14
  */
15
15
 
16
16
  /**
@@ -9,8 +9,8 @@ import Formatter from "./formatter.js";
9
9
 
10
10
  /**
11
11
  * @typedef {import("node:stream").Writable} Writable
12
- * @typedef {import("../../type/index.js").Level} Level
13
- * @typedef {import("../../type/index.js").Notice} Notice
12
+ * @typedef {import("../../types/level.d.ts").default} Level
13
+ * @typedef {import("../../types/notice.d.ts").default} Notice
14
14
  */
15
15
 
16
16
  /**
package/src/core/index.js CHANGED
@@ -4,149 +4,232 @@
4
4
  * @author Sébastien Règne
5
5
  */
6
6
 
7
+ import path from "node:path";
8
+ import process from "node:process";
9
+ import { pathToFileURL } from "node:url";
10
+ import flatten from "./configuration/flatten.js";
11
+ import normalize from "./configuration/normalize.js";
7
12
  import { mergeLinters } from "./configuration/override.js";
8
- import Severities from "./severities.js";
13
+ import Results from "./results.js";
14
+ import { exists } from "./utils/file.js";
9
15
  import Glob from "./utils/glob.js";
10
16
 
11
17
  /**
12
- * @typedef {import("../type/index.d.ts").FlattenedConfigChecker} FlattenedConfigChecker
13
- * @typedef {import("../type/index.d.ts").Notice} Notice
18
+ * @typedef {import("../types/configuration/flattened.d.ts").FlattenedConfig} FlattenedConfig
19
+ * @typedef {import("../types/configuration/flattened.d.ts").FlattenedConfigChecker} FlattenedConfigChecker
20
+ * @typedef {import("../types/level.d.ts").default} Level
21
+ * @typedef {import("../types/notice.d.ts").default} Notice
22
+ * @typedef {import("../types/severity.d.ts").default} Severity
23
+ * @typedef {import("../types/typeofformatter.d.ts").default} TypeofFormatter
24
+ * @typedef {import("./formatter/formatter.js").default} Formatter
25
+ * @typedef {import("./wrapper/wrapper.js").default} Wrapper
14
26
  */
15
27
 
16
- /**
17
- * Compare deux notifications. En commençant par le numéro de la ligne, puis
18
- * celui de la colonne.
19
- *
20
- * @param {Notice} notice1 La première notification.
21
- * @param {Notice} notice2 La seconde notification.
22
- * @returns {number} Un nombre négatif si la 1<sup>re</sup> notification est
23
- * inférieure à la 2<sup>de</sup> ; <code>0</code> si elles
24
- * sont égales ; sinon un nombre positif.
25
- */
26
- const compare = function (notice1, notice2) {
27
- for (
28
- let i = 0;
29
- i < notice1.locations.length && i < notice2.locations.length;
30
- ++i
31
- ) {
32
- const locations1 = notice1.locations[i];
33
- const locations2 = notice2.locations[i];
34
-
35
- let diff = locations1.line - locations2.line;
36
- if (0 !== diff) {
37
- return diff;
38
- }
28
+ export default class Metalint {
29
+ /**
30
+ * Le répertoire racine.
31
+ *
32
+ * @type {string}
33
+ */
34
+ #root;
39
35
 
40
- diff = (locations1.column ?? -1) - (locations2.column ?? -1);
41
- if (0 !== diff) {
42
- return diff;
43
- }
44
- }
45
- return notice1.locations.length - notice2.locations.length;
46
- };
36
+ /**
37
+ * Les motifs généraux des fichiers.
38
+ *
39
+ * @type {Glob}
40
+ */
41
+ #glob;
42
+
43
+ /**
44
+ * Les checkers (avec leurs linters).
45
+ */
46
+ #checkers;
47
47
 
48
- const Results = class {
49
48
  /**
50
- * Les données des résultats.
49
+ * Les formatters.
51
50
  *
52
- * @type {Record<string, Notice[]|undefined>}
51
+ * @type {Formatter[]}
53
52
  */
54
- #data;
53
+ #formatters;
55
54
 
56
- constructor(files) {
57
- this.#data = Object.fromEntries(files.map((f) => [f, undefined]));
58
- }
55
+ /**
56
+ * Le cache des wrappers.
57
+ *
58
+ * @type {Map<string, Wrapper[]>}
59
+ */
60
+ #cache = new Map();
59
61
 
60
- add(file, notices) {
61
- // Ajouter un tableau vide dans les données pour indiquer que le fichier
62
- // a été analysé par au moins un linter.
63
- if (undefined === this.#data[file]) {
64
- this.#data[file] = [];
65
- }
66
- for (const notice of notices) {
67
- // Vérifier aussi le fichier de la notification, car il peut être
68
- // différent du fichier d'origine (qui est peut-être un répertoire
69
- // ou une archive).
70
- if (undefined === this.#data[notice.file]) {
71
- this.#data[notice.file] = [];
62
+ /**
63
+ * Crée une instance de Metalint à partir d'une configuration sur le système
64
+ * de fichiers.
65
+ *
66
+ * @param {Object} [options] Les éventuelles options pour
67
+ * créer une instance de
68
+ * Metalint.
69
+ * @param {string} [options.config] L'éventuel chemin vers le
70
+ * fichier de configuration de
71
+ * Metalint.
72
+ * @param {boolean} [options.fix] L'éventuelle marque
73
+ * indiquant si les linters
74
+ * doivent corriger les
75
+ * fichiers.
76
+ * @param {TypeofFormatter} [options.formatter] L'éventuel formatteur à
77
+ * utiliser.
78
+ * @param {Level} [options.level] L'éventuel niveau minimum
79
+ * des notifications à
80
+ * rapporter.
81
+ * @returns {Promise<Metalint>} Une promesse contenant l'instance de
82
+ * Metalint.
83
+ */
84
+ static async fromFileSystem(options = {}) {
85
+ const config = options.config ?? ".metalint/metalint.config.js";
86
+
87
+ // Rechercher le fichier de configuration dans le répertoire courant,
88
+ // puis dans les parents, grands-parents...
89
+ let root = process.cwd();
90
+ while (!(await exists(path.join(root, config)))) {
91
+ // Si on est remonté à la racine.
92
+ if (path.join(root, "..") === root) {
93
+ throw new Error("No such config file.");
72
94
  }
73
- this.#data[notice.file].push({
74
- rule: undefined,
75
- severity: Severities.ERROR,
76
- locations: [],
77
- ...notice,
78
- });
95
+ root = path.join(root, "..");
79
96
  }
97
+
98
+ // eslint-disable-next-line no-unsanitized/method
99
+ const { default: configuration } = await import(
100
+ pathToFileURL(path.join(root, config)).href
101
+ );
102
+ const { patterns, reporters, checkers } = flatten(
103
+ await normalize(configuration, {
104
+ dir: path.dirname(path.join(root, config)),
105
+ }),
106
+ {
107
+ fix: options.fix,
108
+ formatter: options.formatter,
109
+ level: options.level,
110
+ },
111
+ );
112
+ return new Metalint({ patterns, reporters, checkers }, { root });
80
113
  }
81
114
 
82
- toObject() {
83
- // Trier les notifications.
84
- Object.values(this.#data)
85
- .filter((r) => undefined !== r)
86
- .forEach((r) => r.sort(compare));
87
- return this.#data;
115
+ /**
116
+ * Crée une instance de Metalint.
117
+ *
118
+ * @param {FlattenedConfig} config La configuration de Metalint.
119
+ * @param {Object} context Le contexte.
120
+ * @param {string} context.root Le répertoire racine.
121
+ */
122
+ constructor(config, { root }) {
123
+ this.#root = root;
124
+ this.#glob = new Glob(config.patterns, { root: this.#root });
125
+
126
+ this.#formatters = config.reporters.map((reporter) => {
127
+ // eslint-disable-next-line new-cap
128
+ return new reporter.formatter(reporter.level, reporter.options);
129
+ });
130
+
131
+ this.#checkers = config.checkers.map((checker) => ({
132
+ glob: new Glob(checker.patterns, { root: this.#root }),
133
+ linters: checker.linters,
134
+ overrides: checker.overrides.map((override) => ({
135
+ glob: new Glob(override.patterns, { root: this.#root }),
136
+ linters: override.linters,
137
+ })),
138
+ }));
88
139
  }
89
- };
90
140
 
91
- /**
92
- * Vérifie (en appelant des linters) une liste de fichiers.
93
- *
94
- * @param {string[]} files La liste des fichiers.
95
- * @param {FlattenedConfigChecker[]} checkers La liste des vérifications faites
96
- * sur les fichiers.
97
- * @param {string} root L'adresse du répertoire où se
98
- * trouve le répertoire
99
- * <code>.metalint/</code>.
100
- * @returns {Promise<Record<string, Notice[]|undefined>>} Une promesse
101
- * retournant la liste
102
- * des notifications
103
- * regroupées par
104
- * fichier.
105
- */
106
- export default async function metalint(files, checkers, root) {
107
- const cache = new Map();
108
- const results = new Results(files);
109
- for (const file of files) {
110
- for (const checker of checkers) {
111
- const glob = new Glob(checker.patterns, { root });
112
- if (glob.test(file)) {
113
- let key = checker.patterns.join();
114
- const values = [checker.linters];
115
- for (const override of checker.overrides) {
116
- const subglob = new Glob(override.patterns, { root });
117
- if (subglob.test(file)) {
118
- key += "|" + override.patterns.join();
119
- values.push(override.linters);
141
+ /**
142
+ * Vérifie (en appelant des linters) des répertoires et des fichiers.
143
+ *
144
+ * @param {string[]} bases Les répertoires et les fichiers.
145
+ * @returns {Promise<Record<string, Notice[]|undefined>>} Une promesse
146
+ * retournant la
147
+ * liste des
148
+ * notifications
149
+ * regroupées par
150
+ * fichier.
151
+ */
152
+ async lintFiles(bases) {
153
+ const files = /** @type {string[]} */ ([]);
154
+ for (const base of bases) {
155
+ files.push(...(await this.#glob.walk(base)));
156
+ }
157
+ const results = new Results(files);
158
+ for (const file of files) {
159
+ for (const [i, checker] of Object.entries(this.#checkers)) {
160
+ if (checker.glob.test(file)) {
161
+ let key = i.toString();
162
+ const values = [checker.linters];
163
+ for (const [j, override] of Object.entries(
164
+ checker.overrides,
165
+ )) {
166
+ if (override.glob.test(file)) {
167
+ key += `.${j}`;
168
+ values.push(override.linters);
169
+ }
120
170
  }
121
- }
122
171
 
123
- let wrappers = [];
124
- if (cache.has(key)) {
125
- wrappers = cache.get(key);
126
- } else {
127
- const linters = values.reduce(mergeLinters);
128
- for (const linter of linters) {
129
- wrappers.push(
172
+ let wrappers;
173
+ if (this.#cache.has(key)) {
174
+ wrappers = this.#cache.get(key);
175
+ } else {
176
+ wrappers = values.reduce(mergeLinters).map((linter) => {
130
177
  // eslint-disable-next-line new-cap
131
- new linter.wrapper(
178
+ return new linter.wrapper(
132
179
  {
133
180
  fix: linter.fix,
134
181
  level: linter.level,
135
- root,
182
+ root: this.#root,
136
183
  files,
137
184
  },
138
185
  linter.options,
139
- ),
140
- );
186
+ );
187
+ });
188
+ this.#cache.set(key, wrappers);
189
+ }
190
+ for (const wrapper of wrappers) {
191
+ results.add(file, await wrapper.lint(file));
141
192
  }
142
- cache.set(key, wrappers);
143
- }
144
- for (const wrapper of wrappers) {
145
- results.add(file, await wrapper.lint(file));
146
193
  }
147
194
  }
148
195
  }
196
+
197
+ return results.toObject();
149
198
  }
150
199
 
151
- return results.toObject();
200
+ /**
201
+ * Rapporte des résultats dans les formats choisis.
202
+ *
203
+ * @param {Record<string, Notice[]|undefined>} results La liste des
204
+ * notifications
205
+ * regroupées par
206
+ * fichier.
207
+ * @returns {Promise<Severity|undefined>} La sévérité la plus élevée des
208
+ * notifications ; ou
209
+ * <code>undefined</code> si les
210
+ * résultats ont aucune notification.
211
+ */
212
+ async report(results) {
213
+ let severity;
214
+
215
+ for (const [file, notices] of Object.entries(results)) {
216
+ // Déterminer la sévérité la plus élevée des résultats.
217
+ if (undefined !== notices) {
218
+ for (const notice of notices) {
219
+ if (undefined === severity || severity > notice.severity) {
220
+ severity = notice.severity;
221
+ }
222
+ }
223
+ }
224
+
225
+ // Afficher les notifications avec chaque rapporteur.
226
+ for (const formatter of this.#formatters) {
227
+ await formatter.notify(file, notices);
228
+ }
229
+ }
230
+
231
+ // Attendre tous les rapporteurs.
232
+ await Promise.all(this.#formatters.map((f) => f.finalize()));
233
+ return severity;
234
+ }
152
235
  }