microsoft / vscode

Visual Studio Code
https://code.visualstudio.com
MIT License
164.78k stars 29.48k forks source link

Step Into (F11) steps into transpiled JavaScript when debugging TypeScript that uses async/await #4798

Closed jpierson closed 8 years ago

jpierson commented 8 years ago

Steps to Reproduce:

  1. Set a break point on a line of TypeScript code in an async function that uses the await keyword on the Promise result of another async function. Example: const result = await callMyApi().
  2. Start debugging in VS Code by selecting a related launch profile in a TypScript project. See my example snippet below.
  3. Run necessary code so that the debugger is stopped on the breakpoint from step 1.
  4. Press F11 to step into the callMyApi function.

Expected Result: The .ts TypeScript file where callMyApi implementation exists should be opened and the debugger should be stopped on the first line of that function.

Actual Result: The .js transpiled JavaScript file is opened and the debugger is stopped on the first line of the that file.

/// launch.json snippet
{
    "name": "Test",
    "type": "node",
    "request": "launch",
    "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
    "stopOnEntry": false,
    "args": [
        "./bin/test/my.test.js"
    ],
    "cwd": "${workspaceRoot}",
    "runtimeExecutable": null,
    "runtimeArgs": [
        "--lazy",
        "--harmony", "--harmony_modules","--harmony_destructuring"
    ],
    "env": {
        "NODE_ENV": "development"
    },
    "externalConsole": false,
    "sourceMaps": true,
    "outDir": "${workspaceRoot}/bin"
}
// tsconfig.json compiler options snippet
{
    "compilerOptions": {
        "target": "es6",
        "module": "commonjs",
        "sourceMap": true,
        "allowJs": true,
        "outDir": "bin"
    }
   // excludes ...
}
weinand commented 8 years ago

@jpierson this basically a problem of the generated source map and the fact that VS Code does not (yet) tries to be smart. Here is my example:

async function longRunning() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(42);
        }, 3000);
    });
}

async function main() {
    const result = await longRunning();
    console.log(`the answer to everything: ${result}`);
}

main();

As you can see in this visualization of the generated source map, the __awaiter function has no mapping to the TypeScript source. That's the reason why VS Code shows the generated code. I think this is a good strategy because now you can clearly see that you need to do another two F11s to step into the longRunning function.

On the other hand if the source map would have a mapping for __awaiter you would end up in some strange place in the TypeScript source and you would not know how to proceed.

So the current behaviour isn't optimal but it is not buggy either. I've added a 'feature request' label.

I'm planning to make node-debug smarter by detecting the __awaiter function and automatically issuing 'step into' requests.

weinand commented 8 years ago

I've added experimental support for automatically skipping code that has a source map but where the mapping does not exist and would end up in the generated code. This can be enabled in the launch config by setting an attribute smartStep to true.

@jpierson @felixfbecker please let me know if you think this is useful.

felixfbecker commented 8 years ago

@weinand If I understand you correctly, this is more an issue with TypeScript's/Babel's source map generation?

I have tried out the tool with your example, but compiled with the gulpfile from my debug adapter: link You have to scroll down to the TS source because I do two-step compilation with TS+Babel. The await may not be colored, but I hover over it, it highlights the corresponding yield statement in the generator.

I think though that transpilation that adds additional function calls is quite common and if you can somehow detect and ignore those that would be a great feature. As a user, when I land in the compiled source, it is not clear to me that I have to do a step over to get to the TS again.

I tried to test the new feature by running vsce package in the repo and installing the package, but I get countless ENOENT errors like cannot read package.json or locale files, what am I doing wrong? Do I have to recompile VS Code?

weinand commented 8 years ago

@felixfbecker I've fixed the dependencies in vscode-node-debug and after removing node_modules and doing an npm install the vsce package created a vsix that works with the insiders release. There is no need to rebuild VS Code.

weinand commented 8 years ago

@felixfbecker my experimental "smart step" feature should be independent from what transpiler tools are used to generate JavaScript and source maps. The feature should just hide generated code by auto stepping through it. If you enable the feature you can see on the console how many "step in" commands were issued.

felixfbecker commented 8 years ago

@weinand I am still hitting the source files. Ran git pull, rm -re -fo node_modules, npm i, gulp build, vsce package and installed it

weinand commented 8 years ago

@felixfbecker how it should look like: https://dl.dropboxusercontent.com/u/2433608/2016-04-01%2014-23-27.mp4 (before/after)

felixfbecker commented 8 years ago

@weinand No idea why it's not working, can you send me your vsix?

weinand commented 8 years ago

@felixfbecker here is my sample project: async.zip and here the vsix: node-debug-0.10.12.vsix.zip (double zipped...)

felixfbecker commented 8 years ago

I meant the extension file as apparently I can't get it to compile and install correctly

weinand commented 8 years ago

@felixfbecker I'd like to know what problems you are seeing when trying to build vscode-node-debug? Is this on Windows? I just tried it there and for me it works.

weinand commented 8 years ago

@felixfbecker when looking at your sourec map I get the impression that the double mapping might be the issue. I'll have to try my code on the result of your way of transpiling TypScript... With your source map it could be that the mapping process always succeeds even if the result of the mapping is again generated JavaScript (and not TypeScript). That's actually an interesting test case for the feature.

felixfbecker commented 8 years ago

@weinand I think my Insiders build might be broken, getting all kinds of ENOENT and null pointer excs. Don't have time to investigate further right now. But +1 for the feature.

weinand commented 8 years ago

I've add experimental support for this. You can enable this feature by adding an attribute smartStep with a value of true to your launch config.

Spown commented 8 years ago

I recon this smartStep option only works with TypeScript? Can we (pure js users) has this option also? For stepping only into project files and jumping over 'uninteresting code' like node_modules/**? Particularly #3215

weinand commented 8 years ago

@Spown feature request https://github.com/Microsoft/vscode/issues/6245 covers your suggestion.

amcdnl commented 8 years ago

@weinand Is smartStep supposed to cover the issue of the debugger jumping as illistrated here: https://github.com/mozilla/source-map/issues/221

I'm running TypeScript 2.0 & VSCode Insiders and having the jumping issue; although it does fix the step into issue.

Demo can be seen here: https://github.com/swimlane/node-microservice-demo/tree/master/petstore by running Launch, set a breakpoint here: https://github.com/swimlane/node-microservice-demo/blob/master/petstore/src/controllers/PetStoreController.ts#L28 and then hit localhost:8080/pets in your browser and step into that function.

weinand commented 8 years ago

@amcdnl basically yes. But I didn't try it with the examples from mozilla/source-map#221 (because I'm not using Babel). The 'smartstep' approach is trivial: the debug adapter automatically executes additional 'step' requests if it had stopped in JavaScript that is not covered by the source map. This does not always eliminate the 'jumping around' completely, but it avoids showing the generated code.

amcdnl commented 8 years ago

@weinand

But I didn't try it with the examples from mozilla/source-map#221 (because I'm not using Babel).

Yup, I was just posting that for reference as this happens in both situations. My current use case that I posted is TypeScript.

This does not always eliminate the 'jumping around' completely, but it avoids showing the generated code.

Well a step ( pun haha ) in the right direction, do you know if there is an issue tracking the jumping somewhere?

weinand commented 8 years ago

@amcdnl please create a new issue.

amcdnl commented 8 years ago

@weinand done. Thanks for feedback.