UNPKG

webidl2

Version:
147 lines (135 loc) 3.55 kB
/** * @param {string} text */ function lastLine(text) { const splitted = text.split("\n"); return splitted[splitted.length - 1]; } function appendIfExist(base, target) { let result = base; if (target) { result += ` ${target}`; } return result; } function contextAsText(node) { const hierarchy = [node]; while (node && node.parent) { const { parent } = node; hierarchy.unshift(parent); node = parent; } return hierarchy.map((n) => appendIfExist(n.type, n.name)).join(" -> "); } /** * @typedef {object} WebIDL2ErrorOptions * @property {"error" | "warning"} [level] * @property {Function} [autofix] * @property {string} [ruleName] * * @typedef {ReturnType<typeof error>} WebIDLErrorData * * @param {string} message error message * @param {*} position * @param {*} current * @param {*} message * @param {"Syntax" | "Validation"} kind error type * @param {WebIDL2ErrorOptions=} options */ function error( source, position, current, message, kind, { level = "error", autofix, ruleName } = {}, ) { /** * @param {number} count */ function sliceTokens(count) { return count > 0 ? source.slice(position, position + count) : source.slice(Math.max(position + count, 0), position); } /** * @param {import("./tokeniser.js").Token[]} inputs * @param {object} [options] * @param {boolean} [options.precedes] * @returns */ function tokensToText(inputs, { precedes } = {}) { const text = inputs.map((t) => t.trivia + t.value).join(""); const nextToken = source[position]; if (nextToken.type === "eof") { return text; } if (precedes) { return text + nextToken.trivia; } return text.slice(nextToken.trivia.length); } const maxTokens = 5; // arbitrary but works well enough const line = source[position].type !== "eof" ? source[position].line : source.length > 1 ? source[position - 1].line : 1; const precedingLastLine = lastLine( tokensToText(sliceTokens(-maxTokens), { precedes: true }), ); const subsequentTokens = sliceTokens(maxTokens); const subsequentText = tokensToText(subsequentTokens); const subsequentFirstLine = subsequentText.split("\n")[0]; const spaced = " ".repeat(precedingLastLine.length) + "^"; const sourceContext = precedingLastLine + subsequentFirstLine + "\n" + spaced; const contextType = kind === "Syntax" ? "since" : "inside"; const inSourceName = source.name ? ` in ${source.name}` : ""; const grammaticalContext = current && current.name ? `, ${contextType} \`${current.partial ? "partial " : ""}${contextAsText( current, )}\`` : ""; const context = `${kind} error at line ${line}${inSourceName}${grammaticalContext}:\n${sourceContext}`; return { message: `${context} ${message}`, bareMessage: message, context, line, sourceName: source.name, level, ruleName, autofix, input: subsequentText, tokens: subsequentTokens, }; } /** * @param {string} message error message */ export function syntaxError(source, position, current, message) { return error(source, position, current, message, "Syntax"); } /** * @param {string} message error message * @param {WebIDL2ErrorOptions} [options] */ export function validationError( token, current, ruleName, message, options = {}, ) { options.ruleName = ruleName; return error( current.source, token.index, current, message, "Validation", options, ); }