index.js 6.5 KB

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