Skip to content

v3.1.2

Choose a tag to compare

@windischb windischb released this 18 Apr 13:51
· 43 commits to develop since this release

TypeScript transpiler surfaces errors you can act on, and optional Source Maps.

Two fixes in Cocoar.JsEval.TypeScript that move the save-time story from "hope the JS is valid" to "fail fast with structured, editor-actionable diagnostics." Plus an opt-in Source Map pipeline so a Monaco-backed editor can map a runtime-error position in generated JS back to the original TypeScript line.

The failure 3.1.1 and earlier users are hitting

TsTranspiler.Transpile("const x: number = 42 @@@ broken") returned… "use strict";. No exception. No signal. The broken JS got persisted, and an admin saw the failure later — miles downstream — as a generic Jint.ParserException at EvaluateExpression time, with no line/column anchored to the original TypeScript.

Root cause: ts.transpileModule only reports diagnostics when called with reportDiagnostics: true. The transpiler was calling it without that flag, so every diagnostic (syntax error, missing brace, garbage input) was silently dropped.

What 3.1.2 does

try
{
    var js = transpiler.Transpile(userSource);
    // use js…
}
catch (TsTranspileException ex)
{
    foreach (var d in ex.Errors)
        Console.WriteLine($"TS{d.Code} at line {d.Line}, col {d.Column}: {d.Message}");
    // d.Category / d.Code / d.Message / d.Line / d.Column — strongly typed
}

Every TsDiagnostic carries:

  • Category — Error / Warning / Suggestion / Message (matches TypeScript's own enum)
  • Code — the TS error number (e.g. 1005 for "',' expected"), so editors can cross-reference the official catalogue
  • Message — flattened human-readable message (chains unfolded)
  • Line, Column — 1-based position in the source the compiler saw

Behavioural change for consumers that previously got silent broken output: the exception now fires at save time, where the admin can fix it, instead of query time, where the app is already processing a request.

Source Maps

New opt-in API returns JS, Source Map v3 JSON, and any non-error diagnostics:

var result = transpiler.TranspileWithSourceMap(userSource);
// result.Js         — plain JS, trailing //# sourceMappingURL= stripped
// result.SourceMap  — Source Map v3 JSON string
// result.Warnings   — IReadOnlyList<TsDiagnostic> (only non-errors; errors throw)

The Source Map lets a Monaco-backed editor turn a JS-side runtime-error position into the original TypeScript line and column. The //# sourceMappingURL= comment is stripped from Js so consumers pick their own embedding strategy (inline base64, sidecar file, in-memory cache).

Known limitation (and future path)

ts.transpileModule is single-file and skips semantic checking. Type errors like const x: number = "oops" still slip through — this release only catches syntax-level issues. Catching type errors requires ts.createProgram, which means:

  • Embedding lib.d.ts and the library declaration files
  • Implementing a virtual compiler host (readFile / writeFile / fileExists / …)
  • ~1-5 second cold start, significantly more memory

Out of scope for 3.1.2 — deferred to a possible future Cocoar.JsEval.TypeScript.TypeCheck add-on. Meanwhile: Monaco's language service covers authoring-time type-checking client-side, and this release covers save-time syntax validation server-side — together that's strong coverage for the typical admin-authoring flow.

Compatibility

  • Behavioural break for consumers relying on the silent-failure behaviour. If your code was catching the downstream Jint.ParserException from a broken EvaluateExpression, you'll now catch TsTranspileException earlier instead. Recommended: do so at save-time and show the diagnostics to the admin.
  • No other API change. Transpile(string) : string keeps the same signature — it just throws where it used to silently succeed with broken output.
  • TranspileWithSourceMap is strictly additive.

Tests

Nine new unit tests in JsEval.Tests.TypeScript cover the diagnostic shape, multi-line error positions, the ts.transpileModule type-error limitation (locked in so we notice if a future upgrade changes it), the Source Map JSON shape, and the sourceMappingURL-stripping contract. Full suite green: 305 tests across all packages.

Also in 3.1.2: Guid is no longer masked as string (Cocoar.JsEval.TsDefinition)

The type-mapping table had [typeof(Guid)] = "string". In Monaco that meant t.CustomerId === 'abc-…' and t.CustomerId === linq.guid('abc-…') both type-checked as string === string — admins writing access-policy scripts could bypass the typed literal by accident and never know. The override is gone; Guid now falls through the normal rendering path and resolves to Guid (or System.Guid with namespaces enabled) so Monaco sees it as its own type.

One line gone, one mis-cast with it. Consumers who actually wanted the old mapping can restore it in their own TypeScriptRendererDefaults.TypeMappings.