index.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /**
  2. * @typedef {import('unist').Node} Node
  3. * @typedef {import('unist').Point} Point
  4. * @typedef {import('unist').Position} Position
  5. */
  6. /**
  7. * @typedef {object & {type: string, position?: Position | undefined}} NodeLike
  8. *
  9. * @typedef Options
  10. * Configuration.
  11. * @property {Array<Node> | null | undefined} [ancestors]
  12. * Stack of (inclusive) ancestor nodes surrounding the message (optional).
  13. * @property {Error | null | undefined} [cause]
  14. * Original error cause of the message (optional).
  15. * @property {Point | Position | null | undefined} [place]
  16. * Place of message (optional).
  17. * @property {string | null | undefined} [ruleId]
  18. * Category of message (optional, example: `'my-rule'`).
  19. * @property {string | null | undefined} [source]
  20. * Namespace of who sent the message (optional, example: `'my-package'`).
  21. */
  22. import {stringifyPosition} from 'unist-util-stringify-position'
  23. /**
  24. * Message.
  25. */
  26. export class VFileMessage extends Error {
  27. /**
  28. * Create a message for `reason`.
  29. *
  30. * > 🪦 **Note**: also has obsolete signatures.
  31. *
  32. * @overload
  33. * @param {string} reason
  34. * @param {Options | null | undefined} [options]
  35. * @returns
  36. *
  37. * @overload
  38. * @param {string} reason
  39. * @param {Node | NodeLike | null | undefined} parent
  40. * @param {string | null | undefined} [origin]
  41. * @returns
  42. *
  43. * @overload
  44. * @param {string} reason
  45. * @param {Point | Position | null | undefined} place
  46. * @param {string | null | undefined} [origin]
  47. * @returns
  48. *
  49. * @overload
  50. * @param {string} reason
  51. * @param {string | null | undefined} [origin]
  52. * @returns
  53. *
  54. * @overload
  55. * @param {Error | VFileMessage} cause
  56. * @param {Node | NodeLike | null | undefined} parent
  57. * @param {string | null | undefined} [origin]
  58. * @returns
  59. *
  60. * @overload
  61. * @param {Error | VFileMessage} cause
  62. * @param {Point | Position | null | undefined} place
  63. * @param {string | null | undefined} [origin]
  64. * @returns
  65. *
  66. * @overload
  67. * @param {Error | VFileMessage} cause
  68. * @param {string | null | undefined} [origin]
  69. * @returns
  70. *
  71. * @param {Error | VFileMessage | string} causeOrReason
  72. * Reason for message, should use markdown.
  73. * @param {Node | NodeLike | Options | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
  74. * Configuration (optional).
  75. * @param {string | null | undefined} [origin]
  76. * Place in code where the message originates (example:
  77. * `'my-package:my-rule'` or `'my-rule'`).
  78. * @returns
  79. * Instance of `VFileMessage`.
  80. */
  81. // eslint-disable-next-line complexity
  82. constructor(causeOrReason, optionsOrParentOrPlace, origin) {
  83. super()
  84. if (typeof optionsOrParentOrPlace === 'string') {
  85. origin = optionsOrParentOrPlace
  86. optionsOrParentOrPlace = undefined
  87. }
  88. /** @type {string} */
  89. let reason = ''
  90. /** @type {Options} */
  91. let options = {}
  92. let legacyCause = false
  93. if (optionsOrParentOrPlace) {
  94. // Point.
  95. if (
  96. 'line' in optionsOrParentOrPlace &&
  97. 'column' in optionsOrParentOrPlace
  98. ) {
  99. options = {place: optionsOrParentOrPlace}
  100. }
  101. // Position.
  102. else if (
  103. 'start' in optionsOrParentOrPlace &&
  104. 'end' in optionsOrParentOrPlace
  105. ) {
  106. options = {place: optionsOrParentOrPlace}
  107. }
  108. // Node.
  109. else if ('type' in optionsOrParentOrPlace) {
  110. options = {
  111. ancestors: [optionsOrParentOrPlace],
  112. place: optionsOrParentOrPlace.position
  113. }
  114. }
  115. // Options.
  116. else {
  117. options = {...optionsOrParentOrPlace}
  118. }
  119. }
  120. if (typeof causeOrReason === 'string') {
  121. reason = causeOrReason
  122. }
  123. // Error.
  124. else if (!options.cause && causeOrReason) {
  125. legacyCause = true
  126. reason = causeOrReason.message
  127. options.cause = causeOrReason
  128. }
  129. if (!options.ruleId && !options.source && typeof origin === 'string') {
  130. const index = origin.indexOf(':')
  131. if (index === -1) {
  132. options.ruleId = origin
  133. } else {
  134. options.source = origin.slice(0, index)
  135. options.ruleId = origin.slice(index + 1)
  136. }
  137. }
  138. if (!options.place && options.ancestors && options.ancestors) {
  139. const parent = options.ancestors[options.ancestors.length - 1]
  140. if (parent) {
  141. options.place = parent.position
  142. }
  143. }
  144. const start =
  145. options.place && 'start' in options.place
  146. ? options.place.start
  147. : options.place
  148. /* eslint-disable no-unused-expressions */
  149. /**
  150. * Stack of ancestor nodes surrounding the message.
  151. *
  152. * @type {Array<Node> | undefined}
  153. */
  154. this.ancestors = options.ancestors || undefined
  155. /**
  156. * Original error cause of the message.
  157. *
  158. * @type {Error | undefined}
  159. */
  160. this.cause = options.cause || undefined
  161. /**
  162. * Starting column of message.
  163. *
  164. * @type {number | undefined}
  165. */
  166. this.column = start ? start.column : undefined
  167. /**
  168. * State of problem.
  169. *
  170. * * `true` — error, file not usable
  171. * * `false` — warning, change may be needed
  172. * * `undefined` — change likely not needed
  173. *
  174. * @type {boolean | null | undefined}
  175. */
  176. this.fatal = undefined
  177. /**
  178. * Path of a file (used throughout the `VFile` ecosystem).
  179. *
  180. * @type {string | undefined}
  181. */
  182. this.file
  183. // Field from `Error`.
  184. /**
  185. * Reason for message.
  186. *
  187. * @type {string}
  188. */
  189. this.message = reason
  190. /**
  191. * Starting line of error.
  192. *
  193. * @type {number | undefined}
  194. */
  195. this.line = start ? start.line : undefined
  196. // Field from `Error`.
  197. /**
  198. * Serialized positional info of message.
  199. *
  200. * On normal errors, this would be something like `ParseError`, buit in
  201. * `VFile` messages we use this space to show where an error happened.
  202. */
  203. this.name = stringifyPosition(options.place) || '1:1'
  204. /**
  205. * Place of message.
  206. *
  207. * @type {Point | Position | undefined}
  208. */
  209. this.place = options.place || undefined
  210. /**
  211. * Reason for message, should use markdown.
  212. *
  213. * @type {string}
  214. */
  215. this.reason = this.message
  216. /**
  217. * Category of message (example: `'my-rule'`).
  218. *
  219. * @type {string | undefined}
  220. */
  221. this.ruleId = options.ruleId || undefined
  222. /**
  223. * Namespace of message (example: `'my-package'`).
  224. *
  225. * @type {string | undefined}
  226. */
  227. this.source = options.source || undefined
  228. // Field from `Error`.
  229. /**
  230. * Stack of message.
  231. *
  232. * This is used by normal errors to show where something happened in
  233. * programming code, irrelevant for `VFile` messages,
  234. *
  235. * @type {string}
  236. */
  237. this.stack =
  238. legacyCause && options.cause && typeof options.cause.stack === 'string'
  239. ? options.cause.stack
  240. : ''
  241. // The following fields are “well known”.
  242. // Not standard.
  243. // Feel free to add other non-standard fields to your messages.
  244. /**
  245. * Specify the source value that’s being reported, which is deemed
  246. * incorrect.
  247. *
  248. * @type {string | undefined}
  249. */
  250. this.actual
  251. /**
  252. * Suggest acceptable values that can be used instead of `actual`.
  253. *
  254. * @type {Array<string> | undefined}
  255. */
  256. this.expected
  257. /**
  258. * Long form description of the message (you should use markdown).
  259. *
  260. * @type {string | undefined}
  261. */
  262. this.note
  263. /**
  264. * Link to docs for the message.
  265. *
  266. * > 👉 **Note**: this must be an absolute URL that can be passed as `x`
  267. * > to `new URL(x)`.
  268. *
  269. * @type {string | undefined}
  270. */
  271. this.url
  272. /* eslint-enable no-unused-expressions */
  273. }
  274. }
  275. VFileMessage.prototype.file = ''
  276. VFileMessage.prototype.name = ''
  277. VFileMessage.prototype.reason = ''
  278. VFileMessage.prototype.message = ''
  279. VFileMessage.prototype.stack = ''
  280. VFileMessage.prototype.column = undefined
  281. VFileMessage.prototype.line = undefined
  282. VFileMessage.prototype.ancestors = undefined
  283. VFileMessage.prototype.cause = undefined
  284. VFileMessage.prototype.fatal = undefined
  285. VFileMessage.prototype.place = undefined
  286. VFileMessage.prototype.ruleId = undefined
  287. VFileMessage.prototype.source = undefined