-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplugin.js
229 lines (201 loc) · 7.44 KB
/
plugin.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// *********************************************************************
// Copyright 2018 Healthy Bytes Technology & Wellness
//
// A TSLint plugin for Brunch.
'use strict';
const debug = require('debug')('brunch:tslint');
const fs = require('fs');
const path = require('path');
// Use the real TSLint library, or our mock library if we're testing.
let tslint;
if (! process.env.hasOwnProperty('TEST'))
{
tslint = require('tslint');
}
else
{
debug('Using mock TSLint library');
tslint = require('./test/MockTslint');
}
class TSLinter
{
// Construct the plugin with the entire Brunch config object. We will pick
// out our options from the plugins.tslint object. See README.md for
// documentation of the options.
constructor(brunchCfg)
{
// Find out plugin's options within the global Brunch plugin
// configuration.
const cfg = (brunchCfg && brunchCfg.plugins && brunchCfg.plugins.tslint
? brunchCfg.plugins.tslint
: { config: {} });
// Advertise to Brunch the types of files we will lint. Use
// the pattern supplied by the Brunch config, or use a typical
// default.
if (cfg.hasOwnProperty('pattern'))
{
if (! (cfg.pattern instanceof RegExp))
{
this.pattern = new RegExp(cfg.pattern);
}
else
{
this.pattern = cfg.pattern;
}
}
else
{
this.pattern = /^app\/.*\.ts$/;
}
// Start with a root of '/'.
this.root = path.sep;
if (cfg.root)
{
// If a root was given, make sure the root is a directory.
const root = path.resolve(cfg.root);
try
{
if (fs.statSync(root).isDirectory())
{
this.root = root;
}
}
catch (error) {}
if (this.root !== root)
{
debug(`Ignoring non-directory root=${root}`);
}
}
// Our base config format matches the tslint.json form, only
// it is in JavaScript object form. This is the intermediate
// form that TSLint uses when reading tslint.json files, but
// before parsing them. In order for our base config to be
// applied properly on a per-file basis, we must get TSLint to
// parse it.
this.baseConfig = tslint.Configuration.parseConfigFile(
cfg.config, '.', tslint.Configuration.readConfigurationFile);
// If the options object exists in cfg, and options has the given
// field, return its value, otherwise return the given default value.
function getOption(field, defaultValue)
{
if (typeof cfg.options === 'object'
&& cfg.options.hasOwnProperty(field))
{
return cfg.options[field];
}
return defaultValue;
}
// Setup global options.
const options =
{
// Are we fixing errors, or just reporting on them?
fix: getOption('fix', false),
// Emit all findings, or just errors?
quiet: getOption('quiet', false),
// How are errors being formatted? The default 'prose'. This
// may be a formatter name, or a FormatterConstructor function.
formatter: getOption('formatter', 'prose'),
// Where can the formatter be found?
formattersDirectory: getOption('formattersDirectory', undefined),
// Where can the custom rules be found? A string or array of strings.
rulesDirectory: getOption('rulesDirectory', undefined)
};
// Construct the TSLint engine with the global options.
this.linter = new tslint.Linter(options);
}
// Lint a single file. Brunch provides the file as an object
// containing the file's path and its contents (already read from
// the filesystem).
//
// The base config set at construction is merged with any
// tslint.json configurations found by TSLint for the given file.
// The base config is given the lowest priority.
//
// Note that TSLint _doesn't_ recursively search up the given file's
// directory tree looking for configuration files. This differs
// from other linters like ESLint. We prefer the recrusive
// application of config, so this function will combine all configs
// found up the directory tree, with configuration "closer" to the
// file being linted taking precedence.
//
// This returns a resolved Promise if linting was successful, and
// returns a rejected Promise containing any results if linting
// encountered errors.
//
lint(brunchFile)
{
// Make a copy of the base config to start with.
let mergedConfig = {};
mergedConfig.defaultSeverity = this.baseConfig.defaultSeverity;
mergedConfig.extends = Array.from(this.baseConfig.extends);
mergedConfig.jsRules = new Map(this.baseConfig.jsRules);
mergedConfig.rules = new Map(this.baseConfig.rules);
mergedConfig.rulesDirectory = Array.from(this.baseConfig.rulesDirectory);
// We expect the file being linted to live with our root. If not,
// we won't apply any config but the base one.
const filePathAbs = path.resolve(brunchFile.path);
if (! filePathAbs.startsWith(this.root))
{
debug(`File ${filePathAbs} doesn't live within root ${this.root}; `
+ 'using only base config');
}
else
{
// Strip our root off of the file's path, since we aren't going to
// look for configs any further up than the root.
let stripped = filePathAbs.slice(this.root.length);
// Split that into dir parts.
const split = stripped.split(path.sep);
// Start at the outermost dir part and read configs, merging as
// we work our way in to the innermost dir part.
let configPath = this.root;
for (const dirPart of split)
{
configPath += path.sep + dirPart;
const resolved = path.resolve(configPath);
// Determine the config filename for the current dir.
const configFile = tslint.Configuration.findConfigurationPath(
// Specify undefined for the config filename to tell TSLint to
// search for typical config filenames within the resolved path.
undefined,
resolved);
if (configFile !== undefined)
{
// Load it.
const loadedConfig =
tslint.Configuration.loadConfigurationFromPath(configFile);
// Merge it, giving ancestors less precedence as we go.
mergedConfig = tslint.Configuration.extendConfigurationFile(
mergedConfig, loadedConfig);
}
}
}
// Brunch lints one file at a time and tracks any failures per-file. The
// TSLint Linter object tracks all failures across all lint() calls. As
// such, we must clear the Linter's failures before each lint(), or we'll
// attribute all previous failures to this lint() and end up with an
// ever-growing list of failures. This would confound Brunch's watcher
// mechanism, making it unable to re-compile a failed compilation.
this.linter.failures.length = 0;
// Perform the linting.
this.linter.lint(brunchFile.path, brunchFile.data, mergedConfig);
const result = this.linter.getResult();
if (result.errorCount === 0)
{
return Promise.resolve(true);
}
else
{
return Promise.reject(result.output);
}
}
}
// Tell Brunch that we are a plugin, and we handle code source files.
//
// Note that the presence of a lint() function on TSLinter tells
// Brunch that we're a linter.
//
TSLinter.prototype.brunchPlugin = true;
TSLinter.prototype.type = 'javascript';
module.exports = TSLinter;
// *********************************************************************