vscode-js-debug
vscode-js-debug copied to clipboard
Transpile TypeScript in the REPL
We already use TypeScript for walking the tree and, for instance, determining whether we need to wrap async/await. It would be a simple task to have it transpile the code down as well. For that we only need to know the target and language (i.e. jsx, or not). The target could safely default to ES6 nowadays, and be configurable with an option. I think we can determine the language programatically without needing extra config.
cc @eamodio
One problem is that if you are paused, you actually need to transpile code in the context of the scope in which you are paused, and using the same settings. Otherwise you might reference a variable in the user code and have it renamed in one and not the other. TS doesn't give you a way to do that. Or your transpiled code might rename a variable and accidentally overwrite a variable in the user code.
I don't think transpilation will ever rename or unintentionally reassign variables referenced externally. That would be a major problem if that happened when running in browsers particularly, where it's common practice to have libraries exposed as globals and helpers attached to the window object.
I guess that's true, but in some cases it should rename the variable, and it can't know whether the referenced variable was renamed.
I guess I'm imagining a case where it renames a locally declared variable in the eval'd code, accidentally overwriting a variable in the user code that it doesn't know exists.
These are the reasons we couldn't do this in 2014, as best I can remember. 🤣 Ron or someone else on the TS team would probably have a better understanding of the risks
@rbuckton any thoughts here? Discussing consequences of making the debug REPL in VS Code automatically transpile TS 🙂
Thinking about this more, I believe it should be safe. Even if it reused a variable name referenced elsewhere in user code, this should shadow the name, not overwrite it. For an overwrite to happen to variable x TS compiler would need to emit something like x = foo;, which would (if x did not have a previous let/var) pollute the global scope. If it instead emits const x = foo; it would of course be shadowed in the REPL's function.
Here are a few caveats to consider, off the top of my head:
- Some transformations depend on various emit helpers at runtime, even for
--target ES2015, such as for object spread, async functions, async generators, etc. - Some of these emit helpers are locally scoped (such as the
_superhelper used in async methods that callsupermethods. - Some TS syntax depends on surrounding context to properly transpile:
importstatements that import type-only declarations will result in static errors if transpiling to ES modules:// a.ts export type Foo = 1; // REPL import { Foo } from "./a"; // error: `./a.mjs` doesn't have an export named `Foo`.- merged
namespacedeclarations auto-qualify symbols// REPL namespace N { export function foo() { } } namespace N { foo(); } // ts would resolve `foo` in `N` and transpile to `N.foo();` const enumdeclarations do not emit a value:// REPL const enum Color { Red, Green Blue }; Color.Red; // ts would normally replace `Color.Red` with `0` here.
Hm. I admit I haven't examined transpiled TS code in great detail, though many of those context-related issues are familiar to me in dealing with the frustrating isolated modules that come in create-react-app's TS mode 😛
I'm not too concerned about the 'global' helpers like async generators. I think generating helpers for REPL expressions is fine, and worst case am not opposed to sending tslib over the wire; wouldn't be the fastest or most beautiful approach, but sufficient for a REPL.
Locally scoped helpers are interesting, I didn't know about those. I does looks like tossing super.foo() through ts.transpileModule emits _super.foo.call(this);, supposedly looking for a TS helper. This would actually be a desirable behavior when debugging TS code, but quite the opposite if the user was debugging plain JavaScript sources. Is there any compiler API to disable the assumption that these local helpers are present? Putting this in the ts playground works as I'd like it to, but I assume that's because it's doing full type checking to see that it's not in a method call.