index.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. /**
  2. * Based on Stylish reporter from Sindre Sorhus
  3. */
  4. 'use strict';
  5. var chalk = require('chalk'),
  6. stripAnsi = require('strip-ansi'),
  7. table = require('text-table'),
  8. extend = require('extend');
  9. var path = require('path');
  10. var process = require('./process');
  11. var minimist = require('minimist');
  12. var clsc = require('coalescy');
  13. //------------------------------------------------------------------------------
  14. // Helpers
  15. //------------------------------------------------------------------------------
  16. /**
  17. * Given a word and a count, append an s if count is not one.
  18. * @param {string} word A word in its singular form.
  19. * @param {int} count A number controlling whether word should be pluralized.
  20. * @returns {string} The original word with an s on the end if count is not one.
  21. */
  22. function pluralize(word, count) {
  23. return (count === 1 ? word : word + 's');
  24. }
  25. var parseBoolEnvVar = function(varName) {
  26. var env = process.env || { };
  27. return env[varName] === 'true';
  28. };
  29. var subtleLog = function(args) {
  30. return parseBoolEnvVar('EFF_NO_GRAY') ? args : chalk.gray(args);
  31. };
  32. var getEnvVar = function(varName) {
  33. var env = process.env || { };
  34. return env[varName] || false;
  35. };
  36. var getFileLink = function(_path, line, column) {
  37. var scheme = getEnvVar('EFF_EDITOR_SCHEME');
  38. if (scheme === false) {
  39. return false;
  40. }
  41. return scheme.replace('{file}', encodeURIComponent(_path)).replace('{line}', line).replace('{column}', column);
  42. };
  43. var getKeyLink = function(key) {
  44. var noLinkRules = parseBoolEnvVar('EFF_NO_LINK_RULES');
  45. var url = key.indexOf('/') > -1 ? 'https://google.com/#q=' : 'http://eslint.org/docs/rules/';
  46. return (!noLinkRules) ? chalk.underline(subtleLog(url + chalk.white(encodeURIComponent(key)))) : chalk.white(key);
  47. };
  48. var printSummary = function(hash, title, method) {
  49. var res = '\n\n' + chalk[method](title + ':') + chalk.white('\n');
  50. res += table(
  51. Object.keys(hash).sort(function(a, b) {
  52. return hash[a] > hash[b] ? -1 : 1;
  53. }).map(function(key) {
  54. return [
  55. '',
  56. hash[key],
  57. getKeyLink(key)
  58. ];
  59. }), {
  60. align: [
  61. '',
  62. 'r',
  63. 'l'
  64. ],
  65. stringLength: function(str) {
  66. return stripAnsi(str).length;
  67. }
  68. });
  69. return res;
  70. };
  71. //------------------------------------------------------------------------------
  72. // Public Interface
  73. //------------------------------------------------------------------------------
  74. module.exports = function(results) {
  75. var output = '\n',
  76. total = 0,
  77. errors = 0,
  78. warnings = 0,
  79. summaryColor = 'yellow';
  80. results = results || [];
  81. var entries = [];
  82. var absolutePathsToFile = parseBoolEnvVar('EFF_ABSOLUTE_PATHS');
  83. var restArgs = process.argv.slice(process.argv.indexOf('--') + 1);
  84. var parsedArgs = minimist(restArgs);
  85. var groupByIssue = parsedArgs['eff-by-issue'];
  86. var filterRule = parsedArgs['eff-filter'];
  87. absolutePathsToFile = clsc(parsedArgs['eff-absolute-paths'], absolutePathsToFile);
  88. var errorsHash = { };
  89. var warningsHash = { };
  90. results.forEach(function(result) {
  91. var messages = result.messages || [];
  92. entries = entries.concat(messages.map(function(message) {
  93. return extend({
  94. filePath: absolutePathsToFile ? path.resolve(result.filePath) : path.relative('.', result.filePath)
  95. }, message);
  96. }));
  97. });
  98. entries.sort(function(a, b) {
  99. if (a.severity > b.severity) {
  100. return 1;
  101. }
  102. if (a.severity < b.severity) {
  103. return -1;
  104. }
  105. if (groupByIssue) {
  106. if (a.ruleId > b.ruleId) {
  107. return 1;
  108. }
  109. if (a.ruleId < b.ruleId) {
  110. return -1;
  111. }
  112. }
  113. var pathSort = a.filePath.localeCompare(b.filePath);
  114. if (pathSort) {
  115. return pathSort;
  116. }
  117. if (a.line > b.line) {
  118. return 1;
  119. }
  120. if (a.line < b.line) {
  121. return -1;
  122. }
  123. if (a.column > b.column) {
  124. return 1;
  125. }
  126. if (a.column < b.column) {
  127. return -1;
  128. }
  129. return 0;
  130. });
  131. output += table(
  132. entries.reduce(function(seq, message) {
  133. var messageType;
  134. if (filterRule) {
  135. if (message.ruleId !== filterRule) {
  136. return seq;
  137. }
  138. }
  139. if (message.fatal || message.severity === 2) {
  140. messageType = chalk.red('✘');
  141. summaryColor = 'red';
  142. errorsHash[message.ruleId] = (errorsHash[message.ruleId] || 0) + 1;
  143. errors++;
  144. } else {
  145. messageType = chalk.yellow('⚠');
  146. warningsHash[message.ruleId] = (warningsHash[message.ruleId] || 0) + 1;
  147. warnings++;
  148. }
  149. var line = message.line || 0;
  150. var column = message.column || 0;
  151. var arrow = '';
  152. var hasSource = message.source && message.source.length < 1000;
  153. if (hasSource) {
  154. for (var i = 0; i < message.column; i++) {
  155. if (message.source.charAt(i) === '\t') {
  156. arrow += '\t';
  157. } else {
  158. arrow += ' ';
  159. }
  160. }
  161. arrow += '^';
  162. }
  163. var filePath = message.filePath;
  164. var link = getFileLink(filePath, line, column);
  165. var filename = subtleLog(filePath + ':' + line + ':' + column);
  166. seq.push([
  167. '',
  168. messageType + ' ' + getKeyLink(message.ruleId || ''),
  169. message.message.replace(/\.$/, ''),
  170. '$MARKER$ ' + (link === false ? chalk.underline(filename) : filename) +
  171. (link === false ? '' : '$MARKER$ ' + chalk.underline(subtleLog(link))) + '$MARKER$ ' +
  172. (hasSource ? subtleLog(message.source) + '$MARKER$ ' + subtleLog(arrow) : '') + '$MARKER$'
  173. ]);
  174. return seq;
  175. }, []), {
  176. align: [
  177. '',
  178. 'l',
  179. 'l',
  180. 'l'
  181. ],
  182. stringLength: function(str) {
  183. return stripAnsi(str).length;
  184. }
  185. }).replace(/\$MARKER\$/g, '\n') + '\n\n';
  186. total = entries.length;
  187. if (total > 0) {
  188. output += chalk[summaryColor].bold([
  189. '✘ ',
  190. total,
  191. pluralize(' problem', total),
  192. ' (',
  193. errors,
  194. pluralize(' error', errors),
  195. ', ',
  196. warnings,
  197. pluralize(' warning', warnings),
  198. ')'
  199. ].join('')) + chalk.white('\n');
  200. if (errors > 0) {
  201. output += printSummary(errorsHash, 'Errors', 'red');
  202. }
  203. if (warnings > 0) {
  204. output += printSummary(warningsHash, 'Warnings', 'yellow');
  205. }
  206. }
  207. if (process.env.FORCE_ITERM_HINT === 'true' || (process.stdout.isTTY && !process.env.CI)) {
  208. output = '\u001B]1337;CurrentDir=' + process.cwd() + '\u0007' + output;
  209. }
  210. return total > 0 ? output : '';
  211. };