New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add TS support to @babel/parser's Scope #9766
Add TS support to @babel/parser's Scope #9766
Conversation
nicolo-ribaudo
commented
Mar 25, 2019
•
edited
edited
Q | A |
---|---|
Fixed Issues? | Fixes #9763, fixes #9480 |
Patch: Bug Fix? | Yes |
Major: Breaking Change? | |
Minor: New Feature? | |
Tests Added + Pass? | Yes |
Documentation PR Link | |
Any Dependency Changes? | |
License | MIT |
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/10668/ |
0f7a245
to
ed7b6d6
Compare
const scope = this.currentScope(); | ||
scope.lexical.push(name); | ||
} else if (bindingType === BIND_FUNCTION) { | ||
if ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes in this file are mostly refactoring things in different functions, to make it possible to overwrite them.
@@ -41,12 +41,14 @@ export const BIND_NONE = 0, // Not a binding | |||
BIND_LEXICAL = 2, // Let- or const-style binding | |||
BIND_FUNCTION = 3, // Function declaration | |||
BIND_SIMPLE_CATCH = 4, // Simple (identifier pattern) catch binding | |||
BIND_OUTSIDE = 5; // Special case for function names as bound inside the function | |||
BIND_OUTSIDE = 5, // Special case for function names as bound inside the function | |||
BIND_TS_ENUM = 6; // TypeScript enums (block-scoped, but can be re-declared using var/function) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A complete fix for #9763 would also need to keep track of other typescript declarations.
Also, this is not quite how TS Enums work. enum
can be declared multiple times, but only with enum
or namespace
. It might be useful to keep track of which types are in "type space" or "value space".
For example, type A = {}; function A() {}
are completely different and unrelated -- they are two separate bindings (and both are hoisted!); they just share the same name.
Here's an attempt to explain how the declarations (are supposed to) work. The "type" part of declarations is always hoisted.
\ | value | type |
---|---|---|
namespace |
maybe | yes, can hold other declarations |
enum |
yes, let -like |
yes, namespace -like |
class |
yes, let -like |
yes (refers to the instance) |
interface |
no | yes |
type |
no | yes |
var |
yes | no |
let |
yes | no |
const |
yes | no |
function |
yes | no |
Generic arguments aren't here but they are type
-like.
namespace
and enum
(besides an ES module itself) are the only kinds of type that can hold other types or declarations inside them. class
"looks like" it would behave like that but they are interface
-like instead of namespace
-like -- but class
can merge with a namespace
, which affects the value portion class
(i.e. they're part of the constructor, not the instance). class
can also merge with interface
, which affects the type portion of class
(the instance type).
✅ = merges
🔴 = invalid
⚪️ = no merging and no conflict; they are separate entities
❗️ = no conflict if and only if the type annotations are identical
Merging | namespace |
enum |
class |
interface |
type |
var |
let |
const |
function |
---|---|---|---|---|---|---|---|---|---|
namespace |
✅ | ✅ | ✅ | ✅ | 🔴 | ⚠ | ⚠ | ✅ | |
enum |
✅ | ✅ | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 |
class |
✅ | 🔴 | 🔴 | ✅ | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 |
interface |
✅ | 🔴 | ✅ | ✅ | 🔴 | ⚪️ | ⚪️ | ⚪️ | ⚪️ |
type |
🔴 | 🔴 | 🔴 | 🔴 | 🔴 | ⚪️ | ⚪️ | ⚪️ | ⚪️ |
var |
⚠ | 🔴 | 🔴 | ⚪️ | ⚪️ | ❗️ | 🔴 | 🔴 | 🔴 |
let |
⚠ | 🔴 | 🔴 | ⚪️ | ⚪️ | 🔴 | 🔴 | 🔴 | 🔴 |
const |
⚠ | 🔴 | 🔴 | ⚪️ | ⚪️ | 🔴 | 🔴 | 🔴 | 🔴 |
function |
✅ | 🔴 | 🔴 | ⚪️ | ⚪️ | 🔴 | 🔴 | 🔴 | * |
*
: multiple declarations of a function / method on the same scope are only valid if the declarations are overloads; only one of the declarations is allowed to have an implementation (the curly braces block), and it must be the last one.
type
can maybe be understood as the type-only const
equivalent, as it is block-scoped and it can't merge with anything or be reassigned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks you for the detailed table!
I think that we for the (var
...functon
)x(var
...functon
) cells, we should follow JavaScript's behavior, since we can't rely on type info in Babel.
As for
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "type" part of declarations is always hoisted.
Always to the block, not to the function, right?
today it seems we have 3 issues with named exports in typescript:
this PR should fix 1) and maybe 2). Issue 3 is hidden today since parser fails before transform. I have a branch attempting to fix these, but I'm doing so with existing scope module, and it's pretty dirty. Here is one of the tests from my branch The parser should be able to parse this with no errors |
This example now fails although it shouldn't, and may be because the SIMPLE_CATCH was removed?: try {} catch (a) {
if(1) function a(){}
} https://tc39.github.io/ecma262/#sec-variablestatements-in-catch-blocks Although I just noticed we already have a testcase for this which was untouched. So not sure why it is failing in the repl. |
@danez It is already failing on master: https://babeljs.io/repl/build/master#?babili=false&browsers=IE%207&build=&builtIns=false&spec=false&loose=false&code_lz=C4JwngBA3gvhDGBDY8AWEAUiCU0ICgIIBLAMwwEZdSBXAO3mGIHs6JENtYCYg&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=script&lineWrap=false&presets=es2015%2Creact%2Cstage-2&prettier=true&targets=&version=7.4.3&externalPlugins= I'd prefer to fix it in a separate PR since it seems unrelated.
Maybe it's |
Sure, but I just wonder why the test works. https://github.com/babel/babel/blob/master/packages/babel-parser/test/fixtures/core/scope/dupl-bind-catch-hang-func/input.js But in the repl it doesn't. |
Because that tests doesn't run |
🤦♂️I thought the error comes from the parser. |
@danez I will add a few more commits to address the link in #9766 (comment) |
@airato Your test is now correctly parsed. |
Hey, is this PR going anywhere? It doesn't seem to have much activity in the past couple of weeks. What all is it waiting on? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good 👍
Sorry for taking so long to review this 🙇♀️
It could be good to have const enum
tests as well, just in case. TypeScript requires the const
ness to be the same (but the error message it emits when they differ is nonsense).
): boolean { | ||
if (scope.enums.indexOf(name) > -1) { | ||
// Enums can be merged with other enums | ||
return !(bindingType & BIND_FLAGS_TS_ENUM); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enums can merge with namespaces too; I guess the error is avoided by not calling super.declareName
for namespaces?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah exactly.
SCOPE_SIMPLE_CATCH was used instead
1) Move this.scope.exit() for functions from parseFunctionBody to the callers. Otherwise the scope of body-less functions was never closed. Also, it is easier to track scope.exit() if it is near to scope.enter() 2) Register namespace ids for export
89fc433
to
be8e163
Compare
babel/babel#9766 was really needed.