```ts
type A = any;
const B = 0;
export { A, B }
^^^^^^^^ ExportSpecifiers
export { A }
^^^^^ type-only ExportSpecifiers
```
non-type-only `ExportSpecifier` can reference value and type symbols. but currently, `IdentifierReference` in ExportSpecifier only has a `ReferenceFlags::Read`
close: #3969close: #2023
We need to add scope according to [this](d8086f14b6/src/compiler/binder.ts (L3883-L3962)). There are still some ASTs that need to be added to the scope.
---
Context from @Boshen:
Before this whole journey of fixing symbols and scopes I asked @Dunqing to debug through binder.ts via a debugger to fully understand how it does resolution.
We then agreed to align the implementation so that when a problem occurs, we can debug through both implementations and find where our problem is.
tsc doesn't have a specification, so we need to align with the reference implementation instead.
We have a strange workaround for `visit_function` where we pass in `ScopeFlags`, to support creating the scope inside `Function`, but setting different flags for `MethodDefinition`s.
Previously `visit_function` took `Option<ScopeFlags>` and then did `flags.unwrap_or(ScopeFlags::empty()) | ScopeFlags::Function` to it. Personally, I found this confusing. When I was looking at `MethodDefinition`, I was wondering "It's a function, why doesn't it set Function flag too?"
This changes makes it more explicit and clear what `ScopeFlags` everything has.
Instead of calling `bind_function_or_class_expression` for every scope, and then branching on the AST node kind, insert the relevant code into the visitors for functions and classes. This reduces work on all other kinds of scopes e.g. block statements.
Previously:
```rs
let ix = control_flow!(|self, cfg| cfg.current_node_ix);
```
after this PR:
```rs
let ix = control_flow!(self, |cfg| cfg.current_node_ix);
```
It expands to:
```rs
let ix = if let Some(ref mut cfg) = self.cfg {
cfg.current_node_ix
} else {
Default::default()
};
```
So rationale for this change is that it makes it clearer that `self` is passed *in* and `cfg` comes *out* into the "closure".
Control flow graph builder records AST node IDs in a temp structure `ast_nodes_records`. Only the first AST node ID inserted into the `Vec` is ever used, so turn it into just a single value.
Using `AstNodeId::dummy()` as sentinel for "not set" instead of using `Option<AstNodeId>` to reduce size of the records stack. `Option<AstNodeId>` is 16 bytes.
```ts
export default class Binding {}
// ^^^^^^^ SymbolFlags::Export
export default function Binding () {}
// ^^^^^^^ SymbolFlags::Export
// No binding, so we should not have SymbolFlags::Export
export default function() {}
export default class {}
```
related: https://github.com/oxc-project/oxc/issues/4142#issuecomment-2219125356
Although we called `enter_node(Class)`, that doesn't mean we're in the `class` scope. It causes our must to visit decorators before `enter_node`.
Let's look at this case. It causes a syntax error if we don't visit decorators before `enter_node`
```js
// This file was procedurally generated from the following sources:
// - src/decorator/decorator-call-expr-identifier-reference-yield.case
// - src/decorator/syntax/valid/cls-expr-decorators-valid-syntax.template
/*---
description: Decorator @ DecoratorCallExpression (Valid syntax for decorator on class expression)
esid: prod-ClassExpression
features: [class, decorators]
flags: [generated, noStrict]
info: |
ClassExpression[Yield, Await] :
DecoratorList[?Yield, ?Await]opt class BindingIdentifier[?Yield, ?Await]opt ClassTail[?Yield, ?Await]
DecoratorList[Yield, Await] :
DecoratorList[?Yield, ?Await]opt Decorator[?Yield, ?Await]
Decorator[Yield, Await] :
@ DecoratorMemberExpression[?Yield, ?Await]
@ DecoratorParenthesizedExpression[?Yield, ?Await]
@ DecoratorCallExpression[?Yield, ?Await]
...
DecoratorCallExpression[Yield, Await] :
DecoratorMemberExpression[?Yield, ?Await] Arguments[?Yield, ?Await]
DecoratorMemberExpression[Yield, Await] :
IdentifierReference[?Yield, ?Await]
DecoratorMemberExpression[?Yield, ?Await] . IdentifierName
DecoratorMemberExpression[?Yield, ?Await] . PrivateIdentifier
IdentifierReference[Yield, Await] :
[~Yield] yield
...
---*/
function decorator() {
return () => {};
}
var yield = decorator;
var C = @yield() class {};
```
Errors:
```shell
× The keyword 'yield' is reserved
╭─[language/statements/class/decorator/syntax/valid/decorator-call-expr-identifier-reference-yield.js:45:2]
44 │
45 │ @yield() class C {}
· ─────
╰────
```
The changed code makes more sense. Only if we call `enter_scope` for class, the flags will contain `StrictMode`. Also, we can get the exact `flags` of the `scope` in the `class` at the transformer
For example:
```
class A {
B() {
// Before: flags is `Function`
// After: flags is `Function | StrictMode`
}
}
```
The regression tests will be fixed in follow-up PRs
```ts
class Klass <T> extends Root <R> {}
// ^^^^^ ^^^ ^^^^ ^^^ ^^
// id type_paramter super_class super_type_parameters body
```
I reorder fields according to the order above
The class scope is not defined in the spec. But we need to create a scope for `class` to store `TypeParamters`
Semantic resolves references when exiting `FormalParameters` to ensure references in param initializers don't get bound to bindings inside the function body.
However, it shouldn't do this for `FormalParameters` in TS types e.g. `TSTypeAnnotation` because they don't have their own scopes.
I'm going to be AFK today(till about 9 PM UTC). Meanwhile, I Didn't want to be a blocker so here we go.
It would fix the #4200 merge if you guys find it in the correct order otherwise feel free to close it.
Reduce memory copies when resolving references in `Semantic`.
If parent scope has no unresolved references for `name`, there is no need to generate a new `Vec<ReferenceId>` for `name` and copy in contents from current scope. Just move the existing `Vec` to the parent.
Closes#4169.
Reduce allocations while building `Semantic` by recycling hash maps used for tracking unresolved references, rather than creating a new one for every scope.
close: #4186
CatchClause has two scopes. The first one is `CatchClause`, which will add a `CatchParameter` to it. The second one is `Block`, which will add binding that declares in the current block scope.
The spec has a syntax error about `CatchParameter`
- It is a Syntax Error if any element of the BoundNames of CatchParameter also occurs in the LexicallyDeclaredNames of Block.
This reworks `ScopeTree` in order to keep a single (root) map of
unresolved references. The `SemanticBuilder` keeps track of all
intermediate ones while walking scopes, and it can get rid of all
non-root ones once done.
Ref: https://github.com/oxc-project/backlog/issues/32
Adds checks to `TSImportEqualsDeclaration` for invalid use of `import type` modifier.
```ts
import { Foo } from './foo'
namespace Bar {
export class Baz {}
}
import type A = Foo.Baz; // not allowed
import type B = Bar.Baz; // not allowed
import type C = require('./c'); // allowed
```