// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. // Diagnostic provides an abstraction for advice/errors received from a // compiler, which is strongly influenced by the format of TypeScript // diagnostics. /** The log category for a diagnostic message */ export enum DiagnosticCategory { Log = 0, Debug = 1, Info = 2, Error = 3, Warning = 4, Suggestion = 5 } export interface DiagnosticMessageChain { message: string; category: DiagnosticCategory; code: number; next?: DiagnosticMessageChain[]; } export interface DiagnosticItem { /** A string message summarizing the diagnostic. */ message: string; /** An ordered array of further diagnostics. */ messageChain?: DiagnosticMessageChain; /** Information related to the diagnostic. This is present when there is a * suggestion or other additional diagnostic information */ relatedInformation?: DiagnosticItem[]; /** The text of the source line related to the diagnostic */ sourceLine?: string; /** The line number that is related to the diagnostic */ lineNumber?: number; /** The name of the script resource related to the diagnostic */ scriptResourceName?: string; /** The start position related to the diagnostic */ startPosition?: number; /** The end position related to the diagnostic */ endPosition?: number; /** The category of the diagnostic */ category: DiagnosticCategory; /** A number identifier */ code: number; /** The the start column of the sourceLine related to the diagnostic */ startColumn?: number; /** The end column of the sourceLine related to the diagnostic */ endColumn?: number; } export interface Diagnostic { /** An array of diagnostic items. */ items: DiagnosticItem[]; } interface SourceInformation { sourceLine: string; lineNumber: number; scriptResourceName: string; startColumn: number; endColumn: number; } function fromDiagnosticCategory( category: ts.DiagnosticCategory ): DiagnosticCategory { switch (category) { case ts.DiagnosticCategory.Error: return DiagnosticCategory.Error; case ts.DiagnosticCategory.Message: return DiagnosticCategory.Info; case ts.DiagnosticCategory.Suggestion: return DiagnosticCategory.Suggestion; case ts.DiagnosticCategory.Warning: return DiagnosticCategory.Warning; default: throw new Error( `Unexpected DiagnosticCategory: "${category}"/"${ ts.DiagnosticCategory[category] }"` ); } } function getSourceInformation( sourceFile: ts.SourceFile, start: number, length: number ): SourceInformation { const scriptResourceName = sourceFile.fileName; const { line: lineNumber, character: startColumn } = sourceFile.getLineAndCharacterOfPosition(start); const endPosition = sourceFile.getLineAndCharacterOfPosition(start + length); const endColumn = lineNumber === endPosition.line ? endPosition.character : startColumn; const lastLineInFile = sourceFile.getLineAndCharacterOfPosition( sourceFile.text.length ).line; const lineStart = sourceFile.getPositionOfLineAndCharacter(lineNumber, 0); const lineEnd = lineNumber < lastLineInFile ? sourceFile.getPositionOfLineAndCharacter(lineNumber + 1, 0) : sourceFile.text.length; const sourceLine = sourceFile.text .slice(lineStart, lineEnd) .replace(/\s+$/g, "") .replace("\t", " "); return { sourceLine, lineNumber, scriptResourceName, startColumn, endColumn }; } /** Converts a TypeScript diagnostic message chain to a Deno one. */ function fromDiagnosticMessageChain( messageChain: ts.DiagnosticMessageChain[] | undefined ): DiagnosticMessageChain[] | undefined { if (!messageChain) { return undefined; } return messageChain.map(({ messageText: message, code, category, next }) => { return { message, code, category: fromDiagnosticCategory(category), next: fromDiagnosticMessageChain(next) }; }); } /** Parse out information from a TypeScript diagnostic structure. */ function parseDiagnostic( item: ts.Diagnostic | ts.DiagnosticRelatedInformation ): DiagnosticItem { const { messageText, category: sourceCategory, code, file, start: startPosition, length } = item; const sourceInfo = file && startPosition && length ? getSourceInformation(file, startPosition, length) : undefined; const endPosition = startPosition && length ? startPosition + length : undefined; const category = fromDiagnosticCategory(sourceCategory); let message: string; let messageChain: DiagnosticMessageChain | undefined; if (typeof messageText === "string") { message = messageText; } else { message = messageText.messageText; messageChain = fromDiagnosticMessageChain([messageText])![0]; } const base = { message, messageChain, code, category, startPosition, endPosition }; return sourceInfo ? { ...base, ...sourceInfo } : base; } /** Convert a diagnostic related information array into a Deno diagnostic * array. */ function parseRelatedInformation( relatedInformation: readonly ts.DiagnosticRelatedInformation[] ): DiagnosticItem[] { const result: DiagnosticItem[] = []; for (const item of relatedInformation) { result.push(parseDiagnostic(item)); } return result; } /** Convert TypeScript diagnostics to Deno diagnostics. */ export function fromTypeScriptDiagnostic( diagnostics: readonly ts.Diagnostic[] ): Diagnostic { const items: DiagnosticItem[] = []; for (const sourceDiagnostic of diagnostics) { const item: DiagnosticItem = parseDiagnostic(sourceDiagnostic); if (sourceDiagnostic.relatedInformation) { item.relatedInformation = parseRelatedInformation( sourceDiagnostic.relatedInformation ); } items.push(item); } return { items }; }