index.js 8.0 KB

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