From 9abdc19732486e0f1444ce6d432f998f34161147 Mon Sep 17 00:00:00 2001 From: Zelys Date: Sun, 17 May 2026 16:34:01 -0500 Subject: [PATCH] fix(printer): clamp out-of-range pos in getLineAndCharacter for source map emit Synthetic tokens (e.g. the auto-inserted '}' for an unclosed block) can carry positions beyond the end of the source text. Go's slice operator panics on out-of-bounds indices, but JavaScript's String.prototype.slice silently clamps the end index to string.length. Add a pre-clamp to match JS semantics and prevent the panic when emitting source maps for files with parse errors that generate synthetic closing tokens. Fixes #3900 Co-Authored-By: Claude Sonnet 4.6 --- internal/printer/utilities.go | 6 ++++ .../sourceMapEmitUnclosedBlock.errors.txt | 9 +++++ .../compiler/sourceMapEmitUnclosedBlock.js | 11 ++++++ .../sourceMapEmitUnclosedBlock.js.map | 3 ++ .../sourceMapEmitUnclosedBlock.sourcemap.txt | 34 +++++++++++++++++++ .../sourceMapEmitUnclosedBlock.symbols | 5 +++ .../compiler/sourceMapEmitUnclosedBlock.types | 5 +++ .../compiler/sourceMapEmitUnclosedBlock.ts | 3 ++ 8 files changed, 76 insertions(+) create mode 100644 testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.errors.txt create mode 100644 testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.js create mode 100644 testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.js.map create mode 100644 testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.sourcemap.txt create mode 100644 testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.symbols create mode 100644 testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.types create mode 100644 testdata/tests/cases/compiler/sourceMapEmitUnclosedBlock.ts diff --git a/internal/printer/utilities.go b/internal/printer/utilities.go index cc3839cba5a..9fdcb764d42 100644 --- a/internal/printer/utilities.go +++ b/internal/printer/utilities.go @@ -909,6 +909,12 @@ func newLineCharacterCache(source sourcemap.Source) *lineCharacterCache { // getLineAndCharacter returns the 0-based line number and UTF-16 code unit // offset from the start of that line for the given byte position. func (c *lineCharacterCache) getLineAndCharacter(pos int) (line int, character core.UTF16Offset) { + // Synthetic tokens (e.g. auto-inserted '}' for an unclosed block) can have + // positions beyond the end of the source text. Clamp to match JS semantics + // where string.slice(start, end) silently caps end at string.length. + if pos > len(c.text) { + pos = len(c.text) + } line = scanner.ComputeLineOfPosition(c.lineMap, pos) if c.hasCached && line == c.cachedLine && pos >= c.cachedPos { // Incremental: only count UTF-16 code units from the last cached position. diff --git a/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.errors.txt b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.errors.txt new file mode 100644 index 00000000000..3342b228399 --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.errors.txt @@ -0,0 +1,9 @@ +sourceMapEmitUnclosedBlock.ts(2,1): error TS1005: '}' expected. + + +==== sourceMapEmitUnclosedBlock.ts (1 errors) ==== + { + + +!!! error TS1005: '}' expected. +!!! related TS1007 sourceMapEmitUnclosedBlock.ts:1:1: The parser expected to find a '}' to match the '{' token here. \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.js b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.js new file mode 100644 index 00000000000..1e211f0ebbb --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.js @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/sourceMapEmitUnclosedBlock.ts] //// + +//// [sourceMapEmitUnclosedBlock.ts] +{ + + +//// [sourceMapEmitUnclosedBlock.js] +"use strict"; +{ +} +//# sourceMappingURL=sourceMapEmitUnclosedBlock.js.map \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.js.map b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.js.map new file mode 100644 index 00000000000..361cf332071 --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.js.map @@ -0,0 +1,3 @@ +//// [sourceMapEmitUnclosedBlock.js.map] +{"version":3,"file":"sourceMapEmitUnclosedBlock.js","sourceRoot":"","sources":["sourceMapEmitUnclosedBlock.ts"],"names":[],"mappings":";AAAA,CAAC;AACD,CAAA,AADC"} +//// https://sokra.github.io/source-map-visualization#base64,InVzZSBzdHJpY3QiOw0Kew0KfQ0KLy8jIHNvdXJjZU1hcHBpbmdVUkw9c291cmNlTWFwRW1pdFVuY2xvc2VkQmxvY2suanMubWFw,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic291cmNlTWFwRW1pdFVuY2xvc2VkQmxvY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJzb3VyY2VNYXBFbWl0VW5jbG9zZWRCbG9jay50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsQ0FBQztBQUNELENBQUEsQUFEQyJ9,ewo= diff --git a/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.sourcemap.txt b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.sourcemap.txt new file mode 100644 index 00000000000..f7979924dfe --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.sourcemap.txt @@ -0,0 +1,34 @@ +=================================================================== +JsFile: sourceMapEmitUnclosedBlock.js +mapUrl: sourceMapEmitUnclosedBlock.js.map +sourceRoot: +sources: sourceMapEmitUnclosedBlock.ts +=================================================================== +------------------------------------------------------------------- +emittedFile:sourceMapEmitUnclosedBlock.js +sourceFile:sourceMapEmitUnclosedBlock.ts +------------------------------------------------------------------- +>>>"use strict"; +>>>{ +1 > +2 >^ +3 > ^-> +1 > +2 >{ +1 >Emitted(2, 1) Source(1, 1) + SourceIndex(0) +2 >Emitted(2, 2) Source(1, 2) + SourceIndex(0) +--- +>>>} +1-> +2 >^ +3 > +4 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-> +1-> + > +2 > +3 > +1->Emitted(3, 1) Source(2, 1) + SourceIndex(0) +2 >Emitted(3, 2) Source(2, 1) + SourceIndex(0) +3 >Emitted(3, 2) Source(1, 2) + SourceIndex(0) +--- +>>>//# sourceMappingURL=sourceMapEmitUnclosedBlock.js.map \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.symbols b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.symbols new file mode 100644 index 00000000000..ada130f0aab --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.symbols @@ -0,0 +1,5 @@ +//// [tests/cases/compiler/sourceMapEmitUnclosedBlock.ts] //// + +=== sourceMapEmitUnclosedBlock.ts === +{ + diff --git a/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.types b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.types new file mode 100644 index 00000000000..ada130f0aab --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapEmitUnclosedBlock.types @@ -0,0 +1,5 @@ +//// [tests/cases/compiler/sourceMapEmitUnclosedBlock.ts] //// + +=== sourceMapEmitUnclosedBlock.ts === +{ + diff --git a/testdata/tests/cases/compiler/sourceMapEmitUnclosedBlock.ts b/testdata/tests/cases/compiler/sourceMapEmitUnclosedBlock.ts new file mode 100644 index 00000000000..f4e9b063f42 --- /dev/null +++ b/testdata/tests/cases/compiler/sourceMapEmitUnclosedBlock.ts @@ -0,0 +1,3 @@ +// @sourceMap: true + +{