/** * @typedef {import('unist').Node} Node * @typedef {import('unist').Position} Position * @typedef {import('unist').Point} Point * @typedef {object & {type: string, position?: Position | undefined}} NodeLike */ import {stringifyPosition} from 'unist-util-stringify-position' /** * Message. */ export class VFileMessage extends Error { /** * Create a message for `reason` at `place` from `origin`. * * When an error is passed in as `reason`, the `stack` is copied. * * @param {string | Error | VFileMessage} reason * Reason for message, uses the stack and message of the error if given. * * > ๐Ÿ‘‰ **Note**: you should use markdown. * @param {Node | NodeLike | Position | Point | null | undefined} [place] * Place in file where the message occurred. * @param {string | null | undefined} [origin] * Place in code where the message originates (example: * `'my-package:my-rule'` or `'my-rule'`). * @returns * Instance of `VFileMessage`. */ // To do: next major: expose `undefined` everywhere instead of `null`. constructor(reason, place, origin) { /** @type {[string | null, string | null]} */ const parts = [null, null] /** @type {Position} */ let position = { // @ts-expect-error: we always follows the structure of `position`. start: {line: null, column: null}, // @ts-expect-error: " end: {line: null, column: null} } super() if (typeof place === 'string') { origin = place place = undefined } if (typeof origin === 'string') { const index = origin.indexOf(':') if (index === -1) { parts[1] = origin } else { parts[0] = origin.slice(0, index) parts[1] = origin.slice(index + 1) } } if (place) { // Node. if ('type' in place || 'position' in place) { if (place.position) { // To do: next major: deep clone. // @ts-expect-error: looks like a position. position = place.position } } // Position. else if ('start' in place || 'end' in place) { // @ts-expect-error: looks like a position. // To do: next major: deep clone. position = place } // Point. else if ('line' in place || 'column' in place) { // To do: next major: deep clone. position.start = place } } // Fields from `Error`. /** * Serialized positional info of error. * * On normal errors, this would be something like `ParseError`, buit in * `VFile` messages we use this space to show where an error happened. */ this.name = stringifyPosition(place) || '1:1' /** * Reason for message. * * @type {string} */ this.message = typeof reason === 'object' ? reason.message : reason /** * Stack of message. * * This is used by normal errors to show where something happened in * programming code, irrelevant for `VFile` messages, * * @type {string} */ this.stack = '' if (typeof reason === 'object' && reason.stack) { this.stack = reason.stack } /** * Reason for message. * * > ๐Ÿ‘‰ **Note**: you should use markdown. * * @type {string} */ this.reason = this.message /* eslint-disable no-unused-expressions */ /** * State of problem. * * * `true` โ€” marks associated file as no longer processable (error) * * `false` โ€” necessitates a (potential) change (warning) * * `null | undefined` โ€” for things that might not need changing (info) * * @type {boolean | null | undefined} */ this.fatal /** * Starting line of error. * * @type {number | null} */ this.line = position.start.line /** * Starting column of error. * * @type {number | null} */ this.column = position.start.column /** * Full unist position. * * @type {Position | null} */ this.position = position /** * Namespace of message (example: `'my-package'`). * * @type {string | null} */ this.source = parts[0] /** * Category of message (example: `'my-rule'`). * * @type {string | null} */ this.ruleId = parts[1] /** * Path of a file (used throughout the `VFile` ecosystem). * * @type {string | null} */ this.file // The following fields are โ€œwell knownโ€. // Not standard. // Feel free to add other non-standard fields to your messages. /** * Specify the source value thatโ€™s being reported, which is deemed * incorrect. * * @type {string | null} */ this.actual /** * Suggest acceptable values that can be used instead of `actual`. * * @type {Array | null} */ this.expected /** * Link to docs for the message. * * > ๐Ÿ‘‰ **Note**: this must be an absolute URL that can be passed as `x` * > to `new URL(x)`. * * @type {string | null} */ this.url /** * Long form description of the message (you should use markdown). * * @type {string | null} */ this.note /* eslint-enable no-unused-expressions */ } } VFileMessage.prototype.file = '' VFileMessage.prototype.name = '' VFileMessage.prototype.reason = '' VFileMessage.prototype.message = '' VFileMessage.prototype.stack = '' VFileMessage.prototype.fatal = null VFileMessage.prototype.column = null VFileMessage.prototype.line = null VFileMessage.prototype.source = null VFileMessage.prototype.ruleId = null VFileMessage.prototype.position = null