/
linting.js
139 lines (125 loc) · 4.11 KB
/
linting.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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
* @fileoverview The main linting file.
* This is the object responsible for the actual linting of each file.
* Each instance represents a single file being linted, including results and
* current state.
* It receives the parsed AST and rules from ../svglint.js, and then runs each
* rule and gathers the results.
*/
const EventEmitter = require("events").EventEmitter;
const path = require("path");
const Reporter = require("./reporter");
const logger = require("./logger");
const STATES = Object.freeze({
"ignored": -1,
"linting": undefined,
"success": "success",
"warn": "warning",
"error": "error",
"_-1": "ignored",
"_undefined": "linting",
"_success": "success",
"_warning": "warn",
"_error": "error",
});
/**
* Represents a single file that is being linted.
* Contains the status and potential result of the linting.
* @event rule Emitted when a rule is finished
* @event done Emitted when the linting is done
*/
class Linting extends EventEmitter {
/**
* Creates and starts a new linting.
* @param {String} file The file to lint
* @param {AST} ast The AST of the file
* @param {NormalizedRules} rules The rules that represent
*/
constructor(file, ast, rules) {
super();
this.ast = ast;
this.rules = rules;
this.path = file;
this.state = STATES.linting;
this.name = file
? path.relative(process.cwd(), file)
: "";
/** @type Object<string,Reporter> */
this.results = {};
this.lint();
// TODO: add reporter
}
/**
* Starts the linting.
* Errors from rules are safely caught and logged as exceptions from the rule.
*/
lint() {
this.state = STATES.linting;
// keep track of when every rule has finished
const rules = Object.keys(this.rules);
this.activeRules = rules.length;
logger.debug("Started linting", (this.name || "API-provided file"));
logger.debug(" Rules:", rules);
// start every rule
rules.forEach(ruleName => {
// gather results from the rule through a reporter
const reporter = this._generateReporter(ruleName);
const onDone = () => {
this._onRuleFinish(ruleName, reporter);
};
// execute the rule, potentially waiting for async rules
// also handles catching errors from the rule
Promise.resolve()
.then(() => this.rules[ruleName](reporter))
.catch(e => reporter.exception(e))
.then(onDone);
});
}
/**
* Handles a rule finishing.
* @param {String} ruleName The name of the rule that just finished
* @param {Reporter} reporter The reporter containing rule results
* @emits rule
* @private
*/
_onRuleFinish(ruleName, reporter) {
logger.debug("Rule finished", logger.colorize(ruleName));
this.emit("rule", {
name: ruleName,
reporter,
});
this.results[ruleName] = reporter;
--this.activeRules;
if (this.activeRules === 0) {
this.state = this._calculateState();
logger.debug("Linting finished", logger.colorize(STATES["_"+this.state]));
this.emit("done");
}
}
/**
* Calculates the current state from this.results.
* @returns One of the valid states
*/
_calculateState() {
let state = STATES.success;
for (let k in this.results) {
const result = this.results[k];
if (result.errors.length) { return STATES.error; }
if (result.warns.length || state === STATES.warn) {
state = STATES.warn;
}
}
return state;
}
/**
* Generates a Reporter for use with this file.
* Remember to call .done() on it.
* @param {String} ruleName The name of the rule that this reporter is used for
* @private
*/
_generateReporter(ruleName) {
return new Reporter(ruleName);
}
}
Linting.STATES = STATES;
module.exports = Linting;