mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-04-26 07:22:21 +00:00
356 lines
9.8 KiB
TypeScript
356 lines
9.8 KiB
TypeScript
/**
|
|
* Shared diagnostic handling inherited by the parser and the compiler.
|
|
* @module diagnostics
|
|
* @preferred
|
|
*//***/
|
|
|
|
import {
|
|
Range
|
|
} from "./ast";
|
|
|
|
import {
|
|
DiagnosticCode,
|
|
diagnosticCodeToString
|
|
} from "./diagnosticMessages.generated";
|
|
|
|
import {
|
|
isLineBreak, CharCode
|
|
} from "./util";
|
|
|
|
export {
|
|
DiagnosticCode,
|
|
diagnosticCodeToString
|
|
} from "./diagnosticMessages.generated";
|
|
|
|
/** Indicates the category of a {@link DiagnosticMessage}. */
|
|
export enum DiagnosticCategory {
|
|
/** Informatory message. */
|
|
INFO,
|
|
/** Warning message. */
|
|
WARNING,
|
|
/** Error message. */
|
|
ERROR
|
|
}
|
|
|
|
/** Returns the string representation of the specified diagnostic category. */
|
|
export function diagnosticCategoryToString(category: DiagnosticCategory): string {
|
|
switch (category) {
|
|
case DiagnosticCategory.INFO: return "INFO";
|
|
case DiagnosticCategory.WARNING: return "WARNING";
|
|
case DiagnosticCategory.ERROR: return "ERROR";
|
|
default: {
|
|
assert(false);
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
/** ANSI escape sequence for blue foreground. */
|
|
export const COLOR_BLUE: string = "\u001b[96m";
|
|
/** 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";
|
|
|
|
/** Returns the ANSI escape sequence for the specified category. */
|
|
export function diagnosticCategoryToColor(category: DiagnosticCategory): string {
|
|
switch (category) {
|
|
case DiagnosticCategory.INFO: return COLOR_BLUE;
|
|
case DiagnosticCategory.WARNING: return COLOR_YELLOW;
|
|
case DiagnosticCategory.ERROR: return COLOR_RED;
|
|
default: {
|
|
assert(false);
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Represents a diagnostic message. */
|
|
export class DiagnosticMessage {
|
|
|
|
/** Message code. */
|
|
code: i32;
|
|
/** Message category. */
|
|
category: DiagnosticCategory;
|
|
/** Message text. */
|
|
message: string;
|
|
/** Respective source range, if any. */
|
|
range: Range | null = null;
|
|
/** Related range, if any. */
|
|
relatedRange: Range | null = null;
|
|
|
|
/** Constructs a new diagnostic message. */
|
|
private constructor(code: i32, category: DiagnosticCategory, message: string) {
|
|
this.code = code;
|
|
this.category = category;
|
|
this.message = message;
|
|
}
|
|
|
|
/** Creates a new diagnostic message of the specified category. */
|
|
static create(
|
|
code: DiagnosticCode,
|
|
category: DiagnosticCategory,
|
|
arg0: string | null = null,
|
|
arg1: string | null = null,
|
|
arg2: string | null = null
|
|
): DiagnosticMessage {
|
|
var message = diagnosticCodeToString(code);
|
|
if (arg0 != null) message = message.replace("{0}", arg0);
|
|
if (arg1 != null) message = message.replace("{1}", arg1);
|
|
if (arg2 != null) message = message.replace("{2}", arg2);
|
|
return new DiagnosticMessage(code, category, message);
|
|
}
|
|
|
|
/** Creates a new informatory diagnostic message. */
|
|
static createInfo(
|
|
code: DiagnosticCode,
|
|
arg0: string | null = null,
|
|
arg1: string | null = null
|
|
): DiagnosticMessage {
|
|
return DiagnosticMessage.create(code, DiagnosticCategory.INFO, arg0, arg1);
|
|
}
|
|
|
|
/** Creates a new warning diagnostic message. */
|
|
static createWarning(
|
|
code: DiagnosticCode,
|
|
arg0: string | null = null,
|
|
arg1: string | null = null
|
|
): DiagnosticMessage {
|
|
return DiagnosticMessage.create(code, DiagnosticCategory.WARNING, arg0, arg1);
|
|
}
|
|
|
|
/** Creates a new error diagnostic message. */
|
|
static createError(
|
|
code: DiagnosticCode,
|
|
arg0: string | null = null,
|
|
arg1: string | null = null
|
|
): DiagnosticMessage {
|
|
return DiagnosticMessage.create(code, DiagnosticCategory.ERROR, arg0, arg1);
|
|
}
|
|
|
|
/** Adds a source range to this message. */
|
|
withRange(range: Range): this {
|
|
this.range = range;
|
|
return this;
|
|
}
|
|
|
|
/** Adds a related source range to this message. */
|
|
withRelatedRange(range: Range): this {
|
|
this.relatedRange = range;
|
|
return this;
|
|
}
|
|
|
|
/** Converts this message to a string. */
|
|
toString(): string {
|
|
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)
|
|
);
|
|
}
|
|
return (
|
|
diagnosticCategoryToString(this.category) +
|
|
" " +
|
|
this.code.toString(10) +
|
|
": " +
|
|
this.message
|
|
);
|
|
}
|
|
}
|
|
|
|
/** Formats a diagnostic message, optionally with terminal colors and source context. */
|
|
export function formatDiagnosticMessage(
|
|
message: DiagnosticMessage,
|
|
useColors: bool = false,
|
|
showContext: bool = false
|
|
): string {
|
|
|
|
// general information
|
|
var sb: string[] = [];
|
|
if (useColors) sb.push(diagnosticCategoryToColor(message.category));
|
|
sb.push(diagnosticCategoryToString(message.category));
|
|
if (useColors) sb.push(COLOR_RESET);
|
|
sb.push(message.code < 1000 ? " AS" : " TS");
|
|
sb.push(message.code.toString(10));
|
|
sb.push(": ");
|
|
sb.push(message.message);
|
|
|
|
// include range information if available
|
|
if (message.range) {
|
|
|
|
// include context information if requested
|
|
let range = message.range;
|
|
if (showContext) {
|
|
sb.push("\n");
|
|
sb.push(formatDiagnosticContext(range, 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(")");
|
|
|
|
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(")");
|
|
}
|
|
}
|
|
return sb.join("");
|
|
}
|
|
|
|
/** Formats the diagnostic context for the specified range, optionally with terminal colors. */
|
|
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 "
|
|
];
|
|
while (start < range.start) {
|
|
sb.push(" ");
|
|
start++;
|
|
}
|
|
if (useColors) sb.push(COLOR_RED);
|
|
if (range.start == range.end) {
|
|
sb.push("^");
|
|
} else {
|
|
while (start++ < range.end) {
|
|
if (isLineBreak(text.charCodeAt(start))) {
|
|
sb.push(start == range.start + 1 ? "^" : "~");
|
|
break;
|
|
}
|
|
sb.push("~");
|
|
}
|
|
}
|
|
if (useColors) sb.push(COLOR_RESET);
|
|
return sb.join("");
|
|
}
|
|
|
|
/** Base class of all diagnostic emitters. */
|
|
export abstract class DiagnosticEmitter {
|
|
|
|
/** Diagnostic messages emitted so far. */
|
|
diagnostics: DiagnosticMessage[];
|
|
|
|
/** Initializes this diagnostic emitter. */
|
|
protected constructor(diagnostics: DiagnosticMessage[] | null = null) {
|
|
this.diagnostics = diagnostics ? <DiagnosticMessage[]>diagnostics : new Array();
|
|
}
|
|
|
|
/** Emits a diagnostic message of the specified category. */
|
|
emitDiagnostic(
|
|
code: DiagnosticCode,
|
|
category: DiagnosticCategory,
|
|
range: Range,
|
|
relatedRange: Range | null,
|
|
arg0: string | null = null,
|
|
arg1: string | null = null,
|
|
arg2: string | null = null
|
|
): void {
|
|
var message = DiagnosticMessage.create(code, category, arg0, arg1, arg2).withRange(range);
|
|
if (relatedRange) message.relatedRange = relatedRange;
|
|
this.diagnostics.push(message);
|
|
console.log(formatDiagnosticMessage(message, true, true) + "\n"); // temporary
|
|
console.log(<string>new Error("stack").stack);
|
|
}
|
|
|
|
/** 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);
|
|
}
|
|
|
|
/** 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);
|
|
}
|
|
|
|
/** 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);
|
|
}
|
|
}
|