UNPKG

7.16 kBJavaScriptView Raw
1"use strict";
2
3//var fs = require("fs");
4var assign = require("object-assign");
5var loaderUtils = require("loader-utils");
6var objectHash = require("object-hash");
7var createCache = require("loader-fs-cache");
8
9var pkg = require("./package.json");
10
11var cache = createCache("eslint-loader");
12
13var engines = {};
14
15/**
16 * Class representing an ESLintError.
17 * @extends Error
18 */
19class ESLintError extends Error {
20 /**
21 * Create an ESLintError.
22 * @param {string} messages - Formatted eslint errors.
23 */
24 constructor(messages) {
25 super();
26 this.name = "ESLintError";
27 this.message = messages;
28 this.stack = "";
29 }
30
31 /**
32 * Returns a stringified representation of our error. This method is called
33 * when an error is consumed by console methods
34 * ex: console.error(new ESLintError(formattedMessage))
35 * @return {string} error - A stringified representation of the error.
36 */
37 inspect() {
38 return this.message;
39 }
40}
41
42/**
43 * printLinterOutput
44 *
45 * @param {Object} eslint.executeOnText return value
46 * @param {Object} config eslint configuration
47 * @param {Object} webpack webpack instance
48 * @return {void}
49 */
50function printLinterOutput(res, config, webpack) {
51 // skip ignored file warning
52 if (
53 !(
54 res.warningCount === 1 &&
55 res.results[0].messages[0] &&
56 res.results[0].messages[0].message &&
57 res.results[0].messages[0].message.indexOf("ignore") > 1
58 )
59 ) {
60 // quiet filter done now
61 // eslint allow rules to be specified in the input between comments
62 // so we can found warnings defined in the input itself
63 if (res.warningCount && config.quiet) {
64 res.warningCount = 0;
65 res.results[0].warningCount = 0;
66 res.results[0].messages = res.results[0].messages.filter(function(
67 message
68 ) {
69 return message.severity !== 1;
70 });
71 }
72
73 // if enabled, use eslint auto-fixing where possible
74 if (
75 config.fix &&
76 (res.results[0].output !== res.src ||
77 res.results[0].fixableErrorCount > 0 ||
78 res.results[0].fixableWarningCount > 0)
79 ) {
80 var eslint = require(config.eslintPath);
81 eslint.CLIEngine.outputFixes(res);
82 }
83
84 if (res.errorCount || res.warningCount) {
85 // add filename for each results so formatter can have relevant filename
86 res.results.forEach(function(r) {
87 r.filePath = webpack.resourcePath;
88 });
89 var messages = config.formatter(res.results);
90
91 if (config.outputReport && config.outputReport.filePath) {
92 var reportOutput;
93 // if a different formatter is passed in as an option use that
94 if (config.outputReport.formatter) {
95 reportOutput = config.outputReport.formatter(res.results);
96 } else {
97 reportOutput = messages;
98 }
99 var filePath = loaderUtils.interpolateName(
100 webpack,
101 config.outputReport.filePath,
102 {
103 content: res.results
104 .map(function(r) {
105 return r.source;
106 })
107 .join("\n")
108 }
109 );
110 webpack.emitFile(filePath, reportOutput);
111 }
112
113 // default behavior: emit error only if we have errors
114 var emitter = res.errorCount ? webpack.emitError : webpack.emitWarning;
115
116 // force emitError or emitWarning if user want this
117 if (config.emitError) {
118 emitter = webpack.emitError;
119 } else if (config.emitWarning) {
120 emitter = webpack.emitWarning;
121 }
122
123 if (emitter) {
124 if (config.failOnError && res.errorCount) {
125 throw new ESLintError(
126 "Module failed because of a eslint error.\n" + messages
127 );
128 } else if (config.failOnWarning && res.warningCount) {
129 throw new ESLintError(
130 "Module failed because of a eslint warning.\n" + messages
131 );
132 }
133
134 emitter(new ESLintError(messages));
135 } else {
136 throw new Error(
137 "Your module system doesn't support emitWarning. " +
138 "Update available? \n" +
139 messages
140 );
141 }
142 }
143 }
144}
145
146/**
147 * webpack loader
148 *
149 * @param {String|Buffer} input JavaScript string
150 * @param {Object} map input source map
151 * @return {void}
152 */
153module.exports = function(input, map) {
154 var webpack = this;
155
156 var userOptions = assign(
157 // user defaults
158 (webpack.options && webpack.options.eslint) || webpack.query || {},
159 // loader query string
160 loaderUtils.getOptions(webpack)
161 );
162
163 var userEslintPath = userOptions.eslintPath;
164
165 var config = assign(
166 // loader defaults
167 {
168 cacheIdentifier: JSON.stringify({
169 "eslint-loader": pkg.version,
170 eslint: require(userEslintPath || "eslint").version
171 }),
172 eslintPath: "eslint"
173 },
174 userOptions
175 );
176
177 if (typeof config.formatter === "string") {
178 try {
179 config.formatter = require(config.formatter);
180 if (
181 config.formatter &&
182 typeof config.formatter !== "function" &&
183 typeof config.formatter.default === "function"
184 ) {
185 config.formatter = config.formatter.default;
186 }
187 } catch (_) {
188 // ignored
189 }
190 }
191 if (config.formatter == null || typeof config.formatter !== "function") {
192 if (userEslintPath) {
193 try {
194 config.formatter = require(userEslintPath + "/lib/formatters/stylish");
195 } catch (e) {
196 config.formatter = require("eslint/lib/formatters/stylish");
197 }
198 } else {
199 config.formatter = require("eslint/lib/formatters/stylish");
200 }
201 }
202
203 var cacheDirectory = config.cache;
204 var cacheIdentifier = config.cacheIdentifier;
205
206 delete config.cacheIdentifier;
207
208 // Create the engine only once per config
209 var configHash = objectHash(config);
210 if (!engines[configHash]) {
211 var eslint = require(config.eslintPath);
212 engines[configHash] = new eslint.CLIEngine(config);
213 }
214
215 webpack.cacheable();
216
217 var resourcePath = webpack.resourcePath;
218 var cwd = process.cwd();
219
220 // remove cwd from resource path in case webpack has been started from project
221 // root, to allow having relative paths in .eslintignore
222 if (resourcePath.indexOf(cwd) === 0) {
223 resourcePath = resourcePath.substr(cwd.length + 1);
224 }
225
226 var engine = engines[configHash];
227 // return early if cached
228 if (config.cache) {
229 var callback = webpack.async();
230 return cache(
231 {
232 directory: cacheDirectory,
233 identifier: cacheIdentifier,
234 options: config,
235 source: input,
236 transform: function() {
237 return lint(engine, input, resourcePath);
238 }
239 },
240 function(err, res) {
241 if (err) {
242 return callback(err);
243 }
244
245 try {
246 printLinterOutput(
247 assign({}, res || {}, { src: input }),
248 config,
249 webpack
250 );
251 } catch (e) {
252 err = e;
253 }
254 return callback(err, input, map);
255 }
256 );
257 }
258 printLinterOutput(lint(engine, input, resourcePath), config, webpack);
259 webpack.callback(null, input, map);
260};
261
262function lint(engine, input, resourcePath) {
263 return engine.executeOnText(input, resourcePath, true);
264}