319 lines
8.1 KiB
Plaintext
319 lines
8.1 KiB
Plaintext
|
/**
|
|||
|
* @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<Node> | 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<Node> | 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<string> | 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
|