1 | /**
|
2 | * @module index
|
3 | */
|
4 |
|
5 | ;
|
6 |
|
7 | const fs = require("fs");
|
8 | const glob = require("./glob");
|
9 | const SEVERITY = require("./severity");
|
10 |
|
11 | /**
|
12 | * Compare deux notifications. En commançant par le numéro de ligne, puis celui
|
13 | * de la colonne.
|
14 | *
|
15 | * @param {object} notice1 La première notification.
|
16 | * @param {object} notice2 La seconde notification.
|
17 | * @returns {number} Un nombre négatif si la 1<sup>re</sup> notification est
|
18 | * inférieure à la 2<sup>de</sup> ; <code>0</code> si elles
|
19 | * sont égales ; sinon un nombre positif.
|
20 | */
|
21 | const compare = function (notice1, notice2) {
|
22 | for (let i = 0; i < notice1.locations.length &&
|
23 | i < notice2.locations.length; ++i) {
|
24 | const locations1 = notice1.locations[i];
|
25 | const locations2 = notice2.locations[i];
|
26 |
|
27 | let diff = locations1.line - locations2.line;
|
28 | if (0 !== diff) {
|
29 | return diff;
|
30 | }
|
31 |
|
32 | diff = ("column" in locations1 ? locations1.column : -1) -
|
33 | ("column" in locations2 ? locations2.column : -1);
|
34 | if (0 !== diff) {
|
35 | return diff;
|
36 | }
|
37 | }
|
38 | return notice1.locations.length - notice2.locations.length;
|
39 | };
|
40 |
|
41 | /**
|
42 | * Vérifie (en appelant des linters) une liste de fichiers.
|
43 | *
|
44 | * @param {Array.<string>} files La liste des fichiers.
|
45 | * @param {Array.<object>} checkers La liste des vérifications faites sur les
|
46 | * fichiers.
|
47 | * @param {string} root L’adresse du répertoire où se trouve le
|
48 | * dossier <code>.metalint/</code>.
|
49 | * @returns {Promise.<object>} Une promesse retournant la liste des
|
50 | * notifications regrouper par fichier.
|
51 | */
|
52 | const metalint = function (files, checkers, root) {
|
53 | const results = {};
|
54 |
|
55 | const promises = [];
|
56 | for (const file of files) {
|
57 | results[file] = null;
|
58 |
|
59 | const directory = fs.lstatSync(file).isDirectory();
|
60 | for (const checker of checkers) {
|
61 | if (glob.test(file, checker.patterns, root, directory)) {
|
62 | results[file] = [];
|
63 | for (const name in checker.linters) {
|
64 | // Charger l’enrobage du linter et l’utiliser pour vérifier
|
65 | // le fichier.
|
66 | const { wrapper } = require(name);
|
67 | promises.push(wrapper(file, checker.level,
|
68 | checker.linters[name], root));
|
69 | }
|
70 | }
|
71 | }
|
72 | }
|
73 |
|
74 | return Promise.all(promises).then(function (notices) {
|
75 | for (const notice of [].concat(...notices)) {
|
76 | // Ajouter les propriétés par défaut.
|
77 | if (!("rule" in notice)) {
|
78 | notice.rule = null;
|
79 | }
|
80 | if (!("severity" in notice)) {
|
81 | notice.severity = SEVERITY.ERROR;
|
82 | }
|
83 | if (!("locations" in notice)) {
|
84 | notice.locations = [];
|
85 | }
|
86 |
|
87 | // Regrouper les notifications par fichiers.
|
88 | if (!(notice.file in results) || null === results[notice.file]) {
|
89 | results[notice.file] = [notice];
|
90 | } else {
|
91 | results[notice.file].push(notice);
|
92 | }
|
93 | }
|
94 |
|
95 | // Trier les notifications.
|
96 | Object.values(results).filter((r) => null !== r)
|
97 | .forEach((r) => r.sort(compare));
|
98 |
|
99 | return results;
|
100 | });
|
101 | };
|
102 |
|
103 |
|
104 | // Exposer la fonction pour vérifier un code source.
|
105 | module.exports = metalint;
|