All files / src/compiler/utils compile_diagnostic.js

96.22% Statements 102/106
100% Branches 14/14
100% Functions 6/6
96.19% Lines 101/105

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 1062x 2x 2x 2x 2x 2x 2x 2x 9397x 9397x 9397x 2x 2x 2x 2x 2x 2x 1732x 1732x 1732x 1732x 1732x 1732x 1732x 1732x 7665x 7665x 7665x 1732x 1732x 1732x 1732x 5933x 1732x 1732x 1732x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1737x 1737x 1737x 1737x 1737x 1737x 1737x 1737x 1737x 1737x 1737x 1737x 1734x 1734x 1737x 1737x 1732x 1732x 1732x 1732x 1732x 1732x 1732x 1737x 1737x 1737x 1x 1x 1x 1x 1x 1x     1x 1x 1x     1x 1x 1x 1737x 1737x 903x 903x 903x 903x 903x 903x 903x 903x 903x 903x 1737x  
/** @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
		};
	}
}