280 lines
7.2 KiB
JavaScript
280 lines
7.2 KiB
JavaScript
|
"use strict";
|
||
|
|
||
|
//var fs = require("fs");
|
||
|
var assign = require("object-assign");
|
||
|
var loaderUtils = require("loader-utils");
|
||
|
var objectHash = require("object-hash");
|
||
|
var createCache = require("loader-fs-cache");
|
||
|
|
||
|
var pkg = require("./package.json");
|
||
|
|
||
|
var cache = createCache("eslint-loader");
|
||
|
|
||
|
var engines = {};
|
||
|
|
||
|
/**
|
||
|
* Class representing an ESLintError.
|
||
|
* @extends Error
|
||
|
*/
|
||
|
class ESLintError extends Error {
|
||
|
/**
|
||
|
* Create an ESLintError.
|
||
|
* @param {string} messages - Formatted eslint errors.
|
||
|
*/
|
||
|
constructor(messages) {
|
||
|
super();
|
||
|
this.name = "ESLintError";
|
||
|
this.message = messages;
|
||
|
this.stack = "";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a stringified representation of our error. This method is called
|
||
|
* when an error is consumed by console methods
|
||
|
* ex: console.error(new ESLintError(formattedMessage))
|
||
|
* @return {string} error - A stringified representation of the error.
|
||
|
*/
|
||
|
inspect() {
|
||
|
return this.message;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* printLinterOutput
|
||
|
*
|
||
|
* @param {Object} eslint.executeOnText return value
|
||
|
* @param {Object} config eslint configuration
|
||
|
* @param {Object} webpack webpack instance
|
||
|
* @return {void}
|
||
|
*/
|
||
|
function printLinterOutput(res, config, webpack) {
|
||
|
// skip ignored file warning
|
||
|
if (
|
||
|
!(
|
||
|
res.warningCount === 1 &&
|
||
|
res.results[0].messages[0] &&
|
||
|
res.results[0].messages[0].message &&
|
||
|
res.results[0].messages[0].message.indexOf("ignore") > 1
|
||
|
)
|
||
|
) {
|
||
|
// quiet filter done now
|
||
|
// eslint allow rules to be specified in the input between comments
|
||
|
// so we can found warnings defined in the input itself
|
||
|
if (res.warningCount && config.quiet) {
|
||
|
res.warningCount = 0;
|
||
|
res.results[0].warningCount = 0;
|
||
|
res.results[0].messages = res.results[0].messages.filter(function(
|
||
|
message
|
||
|
) {
|
||
|
return message.severity !== 1;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// if enabled, use eslint auto-fixing where possible
|
||
|
if (
|
||
|
config.fix &&
|
||
|
(res.results[0].output !== res.src ||
|
||
|
res.results[0].fixableErrorCount > 0 ||
|
||
|
res.results[0].fixableWarningCount > 0)
|
||
|
) {
|
||
|
var eslint = require(config.eslintPath);
|
||
|
eslint.CLIEngine.outputFixes(res);
|
||
|
}
|
||
|
|
||
|
if (res.errorCount || res.warningCount) {
|
||
|
// add filename for each results so formatter can have relevant filename
|
||
|
res.results.forEach(function(r) {
|
||
|
r.filePath = webpack.resourcePath;
|
||
|
});
|
||
|
var messages = config.formatter(res.results);
|
||
|
|
||
|
if (config.outputReport && config.outputReport.filePath) {
|
||
|
var reportOutput;
|
||
|
// if a different formatter is passed in as an option use that
|
||
|
if (config.outputReport.formatter) {
|
||
|
reportOutput = config.outputReport.formatter(res.results);
|
||
|
} else {
|
||
|
reportOutput = messages;
|
||
|
}
|
||
|
var filePath = loaderUtils.interpolateName(
|
||
|
webpack,
|
||
|
config.outputReport.filePath,
|
||
|
{
|
||
|
content: res.results
|
||
|
.map(function(r) {
|
||
|
return r.source;
|
||
|
})
|
||
|
.join("\n")
|
||
|
}
|
||
|
);
|
||
|
webpack.emitFile(filePath, reportOutput);
|
||
|
}
|
||
|
|
||
|
// default behavior: emit error only if we have errors
|
||
|
var emitter = res.errorCount ? webpack.emitError : webpack.emitWarning;
|
||
|
|
||
|
// force emitError or emitWarning if user want this
|
||
|
if (config.emitError) {
|
||
|
emitter = webpack.emitError;
|
||
|
} else if (config.emitWarning) {
|
||
|
emitter = webpack.emitWarning;
|
||
|
}
|
||
|
|
||
|
if (emitter) {
|
||
|
if (config.failOnError && res.errorCount) {
|
||
|
throw new ESLintError(
|
||
|
"Module failed because of a eslint error.\n" + messages
|
||
|
);
|
||
|
} else if (config.failOnWarning && res.warningCount) {
|
||
|
throw new ESLintError(
|
||
|
"Module failed because of a eslint warning.\n" + messages
|
||
|
);
|
||
|
}
|
||
|
|
||
|
emitter(new ESLintError(messages));
|
||
|
} else {
|
||
|
throw new Error(
|
||
|
"Your module system doesn't support emitWarning. " +
|
||
|
"Update available? \n" +
|
||
|
messages
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* webpack loader
|
||
|
*
|
||
|
* @param {String|Buffer} input JavaScript string
|
||
|
* @param {Object} map input source map
|
||
|
* @return {void}
|
||
|
*/
|
||
|
module.exports = function(input, map) {
|
||
|
var webpack = this;
|
||
|
|
||
|
var userOptions = assign(
|
||
|
// user defaults
|
||
|
(webpack.options && webpack.options.eslint) || webpack.query || {},
|
||
|
// loader query string
|
||
|
loaderUtils.getOptions(webpack)
|
||
|
);
|
||
|
|
||
|
var eslintPkgPath = "eslint/package.json";
|
||
|
var userEslintPath = eslintPkgPath;
|
||
|
|
||
|
if (userOptions.eslintPath) {
|
||
|
userEslintPath = userOptions.eslintPath + "/package.json";
|
||
|
}
|
||
|
|
||
|
var eslintVersion;
|
||
|
|
||
|
try {
|
||
|
eslintVersion = require(require.resolve(userEslintPath)).version;
|
||
|
} catch (_) {
|
||
|
// ignored
|
||
|
}
|
||
|
|
||
|
if (!eslintVersion) {
|
||
|
try {
|
||
|
eslintVersion = require(require.resolve(eslintPkgPath)).version;
|
||
|
} catch (_) {
|
||
|
// ignored
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var config = assign(
|
||
|
// loader defaults
|
||
|
{
|
||
|
cacheIdentifier: JSON.stringify({
|
||
|
"eslint-loader": pkg.version,
|
||
|
eslint: eslintVersion || "unknown version"
|
||
|
}),
|
||
|
eslintPath: "eslint"
|
||
|
},
|
||
|
userOptions
|
||
|
);
|
||
|
|
||
|
if (typeof config.formatter === "string") {
|
||
|
try {
|
||
|
config.formatter = require(config.formatter);
|
||
|
if (
|
||
|
config.formatter &&
|
||
|
typeof config.formatter !== "function" &&
|
||
|
typeof config.formatter.default === "function"
|
||
|
) {
|
||
|
config.formatter = config.formatter.default;
|
||
|
}
|
||
|
} catch (_) {
|
||
|
// ignored
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var cacheDirectory = config.cache;
|
||
|
var cacheIdentifier = config.cacheIdentifier;
|
||
|
|
||
|
delete config.cacheIdentifier;
|
||
|
|
||
|
// Create the engine only once per config
|
||
|
var configHash = objectHash(config);
|
||
|
|
||
|
if (!engines[configHash]) {
|
||
|
var eslint = require(config.eslintPath);
|
||
|
engines[configHash] = new eslint.CLIEngine(config);
|
||
|
}
|
||
|
|
||
|
var engine = engines[configHash];
|
||
|
if (config.formatter == null || typeof config.formatter !== "function") {
|
||
|
config.formatter = engine.getFormatter("stylish");
|
||
|
}
|
||
|
|
||
|
webpack.cacheable();
|
||
|
|
||
|
var resourcePath = webpack.resourcePath;
|
||
|
var cwd = process.cwd();
|
||
|
|
||
|
// remove cwd from resource path in case webpack has been started from project
|
||
|
// root, to allow having relative paths in .eslintignore
|
||
|
if (resourcePath.indexOf(cwd) === 0) {
|
||
|
resourcePath = resourcePath.substr(cwd.length + 1);
|
||
|
}
|
||
|
|
||
|
// return early if cached
|
||
|
if (config.cache) {
|
||
|
var callback = webpack.async();
|
||
|
return cache(
|
||
|
{
|
||
|
directory: cacheDirectory,
|
||
|
identifier: cacheIdentifier,
|
||
|
options: config,
|
||
|
source: input,
|
||
|
transform: function() {
|
||
|
return lint(engine, input, resourcePath);
|
||
|
}
|
||
|
},
|
||
|
function(err, res) {
|
||
|
if (err) {
|
||
|
return callback(err);
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
printLinterOutput(
|
||
|
assign({}, res || {}, { src: input }),
|
||
|
config,
|
||
|
webpack
|
||
|
);
|
||
|
} catch (e) {
|
||
|
err = e;
|
||
|
}
|
||
|
return callback(err, input, map);
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
printLinterOutput(lint(engine, input, resourcePath), config, webpack);
|
||
|
webpack.callback(null, input, map);
|
||
|
};
|
||
|
|
||
|
function lint(engine, input, resourcePath) {
|
||
|
return engine.executeOnText(input, resourcePath, true);
|
||
|
}
|