index.js 6.2 KB

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