-
Notifications
You must be signed in to change notification settings - Fork 3
/
StylusLint.js
111 lines (100 loc) · 3.56 KB
/
StylusLint.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
'use strict';
// TODO: write tests for this hook
const HookMessage = require('../Message');
const PreCommitBase = require('./Base');
const utils = require('../../utils');
/**
* @class StylusLint
* @extends PreCommitBase
* @classdesc Run stylint on changed files
*/
module.exports = class StylusLint extends PreCommitBase {
/**
* Run `stylint` against files that apply.
* Uses `#spawnPromise` directly because stylint can only
* run against single files or specific directories.
*
* @returns {Promise}
* @resolves {HookMessage[]} An array of hook messages produced by the hook
* @rejects {Error} An Error thrown or emitted while running the hook
*/
run() {
return new Promise((resolve, reject) => {
let applicableFiles = this.applicableFiles();
if (!applicableFiles.length) { return resolve('pass'); }
let flags = this.flags();
let command = this.command();
let commandPromises = applicableFiles.map((currentFile) => {
let commandArgs = flags.concat(currentFile);
return this.spawnPromise(command, commandArgs);
});
// Once all commands have finished, parse output and append results
// This is done once all commands are finished so that the output order is consistent.
Promise.all(commandPromises).then((commandResults) => {
let outputMessages = [];
let reachedError = false;
commandResults.forEach((result) => {
// If we've already had an error, don't try to run again
if (reachedError) { return; }
// The process can't be started.
if (result.error) {
reachedError = true;
let resultErrorOutput = result.stderr ? result.stderr : (result.error.message || result.error.toString());
return resolve(['fail', command + ' encountered an error\n' + resultErrorOutput]);
}
outputMessages = outputMessages.concat(this.parseStylintResult(result));
});
resolve(outputMessages);
}, reject);
});
}
/**
* Parse a stylint result to generate an array of
* HookMessage objects. the reporter we use produces a json object like:
* {
* severity: 'error'|'warning',
* message: 'error or warning message',
* rule: 'rule-name',
* line: 15,
* column: 16
* }
*
* @param {SpawnResult} result - the result of the stylint run
* @returns {HookMessage[]}
*/
parseStylintResult(result) {
let resultHash = null;
// Sometimes JSON.parse fails as if there is no output provided.
// This is unexpected, so we've added some debug logging to try to understand
try {
resultHash = JSON.parse(result.stdout.toString().trim());
} catch(e) {
utils.logger().debug(
'Error parsing stylus output:\n' +
`status: ${result.status}\n` +
`signal: ${result.signal}\n` +
`stdout: ${result.stdout.toString()}\n` +
`stderr: ${result.stderr.toString()}`
);
return [new HookMessage(
'warning',
'',
0,
`Error parsing stylus output:\nstdout: ${result.stdout.toString().trim()}`
)];
}
let hookMessages = [];
let fileResult = resultHash[0];
for (let i in fileResult.messages) {
let currentMessage = fileResult.messages[i];
let type = currentMessage.severity.toLowerCase();
hookMessages.push(new HookMessage(
type,
fileResult.filePath,
currentMessage.line,
fileResult.filePath + ':' + currentMessage.line + ':' + type + ' ' + currentMessage.message
));
}
return hookMessages;
}
};