css-syntax-error.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. 'use strict'
  2. let pico = require('picocolors')
  3. let terminalHighlight = require('./terminal-highlight')
  4. class CssSyntaxError extends Error {
  5. constructor(message, line, column, source, file, plugin) {
  6. super(message)
  7. this.name = 'CssSyntaxError'
  8. this.reason = message
  9. if (file) {
  10. this.file = file
  11. }
  12. if (source) {
  13. this.source = source
  14. }
  15. if (plugin) {
  16. this.plugin = plugin
  17. }
  18. if (typeof line !== 'undefined' && typeof column !== 'undefined') {
  19. if (typeof line === 'number') {
  20. this.line = line
  21. this.column = column
  22. } else {
  23. this.line = line.line
  24. this.column = line.column
  25. this.endLine = column.line
  26. this.endColumn = column.column
  27. }
  28. }
  29. this.setMessage()
  30. if (Error.captureStackTrace) {
  31. Error.captureStackTrace(this, CssSyntaxError)
  32. }
  33. }
  34. setMessage() {
  35. this.message = this.plugin ? this.plugin + ': ' : ''
  36. this.message += this.file ? this.file : '<css input>'
  37. if (typeof this.line !== 'undefined') {
  38. this.message += ':' + this.line + ':' + this.column
  39. }
  40. this.message += ': ' + this.reason
  41. }
  42. showSourceCode(color) {
  43. if (!this.source) return ''
  44. let css = this.source
  45. if (color == null) color = pico.isColorSupported
  46. let aside = text => text
  47. let mark = text => text
  48. let highlight = text => text
  49. if (color) {
  50. let { bold, gray, red } = pico.createColors(true)
  51. mark = text => bold(red(text))
  52. aside = text => gray(text)
  53. if (terminalHighlight) {
  54. highlight = text => terminalHighlight(text)
  55. }
  56. }
  57. let lines = css.split(/\r?\n/)
  58. let start = Math.max(this.line - 3, 0)
  59. let end = Math.min(this.line + 2, lines.length)
  60. let maxWidth = String(end).length
  61. return lines
  62. .slice(start, end)
  63. .map((line, index) => {
  64. let number = start + 1 + index
  65. let gutter = ' ' + (' ' + number).slice(-maxWidth) + ' | '
  66. if (number === this.line) {
  67. if (line.length > 160) {
  68. let padding = 20
  69. let subLineStart = Math.max(0, this.column - padding)
  70. let subLineEnd = Math.max(
  71. this.column + padding,
  72. this.endColumn + padding
  73. )
  74. let subLine = line.slice(subLineStart, subLineEnd)
  75. let spacing =
  76. aside(gutter.replace(/\d/g, ' ')) +
  77. line
  78. .slice(0, Math.min(this.column - 1, padding - 1))
  79. .replace(/[^\t]/g, ' ')
  80. return (
  81. mark('>') +
  82. aside(gutter) +
  83. highlight(subLine) +
  84. '\n ' +
  85. spacing +
  86. mark('^')
  87. )
  88. }
  89. let spacing =
  90. aside(gutter.replace(/\d/g, ' ')) +
  91. line.slice(0, this.column - 1).replace(/[^\t]/g, ' ')
  92. return (
  93. mark('>') +
  94. aside(gutter) +
  95. highlight(line) +
  96. '\n ' +
  97. spacing +
  98. mark('^')
  99. )
  100. }
  101. return ' ' + aside(gutter) + highlight(line)
  102. })
  103. .join('\n')
  104. }
  105. toString() {
  106. let code = this.showSourceCode()
  107. if (code) {
  108. code = '\n\n' + code + '\n'
  109. }
  110. return this.name + ': ' + this.message + code
  111. }
  112. }
  113. module.exports = CssSyntaxError
  114. CssSyntaxError.default = CssSyntaxError