From 3edb680c1948e86c1ec596fcfaeaab852068a94c Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 11 Dec 2021 16:49:12 +0100 Subject: [PATCH] Improve preprocessor error reporting --- packages/compiler/src/compiler/codegen.ts | 17 +++++++++++------ packages/compiler/src/compiler/grammar.ts | 4 ++-- packages/compiler/src/syntax/position.ts | 4 ++++ packages/compiler/src/syntax/tokens.ts | 8 ++++++++ packages/syndicatec/src/cli.ts | 14 +++++++------- packages/ts-plugin/src/index.ts | 23 +++++++++++++++++++---- packages/tsc/src/tsc.ts | 6 +++--- 7 files changed, 54 insertions(+), 22 deletions(-) diff --git a/packages/compiler/src/compiler/codegen.ts b/packages/compiler/src/compiler/codegen.ts index eefabec..b88943e 100644 --- a/packages/compiler/src/compiler/codegen.ts +++ b/packages/compiler/src/compiler/codegen.ts @@ -6,7 +6,7 @@ import { laxRead, itemText, Items, Pattern, Templates, Substitution, TokenType, - SourceMap, CodeWriter, TemplateFunction, Token, SpanIndex, + SourceMap, CodeWriter, TemplateFunction, Token, SpanIndex, match, TokenBase, getRange, Pos, } from '../syntax/index.js'; import { SyndicateParser, SyndicateTypedParser, @@ -29,7 +29,7 @@ export function stripShebang(items: Items): Items { export type ModuleType ='es6' | 'require' | 'global'; -export type ErrorSink = (message: string) => void; +export type ErrorSink = (message: string, start: Pos | undefined, end: Pos | undefined) => void; export interface CompileOptions { source: string, @@ -52,17 +52,17 @@ export class ExpansionContext { readonly parser: SyndicateParser; readonly moduleType: ModuleType; readonly typescript: boolean; - readonly emitError: (message: string) => void; + readonly errorEmitter: ErrorSink; nextIdNumber = 0; constructor(moduleType: ModuleType, typescript: boolean, - emitError: ErrorSink) + errorEmitter: ErrorSink) { this.parser = typescript ? new SyndicateTypedParser : new SyndicateParser(); this.moduleType = moduleType; this.typescript = typescript; - this.emitError = emitError; + this.errorEmitter = errorEmitter; } quasiRandomId(): string { @@ -72,6 +72,10 @@ export class ExpansionContext { argDecl(t: TemplateFunction, name: Substitution, type: Substitution): Items { return (this.typescript) ? t`${name}: ${type}` : t`${name}`; } + + emitError(m: string, loc: TokenBase) { + this.errorEmitter(m, loc.start, loc.end); + } } function stringifyId(i: Identifier): Items { @@ -99,7 +103,8 @@ function binderTypeGuard(ctx: ExpansionContext, t: TemplateFunction): (binder: B case 'any': return bind; default: - ctx.emitError(`Cannot emit guard for binding of type: ${JSON.stringify(typeText)}`); + ctx.emitError(`Cannot emit guard for binding of type: ${JSON.stringify(typeText)}`, + getRange(binder.type)); return bind; /* act as if "any", for now */ } } diff --git a/packages/compiler/src/compiler/grammar.ts b/packages/compiler/src/compiler/grammar.ts index f3bb9cb..e57e376 100644 --- a/packages/compiler/src/compiler/grammar.ts +++ b/packages/compiler/src/compiler/grammar.ts @@ -2,13 +2,13 @@ /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones import { - Token, Item, Items, + Token, Items, Pattern, foldItems, match, anonymousTemplate as template, commaJoin, scope, bind, seq, alt, upTo, atom, atomString, group, repeat, option, withoutSpace, map, mapm, rest, discard, - value, succeed, fail, separatedOrTerminatedBy, anything, not + value, succeed, fail, separatedOrTerminatedBy, not, TokenBase } from '../syntax/index.js'; import * as Matcher from '../syntax/matcher.js'; diff --git a/packages/compiler/src/syntax/position.ts b/packages/compiler/src/syntax/position.ts index 29c488c..93b193b 100644 --- a/packages/compiler/src/syntax/position.ts +++ b/packages/compiler/src/syntax/position.ts @@ -9,6 +9,10 @@ export interface Pos { fixed?: boolean; } +export function formatPos(p?: Pos): string { + return p ? `${p.name ?? '?'}:${p.line}:${p.column}` : '?'; +} + export function startPos(name: string | null): Pos { return { line: 1, column: 0, pos: 0, name }; } diff --git a/packages/compiler/src/syntax/tokens.ts b/packages/compiler/src/syntax/tokens.ts index fa0a5c0..d24deee 100644 --- a/packages/compiler/src/syntax/tokens.ts +++ b/packages/compiler/src/syntax/tokens.ts @@ -34,6 +34,14 @@ export type Items = Array; export type GroupInProgress = Omit; +export function getRange(t: Item | Items): TokenBase { + if (Array.isArray(t)) { + return { start: t[0].start, end: t[t.length - 1].end, synthetic: true }; + } else { + return t; + } +} + export function finishGroup(g: GroupInProgress, end: Pos): Group { return { ... g, end }; } diff --git a/packages/syndicatec/src/cli.ts b/packages/syndicatec/src/cli.ts index ea17ecc..2ca590e 100644 --- a/packages/syndicatec/src/cli.ts +++ b/packages/syndicatec/src/cli.ts @@ -7,7 +7,7 @@ import fs from 'fs'; import path from 'path'; import { glob } from 'glob'; -import { compile } from '@syndicate-lang/compiler'; +import { compile, Syntax } from '@syndicate-lang/compiler'; import { dataURL, sourceMappingComment} from './util.js'; export type ModuleChoice = 'es6' | 'require' | 'global'; @@ -117,7 +117,7 @@ export function main(argv: string[]) { argv => argv) .argv); - const collectedErrors: Array = []; + let errorCount = 0; const rename = makeRenamer(options.outputDirectory ?? '', options.rootDirectory ?? '.', @@ -146,9 +146,9 @@ export function main(argv: string[]) { runtime: options.runtime, module: options.module, typescript: options.typed, - emitError: m => { - console.error(m); - collectedErrors.push(m); + emitError: (m, start, end) => { + console.error(`${Syntax.formatPos(start)}-${Syntax.formatPos(end)}: ${m}`); + errorCount++; } }); map.sourcesContent = [source]; @@ -168,7 +168,7 @@ export function main(argv: string[]) { } } - if (collectedErrors.length > 0) { - throw new Error(`Compilation failed with ${collectedErrors.length} error(s).`); + if (errorCount > 0) { + throw new Error(`Compilation failed with ${errorCount} error(s).`); } } diff --git a/packages/ts-plugin/src/index.ts b/packages/ts-plugin/src/index.ts index bc35117..4531836 100644 --- a/packages/ts-plugin/src/index.ts +++ b/packages/ts-plugin/src/index.ts @@ -11,6 +11,7 @@ const boot: tslib.server.PluginModuleFactory = ({ typescript: ts }) => { interface SyndicateInfo { sourceFile: ts.SourceFile; originalSource: string; + diagnostics: ts.DiagnosticWithLocation[]; targetToSourceMap: Syntax.SpanIndex; sourceToTargetMap: Syntax.SpanIndex; } @@ -159,20 +160,30 @@ const boot: tslib.server.PluginModuleFactory = ({ typescript: ts }) => { onError?.(`Could not read input file ${fileName}`); return undefined; } + const diagnostics: + Array<{ message: string, start?: Syntax.Pos, end?: Syntax.Pos }> = []; console.log('Syndicate compiling', fileName); const { text: expandedText, targetToSourceMap, sourceToTargetMap } = compile({ source: inputText, name: fileName, typescript: true, - emitError: m => { - console.error(m); - onError?.(m); + emitError: (message, start, end) => { + console.error(`${Syntax.formatPos(start)}-${Syntax.formatPos(end)}: ${message}`); + diagnostics.push({ message, start, end }); }, }); const sf = ts.createSourceFile(fileName, expandedText, languageVersion, true); syndicateInfo.set(fileName, { sourceFile: sf, originalSource: inputText, + diagnostics: diagnostics.map(({ message, start, end }) => ({ + category: ts.DiagnosticCategory.Error, + code: 99999, + file: sf, + start: start?.pos ?? 0, + length: Math.max(0, (end?.pos ?? 0) - (start?.pos ?? 0)), + messageText: message, + })), targetToSourceMap, sourceToTargetMap, }); @@ -243,7 +254,11 @@ const boot: tslib.server.PluginModuleFactory = ({ typescript: ts }) => { getSyntacticDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { const ds = this.inner.getSyntacticDiagnostics(fileName); - return withFileName(fileName, () => ds, (fixup) => fixup.diagnostics(ds)); + return withFileName(fileName, () => ds, (fixup) => { + const fixedUp = fixup.diagnostics(ds); + fixedUp.push(... fixup.info.diagnostics); + return fixedUp; + }); } getSemanticDiagnostics(fileName: string): ts.Diagnostic[] { diff --git a/packages/tsc/src/tsc.ts b/packages/tsc/src/tsc.ts index 729f17b..3970bc5 100644 --- a/packages/tsc/src/tsc.ts +++ b/packages/tsc/src/tsc.ts @@ -8,7 +8,7 @@ import crypto from 'crypto'; import fs from 'fs'; import path from 'path'; -import { compile } from '@syndicate-lang/compiler'; +import { compile, Syntax } from '@syndicate-lang/compiler'; import { SpanIndex, Token } from '@syndicate-lang/compiler/lib/syntax'; export type CommandLineArguments = { @@ -150,8 +150,8 @@ function runBuildOnce(options: CommandLineArguments, toWatch = new ToWatch()) { source: inputText, name: fileName, typescript: true, - emitError: m => { - console.error(m); + emitError: (m, start, end) => { + console.error(`${Syntax.formatPos(start)}-${Syntax.formatPos(end)}: ${m}`); onError?.(m); }, });