UNPKG

12.1 kBJavaScriptView Raw
1"use strict";
2
3const fs = require("fs");
4const path = require("path");
5const jsonlint = require("jsonlint");
6const SEVERITY = require("./severity");
7
8/**
9 * Fusionne deux objets.
10 *
11 * @param {*} first Le premier objet.
12 * @param {*} second Le second objet.
13 * @return {*} La fusion des deux objets.
14 */
15const merge = function (first, second) {
16 if ("object" !== typeof first || "object" !== typeof second) {
17 return second;
18 }
19 const third = {};
20 for (const key of new Set([...Object.keys(first),
21 ...Object.keys(second)])) {
22 // Si la propriété est dans les deux objets.
23 if (key in first && key in second) {
24 third[key] = merge(first[key], second[key]);
25 // Si la propriété est seulement dans le premier objet.
26 } else if (key in first) {
27 third[key] = first[key];
28 // Si la propriété est seulement dans le second objet.
29 } else {
30 third[key] = second[key];
31 }
32 }
33 return third;
34};
35
36/**
37 * Lit un fichier contenant un objet JSON.
38 *
39 * @param {string} file L’adresse du fichier qui sera lu.
40 * @return {Object} L’objet JSON récupéré.
41 */
42const read = function (file) {
43 const json = fs.readFileSync(file, "utf-8");
44 try {
45 return jsonlint.parse(json);
46 } catch (err) {
47 throw new Error(file + ": " + err.message);
48 }
49};
50
51/**
52 * Normalise la propriété <code>"patterns"</code>.
53 *
54 * @param {*} rotten La valeur de la proptiété
55 * <code>"patterns"</code>.
56 * @param {Array.<string>} auto La valeur par défaut.
57 * @return {Array.<string>} La valeur normalisée.
58 */
59const patterns = function (rotten, auto) {
60 let standard;
61 if (undefined === rotten) {
62 standard = auto;
63 } else if ("string" === typeof rotten) {
64 standard = [rotten];
65 } else if (Array.isArray(rotten)) {
66 standard = rotten;
67 } else {
68 throw new Error("property 'patterns' is incorrect type (string and" +
69 " array are accepted).");
70 }
71 return standard;
72};
73
74/**
75 * Normalise la propriété <code>"level"</code>.
76 *
77 * @param {*} rotten La valeur de la proptiété <code>"level"</code>.
78 * @param {number} auto La valeur par défaut.
79 * @return {number} La valeur normalisée.
80 */
81const level = function (rotten, auto) {
82 let standard;
83 if (undefined === rotten) {
84 standard = auto;
85 } else if ("string" === typeof rotten) {
86 if (rotten.toUpperCase() in SEVERITY) {
87 standard = SEVERITY[rotten.toUpperCase()];
88 if (standard > auto) {
89 standard = auto;
90 }
91 } else {
92 throw new Error("value of property 'level' is unknown (possibles" +
93 " values : 'off', 'fatal', 'error', 'warn' and" +
94 " 'info').");
95 }
96 } else {
97 throw new Error("property 'level' is incorrect type (only string is" +
98 " accepted).");
99 }
100 return standard;
101};
102
103/**
104 * Normalise la propriété <code>"name"</code>.
105 *
106 * @param {*} rotten La valeur de la proptiété <code>"name"</code>.
107 * @param {string} auto La valeur par défaut.
108 * @param {string} root L’adresse du répertoire où se trouve le dossier
109 * <code>.metalint/</code>.
110 * @return {Object} La valeur normalisée.
111 */
112const name = function (rotten, auto, root) {
113 let standard;
114 if (undefined === rotten) {
115 standard = require("./reporter/" + auto);
116 } else if ("string" === typeof rotten) {
117 if (-1 === ["checkstyle", "console", "csv",
118 "json", "none", "unix"].indexOf(rotten.toLowerCase())) {
119 const file = rotten.startsWith("/") ? rotten
120 : path.join(root, rotten);
121 standard = require(file);
122 } else {
123 standard = require("./reporter/" + rotten.toLowerCase());
124 }
125 } else {
126 throw new Error("property 'name' is incorrect type (only string is" +
127 " accepted).");
128 }
129 return standard;
130};
131
132/**
133 * Normalise la propriété <code>"output"</code>.
134 *
135 * @param {*} rotten La valeur de la proptiété <code>"output"</code>.
136 * @param {Object} auto La valeur par défaut.
137 * @param {string} root L’adresse du répertoire où se trouve le dossier
138 * <code>.metalint/</code>.
139 * @return {Object} La valeur normalisée.
140 */
141const output = function (rotten, auto, root) {
142 let standard;
143 if (undefined === rotten || null === rotten) {
144 standard = auto;
145 } else if ("string" === typeof rotten) {
146 const file = rotten.startsWith("/") ? rotten
147 : path.join(root, rotten);
148 try {
149 standard = fs.createWriteStream(file, { "flags": "w" });
150 } catch (_) {
151 throw new Error("property 'output' is incorrect type (only string" +
152 " is accepted).");
153 }
154 } else {
155 throw new Error("'output' incorrect type.");
156 }
157 return standard;
158};
159
160/**
161 * Normalise la propriété <code>"options"</code>.
162 *
163 * @param {*} rotten La valeur de la proptiété <code>"options"</code>.
164 * @param {Object} auto La valeur par défaut.
165 * @return {Object} La valeur normalisée.
166 */
167const options = function (rotten, auto) {
168 let standard;
169 if (undefined === rotten) {
170 standard = auto;
171 } else if ("object" === typeof rotten) {
172 standard = rotten;
173 } else {
174 throw new Error("property 'options' is incorrect type (only object is" +
175 " accepted).");
176 }
177 return standard;
178};
179
180/**
181 * Normalise la propriété <code>"reporters"</code>.
182 *
183 * @param {*} rottens La valeur de la proptiété <code>"reporters"</code>.
184 * @param {Object} auto Les valeurs par défaut.
185 * @param {string} root L’adresse du répertoire où se trouve le dossier
186 * <code>.metalint/</code>.
187 * @return {Object} La valeur normalisée.
188 */
189const reporters = function (rottens, auto, root) {
190 let standards;
191 if (undefined === rottens) {
192 const Name = name(undefined, "console", root);
193 standards = [
194 new Name(level(undefined, auto.level),
195 output(undefined, process.stdout, root),
196 options(undefined, {}))
197 ];
198 } else if (Array.isArray(rottens)) {
199 standards = rottens.map(function (rotten) {
200 const Name = name(rotten.name, "console", root);
201 return new Name(level(rotten.level, auto.level),
202 output(rotten.output, process.stdout, root),
203 options(rotten.options, {}));
204 });
205 } else if ("object" === typeof rottens) {
206 const Name = name(rottens.name, "console", root);
207 standards = [
208 new Name(level(rottens.level, auto.level),
209 output(rottens.output, process.stdout, root),
210 options(rottens.options, {}))
211 ];
212 } else {
213 throw new Error("'reporters' incorrect type.");
214 }
215 return standards;
216};
217
218/**
219 * Normalise la propriété <code>"linters"</code>.
220 *
221 * @param {Array.<*>} rottens Les valeurs de la proptiété
222 * <code>"linters"</code>.
223 * @param {string} dir Le répertoire où se trouve le fichier de
224 * configuration <code>metalint.json</code>.
225 * @return {Array.<Object.<string, Object>>} La valeur normalisée.
226 */
227const linters = function (rottens, dir) {
228 const standards = {};
229 if (undefined === rottens) {
230 throw new Error("'checkers[].linters' is undefined.");
231 } else if (null === rottens) {
232 throw new Error("'checkers[].linters' is null.");
233 // "linters": "foolint"
234 } else if ("string" === typeof rottens) {
235 standards[rottens] = read(path.join(dir, rottens + ".json"));
236 // "linters": ["foolint", "barlint"]
237 } else if (Array.isArray(rottens)) {
238 for (const linter of rottens) {
239 standards[linter] = read(path.join(dir, linter + ".json"));
240 }
241 // "linters": { "foolint": ..., "barlint": ... }
242 } else if ("object" === typeof rottens) {
243 for (const linter in rottens) {
244 // "linters": { "foolint": "qux.json" }
245 if ("string" === typeof rottens[linter]) {
246 standards[linter] = read(path.join(dir, rottens[linter]));
247 // "linters": { "foolint": [..., ...] }
248 } else if (Array.isArray(rottens[linter])) {
249 standards[linter] = {};
250 for (const option of rottens[linter]) {
251 if (null === option) {
252 throw new Error("linter option is null.");
253 // "linters": { "foolint": ["qux.json", ...] }
254 } else if ("string" === typeof option) {
255 standards[linter] = merge(standards[linter],
256 read(path.join(dir, option)));
257 // "linters": { "foolint": [{ "qux": ..., ... }, ...]
258 } else if ("object" === typeof option) {
259 standards[linter] = merge(standards[linter], option);
260 } else {
261 throw new Error("linter option incorrect type.");
262 }
263 }
264 // "linters": { "foolint": { "qux": ..., "corge": ... } }
265 // "linters": { "foolint": null }
266 } else if ("object" === typeof rottens[linter]) {
267 standards[linter] = rottens[linter];
268 } else {
269 throw new Error("linter incorrect type.");
270 }
271 }
272 } else {
273 throw new Error("'checkers[].linters' incorrect type.");
274 }
275
276 return standards;
277};
278
279/**
280 * Normalise la propriété <code>"checkers"</code>.
281 *
282 * @param {*} rottens La valeur de la proptiété
283 * <code>"checkers"</code>.
284 * @param {Object.<string, *>} auto Les valeurs par défaut.
285 * @param {string} dir Le répertoire où se trouve le fichier de
286 * configuration <code>metalint.json</code>.
287 * @return {Array.<Object>} La valeur normalisée.
288 */
289const checkers = function (rottens, auto, dir) {
290 let standards;
291 if (Array.isArray(rottens)) {
292 if (0 === rottens.length) {
293 throw new Error("'checkers' is empty.");
294 } else {
295 standards = rottens.map(function (rotten) {
296 return {
297 "patterns": patterns(rotten.patterns, ["**"]),
298 "level": level(rotten.level, auto.level),
299 "linters": linters(rotten.linters, dir)
300 };
301 });
302 }
303 } else {
304 throw new Error("'checkers' is not an array.");
305 }
306 return standards;
307};
308
309/**
310 * Normalise la configuration. La structure de l’objet JSON contenant la
311 * configuration est souple pour rendre le fichier moins verbeuse. Cette
312 * fonction renseigne les valeurs par défaut pour les propriétes non-présentes.
313 *
314 * @param {Object} rotten L’objet JSON contenant la configuration.
315 * @param {string} root L’adresse du répertoire où se trouve le dossier
316 * <code>.metalint/</code>.
317 * @param {string} dir Le répertoire où se trouve le fichier de
318 * configuration.
319 * @return {Object} L’objet JSON normalisé.
320 */
321const normalize = function (rotten, root, dir) {
322 const standard = {
323 "patterns": patterns(rotten.patterns, ["**"]),
324 "level": level(rotten.level, SEVERITY.INFO)
325 };
326 standard.reporters = reporters(rotten.reporters, standard, root);
327 standard.checkers = checkers(rotten.checkers, standard, dir);
328 return standard;
329};
330
331module.exports = normalize;