linter.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = linter;
  6. var _path = require("path");
  7. var _arrify = _interopRequireDefault(require("arrify"));
  8. var _StylelintError = _interopRequireDefault(require("./StylelintError"));
  9. var _getStylelint = _interopRequireDefault(require("./getStylelint"));
  10. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  11. // @ts-ignore
  12. /** @typedef {import('stylelint')} Stylelint */
  13. /** @typedef {import('stylelint').LintResult} LintResult */
  14. /** @typedef {import('webpack').Compiler} Compiler */
  15. /** @typedef {import('webpack').Compilation} Compilation */
  16. /** @typedef {import('./options').Options} Options */
  17. /** @typedef {import('./options').FormatterType} FormatterType */
  18. /** @typedef {((results: LintResult[]) => string)} FormatterFunction */
  19. /** @typedef {(compilation: Compilation) => Promise<void>} GenerateReport */
  20. /** @typedef {{errors?: StylelintError, warnings?: StylelintError, generateReportAsset?: GenerateReport}} Report */
  21. /** @typedef {() => Promise<Report>} Reporter */
  22. /** @typedef {(files: string|string[]) => void} Linter */
  23. /** @typedef {{[files: string]: LintResult}} LintResultMap */
  24. /** @type {WeakMap<Compiler, LintResultMap>} */
  25. const resultStorage = new WeakMap();
  26. /**
  27. * @param {string|undefined} key
  28. * @param {Options} options
  29. * @param {Compilation} compilation
  30. * @returns {{lint: Linter, report: Reporter, threads: number}}
  31. */
  32. function linter(key, options, compilation) {
  33. /** @type {Stylelint} */
  34. let stylelint;
  35. /** @type {(files: string|string[]) => Promise<LintResult[]>} */
  36. let lintFiles;
  37. /** @type {() => Promise<void>} */
  38. let cleanup;
  39. /** @type number */
  40. let threads;
  41. /** @type {Promise<LintResult[]>[]} */
  42. const rawResults = [];
  43. const crossRunResultStorage = getResultStorage(compilation);
  44. try {
  45. ({
  46. stylelint,
  47. lintFiles,
  48. cleanup,
  49. threads
  50. } = (0, _getStylelint.default)(key, options));
  51. } catch (e) {
  52. throw new _StylelintError.default(e.message);
  53. }
  54. return {
  55. lint,
  56. report,
  57. threads
  58. };
  59. /**
  60. * @param {string | string[]} files
  61. */
  62. function lint(files) {
  63. for (const file of (0, _arrify.default)(files)) {
  64. delete crossRunResultStorage[file];
  65. }
  66. rawResults.push(lintFiles(files).catch(e => {
  67. compilation.errors.push(e);
  68. return [];
  69. }));
  70. }
  71. async function report() {
  72. // Filter out ignored files.
  73. let results = removeIgnoredWarnings( // Get the current results, resetting the rawResults to empty
  74. await flatten(rawResults.splice(0, rawResults.length)));
  75. await cleanup();
  76. for (const result of results) {
  77. crossRunResultStorage[String(result.source)] = result;
  78. }
  79. results = Object.values(crossRunResultStorage); // do not analyze if there are no results or stylelint config
  80. if (!results || results.length < 1) {
  81. return {};
  82. }
  83. const formatter = loadFormatter(stylelint, options.formatter);
  84. const {
  85. errors,
  86. warnings
  87. } = formatResults(formatter, parseResults(options, results));
  88. return {
  89. errors,
  90. warnings,
  91. generateReportAsset
  92. };
  93. /**
  94. * @param {Compilation} compilation
  95. * @returns {Promise<void>}
  96. */
  97. async function generateReportAsset({
  98. compiler
  99. }) {
  100. const {
  101. outputReport
  102. } = options;
  103. /**
  104. * @param {string} name
  105. * @param {string | Buffer} content
  106. */
  107. const save = (name, content) =>
  108. /** @type {Promise<void>} */
  109. new Promise((finish, bail) => {
  110. const {
  111. mkdir,
  112. writeFile
  113. } = compiler.outputFileSystem; // ensure directory exists
  114. // @ts-ignore - the types for `outputFileSystem` are missing the 3 arg overload
  115. mkdir((0, _path.dirname)(name), {
  116. recursive: true
  117. }, err => {
  118. /* istanbul ignore if */
  119. if (err) bail(err);else writeFile(name, content, err2 => {
  120. /* istanbul ignore if */
  121. if (err2) bail(err2);else finish();
  122. });
  123. });
  124. });
  125. if (!outputReport || !outputReport.filePath) {
  126. return;
  127. }
  128. const content = outputReport.formatter ? loadFormatter(stylelint, outputReport.formatter)(results) : formatter(results);
  129. let {
  130. filePath
  131. } = outputReport;
  132. if (!(0, _path.isAbsolute)(filePath)) {
  133. filePath = (0, _path.join)(compiler.outputPath, filePath);
  134. }
  135. await save(filePath, content);
  136. }
  137. }
  138. }
  139. /**
  140. * @param {FormatterFunction} formatter
  141. * @param {{ errors: LintResult[]; warnings: LintResult[]; }} results
  142. * @returns {{errors?: StylelintError, warnings?: StylelintError}}
  143. */
  144. function formatResults(formatter, results) {
  145. let errors;
  146. let warnings;
  147. if (results.warnings.length > 0) {
  148. warnings = new _StylelintError.default(formatter(results.warnings));
  149. }
  150. if (results.errors.length > 0) {
  151. errors = new _StylelintError.default(formatter(results.errors));
  152. }
  153. return {
  154. errors,
  155. warnings
  156. };
  157. }
  158. /**
  159. * @param {Options} options
  160. * @param {LintResult[]} results
  161. * @returns {{errors: LintResult[], warnings: LintResult[]}}
  162. */
  163. function parseResults(options, results) {
  164. /** @type {LintResult[]} */
  165. const errors = [];
  166. /** @type {LintResult[]} */
  167. const warnings = [];
  168. results.forEach(file => {
  169. const fileErrors = file.warnings.filter(message => options.emitError && message.severity === 'error');
  170. if (fileErrors.length > 0) {
  171. errors.push({ ...file,
  172. warnings: fileErrors
  173. });
  174. }
  175. const fileWarnings = file.warnings.filter(message => options.emitWarning && message.severity === 'warning');
  176. if (fileWarnings.length > 0) {
  177. warnings.push({ ...file,
  178. warnings: fileWarnings
  179. });
  180. }
  181. });
  182. return {
  183. errors,
  184. warnings
  185. };
  186. }
  187. /**
  188. * @param {Stylelint} stylelint
  189. * @param {FormatterType=} formatter
  190. * @returns {FormatterFunction}
  191. */
  192. function loadFormatter(stylelint, formatter) {
  193. if (typeof formatter === 'function') {
  194. return formatter;
  195. }
  196. if (typeof formatter === 'string') {
  197. try {
  198. return stylelint.formatters[formatter];
  199. } catch (_) {// Load the default formatter.
  200. }
  201. }
  202. return stylelint.formatters.string;
  203. }
  204. /**
  205. * @param {LintResult[]} results
  206. * @returns {LintResult[]}
  207. */
  208. function removeIgnoredWarnings(results) {
  209. return results.filter(result => !result.ignored);
  210. }
  211. /**
  212. * @param {Promise<LintResult[]>[]} results
  213. * @returns {Promise<LintResult[]>}
  214. */
  215. async function flatten(results) {
  216. /**
  217. * @param {LintResult[]} acc
  218. * @param {LintResult[]} list
  219. */
  220. const flat = (acc, list) => [...acc, ...list];
  221. return (await Promise.all(results)).reduce(flat, []);
  222. }
  223. /**
  224. * @param {Compilation} compilation
  225. * @returns {LintResultMap}
  226. */
  227. function getResultStorage({
  228. compiler
  229. }) {
  230. let storage = resultStorage.get(compiler);
  231. if (!storage) {
  232. resultStorage.set(compiler, storage = {});
  233. }
  234. return storage;
  235. }