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 11204x 11204x 11204x 2x 2x 2x 2x 2x 2x 2069x 2069x 2069x 2069x 2069x 2069x 2069x 2069x 9135x 9135x 9135x 2069x 2069x 2069x 2069x 7066x 2069x 2069x 2069x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2074x 2074x 2074x 2074x 2074x 2074x 2074x 2074x 2074x 2074x 2074x 2074x 2071x 2071x 2074x 2074x 2069x 2069x 2069x 2069x 2069x 2069x 2069x 2074x 2074x 2074x 1x 1x 1x 1x 1x 1x     1x 1x 1x     1x 1x 1x 2074x 2074x 1249x 1249x 1249x 1249x 1249x 1249x 1249x 1249x 1249x 1249x 2074x  
/** @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
		};
	}
}