index.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. 'use strict'
  2. var supported = require('supports-color').stderr.hasBasic
  3. var width = require('string-width')
  4. var stringify = require('unist-util-stringify-position')
  5. var repeat = require('repeat-string')
  6. var statistics = require('vfile-statistics')
  7. var sort = require('vfile-sort')
  8. module.exports = reporter
  9. // Check which characters should be used.
  10. var windows = process.platform === 'win32'
  11. // `log-symbols` without chalk:
  12. /* istanbul ignore next - Windows. */
  13. var chars = windows ? {error: '×', warning: '‼'} : {error: '✖', warning: '⚠'}
  14. // Match trailing white-space.
  15. var trailing = /\s*$/
  16. // Default filename.
  17. var defaultName = '<stdin>'
  18. var noop = {open: '', close: ''}
  19. var colors = {
  20. underline: {open: '\u001B[4m', close: '\u001B[24m'},
  21. red: {open: '\u001B[31m', close: '\u001B[39m'},
  22. yellow: {open: '\u001B[33m', close: '\u001B[39m'},
  23. green: {open: '\u001B[32m', close: '\u001B[39m'}
  24. }
  25. var noops = {
  26. underline: noop,
  27. red: noop,
  28. yellow: noop,
  29. green: noop
  30. }
  31. var labels = {
  32. true: 'error',
  33. false: 'warning',
  34. null: 'info',
  35. undefined: 'info'
  36. }
  37. // Report a file’s messages.
  38. function reporter(files, options) {
  39. var settings = options || {}
  40. var one
  41. if (!files) {
  42. return ''
  43. }
  44. // Error.
  45. if ('name' in files && 'message' in files) {
  46. return String(files.stack || files)
  47. }
  48. // One file.
  49. if (!('length' in files)) {
  50. one = true
  51. files = [files]
  52. }
  53. return compile(parse(filter(files, settings), settings), one, settings)
  54. }
  55. function filter(files, options) {
  56. var result = []
  57. var length = files.length
  58. var index = -1
  59. var file
  60. if (!options.quiet && !options.silent) {
  61. return files.concat()
  62. }
  63. while (++index < length) {
  64. file = files[index]
  65. if (applicable(file, options).length !== 0) {
  66. result.push(file)
  67. }
  68. }
  69. return result
  70. }
  71. function parse(files, options) {
  72. var length = files.length
  73. var index = -1
  74. var rows = []
  75. var all = []
  76. var locationSize = 0
  77. var labelSize = 0
  78. var reasonSize = 0
  79. var ruleIdSize = 0
  80. var file
  81. var destination
  82. var origin
  83. var messages
  84. var offset
  85. var count
  86. var message
  87. var loc
  88. var reason
  89. var label
  90. var id
  91. while (++index < length) {
  92. file = files[index]
  93. destination = file.path
  94. origin = file.history[0] || destination
  95. messages = sort({messages: applicable(file, options)}).messages
  96. if (rows.length !== 0 && rows[rows.length - 1].type !== 'header') {
  97. rows.push({type: 'separator'})
  98. }
  99. rows.push({
  100. type: 'header',
  101. origin: origin,
  102. destination: destination,
  103. name: origin || options.defaultName || defaultName,
  104. stored: Boolean(file.stored),
  105. moved: Boolean(file.stored && destination !== origin),
  106. stats: statistics(messages)
  107. })
  108. offset = -1
  109. count = messages.length
  110. while (++offset < count) {
  111. message = messages[offset]
  112. id = message.ruleId || ''
  113. reason = message.stack || message.message
  114. loc = message.location
  115. loc = stringify(loc.end.line && loc.end.column ? loc : loc.start)
  116. if (options.verbose && message.note) {
  117. reason += '\n' + message.note
  118. }
  119. label = labels[message.fatal]
  120. rows.push({
  121. location: loc,
  122. label: label,
  123. reason: reason,
  124. ruleId: id,
  125. source: message.source
  126. })
  127. locationSize = Math.max(realLength(loc), locationSize)
  128. labelSize = Math.max(realLength(label), labelSize)
  129. reasonSize = Math.max(realLength(reason), reasonSize)
  130. ruleIdSize = Math.max(realLength(id), ruleIdSize)
  131. }
  132. all = all.concat(messages)
  133. }
  134. return {
  135. rows: rows,
  136. statistics: statistics(all),
  137. location: locationSize,
  138. label: labelSize,
  139. reason: reasonSize,
  140. ruleId: ruleIdSize
  141. }
  142. }
  143. // eslint-disable-next-line complexity
  144. function compile(map, one, options) {
  145. var enabled = options.color
  146. var all = map.statistics
  147. var rows = map.rows
  148. var length = rows.length
  149. var index = -1
  150. var lines = []
  151. var row
  152. var line
  153. var style
  154. var color
  155. var reason
  156. var rest
  157. var position
  158. if (enabled === null || enabled === undefined) {
  159. enabled = supported
  160. }
  161. style = enabled ? colors : noops
  162. while (++index < length) {
  163. row = rows[index]
  164. if (row.type === 'separator') {
  165. lines.push('')
  166. } else if (row.type === 'header') {
  167. if (one && !options.defaultName && !row.origin) {
  168. line = ''
  169. } else {
  170. color =
  171. style[row.stats.fatal ? 'red' : row.stats.total ? 'yellow' : 'green']
  172. line =
  173. style.underline.open +
  174. color.open +
  175. row.name +
  176. color.close +
  177. style.underline.close
  178. line += row.moved ? ' > ' + row.destination : ''
  179. }
  180. if (!row.stats.total) {
  181. line += line ? ': ' : ''
  182. if (row.stored) {
  183. line += style.yellow.open + 'written' + style.yellow.close
  184. } else {
  185. line += 'no issues found'
  186. }
  187. }
  188. if (line) {
  189. lines.push(line)
  190. }
  191. } else {
  192. color = style[row.label === 'error' ? 'red' : 'yellow']
  193. reason = row.reason
  194. rest = ''
  195. position = reason.indexOf('\n')
  196. if (position !== -1) {
  197. rest = reason.slice(position)
  198. reason = reason.slice(0, position)
  199. }
  200. lines.push(
  201. [
  202. '',
  203. padLeft(row.location, map.location),
  204. padRight(color.open + row.label + color.close, map.label),
  205. padRight(reason, map.reason),
  206. padRight(row.ruleId, map.ruleId),
  207. row.source || ''
  208. ]
  209. .join(' ')
  210. .replace(trailing, '') + rest
  211. )
  212. }
  213. }
  214. if (all.fatal || all.warn) {
  215. line = []
  216. if (all.fatal) {
  217. line.push(
  218. [
  219. style.red.open + chars.error + style.red.close,
  220. all.fatal,
  221. plural(labels.true, all.fatal)
  222. ].join(' ')
  223. )
  224. }
  225. if (all.warn) {
  226. line.push(
  227. [
  228. style.yellow.open + chars.warning + style.yellow.close,
  229. all.warn,
  230. plural(labels.false, all.warn)
  231. ].join(' ')
  232. )
  233. }
  234. line = line.join(', ')
  235. if (all.total !== all.fatal && all.total !== all.warn) {
  236. line = all.total + ' messages (' + line + ')'
  237. }
  238. lines.push('', line)
  239. }
  240. return lines.join('\n')
  241. }
  242. function applicable(file, options) {
  243. var messages = file.messages
  244. var length = messages.length
  245. var index = -1
  246. var result = []
  247. if (options.silent) {
  248. while (++index < length) {
  249. if (messages[index].fatal) {
  250. result.push(messages[index])
  251. }
  252. }
  253. } else {
  254. result = messages.concat()
  255. }
  256. return result
  257. }
  258. // Get the length of `value`, ignoring ANSI sequences.
  259. function realLength(value) {
  260. var length = value.indexOf('\n')
  261. return width(length === -1 ? value : value.slice(0, length))
  262. }
  263. // Pad `value` on the left.
  264. function padLeft(value, minimum) {
  265. return repeat(' ', minimum - realLength(value)) + value
  266. }
  267. // Pad `value` on the right.
  268. function padRight(value, minimum) {
  269. return value + repeat(' ', minimum - realLength(value))
  270. }
  271. function plural(value, count) {
  272. return count === 1 ? value : value + 's'
  273. }