assemblyscript/src/diagnostics.ts

356 lines
9.8 KiB
TypeScript
Raw Normal View History

2018-03-17 23:41:48 +01:00
/**
2018-03-19 01:12:18 +01:00
* Shared diagnostic handling inherited by the parser and the compiler.
* @module diagnostics
* @preferred
*//***/
2018-03-17 23:41:48 +01:00
2017-12-24 03:19:47 +01:00
import {
Range
} from "./ast";
2017-09-28 13:08:25 +02:00
2017-12-24 03:19:47 +01:00
import {
DiagnosticCode,
diagnosticCodeToString
} from "./diagnosticMessages.generated";
import {
2019-06-05 23:15:39 +02:00
isLineBreak, CharCode
2018-03-19 01:12:18 +01:00
} from "./util";
2017-12-24 03:19:47 +01:00
export {
DiagnosticCode,
diagnosticCodeToString
} from "./diagnosticMessages.generated";
2017-09-28 13:08:25 +02:00
2018-03-17 23:41:48 +01:00
/** Indicates the category of a {@link DiagnosticMessage}. */
2017-09-28 13:08:25 +02:00
export enum DiagnosticCategory {
2018-03-17 23:41:48 +01:00
/** Informatory message. */
2017-09-28 13:08:25 +02:00
INFO,
2018-03-17 23:41:48 +01:00
/** Warning message. */
2017-09-28 13:08:25 +02:00
WARNING,
2018-03-17 23:41:48 +01:00
/** Error message. */
2017-09-28 13:08:25 +02:00
ERROR
}
2018-03-17 23:41:48 +01:00
/** Returns the string representation of the specified diagnostic category. */
2017-09-28 13:08:25 +02:00
export function diagnosticCategoryToString(category: DiagnosticCategory): string {
2017-11-17 14:33:51 +01:00
switch (category) {
case DiagnosticCategory.INFO: return "INFO";
case DiagnosticCategory.WARNING: return "WARNING";
case DiagnosticCategory.ERROR: return "ERROR";
2018-03-17 23:41:48 +01:00
default: {
assert(false);
return "";
}
2017-11-17 14:33:51 +01:00
}
2017-09-28 13:08:25 +02:00
}
2018-03-17 23:41:48 +01:00
/** ANSI escape sequence for blue foreground. */
2018-06-14 15:57:04 +02:00
export const COLOR_BLUE: string = "\u001b[96m";
2018-03-17 23:41:48 +01:00
/** ANSI escape sequence for yellow foreground. */
export const COLOR_YELLOW: string = "\u001b[93m";
/** ANSI escape sequence for red foreground. */
export const COLOR_RED: string = "\u001b[91m";
/** ANSI escape sequence to reset the foreground color. */
export const COLOR_RESET: string = "\u001b[0m";
2017-09-28 13:08:25 +02:00
2018-03-17 23:41:48 +01:00
/** Returns the ANSI escape sequence for the specified category. */
2017-09-28 13:08:25 +02:00
export function diagnosticCategoryToColor(category: DiagnosticCategory): string {
2017-11-17 14:33:51 +01:00
switch (category) {
2018-03-17 23:41:48 +01:00
case DiagnosticCategory.INFO: return COLOR_BLUE;
case DiagnosticCategory.WARNING: return COLOR_YELLOW;
case DiagnosticCategory.ERROR: return COLOR_RED;
default: {
assert(false);
return "";
}
2017-11-17 14:33:51 +01:00
}
2017-09-28 13:08:25 +02:00
}
2018-03-17 23:41:48 +01:00
/** Represents a diagnostic message. */
2017-09-28 13:08:25 +02:00
export class DiagnosticMessage {
2018-03-17 23:41:48 +01:00
/** Message code. */
2017-09-28 13:08:25 +02:00
code: i32;
2018-03-17 23:41:48 +01:00
/** Message category. */
2017-09-28 13:08:25 +02:00
category: DiagnosticCategory;
2018-03-17 23:41:48 +01:00
/** Message text. */
2017-09-28 13:08:25 +02:00
message: string;
2018-03-17 23:41:48 +01:00
/** Respective source range, if any. */
2017-09-28 13:08:25 +02:00
range: Range | null = null;
/** Related range, if any. */
relatedRange: Range | null = null;
2017-09-28 13:08:25 +02:00
2018-03-17 23:41:48 +01:00
/** Constructs a new diagnostic message. */
private constructor(code: i32, category: DiagnosticCategory, message: string) {
2017-09-28 13:08:25 +02:00
this.code = code;
this.category = category;
this.message = message;
}
2018-03-17 23:41:48 +01:00
/** Creates a new diagnostic message of the specified category. */
2018-02-25 00:13:39 +01:00
static create(
code: DiagnosticCode,
category: DiagnosticCategory,
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
2018-02-25 00:13:39 +01:00
): DiagnosticMessage {
var message = diagnosticCodeToString(code);
2018-02-25 00:13:39 +01:00
if (arg0 != null) message = message.replace("{0}", arg0);
if (arg1 != null) message = message.replace("{1}", arg1);
if (arg2 != null) message = message.replace("{2}", arg2);
2017-09-28 13:08:25 +02:00
return new DiagnosticMessage(code, category, message);
}
2018-03-17 23:41:48 +01:00
/** Creates a new informatory diagnostic message. */
2018-02-25 00:13:39 +01:00
static createInfo(
code: DiagnosticCode,
arg0: string | null = null,
arg1: string | null = null
): DiagnosticMessage {
2017-09-28 13:08:25 +02:00
return DiagnosticMessage.create(code, DiagnosticCategory.INFO, arg0, arg1);
}
2018-03-17 23:41:48 +01:00
/** Creates a new warning diagnostic message. */
2018-02-25 00:13:39 +01:00
static createWarning(
code: DiagnosticCode,
arg0: string | null = null,
arg1: string | null = null
): DiagnosticMessage {
2017-09-28 13:08:25 +02:00
return DiagnosticMessage.create(code, DiagnosticCategory.WARNING, arg0, arg1);
}
2018-03-17 23:41:48 +01:00
/** Creates a new error diagnostic message. */
2018-02-25 00:13:39 +01:00
static createError(
code: DiagnosticCode,
arg0: string | null = null,
arg1: string | null = null
): DiagnosticMessage {
2017-09-28 13:08:25 +02:00
return DiagnosticMessage.create(code, DiagnosticCategory.ERROR, arg0, arg1);
}
2018-03-17 23:41:48 +01:00
/** Adds a source range to this message. */
2017-09-28 13:08:25 +02:00
withRange(range: Range): this {
this.range = range;
return this;
}
2017-12-14 11:55:35 +01:00
/** Adds a related source range to this message. */
withRelatedRange(range: Range): this {
this.relatedRange = range;
return this;
}
2018-03-17 23:41:48 +01:00
/** Converts this message to a string. */
2017-12-14 11:55:35 +01:00
toString(): string {
2018-02-25 00:13:39 +01:00
if (this.range) {
return (
diagnosticCategoryToString(this.category) +
" " +
this.code.toString(10) +
": \"" +
this.message +
"\" in " +
this.range.source.normalizedPath +
":" +
this.range.line.toString(10) +
":" +
this.range.column.toString(10)
2018-02-25 00:13:39 +01:00
);
}
return (
diagnosticCategoryToString(this.category) +
" " +
this.code.toString(10) +
": " +
this.message
);
2017-12-14 11:55:35 +01:00
}
2017-09-28 13:08:25 +02:00
}
2018-03-17 23:41:48 +01:00
/** Formats a diagnostic message, optionally with terminal colors and source context. */
2018-02-25 00:13:39 +01:00
export function formatDiagnosticMessage(
message: DiagnosticMessage,
useColors: bool = false,
showContext: bool = false
): string {
2017-09-28 13:08:25 +02:00
// general information
var sb: string[] = [];
2017-09-28 13:08:25 +02:00
if (useColors) sb.push(diagnosticCategoryToColor(message.category));
sb.push(diagnosticCategoryToString(message.category));
2018-03-17 23:41:48 +01:00
if (useColors) sb.push(COLOR_RESET);
2018-01-28 06:18:27 +01:00
sb.push(message.code < 1000 ? " AS" : " TS");
sb.push(message.code.toString(10));
2017-09-28 13:08:25 +02:00
sb.push(": ");
sb.push(message.message);
2018-03-17 23:41:48 +01:00
// include range information if available
2017-09-28 13:08:25 +02:00
if (message.range) {
2018-03-17 23:41:48 +01:00
// include context information if requested
let range = message.range;
2017-09-28 13:08:25 +02:00
if (showContext) {
sb.push("\n");
sb.push(formatDiagnosticContext(range, useColors));
2017-09-28 13:08:25 +02:00
}
sb.push("\n");
sb.push(" in ");
sb.push(range.source.normalizedPath);
2017-09-28 13:08:25 +02:00
sb.push("(");
sb.push(range.line.toString(10));
2017-09-28 13:08:25 +02:00
sb.push(",");
sb.push(range.column.toString(10));
2017-09-28 13:08:25 +02:00
sb.push(")");
let relatedRange = message.relatedRange;
if (relatedRange) {
if (showContext) {
sb.push("\n");
sb.push(formatDiagnosticContext(relatedRange, useColors));
}
sb.push("\n");
sb.push(" in ");
sb.push(range.source.normalizedPath);
sb.push("(");
sb.push(range.line.toString(10));
sb.push(",");
sb.push(range.column.toString(10));
sb.push(")");
}
2017-09-28 13:08:25 +02:00
}
return sb.join("");
}
2018-03-17 23:41:48 +01:00
/** Formats the diagnostic context for the specified range, optionally with terminal colors. */
2017-09-28 13:08:25 +02:00
export function formatDiagnosticContext(range: Range, useColors: bool = false): string {
var text = range.source.text;
var len = text.length;
var start = range.start;
var end = range.end;
while (start > 0 && !isLineBreak(text.charCodeAt(start - 1))) start--;
while (end < len && !isLineBreak(text.charCodeAt(end))) end++;
var sb: string[] = [
"\n ",
text.substring(start, end),
"\n "
];
2017-09-28 13:08:25 +02:00
while (start < range.start) {
sb.push(" ");
start++;
}
2018-03-17 23:41:48 +01:00
if (useColors) sb.push(COLOR_RED);
2017-09-28 13:08:25 +02:00
if (range.start == range.end) {
sb.push("^");
2018-02-25 00:13:39 +01:00
} else {
2019-06-05 23:15:39 +02:00
while (start++ < range.end) {
if (isLineBreak(text.charCodeAt(start))) {
sb.push(start == range.start + 1 ? "^" : "~");
break;
}
sb.push("~");
}
2018-02-25 00:13:39 +01:00
}
2018-03-17 23:41:48 +01:00
if (useColors) sb.push(COLOR_RESET);
2017-09-28 13:08:25 +02:00
return sb.join("");
}
2018-03-17 23:41:48 +01:00
/** Base class of all diagnostic emitters. */
2017-09-28 13:08:25 +02:00
export abstract class DiagnosticEmitter {
2018-03-17 23:41:48 +01:00
/** Diagnostic messages emitted so far. */
2017-09-28 13:08:25 +02:00
diagnostics: DiagnosticMessage[];
2018-03-17 23:41:48 +01:00
/** Initializes this diagnostic emitter. */
protected constructor(diagnostics: DiagnosticMessage[] | null = null) {
2017-09-28 13:08:25 +02:00
this.diagnostics = diagnostics ? <DiagnosticMessage[]>diagnostics : new Array();
}
2018-03-17 23:41:48 +01:00
/** Emits a diagnostic message of the specified category. */
2018-02-25 00:13:39 +01:00
emitDiagnostic(
code: DiagnosticCode,
category: DiagnosticCategory,
range: Range,
relatedRange: Range | null,
2018-02-25 00:13:39 +01:00
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
2018-03-13 14:03:57 +01:00
): void {
var message = DiagnosticMessage.create(code, category, arg0, arg1, arg2).withRange(range);
if (relatedRange) message.relatedRange = relatedRange;
2017-09-28 13:08:25 +02:00
this.diagnostics.push(message);
console.log(formatDiagnosticMessage(message, true, true) + "\n"); // temporary
console.log(<string>new Error("stack").stack);
2017-09-28 13:08:25 +02:00
}
2018-03-17 23:41:48 +01:00
/** Emits an informatory diagnostic message. */
info(
code: DiagnosticCode,
range: Range,
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
): void {
this.emitDiagnostic(code, DiagnosticCategory.INFO, range, null, arg0, arg1, arg2);
}
/** Emits an informatory diagnostic message with a related range. */
infoRelated(
code: DiagnosticCode,
range: Range,
relatedRange: Range,
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
): void {
this.emitDiagnostic(code, DiagnosticCategory.INFO, range, relatedRange, arg0, arg1, arg2);
2017-09-28 13:08:25 +02:00
}
2018-03-17 23:41:48 +01:00
/** Emits a warning diagnostic message. */
warning(
code: DiagnosticCode,
range: Range,
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
): void {
this.emitDiagnostic(code, DiagnosticCategory.WARNING, range, null, arg0, arg1, arg2);
}
/** Emits a warning diagnostic message with a related range. */
warningRelated(
code: DiagnosticCode,
range: Range,
relatedRange: Range,
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
): void {
this.emitDiagnostic(code, DiagnosticCategory.WARNING, range, relatedRange, arg0, arg1, arg2);
2017-09-28 13:08:25 +02:00
}
2018-03-17 23:41:48 +01:00
/** Emits an error diagnostic message. */
error(
code: DiagnosticCode,
range: Range,
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
): void {
this.emitDiagnostic(code, DiagnosticCategory.ERROR, range, null, arg0, arg1, arg2);
}
/** Emits an error diagnostic message with a related range. */
errorRelated(
code: DiagnosticCode,
range: Range,
relatedRange: Range,
arg0: string | null = null,
arg1: string | null = null,
arg2: string | null = null
): void {
this.emitDiagnostic(code, DiagnosticCategory.ERROR, range, relatedRange, arg0, arg1, arg2);
2017-09-28 13:08:25 +02:00
}
}