diff --git a/internal/printer/utilities.go b/internal/printer/utilities.go index cc3839cba5..35d0137fa1 100644 --- a/internal/printer/utilities.go +++ b/internal/printer/utilities.go @@ -910,15 +910,23 @@ func newLineCharacterCache(source sourcemap.Source) *lineCharacterCache { // offset from the start of that line for the given byte position. func (c *lineCharacterCache) getLineAndCharacter(pos int) (line int, character core.UTF16Offset) { line = scanner.ComputeLineOfPosition(c.lineMap, pos) - if c.hasCached && line == c.cachedLine && pos >= c.cachedPos { + lineStart := int(c.lineMap[line]) + // When pos is beyond the source text (e.g., for error-recovery tokens like + // missing closing braces), we can't slice past the text end. Compute the + // UTF-16 length up to EOF and add the remaining byte offset arithmetically, + // matching TypeScript's computeLineAndCharacterOfPosition which uses + // arithmetic (position - lineStarts[lineNumber]) and handles this implicitly. + endPos := min(pos, len(c.text)) + if c.hasCached && line == c.cachedLine && endPos >= c.cachedPos { // Incremental: only count UTF-16 code units from the last cached position. - character = c.cachedChar + core.UTF16Len(c.text[c.cachedPos:pos]) + character = c.cachedChar + core.UTF16Len(c.text[c.cachedPos:endPos]) } else { // Full computation from line start. - character = core.UTF16Len(c.text[c.lineMap[line]:pos]) + character = core.UTF16Len(c.text[lineStart:endPos]) } + character += core.UTF16Offset(pos - endPos) c.cachedLine = line - c.cachedPos = pos + c.cachedPos = endPos c.cachedChar = character c.hasCached = true return line, character diff --git a/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.errors.txt b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.errors.txt new file mode 100644 index 0000000000..1c60f767b6 --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.errors.txt @@ -0,0 +1,8 @@ +sourceMapUnclosedBlock.ts(1,2): error TS1005: '}' expected. + + +==== sourceMapUnclosedBlock.ts (1 errors) ==== + { + +!!! error TS1005: '}' expected. +!!! related TS1007 sourceMapUnclosedBlock.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/sourceMapUnclosedBlock.js b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.js new file mode 100644 index 0000000000..7cc5e6aef7 --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.js @@ -0,0 +1,9 @@ +//// [tests/cases/compiler/sourceMapUnclosedBlock.ts] //// + +//// [sourceMapUnclosedBlock.ts] +{ + +//// [sourceMapUnclosedBlock.js] +"use strict"; +{ } +//# sourceMappingURL=sourceMapUnclosedBlock.js.map \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.js.map b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.js.map new file mode 100644 index 0000000000..3f1dddc635 --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.js.map @@ -0,0 +1,3 @@ +//// [sourceMapUnclosedBlock.js.map] +{"version":3,"file":"sourceMapUnclosedBlock.js","sourceRoot":"","sources":["sourceMapUnclosedBlock.ts"],"names":[],"mappings":";AAAA,CAAC,CAAA,CAAC"} +//// https://sokra.github.io/source-map-visualization#base64,InVzZSBzdHJpY3QiOw0KeyB9DQovLyMgc291cmNlTWFwcGluZ1VSTD1zb3VyY2VNYXBVbmNsb3NlZEJsb2NrLmpzLm1hcA==,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic291cmNlTWFwVW5jbG9zZWRCbG9jay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInNvdXJjZU1hcFVuY2xvc2VkQmxvY2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLENBQUMsQ0FBQSxDQUFDIn0=,ew== diff --git a/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.sourcemap.txt b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.sourcemap.txt new file mode 100644 index 0000000000..8483eec9c7 --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.sourcemap.txt @@ -0,0 +1,27 @@ +=================================================================== +JsFile: sourceMapUnclosedBlock.js +mapUrl: sourceMapUnclosedBlock.js.map +sourceRoot: +sources: sourceMapUnclosedBlock.ts +=================================================================== +------------------------------------------------------------------- +emittedFile:sourceMapUnclosedBlock.js +sourceFile:sourceMapUnclosedBlock.ts +------------------------------------------------------------------- +>>>"use strict"; +>>>{ } +1 > +2 >^ +3 > ^ +4 > ^ +5 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-> +1 > +2 >{ +3 > +4 > +1 >Emitted(2, 1) Source(1, 1) + SourceIndex(0) +2 >Emitted(2, 2) Source(1, 2) + SourceIndex(0) +3 >Emitted(2, 3) Source(1, 2) + SourceIndex(0) +4 >Emitted(2, 4) Source(1, 3) + SourceIndex(0) +--- +>>>//# sourceMappingURL=sourceMapUnclosedBlock.js.map \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.symbols b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.symbols new file mode 100644 index 0000000000..bedf7c17d8 --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.symbols @@ -0,0 +1,4 @@ +//// [tests/cases/compiler/sourceMapUnclosedBlock.ts] //// + +=== sourceMapUnclosedBlock.ts === +{ diff --git a/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.types b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.types new file mode 100644 index 0000000000..bedf7c17d8 --- /dev/null +++ b/testdata/baselines/reference/compiler/sourceMapUnclosedBlock.types @@ -0,0 +1,4 @@ +//// [tests/cases/compiler/sourceMapUnclosedBlock.ts] //// + +=== sourceMapUnclosedBlock.ts === +{ diff --git a/testdata/tests/cases/compiler/sourceMapUnclosedBlock.ts b/testdata/tests/cases/compiler/sourceMapUnclosedBlock.ts new file mode 100644 index 0000000000..a80b38b0dd --- /dev/null +++ b/testdata/tests/cases/compiler/sourceMapUnclosedBlock.ts @@ -0,0 +1,2 @@ +// @sourceMap: true +{ \ No newline at end of file