Skip to content

Commit

Permalink
feat: return diagnostic messages
Browse files Browse the repository at this point in the history
  • Loading branch information
schoero committed Jun 23, 2024
1 parent a7e74f8 commit 931d97e
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 29 deletions.
12 changes: 8 additions & 4 deletions src/api/browser.entry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ts from "typescript";

import { reportCompilerDiagnostics } from "unwritten:compiler/shared";
import { convertDiagnostics, reportCompilerDiagnostics } from "unwritten:compiler/shared";
import { createConfig } from "unwritten:config/config";
import { interpret } from "unwritten:interpreter/ast/symbol";
import { createContext as createInterpreterContext } from "unwritten:interpreter:utils/context";
Expand All @@ -15,10 +15,10 @@ import { createContext as createDefaultContext } from "unwritten:utils:context";
import type { Program } from "typescript";

import type { BrowserAPIOptions } from "unwritten:type-definitions/options";
import type { RenderOutput } from "unwritten:type-definitions/renderer";
import type { UnwrittenOutput } from "unwritten:type-definitions/unwritten";


export async function unwritten(program: Program, options?: BrowserAPIOptions): Promise<RenderOutput> {
export async function unwritten(program: Program, options?: BrowserAPIOptions): Promise<UnwrittenOutput> {

// logger
const { logger } = options?.silent ? { logger: undefined } : await import("unwritten:platform/logger/browser.js");
Expand All @@ -38,6 +38,7 @@ export async function unwritten(program: Program, options?: BrowserAPIOptions):
// compile
const checker = program.getTypeChecker();
const diagnostics = program.getSemanticDiagnostics();
const diagnosticMessages = convertDiagnostics(defaultContext, diagnostics);
reportCompilerDiagnostics(defaultContext, diagnostics);

// interpret
Expand All @@ -51,6 +52,9 @@ export async function unwritten(program: Program, options?: BrowserAPIOptions):
const renderContext = createRenderContext(defaultContext, renderer, config);
const renderedSymbols = renderer.render(renderContext, interpretedFiles);

return renderedSymbols;
return {
compilerDiagnostics: diagnosticMessages,
rendered: renderedSymbols
};

}
13 changes: 11 additions & 2 deletions src/api/node.entry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ts from "typescript";

import { convertDiagnostics, reportCompilerDiagnostics } from "unwritten:compiler/shared";
import { compile } from "unwritten:compiler:node";
import { createConfig } from "unwritten:config/config";
import { interpret } from "unwritten:interpreter/ast/symbol";
Expand All @@ -14,9 +15,10 @@ import { createContext as createRenderContext } from "unwritten:renderer:utils/c
import { createContext as createDefaultContext } from "unwritten:utils:context";

import type { APIOptions } from "unwritten:type-definitions/options";
import type { UnwrittenOutput } from "unwritten:type-definitions/unwritten";


export async function unwritten(entryFilePaths: string[] | string, options?: APIOptions): Promise<string[]> {
export async function unwritten(entryFilePaths: string[] | string, options?: APIOptions): Promise<UnwrittenOutput> {

entryFilePaths = Array.isArray(entryFilePaths) ? entryFilePaths : [entryFilePaths];

Expand All @@ -38,6 +40,9 @@ export async function unwritten(entryFilePaths: string[] | string, options?: API

// compile
const { checker, program } = compile(defaultContext, entryFilePaths, options?.tsconfig);
const diagnostics = program.getSemanticDiagnostics();
const diagnosticMessages = convertDiagnostics(defaultContext, diagnostics);
reportCompilerDiagnostics(defaultContext, diagnostics);

// config
const config = await createConfig(defaultContext, options?.config, options?.output);
Expand All @@ -63,6 +68,10 @@ export async function unwritten(entryFilePaths: string[] | string, options?: API
return filePath;
});

return outputPaths;
return {
compilerDiagnostics: diagnosticMessages,
paths: outputPaths,
rendered: renderedFiles
};

}
3 changes: 0 additions & 3 deletions src/compiler/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ export function compile(ctx: DefaultNodeContext, entryFilePaths: string[], tsCon
const program = ts.createProgram(absoluteEntryFilePaths, compilerOptions, compilerHost);
const checker = program.getTypeChecker();

// Report any compiler messages
void reportCompilerDiagnostics(ctx, program.getSemanticDiagnostics());

return { checker, program };

}
Expand Down
80 changes: 60 additions & 20 deletions src/compiler/shared.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { highlight as tinyHighlight } from "tinyhighlight";

import { DiagnosticSeverity } from "unwritten:type-definitions/unwritten";
import { findCommonIndentation, removeCommonIndentation } from "unwritten:utils/template";

import type { TokenColors } from "tinyhighlight";
import type { CompilerOptions, Diagnostic, LineAndCharacter } from "typescript";
import type { CompilerOptions, Diagnostic, DiagnosticMessageChain, SourceFile } from "typescript";

import type { DefaultContext } from "unwritten:type-definitions/context";
import type { DiagnosticMessage } from "unwritten:type-definitions/unwritten";


export function getDefaultCompilerOptions(ctx: DefaultContext): CompilerOptions {
Expand All @@ -18,6 +20,25 @@ export function getDefaultCompilerOptions(ctx: DefaultContext): CompilerOptions
};
}

export function convertDiagnostics(ctx: DefaultContext, diagnostics: readonly Diagnostic[]): DiagnosticMessage[] {
return diagnostics.map(diagnostic => {
return {
code: diagnostic.code,
column: diagnostic.file && diagnostic.start
? getColumnFromPosition(ctx, diagnostic.file, diagnostic.start)
: undefined,
line: diagnostic.file && diagnostic.start
? getLineFromPosition(ctx, diagnostic.file, diagnostic.start)
: undefined,
message: convertDiagnosticMessageTextToString(ctx, diagnostic.messageText),
path: diagnostic.file?.fileName,
severity: diagnostic.category === ctx.dependencies.ts.DiagnosticCategory.Error
? DiagnosticSeverity.Error
: DiagnosticSeverity.Warning
};
});
}

export function reportCompilerDiagnostics(ctx: DefaultContext, diagnostics: readonly Diagnostic[]) {

const { lineEndings } = ctx.dependencies.os;
Expand Down Expand Up @@ -50,41 +71,45 @@ export function reportCompilerDiagnostics(ctx: DefaultContext, diagnostics: read
? "The TypeScript compiler has reported an error"
: "The TypeScript compiler has reported a warning";

const message = `${color(`${category}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, lineEndings)}`)} ${logger.gray(`ts(${diagnostic.code})`)}`;
const message = `${color(`${category}: ${convertDiagnosticMessageTextToString(ctx, diagnostic.messageText)}`)} ${logger.gray(`ts(${diagnostic.code})`)}`;

if(diagnostic.file){

const startLocation: LineAndCharacter = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
const line = getLineFromPosition(ctx, diagnostic.file, diagnostic.start!);
const column = getColumnFromPosition(ctx, diagnostic.file, diagnostic.start!);

const filePath = `${logger.gray("at")} ${logger.filePath(`${diagnostic.file.fileName}:${startLocation.line + 1}:${startLocation.character + 1}`)}`;
const filePath = `${logger.gray("at")} ${logger.filePath(`${diagnostic.file.fileName}:${line}:${column}`)}`;

const sourceFile = highlight(ctx, diagnostic.file.text).split(lineEndings);
const minLine = Math.max(startLocation.line - 2, 0);
const maxLine = Math.min(startLocation.line + 3, sourceFile.length);
const maxLineNumberLength = (maxLine + 1).toString().length;
const linesAround = sourceFile.slice(minLine, maxLine);
const minLine = Math.max(line - 2, 1);
const maxLine = Math.min(line + 2, sourceFile.length);


const maxLineNumberLength = maxLine.toString().length;
const linesAround = sourceFile.slice(minLine - 1, maxLine);

const commonIndentation = findCommonIndentation(linesAround.join(lineEndings), lineEndings);
const cleanedLinesAround = linesAround.map(line => {
return removeCommonIndentation(line, commonIndentation);
});
const cleanedLinesAround = linesAround.map(line => removeCommonIndentation(line, commonIndentation));

const sourceCode = cleanedLinesAround.reduce<string[]>((acc, line, index) => {
const sourceCode = cleanedLinesAround.reduce<string[]>((acc, cleanedLine, index) => {

const lineNumber = minLine + 1 + index;
const lineNumber = minLine + index;
const lineIndicator = `${lineNumber.toString().padStart(maxLineNumberLength)}| `;
const tabCount = linesAround[index].substring(0, startLocation.character + 1).match(/\t/g)?.length ?? 0;
const tabCorrectedStartCharacter = startLocation.character - tabCount + tabCount * 4;
const tabCorrectedEndCharacter = tabCorrectedStartCharacter + diagnostic.length!;
const tabCorrectedLine = line.replace(/\t/g, " ");

const tabCount = linesAround[index].substring(0, column).match(/\t/g)?.length ?? 0;
const tabCorrectedStartColumn = column - 1 - tabCount + tabCount * 4;
const tabCorrectedEndColumn = tabCorrectedStartColumn + diagnostic.length!;
const tabCorrectedLine = cleanedLine.replace(/\t/g, " ");

const syntaxHighlightedLine = tabCorrectedLine;

acc.push(logger.gray(`${lineIndicator}${syntaxHighlightedLine}`));

if(lineNumber === startLocation.line + 1){
if(lineNumber === line){
acc.push(logger.yellow(`${
" ".repeat(lineIndicator.length + tabCorrectedStartCharacter - commonIndentation)
" ".repeat(lineIndicator.length + tabCorrectedStartColumn - commonIndentation)
}${
color("~".repeat(tabCorrectedEndCharacter - tabCorrectedStartCharacter))
color("~".repeat(tabCorrectedEndColumn - tabCorrectedStartColumn))
}`));
}

Expand Down Expand Up @@ -163,3 +188,18 @@ function highlight(ctx: DefaultContext, code: string) {
});

}

function getLineFromPosition(ctx: DefaultContext, sourceFile: SourceFile, position: number): number {
const { ts } = ctx.dependencies;
return ts.getLineAndCharacterOfPosition(sourceFile, position).line + 1;
}

function getColumnFromPosition(ctx: DefaultContext, sourceFile: SourceFile, position: number): number {
const { ts } = ctx.dependencies;
return ts.getLineAndCharacterOfPosition(sourceFile, position).character + 1;
}

function convertDiagnosticMessageTextToString(ctx: DefaultContext, message: DiagnosticMessageChain | string): string {
const { os, ts } = ctx.dependencies;
return ts.flattenDiagnosticMessageText(message, os.lineEndings);
}
22 changes: 22 additions & 0 deletions src/type-definitions/unwritten.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { RenderOutput } from "unwritten:type-definitions/renderer";


export enum DiagnosticSeverity {
Error = "error",
Warning = "warning"
}

export interface DiagnosticMessage {
code: number;
message: string;
severity: DiagnosticSeverity;
column?: number;
line?: number;
path?: string;
}

export interface UnwrittenOutput {
compilerDiagnostics: DiagnosticMessage[];
rendered: RenderOutput;
paths?: string[];
}

0 comments on commit 931d97e

Please sign in to comment.