From:  Nick Fitzgerald <>
Date:  04 Feb 2015 01:21:30 Hong Kong Time

Re: Source Maps, Debuggers, and Generated Code


On Mon, Feb 2, 2015 at 2:06 PM, Ron Buckton 

> > 1. When the compiler needs to embed a runtime with various helper
> > functions in the generated JS code. In this case, I think it makes sense
> to add
> > an entry to the sources list and map the appropriate code to this runtime
> > source.
> Is there a general best-practice for this? Traceur adds an entry to the
> 'sources' list that is prefixed with "@traceur/generated", as well as
> adding an entry to 'sourcesContent' containing the same text written to the
> generated output file. Adding a string to sourceContent for this purpose
> seems like unnecessary overhead. TypeScript emits null mappings for each
> line of generated content.

​It depends on whether you want to step through the runtime source in
debuggers. If you do, then embed the runtime as a source in the sourcemap
like Traceur does. If you don't want to step through your runtime's source,
then use null mappings and debuggers should step through it.​

If you're concerned about sending the runtime source over the wire twice
(once in the generated JS code and once embedded in the source map) then
you can load the runtime as a separate script in debug builds. I don't have
any other great answers.

> > 2. When a single statement in the source language results in many
> > statements in the JS code. In this case, I believe that all the generated
> > statements in the JS code should map back to the same location in the
> source
> > language. When implementing source level single stepping, debuggers
> > should continue JS object code level single stepping until the source map
> > reports a new source location for the current JS code. This is
> conceptually the
> > same as when single stepping C-source level statements in gdb/lldb/etc
> that
> > map to multiple asm instructions: you don't want to pause after executing
> > each asm instruction, but once all the asm instructions for that C
> statement
> > have been executed.
> While this makes sense for statements that generate a sequential set of
> steps, it breaks down slightly when you are generating something like the
> downlevel emit for a generator or async function, where you might end up
> with something like this:
> ```TypeScript
> async function asyncFunc(p: Promise): Promise {
>   console.log("before");
>   var i = await p;
>   console.log("after");
>   return i;
> }
> ```
> Which generates (with the current Async Functions prototype, plus
> additional comments):
> ```js
> var __awaiter = ...; // runtime helper
> var __generator = ...; // runtime helper
> function asyncFunc(p) {
>   // (a)
>   return new Promise(function(_resolve) {
>     _resolve(__awaiter(__generator(function(_state) {
>         // (b)
>         switch (_state) {
>             // (c)
>             case 0:
>                 // (d)
>                 console.log("before");
>                 return [4 /*yield*/, p];
>             // (e)
>             case 1:
>                 // (f)
>                 i = _state.sent;
>                 console.log("after");
>                 return [2 /*return*/, i + 1];
>         }
>     });
>   });
>   var i;
> }
> ```
> When the debugger steps through 'asyncFunc', it will execute [a], [b],
> [c], and [d] immediately before yielding, and when the state machine is
> resumed it will execute [b], [c], [e], and [f]. As a result, [b] and [c]
> are executed twice (and for a larger function body with multiple 'case'
> clauses, it would likely step through each case clause up to the current
> "label"). Based on my understanding of your response below, we should emit
> "null" mappings for [a]-[c] and for [e]. When the debugger steps into the
> function for the first time it should step through until it hits [d], and
> after it resumes it should step through until it hits [f]. Is this an
> accurate assumption?

​This sounds correct to me.​