1 | #!/usr/bin/env node
|
2 | 'use strict';
|
3 |
|
4 |
|
5 | var console = require('console');
|
6 | var process = require('process');
|
7 | var through2 = require('through2');
|
8 | var byline = require('byline');
|
9 | var table = require('text-table');
|
10 | var isTTY = process.stdout.isTTY;
|
11 | var chalk = require('chalk');
|
12 | var extend = require('xtend');
|
13 | var fmt = require('util').format;
|
14 | var argv = require('minimist')(process.argv.slice(2));
|
15 |
|
16 | var hasErrors = false;
|
17 |
|
18 | function CheckStyleReporter() {
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | var pairs = {
|
26 | '&': '&',
|
27 | '"': '"',
|
28 | '\'': ''',
|
29 | '<': '<',
|
30 | '>': '>'
|
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 |
|
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 |
|
83 | function StylishReporter(options) {
|
84 | var tableOptions = {
|
85 | align: ['l', 'r', 'l', 'l', 'l'],
|
86 | hsep: ''
|
87 | };
|
88 |
|
89 | function formatRow(error) {
|
90 |
|
91 | var line = (' ' + error.line).slice(-4);
|
92 |
|
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 |
|
123 | function 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 |
|
145 | function 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 |
|
179 | function 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 |
|
211 | var reporterMap = {
|
212 | 'json': JSONReporter,
|
213 | 'checkstyle': CheckStyleReporter,
|
214 | 'stylish': StylishReporter
|
215 | };
|
216 |
|
217 | function 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 |
|
246 | module.exports = standardReporter;
|
247 |
|
248 | if (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 | }
|