UNPKG

6.85 kBJavaScriptView Raw
1#!/usr/bin/env node
2'use strict';
3
4/*eslint no-process-exit:0 no-console:0*/
5var console = require('console');
6var process = require('process');
7var through2 = require('through2');
8var byline = require('byline');
9var table = require('text-table');
10var isTTY = process.stdout.isTTY;
11var chalk = require('chalk');
12var extend = require('xtend');
13var fmt = require('util').format;
14var argv = require('minimist')(process.argv.slice(2));
15
16var hasErrors = false;
17
18function CheckStyleReporter() {
19 // ==== begin attribution ==================================================
20 // The pairs object and encode function are borrowed from:
21 // github.com/jshint/jshint src/reporters/checkstyle.js
22 // Author: Boy Baukema, http://github.com/relaxnow
23 // License MIT
24 // This code should be replaced in a future commit of checkstyle-reporter.js
25 var pairs = {
26 '&': '&',
27 '"': '"',
28 '\'': ''',
29 '<': '&lt;',
30 '>': '&gt;'
31 };
32
33 function encode(s) {
34 for (var r in pairs) {
35 if (typeof s !== 'undefined') {
36 s = s.replace(new RegExp(r, 'g'), pairs[r]);
37 }
38 }
39 return s || '';
40 }
41 // ==== end attribution ====================================================
42
43 var errors = [];
44
45 function printFile(fileErrors) {
46 return fmt('\t<file name="%s">\n%s\n\t</file>',
47 encode(fileErrors.file),
48 fileErrors.errors.map(printError).join('\n')
49 );
50 }
51
52 function printError(error) {
53 return fmt(
54 '\t\t<error line="%d" column="%d" severity="%s" ' +
55 'message="%s" source="%s" />',
56 error.line,
57 error.column,
58 'error',
59 encode(error.message),
60 encode(error.rule)
61 );
62 }
63
64 function transform(fileErrors, enc, callback) {
65 errors.push(fileErrors);
66 callback();
67 }
68
69 function flush(callback) {
70 var checkstyle = [
71 '<?xml version="1.0" encoding="utf-8"?>',
72 '<checkstyle version="4.3">',
73 errors.map(printFile).join('\n'),
74 '</checkstyle>'
75 ].join('\n') + '\n';
76 this.push(checkstyle);
77 callback();
78 }
79
80 return through2.obj(transform, flush);
81}
82
83function StylishReporter(options) {
84 var tableOptions = {
85 align: ['l', 'r', 'l', 'l', 'l'],
86 hsep: ''
87 };
88
89 function formatRow(error) {
90 // pad number with spaces in front
91 var line = (' ' + error.line).slice(-4);
92 // pad number with spaces behind
93 var column = (error.column + ' ').slice(0, 3);
94 var message = ' ' + error.message;
95 var rule = error.rule;
96
97 if (options.colors) {
98 line = chalk.magenta(line);
99 column = chalk.blue(column);
100 message = chalk.white(message);
101 rule = chalk.yellow(rule);
102 }
103
104 if (rule.length > 0) {
105 message = message + ' (' + rule + ')';
106 }
107
108 return [' ', line, ':', column, message];
109 }
110
111 function transform(fileErrors, enc, callback) {
112 var file = fileErrors.file;
113 var errors = fileErrors.errors.map(formatRow);
114 var header = options.colors ? chalk.underline.cyan(file) : file;
115 var formattedErrors = table(errors, tableOptions);
116 this.push(header + '\n' + formattedErrors + '\n');
117 callback();
118 }
119
120 return through2.obj(transform);
121}
122
123function JSONReporter() {
124 var errors = {};
125
126 function transform(fileErrors, enc, callback) {
127 errors[fileErrors.file] = fileErrors.errors.map(removeFileProp);
128 callback();
129 }
130
131 function flush(callback) {
132 this.push(JSON.stringify(errors, undefined, 2) + '\n');
133 callback();
134 }
135
136 function removeFileProp(error) {
137 error = extend(error);
138 delete error.file;
139 return error;
140 }
141
142 return through2.obj(transform, flush);
143}
144
145function parseErrors() {
146 function transform(chunk, enc, callback) {
147 var parts = chunk.toString().split(':');
148 if (parts.length !== 4) {
149 return callback();
150 }
151
152 var message = parts[3].trim();
153 var rule = '';
154
155 if (message.lastIndexOf('(') !== -1) {
156 var pos = message.lastIndexOf('(');
157 rule = message.substring(pos + 1, message.length - 1);
158 message = message.substring(0, pos);
159 }
160
161 this.push({
162 file: parts[0].trim(),
163 line: parts[1],
164 column: parts[2],
165 message: message,
166 rule: rule
167 });
168
169 if (!hasErrors) {
170 hasErrors = true;
171 }
172
173 callback();
174 }
175
176 return through2.obj(transform);
177}
178
179function groupErrorsByFile() {
180 var errors = {};
181 var currentFile = null;
182
183 function transform(error, enc, callback) {
184 if (errors[error.file]) {
185 errors[error.file].push(error);
186 } else {
187 errors[error.file] = [error];
188 if (currentFile && currentFile !== error.file) {
189 this.push({
190 file: currentFile,
191 errors: errors[currentFile]
192 });
193 }
194 currentFile = error.file;
195 }
196
197 callback();
198 }
199
200 function flush(callback) {
201 this.push({
202 file: currentFile,
203 errors: errors[currentFile]
204 });
205 callback();
206 }
207
208 return through2.obj(transform, flush);
209}
210
211var reporterMap = {
212 'json': JSONReporter,
213 'checkstyle': CheckStyleReporter,
214 'stylish': StylishReporter
215};
216
217function standardReporter(options) {
218 options = extend({
219 type: 'none',
220 colors: isTTY,
221 sink: process.stdout
222 }, options || {});
223
224 var inputStream = through2(noopTransform);
225 var reporter = reporterMap[options.type];
226
227 if (reporter) {
228 inputStream
229 .pipe(byline())
230 .pipe(parseErrors())
231 .pipe(groupErrorsByFile())
232 .pipe(reporter(options))
233 .pipe(options.sink);
234 } else {
235 inputStream.pipe(options.sink);
236 }
237
238 function noopTransform(chunk, enc, callback) {
239 this.push(chunk);
240 callback();
241 }
242
243 return inputStream;
244}
245
246module.exports = standardReporter;
247
248if (require.main === module) {
249 var type = argv.json ? 'json' :
250 argv.checkstyle ? 'checkstyle' :
251 argv.stylish ? 'stylish' : 'none';
252
253 process.stdin.pipe(standardReporter({
254 type: type
255 }));
256
257 process.on('error', function handleError(err) {
258 console.error(err);
259 hasErrors = true;
260 });
261
262 process.on('exit', function onStandardExit() {
263 if (hasErrors) {
264 return process.exit(1);
265 }
266 process.exit(0);
267 });
268}