From e9841ead3d6e7c57e0cf8e3b5d6d85d51894eb90 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 17:12:48 +0000 Subject: [PATCH 1/2] Initial plan From 5efd343a88f90424a87d2153cc43367c023ec31d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:06:20 +0000 Subject: [PATCH 2/2] Fix declarationMap panic: add bounds checks in SkipTriviaEx and emitPos for cross-file node positions During declaration emit, nodes reused from other files may have positions beyond the current source file's text length. In JavaScript, out-of-bounds string access returns NaN/undefined without crashing, but Go panics. Add bounds checks in: - scanner.SkipTriviaEx: return pos unchanged when pos >= len(text) - printer.emitPos: skip source map emission when pos > len(source text) Add regression test from the issue repro. Fixes #1291 Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/c52384fe-4753-44ce-85eb-f2f49ab53079 Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/printer/printer.go | 8 + internal/scanner/scanner.go | 8 +- ...clarationMapReusedNodeFromDifferentFile.js | 52 ++++ ...ationMapReusedNodeFromDifferentFile.js.map | 11 + ...pReusedNodeFromDifferentFile.sourcemap.txt | 234 ++++++++++++++++++ ...tionMapReusedNodeFromDifferentFile.symbols | 45 ++++ ...rationMapReusedNodeFromDifferentFile.types | 43 ++++ ...clarationMapReusedNodeFromDifferentFile.ts | 24 ++ 8 files changed, 423 insertions(+), 2 deletions(-) create mode 100644 testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.js create mode 100644 testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.js.map create mode 100644 testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.sourcemap.txt create mode 100644 testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.symbols create mode 100644 testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.types create mode 100644 testdata/tests/cases/compiler/declarationMapReusedNodeFromDifferentFile.ts diff --git a/internal/printer/printer.go b/internal/printer/printer.go index 9c408032bb..33a5c8ef1d 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -5762,6 +5762,14 @@ func (p *Printer) emitPos(pos int) { return } + // During declaration emit, nodes reused from other files may have positions + // beyond the current source file's text length. Skip source map emission + // for such positions, matching TypeScript's implicit behavior where + // out-of-bounds string access returns NaN/undefined without crashing. + if pos > len(p.sourceMapSource.Text()) { + return + } + sourceLine, sourceCharacter := p.sourceMapLineCharCache.getLineAndCharacter(pos) if err := p.sourceMapGenerator.AddSourceMapping( p.writer.GetLine(), diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index aec39a8b4b..e187072ecd 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -2270,11 +2270,15 @@ func SkipTriviaEx(text string, pos int, options *SkipTriviaOptions) int { if ast.PositionIsSynthesized(pos) { return pos } + + textLen := len(text) + if pos >= textLen { + return pos + } + if options == nil { options = &SkipTriviaOptions{} } - - textLen := len(text) canConsumeStar := false // Keep in sync with couldStartTrivia for { diff --git a/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.js b/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.js new file mode 100644 index 0000000000..9208619fcf --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.js @@ -0,0 +1,52 @@ +//// [tests/cases/compiler/declarationMapReusedNodeFromDifferentFile.ts] //// + +//// [types.ts] +export interface Widget { + id: string; + name: string; +} + +//// [helper.ts] +import { Widget } from "./types"; + +// Here is a bunch of text in comments to pad the file length so positions in the return type annotation below +// are past the length of the short index.ts file. This is needed to trigger the slice bounds +// out of range crash when node positions from this file are used with the text of index.ts. +export function getWidget(): (w: Widget) => keyof Widget { + return (w) => "id"; +} + +//// [index.ts] +import { Widget } from "./types"; +import { getWidget } from "./helper"; +export type MyWidget = Widget; +export const fn = getWidget(); + + +//// [types.js] +export {}; +//// [helper.js] +// Here is a bunch of text in comments to pad the file length so positions in the return type annotation below +// are past the length of the short index.ts file. This is needed to trigger the slice bounds +// out of range crash when node positions from this file are used with the text of index.ts. +export function getWidget() { + return (w) => "id"; +} +//// [index.js] +import { getWidget } from "./helper"; +export const fn = getWidget(); + + +//// [types.d.ts] +export interface Widget { + id: string; + name: string; +} +//# sourceMappingURL=types.d.ts.map//// [helper.d.ts] +import { Widget } from "./types"; +export declare function getWidget(): (w: Widget) => keyof Widget; +//# sourceMappingURL=helper.d.ts.map//// [index.d.ts] +import { Widget } from "./types"; +export type MyWidget = Widget; +export declare const fn: (w: Widget) => keyof Widget; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.js.map b/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.js.map new file mode 100644 index 0000000000..0f5dd441ef --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.js.map @@ -0,0 +1,11 @@ +//// [helper.d.ts.map] +{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAKjC,wBAAgB,SAAS,IAAI,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,MAAM,CAEvD"} +//// https://sokra.github.io/source-map-visualization#base64,aW1wb3J0IHsgV2lkZ2V0IH0gZnJvbSAiLi90eXBlcyI7DQpleHBvcnQgZGVjbGFyZSBmdW5jdGlvbiBnZXRXaWRnZXQoKTogKHc6IFdpZGdldCkgPT4ga2V5b2YgV2lkZ2V0Ow0KLy8jIHNvdXJjZU1hcHBpbmdVUkw9aGVscGVyLmQudHMubWFw,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJoZWxwZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUtqQyx3QkFBZ0IsU0FBUyxJQUFJLENBQUMsQ0FBQyxFQUFFLE1BQU0sS0FBSyxNQUFNLE1BQU0sQ0FFdkQifQ==,aW1wb3J0IHsgV2lkZ2V0IH0gZnJvbSAiLi90eXBlcyI7CgovLyBIZXJlIGlzIGEgYnVuY2ggb2YgdGV4dCBpbiBjb21tZW50cyB0byBwYWQgdGhlIGZpbGUgbGVuZ3RoIHNvIHBvc2l0aW9ucyBpbiB0aGUgcmV0dXJuIHR5cGUgYW5ub3RhdGlvbiBiZWxvdwovLyBhcmUgcGFzdCB0aGUgbGVuZ3RoIG9mIHRoZSBzaG9ydCBpbmRleC50cyBmaWxlLiBUaGlzIGlzIG5lZWRlZCB0byB0cmlnZ2VyIHRoZSBzbGljZSBib3VuZHMKLy8gb3V0IG9mIHJhbmdlIGNyYXNoIHdoZW4gbm9kZSBwb3NpdGlvbnMgZnJvbSB0aGlzIGZpbGUgYXJlIHVzZWQgd2l0aCB0aGUgdGV4dCBvZiBpbmRleC50cy4KZXhwb3J0IGZ1bmN0aW9uIGdldFdpZGdldCgpOiAodzogV2lkZ2V0KSA9PiBrZXlvZiBXaWRnZXQgewogICAgcmV0dXJuICh3KSA9PiAiaWQiOwp9Cg== + +//// [index.d.ts.map] +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC9B,eAAO,MAAM,EAAE,6BAAc,CAAC"} +//// https://sokra.github.io/source-map-visualization#base64,aW1wb3J0IHsgV2lkZ2V0IH0gZnJvbSAiLi90eXBlcyI7DQpleHBvcnQgdHlwZSBNeVdpZGdldCA9IFdpZGdldDsNCmV4cG9ydCBkZWNsYXJlIGNvbnN0IGZuOiAodzogV2lkZ2V0KSA9PiBrZXlvZiBXaWRnZXQ7DQovLyMgc291cmNlTWFwcGluZ1VSTD1pbmRleC5kLnRzLm1hcA==,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImluZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFFakMsTUFBTSxNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUM7QUFDOUIsZUFBTyxNQUFNLEVBQUUsNkJBQWMsQ0FBQyJ9,aW1wb3J0IHsgV2lkZ2V0IH0gZnJvbSAiLi90eXBlcyI7CmltcG9ydCB7IGdldFdpZGdldCB9IGZyb20gIi4vaGVscGVyIjsKZXhwb3J0IHR5cGUgTXlXaWRnZXQgPSBXaWRnZXQ7CmV4cG9ydCBjb25zdCBmbiA9IGdldFdpZGdldCgpOwo= + +//// [types.d.ts.map] +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CAChB"} +//// https://sokra.github.io/source-map-visualization#base64,ZXhwb3J0IGludGVyZmFjZSBXaWRnZXQgew0KICAgIGlkOiBzdHJpbmc7DQogICAgbmFtZTogc3RyaW5nOw0KfQ0KLy8jIHNvdXJjZU1hcHBpbmdVUkw9dHlwZXMuZC50cy5tYXA=,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInR5cGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE1BQU0sV0FBVyxNQUFNO0lBQ25CLEVBQUUsRUFBRSxNQUFNLENBQUM7SUFDWCxJQUFJLEVBQUUsTUFBTSxDQUFDO0NBQ2hCIn0=,ZXhwb3J0IGludGVyZmFjZSBXaWRnZXQgewogICAgaWQ6IHN0cmluZzsKICAgIG5hbWU6IHN0cmluZzsKfQo= diff --git a/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.sourcemap.txt b/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.sourcemap.txt new file mode 100644 index 0000000000..39ef4673f0 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.sourcemap.txt @@ -0,0 +1,234 @@ +=================================================================== +JsFile: types.d.ts +mapUrl: types.d.ts.map +sourceRoot: +sources: types.ts +=================================================================== +------------------------------------------------------------------- +emittedFile:types.d.ts +sourceFile:types.ts +------------------------------------------------------------------- +>>>export interface Widget { +1 > +2 >^^^^^^ +3 > ^^^^^^^^^^^ +4 > ^^^^^^ +1 > +2 >export +3 > interface +4 > Widget +1 >Emitted(1, 1) Source(1, 1) + SourceIndex(0) +2 >Emitted(1, 7) Source(1, 7) + SourceIndex(0) +3 >Emitted(1, 18) Source(1, 18) + SourceIndex(0) +4 >Emitted(1, 24) Source(1, 24) + SourceIndex(0) +--- +>>> id: string; +1 >^^^^ +2 > ^^ +3 > ^^ +4 > ^^^^^^ +5 > ^ +6 > ^^^-> +1 > { + > +2 > id +3 > : +4 > string +5 > ; +1 >Emitted(2, 5) Source(2, 5) + SourceIndex(0) +2 >Emitted(2, 7) Source(2, 7) + SourceIndex(0) +3 >Emitted(2, 9) Source(2, 9) + SourceIndex(0) +4 >Emitted(2, 15) Source(2, 15) + SourceIndex(0) +5 >Emitted(2, 16) Source(2, 16) + SourceIndex(0) +--- +>>> name: string; +1->^^^^ +2 > ^^^^ +3 > ^^ +4 > ^^^^^^ +5 > ^ +1-> + > +2 > name +3 > : +4 > string +5 > ; +1->Emitted(3, 5) Source(3, 5) + SourceIndex(0) +2 >Emitted(3, 9) Source(3, 9) + SourceIndex(0) +3 >Emitted(3, 11) Source(3, 11) + SourceIndex(0) +4 >Emitted(3, 17) Source(3, 17) + SourceIndex(0) +5 >Emitted(3, 18) Source(3, 18) + SourceIndex(0) +--- +>>>} +1 >^ +2 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-> +1 > + >} +1 >Emitted(4, 2) Source(4, 2) + SourceIndex(0) +--- +>>>//# sourceMappingURL=types.d.ts.map=================================================================== +JsFile: helper.d.ts +mapUrl: helper.d.ts.map +sourceRoot: +sources: helper.ts +=================================================================== +------------------------------------------------------------------- +emittedFile:helper.d.ts +sourceFile:helper.ts +------------------------------------------------------------------- +>>>import { Widget } from "./types"; +1 > +2 >^^^^^^^ +3 > ^^ +4 > ^^^^^^ +5 > ^^ +6 > ^^^^^^ +7 > ^^^^^^^^^ +8 > ^ +9 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-> +1 > +2 >import +3 > { +4 > Widget +5 > } +6 > from +7 > "./types" +8 > ; +1 >Emitted(1, 1) Source(1, 1) + SourceIndex(0) +2 >Emitted(1, 8) Source(1, 8) + SourceIndex(0) +3 >Emitted(1, 10) Source(1, 10) + SourceIndex(0) +4 >Emitted(1, 16) Source(1, 16) + SourceIndex(0) +5 >Emitted(1, 18) Source(1, 18) + SourceIndex(0) +6 >Emitted(1, 24) Source(1, 24) + SourceIndex(0) +7 >Emitted(1, 33) Source(1, 33) + SourceIndex(0) +8 >Emitted(1, 34) Source(1, 34) + SourceIndex(0) +--- +>>>export declare function getWidget(): (w: Widget) => keyof Widget; +1-> +2 >^^^^^^^^^^^^^^^^^^^^^^^^ +3 > ^^^^^^^^^ +4 > ^^^^ +5 > ^ +6 > ^ +7 > ^^ +8 > ^^^^^^ +9 > ^^^^^ +10> ^^^^^^ +11> ^^^^^^ +12> ^ +1-> + > + >// Here is a bunch of text in comments to pad the file length so positions in the return type annotation below + >// are past the length of the short index.ts file. This is needed to trigger the slice bounds + >// out of range crash when node positions from this file are used with the text of index.ts. + > +2 >export function +3 > getWidget +4 > (): +5 > ( +6 > w +7 > : +8 > Widget +9 > ) => +10> keyof +11> Widget +12> { + > return (w) => "id"; + > } +1->Emitted(2, 1) Source(6, 1) + SourceIndex(0) +2 >Emitted(2, 25) Source(6, 17) + SourceIndex(0) +3 >Emitted(2, 34) Source(6, 26) + SourceIndex(0) +4 >Emitted(2, 38) Source(6, 30) + SourceIndex(0) +5 >Emitted(2, 39) Source(6, 31) + SourceIndex(0) +6 >Emitted(2, 40) Source(6, 32) + SourceIndex(0) +7 >Emitted(2, 42) Source(6, 34) + SourceIndex(0) +8 >Emitted(2, 48) Source(6, 40) + SourceIndex(0) +9 >Emitted(2, 53) Source(6, 45) + SourceIndex(0) +10>Emitted(2, 59) Source(6, 51) + SourceIndex(0) +11>Emitted(2, 65) Source(6, 57) + SourceIndex(0) +12>Emitted(2, 66) Source(8, 2) + SourceIndex(0) +--- +>>>//# sourceMappingURL=helper.d.ts.map=================================================================== +JsFile: index.d.ts +mapUrl: index.d.ts.map +sourceRoot: +sources: index.ts +=================================================================== +------------------------------------------------------------------- +emittedFile:index.d.ts +sourceFile:index.ts +------------------------------------------------------------------- +>>>import { Widget } from "./types"; +1 > +2 >^^^^^^^ +3 > ^^ +4 > ^^^^^^ +5 > ^^ +6 > ^^^^^^ +7 > ^^^^^^^^^ +8 > ^ +1 > +2 >import +3 > { +4 > Widget +5 > } +6 > from +7 > "./types" +8 > ; +1 >Emitted(1, 1) Source(1, 1) + SourceIndex(0) +2 >Emitted(1, 8) Source(1, 8) + SourceIndex(0) +3 >Emitted(1, 10) Source(1, 10) + SourceIndex(0) +4 >Emitted(1, 16) Source(1, 16) + SourceIndex(0) +5 >Emitted(1, 18) Source(1, 18) + SourceIndex(0) +6 >Emitted(1, 24) Source(1, 24) + SourceIndex(0) +7 >Emitted(1, 33) Source(1, 33) + SourceIndex(0) +8 >Emitted(1, 34) Source(1, 34) + SourceIndex(0) +--- +>>>export type MyWidget = Widget; +1 > +2 >^^^^^^ +3 > ^^^^^^ +4 > ^^^^^^^^ +5 > ^^^ +6 > ^^^^^^ +7 > ^ +8 > ^^^^^^^^^^^^^^^^^^^^^^^^-> +1 > + >import { getWidget } from "./helper"; + > +2 >export +3 > type +4 > MyWidget +5 > = +6 > Widget +7 > ; +1 >Emitted(2, 1) Source(3, 1) + SourceIndex(0) +2 >Emitted(2, 7) Source(3, 7) + SourceIndex(0) +3 >Emitted(2, 13) Source(3, 13) + SourceIndex(0) +4 >Emitted(2, 21) Source(3, 21) + SourceIndex(0) +5 >Emitted(2, 24) Source(3, 24) + SourceIndex(0) +6 >Emitted(2, 30) Source(3, 30) + SourceIndex(0) +7 >Emitted(2, 31) Source(3, 31) + SourceIndex(0) +--- +>>>export declare const fn: (w: Widget) => keyof Widget; +1-> +2 >^^^^^^^^^^^^^^^ +3 > ^^^^^^ +4 > ^^ +5 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +6 > ^ +1-> + > +2 >export +3 > const +4 > fn +5 > = getWidget() +6 > ; +1->Emitted(3, 1) Source(4, 1) + SourceIndex(0) +2 >Emitted(3, 16) Source(4, 8) + SourceIndex(0) +3 >Emitted(3, 22) Source(4, 14) + SourceIndex(0) +4 >Emitted(3, 24) Source(4, 16) + SourceIndex(0) +5 >Emitted(3, 53) Source(4, 30) + SourceIndex(0) +6 >Emitted(3, 54) Source(4, 31) + SourceIndex(0) +--- +>>>//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.symbols b/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.symbols new file mode 100644 index 0000000000..8ce696acd4 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.symbols @@ -0,0 +1,45 @@ +//// [tests/cases/compiler/declarationMapReusedNodeFromDifferentFile.ts] //// + +=== types.ts === +export interface Widget { +>Widget : Symbol(Widget, Decl(types.ts, 0, 0)) + + id: string; +>id : Symbol(Widget.id, Decl(types.ts, 0, 25)) + + name: string; +>name : Symbol(Widget.name, Decl(types.ts, 1, 15)) +} + +=== helper.ts === +import { Widget } from "./types"; +>Widget : Symbol(Widget, Decl(helper.ts, 0, 8)) + +// Here is a bunch of text in comments to pad the file length so positions in the return type annotation below +// are past the length of the short index.ts file. This is needed to trigger the slice bounds +// out of range crash when node positions from this file are used with the text of index.ts. +export function getWidget(): (w: Widget) => keyof Widget { +>getWidget : Symbol(getWidget, Decl(helper.ts, 0, 33)) +>w : Symbol(w, Decl(helper.ts, 5, 30)) +>Widget : Symbol(Widget, Decl(helper.ts, 0, 8)) +>Widget : Symbol(Widget, Decl(helper.ts, 0, 8)) + + return (w) => "id"; +>w : Symbol(w, Decl(helper.ts, 6, 12)) +} + +=== index.ts === +import { Widget } from "./types"; +>Widget : Symbol(Widget, Decl(index.ts, 0, 8)) + +import { getWidget } from "./helper"; +>getWidget : Symbol(getWidget, Decl(index.ts, 1, 8)) + +export type MyWidget = Widget; +>MyWidget : Symbol(MyWidget, Decl(index.ts, 1, 37)) +>Widget : Symbol(Widget, Decl(index.ts, 0, 8)) + +export const fn = getWidget(); +>fn : Symbol(fn, Decl(index.ts, 3, 12)) +>getWidget : Symbol(getWidget, Decl(index.ts, 1, 8)) + diff --git a/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.types b/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.types new file mode 100644 index 0000000000..49fafa3e2b --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationMapReusedNodeFromDifferentFile.types @@ -0,0 +1,43 @@ +//// [tests/cases/compiler/declarationMapReusedNodeFromDifferentFile.ts] //// + +=== types.ts === +export interface Widget { + id: string; +>id : string + + name: string; +>name : string +} + +=== helper.ts === +import { Widget } from "./types"; +>Widget : any + +// Here is a bunch of text in comments to pad the file length so positions in the return type annotation below +// are past the length of the short index.ts file. This is needed to trigger the slice bounds +// out of range crash when node positions from this file are used with the text of index.ts. +export function getWidget(): (w: Widget) => keyof Widget { +>getWidget : () => (w: Widget) => keyof Widget +>w : Widget + + return (w) => "id"; +>(w) => "id" : (w: Widget) => "id" +>w : Widget +>"id" : "id" +} + +=== index.ts === +import { Widget } from "./types"; +>Widget : any + +import { getWidget } from "./helper"; +>getWidget : () => (w: Widget) => keyof Widget + +export type MyWidget = Widget; +>MyWidget : Widget + +export const fn = getWidget(); +>fn : (w: Widget) => keyof Widget +>getWidget() : (w: Widget) => keyof Widget +>getWidget : () => (w: Widget) => keyof Widget + diff --git a/testdata/tests/cases/compiler/declarationMapReusedNodeFromDifferentFile.ts b/testdata/tests/cases/compiler/declarationMapReusedNodeFromDifferentFile.ts new file mode 100644 index 0000000000..d1d95a19e5 --- /dev/null +++ b/testdata/tests/cases/compiler/declarationMapReusedNodeFromDifferentFile.ts @@ -0,0 +1,24 @@ +// @declaration: true +// @declarationMap: true + +// @filename: types.ts +export interface Widget { + id: string; + name: string; +} + +// @filename: helper.ts +import { Widget } from "./types"; + +// Here is a bunch of text in comments to pad the file length so positions in the return type annotation below +// are past the length of the short index.ts file. This is needed to trigger the slice bounds +// out of range crash when node positions from this file are used with the text of index.ts. +export function getWidget(): (w: Widget) => keyof Widget { + return (w) => "id"; +} + +// @filename: index.ts +import { Widget } from "./types"; +import { getWidget } from "./helper"; +export type MyWidget = Widget; +export const fn = getWidget();