forked from babel/babel
/
scope.ts
158 lines (133 loc) 路 4.5 KB
/
scope.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import type { Position } from "../../util/location";
import ScopeHandler, { Scope } from "../../util/scope";
import {
BIND_KIND_TYPE,
BIND_FLAGS_TS_ENUM,
BIND_FLAGS_TS_CONST_ENUM,
BIND_FLAGS_TS_EXPORT_ONLY,
BIND_KIND_VALUE,
BIND_FLAGS_CLASS,
type ScopeFlags,
type BindingTypes,
BIND_FLAGS_TS_IMPORT,
SCOPE_TS_MODULE,
} from "../../util/scopeflags";
import type * as N from "../../types";
import { Errors } from "../../parse-error";
class TypeScriptScope extends Scope {
types: Set<string> = new Set();
// enums (which are also in .types)
enums: Set<string> = new Set();
// const enums (which are also in .enums and .types)
constEnums: Set<string> = new Set();
// classes (which are also in .lexical) and interface (which are also in .types)
classes: Set<string> = new Set();
// namespaces and ambient functions (or classes) are too difficult to track,
// especially without type analysis.
// We need to track them anyway, to avoid "X is not defined" errors
// when exporting them.
exportOnlyBindings: Set<string> = new Set();
}
// See https://github.com/babel/babel/pull/9766#discussion_r268920730 for an
// explanation of how typescript handles scope.
export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope> {
importsStack: Set<string>[] = [];
createScope(flags: ScopeFlags): TypeScriptScope {
this.importsStack.push(new Set()); // Always keep the top-level scope for export checks.
return new TypeScriptScope(flags);
}
enter(flags: number): void {
if (flags == SCOPE_TS_MODULE) {
this.importsStack.push(new Set());
}
super.enter(flags);
}
exit() {
const flags = super.exit();
if (flags == SCOPE_TS_MODULE) {
this.importsStack.pop();
}
return flags;
}
hasImport(name: string, allowShadow?: boolean) {
const len = this.importsStack.length;
if (this.importsStack[len - 1].has(name)) {
return true;
}
if (!allowShadow && len > 1) {
for (let i = 0; i < len - 1; i++) {
if (this.importsStack[i].has(name)) return true;
}
}
return false;
}
declareName(name: string, bindingType: BindingTypes, loc: Position) {
if (bindingType & BIND_FLAGS_TS_IMPORT) {
if (this.hasImport(name, true)) {
this.parser.raise(Errors.VarRedeclaration, {
at: loc,
identifierName: name,
});
}
this.importsStack[this.importsStack.length - 1].add(name);
return;
}
const scope = this.currentScope();
if (bindingType & BIND_FLAGS_TS_EXPORT_ONLY) {
this.maybeExportDefined(scope, name);
scope.exportOnlyBindings.add(name);
return;
}
super.declareName(name, bindingType, loc);
if (bindingType & BIND_KIND_TYPE) {
if (!(bindingType & BIND_KIND_VALUE)) {
// "Value" bindings have already been registered by the superclass.
this.checkRedeclarationInScope(scope, name, bindingType, loc);
this.maybeExportDefined(scope, name);
}
scope.types.add(name);
}
if (bindingType & BIND_FLAGS_TS_ENUM) scope.enums.add(name);
if (bindingType & BIND_FLAGS_TS_CONST_ENUM) scope.constEnums.add(name);
if (bindingType & BIND_FLAGS_CLASS) scope.classes.add(name);
}
isRedeclaredInScope(
scope: TypeScriptScope,
name: string,
bindingType: BindingTypes,
): boolean {
if (scope.enums.has(name)) {
if (bindingType & BIND_FLAGS_TS_ENUM) {
// Enums can be merged with other enums if they are both
// const or both non-const.
const isConst = !!(bindingType & BIND_FLAGS_TS_CONST_ENUM);
const wasConst = scope.constEnums.has(name);
return isConst !== wasConst;
}
return true;
}
if (bindingType & BIND_FLAGS_CLASS && scope.classes.has(name)) {
if (scope.lexical.has(name)) {
// Classes can be merged with interfaces
return !!(bindingType & BIND_KIND_VALUE);
} else {
// Interface can be merged with other classes or interfaces
return false;
}
}
if (bindingType & BIND_KIND_TYPE && scope.types.has(name)) {
return true;
}
return super.isRedeclaredInScope(scope, name, bindingType);
}
checkLocalExport(id: N.Identifier) {
const { name } = id;
if (this.hasImport(name)) return;
const len = this.scopeStack.length;
for (let i = len - 1; i >= 0; i--) {
const scope = this.scopeStack[i];
if (scope.types.has(name) || scope.exportOnlyBindings.has(name)) return;
}
super.checkLocalExport(id);
}
}