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