/** * @typedef {import('unist').Node} Node * @typedef {import('unist').Point} Point * @typedef {import('unist').Position} Position */ /** * @typedef {object & {type: string, position?: Position | undefined}} NodeLike * * @typedef Options * Configuration. * @property {Array | null | undefined} [ancestors] * Stack of (inclusive) ancestor nodes surrounding the message (optional). * @property {Error | null | undefined} [cause] * Original error cause of the message (optional). * @property {Point | Position | null | undefined} [place] * Place of message (optional). * @property {string | null | undefined} [ruleId] * Category of message (optional, example: `'my-rule'`). * @property {string | null | undefined} [source] * Namespace of who sent the message (optional, example: `'my-package'`). */ import {stringifyPosition} from 'unist-util-stringify-position' /** * Message. */ export class VFileMessage extends Error { /** * Create a message for `reason`. * * > 🪦 **Note**: also has obsolete signatures. * * @overload * @param {string} reason * @param {Options | null | undefined} [options] * @returns * * @overload * @param {string} reason * @param {Node | NodeLike | null | undefined} parent * @param {string | null | undefined} [origin] * @returns * * @overload * @param {string} reason * @param {Point | Position | null | undefined} place * @param {string | null | undefined} [origin] * @returns * * @overload * @param {string} reason * @param {string | null | undefined} [origin] * @returns * * @overload * @param {Error | VFileMessage} cause * @param {Node | NodeLike | null | undefined} parent * @param {string | null | undefined} [origin] * @returns * * @overload * @param {Error | VFileMessage} cause * @param {Point | Position | null | undefined} place * @param {string | null | undefined} [origin] * @returns * * @overload * @param {Error | VFileMessage} cause * @param {string | null | undefined} [origin] * @returns * * @param {Error | VFileMessage | string} causeOrReason * Reason for message, should use markdown. * @param {Node | NodeLike | Options | Point | Position | string | null | undefined} [optionsOrParentOrPlace] * Configuration (optional). * @param {string | null | undefined} [origin] * Place in code where the message originates (example: * `'my-package:my-rule'` or `'my-rule'`). * @returns * Instance of `VFileMessage`. */ // eslint-disable-next-line complexity constructor(causeOrReason, optionsOrParentOrPlace, origin) { super() if (typeof optionsOrParentOrPlace === 'string') { origin = optionsOrParentOrPlace optionsOrParentOrPlace = undefined } /** @type {string} */ let reason = '' /** @type {Options} */ let options = {} let legacyCause = false if (optionsOrParentOrPlace) { // Point. if ( 'line' in optionsOrParentOrPlace && 'column' in optionsOrParentOrPlace ) { options = {place: optionsOrParentOrPlace} } // Position. else if ( 'start' in optionsOrParentOrPlace && 'end' in optionsOrParentOrPlace ) { options = {place: optionsOrParentOrPlace} } // Node. else if ('type' in optionsOrParentOrPlace) { options = { ancestors: [optionsOrParentOrPlace], place: optionsOrParentOrPlace.position } } // Options. else { options = {...optionsOrParentOrPlace} } } if (typeof causeOrReason === 'string') { reason = causeOrReason } // Error. else if (!options.cause && causeOrReason) { legacyCause = true reason = causeOrReason.message options.cause = causeOrReason } if (!options.ruleId && !options.source && typeof origin === 'string') { const index = origin.indexOf(':') if (index === -1) { options.ruleId = origin } else { options.source = origin.slice(0, index) options.ruleId = origin.slice(index + 1) } } if (!options.place && options.ancestors && options.ancestors) { const parent = options.ancestors[options.ancestors.length - 1] if (parent) { options.place = parent.position } } const start = options.place && 'start' in options.place ? options.place.start : options.place /* eslint-disable no-unused-expressions */ /** * Stack of ancestor nodes surrounding the message. * * @type {Array | undefined} */ this.ancestors = options.ancestors || undefined /** * Original error cause of the message. * * @type {Error | undefined} */ this.cause = options.cause || undefined /** * Starting column of message. * * @type {number | undefined} */ this.column = start ? start.column : undefined /** * State of problem. * * * `true` — error, file not usable * * `false` — warning, change may be needed * * `undefined` — change likely not needed * * @type {boolean | null | undefined} */ this.fatal = undefined /** * Path of a file (used throughout the `VFile` ecosystem). * * @type {string | undefined} */ this.file // Field from `Error`. /** * Reason for message. * * @type {string} */ this.message = reason /** * Starting line of error. * * @type {number | undefined} */ this.line = start ? start.line : undefined // Field from `Error`. /** * Serialized positional info of message. * * 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(options.place) || '1:1' /** * Place of message. * * @type {Point | Position | undefined} */ this.place = options.place || undefined /** * Reason for message, should use markdown. * * @type {string} */ this.reason = this.message /** * Category of message (example: `'my-rule'`). * * @type {string | undefined} */ this.ruleId = options.ruleId || undefined /** * Namespace of message (example: `'my-package'`). * * @type {string | undefined} */ this.source = options.source || undefined // Field from `Error`. /** * 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 = legacyCause && options.cause && typeof options.cause.stack === 'string' ? options.cause.stack : '' // 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 | undefined} */ this.actual /** * Suggest acceptable values that can be used instead of `actual`. * * @type {Array | undefined} */ this.expected /** * Long form description of the message (you should use markdown). * * @type {string | undefined} */ this.note /** * 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 | undefined} */ this.url /* eslint-enable no-unused-expressions */ } } VFileMessage.prototype.file = '' VFileMessage.prototype.name = '' VFileMessage.prototype.reason = '' VFileMessage.prototype.message = '' VFileMessage.prototype.stack = '' VFileMessage.prototype.column = undefined VFileMessage.prototype.line = undefined VFileMessage.prototype.ancestors = undefined VFileMessage.prototype.cause = undefined VFileMessage.prototype.fatal = undefined VFileMessage.prototype.place = undefined VFileMessage.prototype.ruleId = undefined VFileMessage.prototype.source = undefined