]> git.proxmox.com Git - pve-eslint.git/blob - eslint/docs/developer-guide/working-with-custom-formatters.md
7efed41933505b083f413235280e3cf0dd9d0dba
[pve-eslint.git] / eslint / docs / developer-guide / working-with-custom-formatters.md
1 # Working with Custom Formatters
2
3 While ESLint has some built-in formatters available to format the linting results, it's also possible to create and distribute your own custom formatters. You can include custom formatters in your project directly or create an npm package to distribute them separately.
4
5 Each formatter is just a function that receives a `results` object and returns a string. For example, the following is how the `json` built-in formatter is implemented:
6
7 ```js
8 //my-awesome-formatter.js
9 module.exports = function(results) {
10 return JSON.stringify(results, null, 2);
11 };
12 ```
13
14 To run ESLint with this formatter, you can use the `-f` (or `--format`) command line flag:
15
16 ```bash
17 eslint -f ./my-awesome-formatter.js src/
18 ```
19
20 In order to use a local file as a custom formatter, you must begin the filename with a dot (such as `./my-awesome-formatter.js` or `../formatters/my-awesome-formatter.js`).
21
22 ### The `data` Argument
23
24 The exported function receives an optional second argument named `data`. The `data` object provides extended information related to the analysis results. Currently, the `data` object consists of a single property named `rulesMeta`. This property is a dictionary of rule metadata, keyed with `ruleId`. The value for each entry is the `meta` property from the corresponding rule object. The dictionary contains an entry for each rule that was run during the analysis.
25
26 Here's what the `data` object would look like if one rule, `no-extra-semi`, had been run:
27
28 ```js
29 {
30 rulesMeta: {
31 "no-extra-semi": {
32 type: "suggestion",
33 docs: {
34 description: "disallow unnecessary semicolons",
35 category: "Possible Errors",
36 recommended: true,
37 url: "https://eslint.org/docs/rules/no-extra-semi"
38 },
39 fixable: "code",
40 schema: [],
41 messages: {
42 unexpected: "Unnecessary semicolon."
43 }
44 }
45 }
46 }
47 ```
48
49 The [Using Rule metadata](#using-rule-metadata) example shows how to use the `data` object in a custom formatter. See the [Working with Rules](https://eslint.org/docs/developer-guide/working-with-rules) page for more information about rules.
50
51 ## Packaging the Custom Formatter
52
53 Custom formatters can also be distributed through npm packages. To do so, create an npm package with a name in the format of `eslint-formatter-*`, where `*` is the name of your formatter (such as `eslint-formatter-awesome`). Projects should then install the package and can use the custom formatter with the `-f` (or `--format`) flag like this:
54
55 ```bash
56 eslint -f awesome src/
57 ```
58
59 Because ESLint knows to look for packages beginning with `eslint-formatter-` when the specified formatter doesn't begin with a dot, there is no need to type `eslint-formatter-` when using a packaged custom formatter.
60
61 Tips for `package.json`:
62
63 * The `main` entry should be the JavaScript file implementing your custom formatter.
64 * Add these `keywords` to help users find your formatter:
65 * `"eslint"`
66 * `"eslint-formatter"`
67 * `"eslintformatter"`
68
69 See all [formatters on npm](https://www.npmjs.com/search?q=eslint-formatter);
70
71 ## The `results` Object
72
73 The `results` object passed into a formatter is an array of objects containing the lint results for individual files. Here's some example output:
74
75 ```js
76 [
77 {
78 filePath: "path/to/file.js",
79 messages: [
80 {
81 ruleId: "curly",
82 severity: 2,
83 message: "Expected { after 'if' condition.",
84 line: 2,
85 column: 1,
86 nodeType: "IfStatement"
87 },
88 {
89 ruleId: "no-process-exit",
90 severity: 2,
91 message: "Don't use process.exit(); throw an error instead.",
92 line: 3,
93 column: 1,
94 nodeType: "CallExpression"
95 }
96 ],
97 errorCount: 2,
98 warningCount: 0,
99 fixableErrorCount: 0,
100 fixableWarningCount: 0,
101 source:
102 "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n"
103 },
104 {
105 filePath: "Gruntfile.js",
106 messages: [],
107 errorCount: 0,
108 warningCount: 0,
109 fixableErrorCount: 0,
110 fixableWarningCount: 0
111 }
112 ]
113 ```
114
115 ### The `result` Object
116
117 <!-- This section is copied from the "Node.js API" page. Changes to this section should
118 also be manually applied to that page. -->
119
120 Each object in the `results` array is a `result` object. Each `result` object contains the path of the file that was linted and information about linting issues that were encountered. Here are the properties available on each `result` object:
121
122 * **filePath**: The absolute path to the file that was linted.
123 * **messages**: An array of `message` objects. See below for more info about messages.
124 * **errorCount**: The number of errors for the given file.
125 * **warningCount**: The number of warnings for the given file.
126 * **source**: The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present.
127 * **output**: The source code for the given file with as many fixes applied as possible. This property is omitted if no fix is available.
128
129 ### The `message` Object
130
131 Each `message` object contains information about the ESLint rule that was triggered by some source code. The properties available on each `message` object are:
132
133 * **ruleId**: the ID of the rule that produced the error or warning.
134 * **severity**: the severity of the failure, `1` for warnings and `2` for errors.
135 * **message**: the human readable description of the error.
136 * **line**: the line where the issue is located.
137 * **column**: the column where the issue is located.
138 * **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/spec.md#node-objects)
139
140 ## Examples
141
142 ### Summary formatter
143
144 A formatter that only cares about the total count of errors and warnings will look like this:
145
146 ```javascript
147 module.exports = function(results) {
148 // accumulate the errors and warnings
149 var summary = results.reduce(
150 function(seq, current) {
151 seq.errors += current.errorCount;
152 seq.warnings += current.warningCount;
153 return seq;
154 },
155 { errors: 0, warnings: 0 }
156 );
157
158 if (summary.errors > 0 || summary.warnings > 0) {
159 return (
160 "Errors: " +
161 summary.errors +
162 ", Warnings: " +
163 summary.warnings +
164 "\n"
165 );
166 }
167
168 return "";
169 };
170 ```
171
172 Running `eslint` with the previous custom formatter,
173
174 ```bash
175 eslint -f ./my-awesome-formatter.js src/
176 ```
177
178 Will produce the following output:
179
180 ```bash
181 Errors: 2, Warnings: 4
182 ```
183
184 ### Detailed formatter
185
186 A more complex report will look something like this:
187
188 ```javascript
189 module.exports = function(results, data) {
190 var results = results || [];
191
192 var summary = results.reduce(
193 function(seq, current) {
194 current.messages.forEach(function(msg) {
195 var logMessage = {
196 filePath: current.filePath,
197 ruleId: msg.ruleId,
198 ruleUrl: data.rulesMeta[msg.ruleId].url,
199 message: msg.message,
200 line: msg.line,
201 column: msg.column
202 };
203
204 if (msg.severity === 1) {
205 logMessage.type = "warning";
206 seq.warnings.push(logMessage);
207 }
208 if (msg.severity === 2) {
209 logMessage.type = "error";
210 seq.errors.push(logMessage);
211 }
212 });
213 return seq;
214 },
215 {
216 errors: [],
217 warnings: []
218 }
219 );
220
221 if (summary.errors.length > 0 || summary.warnings.length > 0) {
222 var lines = summary.errors
223 .concat(summary.warnings)
224 .map(function(msg) {
225 return (
226 "\n" +
227 msg.type +
228 " " +
229 msg.ruleId + (msg.ruleUrl ? " (" + msg.ruleUrl + ")" : ""
230 "\n " +
231 msg.filePath +
232 ":" +
233 msg.line +
234 ":" +
235 msg.column
236 );
237 })
238 .join("\n");
239
240 return lines + "\n";
241 }
242 };
243 ```
244
245 So running `eslint` with this custom formatter:
246
247 ```bash
248 eslint -f ./my-awesome-formatter.js src/
249 ```
250
251 The output will be
252
253 ```bash
254 error space-infix-ops (https://eslint.org/docs/rules/space-infix-ops)
255 src/configs/bundler.js:6:8
256 error semi (https://eslint.org/docs/rules/semi)
257 src/configs/bundler.js:6:10
258 warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars)
259 src/configs/bundler.js:5:6
260 warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars)
261 src/configs/bundler.js:6:6
262 warning no-shadow (https://eslint.org/docs/rules/no-shadow)
263 src/configs/bundler.js:65:32
264 warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars)
265 src/configs/clean.js:3:6
266 ```
267
268 ## Passing Arguments to Formatters
269
270 While custom formatter do not receive arguments in addition to the results object, it is possible to pass additional data into formatters.
271
272 ## Using Environment Variables
273
274 Custom formatters have access to environment variables and so can change their behavior based on environment variable data. Here's an example that uses a `AF_SKIP_WARNINGS` environment variable to determine whether or not to show warnings in the results:
275
276 ```js
277 module.exports = function(results) {
278 var skipWarnings = process.env.AF_SKIP_WARNINGS === "true"; //af stands for awesome-formatter
279
280 var results = results || [];
281 var summary = results.reduce(
282 function(seq, current) {
283 current.messages.forEach(function(msg) {
284 var logMessage = {
285 filePath: current.filePath,
286 ruleId: msg.ruleId,
287 message: msg.message,
288 line: msg.line,
289 column: msg.column
290 };
291
292 if (msg.severity === 1) {
293 logMessage.type = "warning";
294 seq.warnings.push(logMessage);
295 }
296 if (msg.severity === 2) {
297 logMessage.type = "error";
298 seq.errors.push(logMessage);
299 }
300 });
301 return seq;
302 },
303 {
304 errors: [],
305 warnings: []
306 }
307 );
308
309 if (summary.errors.length > 0 || summary.warnings.length > 0) {
310 var warnings = !skipWarnings ? summary.warnings : []; // skip the warnings in that case
311
312 var lines = summary.errors
313 .concat(warnings)
314 .map(function(msg) {
315 return (
316 "\n" +
317 msg.type +
318 " " +
319 msg.ruleId +
320 "\n " +
321 msg.filePath +
322 ":" +
323 msg.line +
324 ":" +
325 msg.column
326 );
327 })
328 .join("\n");
329
330 return lines + "\n";
331 }
332 };
333 ```
334
335 You would run ESLint with this custom formatter and an environment variable set like this:
336
337 ```bash
338 AF_SKIP_WARNINGS=true eslint -f ./my-awesome-formatter.js src/
339 ```
340
341 The output would be:
342
343 ```bash
344 error space-infix-ops
345 src/configs/bundler.js:6:8
346
347 error semi
348 src/configs/bundler.js:6:10
349 ```
350
351
352 ### Complex Argument Passing
353
354 If you find the custom formatter pattern doesn't provide enough options for the way you'd like to format ESLint results, the best option is to use ESLint's built-in [JSON formatter](https://eslint.org/docs/user-guide/formatters/) and pipe the output to a second program. For example:
355
356 ```bash
357 eslint -f json src/ | your-program-that-reads-JSON --option
358 ```
359
360 In this example, the `your-program-that-reads-json` program can accept the raw JSON of ESLint results and process it before outputting its own format of the results. You can pass as many command line arguments to that program as are necessary to customize the output.
361
362 ## Note: Formatting for Terminals
363
364 Modern terminals like [iTerm2](https://www.iterm2.com/) or [Guake](http://guake-project.org/) expect a specific results format to automatically open filenames when they are clicked. Most terminals support this format for that purpose:
365
366 ```bash
367 file:line:column
368 ```