diff --git a/src/adapter/sources.ts b/src/adapter/sources.ts index 414233976..591fb8196 100644 --- a/src/adapter/sources.ts +++ b/src/adapter/sources.ts @@ -70,6 +70,13 @@ export const enum SourceConstants { * this suffix will be ignored when displaying sources or stacktracees. */ InternalExtension = '.cdp', + + /** + * Extension of evaluated REPL source. Stack traces which include frames + * from this suffix will be truncated to keep only frames from code called + * by the REPL. + */ + ReplExtension = '.repl', } export type SourceMapTimeouts = { @@ -332,6 +339,10 @@ export class Source { return '/VM' + this.sourceReference; } + if (this.url.endsWith(SourceConstants.ReplExtension)) { + return 'repl'; + } + if (this.absolutePath.startsWith('')) { return this.absolutePath; } diff --git a/src/adapter/stackTrace.ts b/src/adapter/stackTrace.ts index 01f7f2718..8c8b3c946 100644 --- a/src/adapter/stackTrace.ts +++ b/src/adapter/stackTrace.ts @@ -151,14 +151,26 @@ export class StackTrace { } async formatAsNative(): Promise { - const stackFrames = await this.loadFrames(50); - const promises = stackFrames.map(frame => frame.formatAsNative()); - return (await Promise.all(promises)).join('\n') + '\n'; + return await this.formatWithMapper(frame => frame.formatAsNative()); } async format(): Promise { - const stackFrames = await this.loadFrames(50); - const promises = stackFrames.map(frame => frame.format()); + return await this.formatWithMapper(frame => frame.format()); + } + + private async formatWithMapper( + mapper: (frame: FrameElement) => Promise, + ): Promise { + let stackFrames = await this.loadFrames(50); + // REPL may call back into itself; slice at the highest REPL eval in the call chain. + for (let i = stackFrames.length - 1; i >= 0; i--) { + const frame = stackFrames[i]; + if (frame instanceof StackFrame && frame.isReplEval) { + stackFrames = stackFrames.slice(0, i + 1); + break; + } + } + const promises = stackFrames.map(mapper); return (await Promise.all(promises)).join('\n') + '\n'; } @@ -216,6 +228,7 @@ export class StackFrame implements IFrameElement { | undefined; private _scope: IScope | undefined; private _thread: Thread; + public readonly isReplEval: boolean; public get rawPosition() { // todo: move RawLocation to use Positions, then just return that. @@ -254,6 +267,7 @@ export class StackFrame implements IFrameElement { this._rawLocation = rawLocation; this.uiLocation = once(() => thread.rawLocationToUiLocation(rawLocation)); this._thread = thread; + this.isReplEval = callFrame.url.endsWith(SourceConstants.ReplExtension); } /** @@ -405,9 +419,7 @@ export class StackFrame implements IFrameElement { async formatAsNative(): Promise { const uiLocation = await this.uiLocation(); const url = - (await uiLocation?.source.existingAbsolutePath()) || - uiLocation?.source.url || - (await uiLocation?.source.prettyName()); + (await uiLocation?.source.existingAbsolutePath()) || (await uiLocation?.source.prettyName()); const { lineNumber, columnNumber } = uiLocation || this._rawLocation; return ` at ${this._name} (${url}:${lineNumber}:${columnNumber})`; } diff --git a/src/adapter/threads.ts b/src/adapter/threads.ts index 71339f91f..85b0d46de 100644 --- a/src/adapter/threads.ts +++ b/src/adapter/threads.ts @@ -2,6 +2,7 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ +import { randomBytes } from 'crypto'; import * as nls from 'vscode-nls'; import Cdp from '../cdp/api'; import { DebugType } from '../common/contributionUtils'; @@ -141,6 +142,9 @@ const sourcesEqual = (a: Dap.Source, b: Dap.Source) => a.sourceReference === b.sourceReference && urlUtils.comparePathsWithoutCasing(a.path || '', b.path || ''); +const getReplSourceSuffix = () => + `\n//# sourceURL=eval-${randomBytes(4).toString('hex')}${SourceConstants.ReplExtension}\n`; + export class Thread implements IVariableStoreLocationProvider { private static _lastThreadId = 0; public readonly id: number; @@ -469,6 +473,7 @@ export class Thread implements IVariableStoreLocationProvider { params.awaitPromise = true; } } + params.expression += getReplSourceSuffix(); } const responsePromise = this.evaluator.evaluate( diff --git a/src/test/console/consoleFormatTest.ts b/src/test/console/consoleFormatTest.ts index aa1faa3cd..52b8eaa71 100644 --- a/src/test/console/consoleFormatTest.ts +++ b/src/test/console/consoleFormatTest.ts @@ -447,7 +447,7 @@ describe('console format', () => { const evaluation = handle.dap.evaluate({ expression: 'doLog("hello world");\n//# sourceURL=dont-ignore-me.js', - context: 'repl', + context: 'watch', }); const output = await handle.dap.once('output'); await evaluation; diff --git a/src/test/evaluate/evaluate-default.txt b/src/test/evaluate/evaluate-default.txt index a453ae717..4ede58669 100644 --- a/src/test/evaluate/evaluate-default.txt +++ b/src/test/evaluate/evaluate-default.txt @@ -73,14 +73,14 @@ result: 3 Evaluating#1: setTimeout(() => { throw new Error('bar')}, 0) stderr> Uncaught Error Error: bar - at (http://localhost:8001/eval1.js:1:26) + at (localhost꞉8001/eval1.js:1:26) --- setTimeout --- - at (http://localhost:8001/eval1.js:1:1) + at (localhost꞉8001/eval1.js:1:1) stderr> > Uncaught Error Error: bar - at (http://localhost:8001/eval1.js:1:26) + at (localhost꞉8001/eval1.js:1:26) --- setTimeout --- - at (http://localhost:8001/eval1.js:1:1) + at (localhost꞉8001/eval1.js:1:1) stderr> @ localhost꞉8001/eval1.js:1:26 ◀ setTimeout ▶ diff --git a/src/test/evaluate/evaluate-repl.txt b/src/test/evaluate/evaluate-repl.txt index d55487648..a7331418d 100644 --- a/src/test/evaluate/evaluate-repl.txt +++ b/src/test/evaluate/evaluate-repl.txt @@ -5,15 +5,15 @@ result: 'foo' result: 1234567890n : Uncaught Error Error: foo - at (/VM:1:7) + at (repl:1:7) : Uncaught Object Object - at (/VM:1:1) + at (repl:1:1) : Uncaught Error 42 - at (/VM:1:1) + at (repl:1:1) > result: {foo: 3} @@ -21,7 +21,7 @@ result: 1234567890n > [[Prototype]]: Object : Uncaught ReferenceError ReferenceError: baz is not defined - at (/VM:1:1) + at (repl:1:1) > result: Map(1) {size: 1, hello => ƒ ()} @@ -31,59 +31,59 @@ result: 1234567890n result: 42 stderr> Uncaught Error Error: bar - at (/VM:1:26) + at (repl:1:26) --- setTimeout --- - at (/VM:1:1) + at (repl:1:1) stderr> > Uncaught Error Error: bar - at (/VM:1:26) + at (repl:1:26) --- setTimeout --- - at (/VM:1:1) + at (repl:1:1) stderr> - @ /VM:1:26 + @ repl:1:26 ◀ setTimeout ▶ - @ /VM:1 + @ repl:1 result: 42 stderr> Uncaught Error Error: baz - at (/VM:1:26) + at (repl:1:26) --- setTimeout --- - at (/VM:1:1) + at (repl:1:1) stderr> > Uncaught Error Error: baz - at (/VM:1:26) + at (repl:1:26) --- setTimeout --- - at (/VM:1:1) + at (repl:1:1) stderr> - @ /VM:1:26 + @ repl:1:26 ◀ setTimeout ▶ - @ /VM:1 + @ repl:1 : Uncaught Error Error: error1 at throwError (${workspaceFolder}/web/browserify/module1.ts:6:9) - at (/VM:1:8) + at (repl:1:8) : Uncaught Object Object at throwValue (${workspaceFolder}/web/browserify/module1.ts:9:3) - at (/VM:1:8) + at (repl:1:8) result: 42 stderr> Uncaught Error Error: error2 at throwError (${workspaceFolder}/web/browserify/module1.ts:6:9) - at (/VM:1:27) + at (repl:1:27) --- setTimeout --- - at (/VM:1:1) + at (repl:1:1) stderr> > Uncaught Error Error: error2 at throwError (${workspaceFolder}/web/browserify/module1.ts:6:9) - at (/VM:1:27) + at (repl:1:27) --- setTimeout --- - at (/VM:1:1) + at (repl:1:1) stderr> throwError @ ${workspaceFolder}/web/browserify/module1.ts:6:9 - @ /VM:1:27 + @ repl:1:27 ◀ setTimeout ▶ - @ /VM:1 + @ repl:1