import * as lsp from 'vscode-languageserver' import * as os from 'os' import { ts } from './typescript' import { Logger } from './logger' type PrintOptions = { colors?: boolean noThrow?: boolean } export abstract class Diagnostic { constructor( public readonly message: string, public readonly file: ts.SourceFile, public readonly position: { start: number; end: number }, public readonly code: ts.SyntaxKind, public readonly source: 'fluent' | 'ts', public readonly level: Diagnostic.Level = Diagnostic.Level.Error ) {} public abstract asTypeScriptDiagnostic(): ts.ts.Diagnostic public asLspDiagnostic(): lsp.Diagnostic { const start = this.file.compilerNode.getLineAndCharacterOfPosition(this.position.start) const end = this.file.compilerNode.getLineAndCharacterOfPosition(this.position.end) return { message: this.message, range: { start, end }, severity: Diagnostic.Level.toSeverity(this.level), code: this.code, source: this.source, } } public getFormattedText(colors = true) { const host: ts.ts.FormatDiagnosticsHost = { getCurrentDirectory: () => this.file.getProject().getFileSystem().getCurrentDirectory(), getCanonicalFileName: (fileName) => fileName, getNewLine: () => os.EOL, } return colors ? ts.ts.formatDiagnosticsWithColorAndContext([this.asTypeScriptDiagnostic()], host) : ts.ts.formatDiagnostic(this.asTypeScriptDiagnostic(), host) } public print(logger: Logger, options: PrintOptions = { noThrow: false, colors: true }): void { if (this.level === Diagnostic.Level.Error) { const errorMessage = this.getFormattedText(options.colors) if (options.noThrow) { logger.error(errorMessage) } else { logger.error('Diagnostic errors found in source files, fix errors and re-run transform/build') throw new Error(errorMessage) } } else if (this.level === Diagnostic.Level.Warn) { logger.warn(this.getFormattedText(options.colors)) } } } export namespace Diagnostic { export enum Level { Error = 1, Warn = 2, Info = 3, Hint = 4, } export namespace Level { export function fromCategory(category: ts.DiagnosticCategory): Level { return { [ts.DiagnosticCategory.Error]: Diagnostic.Level.Error, [ts.DiagnosticCategory.Warning]: Diagnostic.Level.Warn, [ts.DiagnosticCategory.Message]: Diagnostic.Level.Info, [ts.DiagnosticCategory.Suggestion]: Diagnostic.Level.Hint, }[category] } export function toCategory(level: Level): ts.DiagnosticCategory { return { [Diagnostic.Level.Error]: ts.DiagnosticCategory.Error, [Diagnostic.Level.Warn]: ts.DiagnosticCategory.Warning, [Diagnostic.Level.Info]: ts.DiagnosticCategory.Message, [Diagnostic.Level.Hint]: ts.DiagnosticCategory.Suggestion, }[level] } export function fromSeverity(severity: lsp.DiagnosticSeverity): Level { return { [lsp.DiagnosticSeverity.Error]: Diagnostic.Level.Error, [lsp.DiagnosticSeverity.Warning]: Diagnostic.Level.Warn, [lsp.DiagnosticSeverity.Information]: Diagnostic.Level.Info, [lsp.DiagnosticSeverity.Hint]: Diagnostic.Level.Hint, }[severity] } export function toSeverity(level: Level): lsp.DiagnosticSeverity { return { [Diagnostic.Level.Error]: lsp.DiagnosticSeverity.Error, [Diagnostic.Level.Warn]: lsp.DiagnosticSeverity.Warning, [Diagnostic.Level.Info]: lsp.DiagnosticSeverity.Information, [Diagnostic.Level.Hint]: lsp.DiagnosticSeverity.Hint, }[level] } } }