All files / src/compiler/utils compile_diagnostic.js

96.26% Statements 103/107
100% Branches 14/14
100% Functions 6/6
96.22% Lines 102/106

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 1072x 2x 2x 2x 2x 2x 2x 2x 9681x 9681x 9681x 2x 2x 2x 2x 2x 2x 1779x 1779x 1779x 1779x 1779x 1779x 1779x 1779x 7902x 7902x 7902x 1779x 1779x 1779x 1779x 6123x 1779x 1779x 1779x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1784x 1784x 1784x 1784x 1784x 1784x 1784x 1784x 1784x 1784x 1784x 1784x 1781x 1781x 1784x 1784x 1779x 1779x 1779x 1779x 1779x 1779x 1779x 1784x 1784x 1784x 1x 1x 1x 1x 1x 1x     1x 1x 1x     1x 1x 1x 1784x 1784x 939x 939x 939x 939x 939x 939x 939x 939x 939x 939x 1784x  
/** @import { Location } from 'locate-character' */
import * as state from '../state.js';
 
const regex_tabs = /^\t+/;
 
/**
 * @param {string} str
 */
function tabs_to_spaces(str) {
	return str.replace(regex_tabs, (match) => match.split('\t').join('  '));
}
 
/**
 * @param {string} source
 * @param {number} line
 * @param {number} column
 */
function get_code_frame(source, line, column) {
	const lines = source.split('\n');
	const frame_start = Math.max(0, line - 2);
	const frame_end = Math.min(line + 3, lines.length);
	const digits = String(frame_end + 1).length;
	return lines
		.slice(frame_start, frame_end)
		.map((str, i) => {
			const is_error_line = frame_start + i === line;
			const line_num = String(i + frame_start + 1).padStart(digits, ' ');
			if (is_error_line) {
				const indicator =
					' '.repeat(digits + 2 + tabs_to_spaces(str.slice(0, column)).length) + '^';
				return `${line_num}: ${tabs_to_spaces(str)}\n${indicator}`;
			}
			return `${line_num}: ${tabs_to_spaces(str)}`;
		})
		.join('\n');
}
 
/**
 * @typedef {{
 * 	code: string;
 * 	message: string;
 * 	filename?: string;
 * 	start?: Location;
 * 	end?: Location;
 * 	position?: [number, number];
 * 	frame?: string;
 * }} ICompileDiagnostic
 */
 
/** @implements {ICompileDiagnostic} */
export class CompileDiagnostic extends Error {
	name = 'CompileDiagnostic';
 
	/**
	 * @param {string} code
	 * @param {string} message
	 * @param {[number, number] | undefined} position
	 */
	constructor(code, message, position) {
		super(message);
		this.code = code;
 
		if (state.filename) {
			this.filename = state.filename;
		}
 
		if (position) {
			this.position = position;
			this.start = state.locator(position[0]);
			this.end = state.locator(position[1]);
			if (this.start && this.end) {
				this.frame = get_code_frame(state.source, this.start.line - 1, this.end.column);
			}
		}
	}
 
	toString() {
		let out = `${this.code}: ${this.message}`;
 
		if (this.filename) {
			out += `\n${this.filename}`;
 
			if (this.start) {
				out += `:${this.start.line}:${this.start.column}`;
			}
		}
 
		if (this.frame) {
			out += `\n${this.frame}`;
		}
 
		return out;
	}
 
	toJSON() {
		return {
			code: this.code,
			message: this.message,
			filename: this.filename,
			start: this.start,
			end: this.end,
			position: this.position,
			frame: this.frame
		};
	}
}