Add TS support to @babel/parser's Scope (#9766)
* [parser] Allow plugins to extend ScopeHandler
* Directly extend Scope
* Don't use new.target to get the ScopeHandler
* [parser] Add TS enum support to the Scope
* Remove duplicated options in tests
* Fix
* Fix flow
* Rename tests
* Add tests
* Full typescript support in scope
* Remove BIND_SIMPLE_CATCH
SCOPE_SIMPLE_CATCH was used instead
* Export TS types
* Register function declarations
* Fix body-less functions and namespaces
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
* Disallow redeclaration of enum with const enum
This commit is contained in:
@@ -105,7 +105,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
checkLVal(
|
||||
expr: N.Expression,
|
||||
bindingType: ?BindingTypes = BIND_NONE,
|
||||
bindingType: BindingTypes = BIND_NONE,
|
||||
checkClashes: ?{ [key: string]: boolean },
|
||||
contextDescription: string,
|
||||
): void {
|
||||
|
||||
@@ -2016,7 +2016,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
checkLVal(
|
||||
expr: N.Expression,
|
||||
bindingType: ?BindingTypes = BIND_NONE,
|
||||
bindingType: BindingTypes = BIND_NONE,
|
||||
checkClashes: ?{ [key: string]: boolean },
|
||||
contextDescription: string,
|
||||
): void {
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
// @flow
|
||||
|
||||
import type { TokenType } from "../tokenizer/types";
|
||||
import { types as tt } from "../tokenizer/types";
|
||||
import { types as ct } from "../tokenizer/context";
|
||||
import * as N from "../types";
|
||||
import type { Pos, Position } from "../util/location";
|
||||
import Parser from "../parser";
|
||||
import { type BindingTypes, BIND_NONE, SCOPE_OTHER } from "../util/scopeflags";
|
||||
import type { TokenType } from "../../tokenizer/types";
|
||||
import { types as tt } from "../../tokenizer/types";
|
||||
import { types as ct } from "../../tokenizer/context";
|
||||
import * as N from "../../types";
|
||||
import type { Pos, Position } from "../../util/location";
|
||||
import type Parser from "../../parser";
|
||||
import {
|
||||
type BindingTypes,
|
||||
BIND_NONE,
|
||||
SCOPE_OTHER,
|
||||
BIND_TS_ENUM,
|
||||
BIND_TS_CONST_ENUM,
|
||||
BIND_TS_TYPE,
|
||||
BIND_TS_INTERFACE,
|
||||
BIND_TS_FN_TYPE,
|
||||
BIND_TS_NAMESPACE,
|
||||
} from "../../util/scopeflags";
|
||||
import TypeScriptScopeHandler from "./scope";
|
||||
|
||||
type TsModifier =
|
||||
| "readonly"
|
||||
@@ -69,6 +80,10 @@ function keywordTypeFromName(
|
||||
|
||||
export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
class extends superClass {
|
||||
getScopeHandler(): Class<TypeScriptScopeHandler> {
|
||||
return TypeScriptScopeHandler;
|
||||
}
|
||||
|
||||
tsIsIdentifier(): boolean {
|
||||
// TODO: actually a bit more complex in TypeScript, but shouldn't matter.
|
||||
// See https://github.com/Microsoft/TypeScript/issues/15008
|
||||
@@ -1017,6 +1032,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
node: N.TsInterfaceDeclaration,
|
||||
): N.TsInterfaceDeclaration {
|
||||
node.id = this.parseIdentifier();
|
||||
this.checkLVal(
|
||||
node.id,
|
||||
BIND_TS_INTERFACE,
|
||||
undefined,
|
||||
"typescript interface declaration",
|
||||
);
|
||||
node.typeParameters = this.tsTryParseTypeParameters();
|
||||
if (this.eat(tt._extends)) {
|
||||
node.extends = this.tsParseHeritageClause("extends");
|
||||
@@ -1031,6 +1052,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
node: N.TsTypeAliasDeclaration,
|
||||
): N.TsTypeAliasDeclaration {
|
||||
node.id = this.parseIdentifier();
|
||||
this.checkLVal(node.id, BIND_TS_TYPE, undefined, "typescript type alias");
|
||||
|
||||
node.typeParameters = this.tsTryParseTypeParameters();
|
||||
node.typeAnnotation = this.tsExpectThenParseType(tt.eq);
|
||||
this.semicolon();
|
||||
@@ -1099,6 +1122,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
): N.TsEnumDeclaration {
|
||||
if (isConst) node.const = true;
|
||||
node.id = this.parseIdentifier();
|
||||
this.checkLVal(
|
||||
node.id,
|
||||
isConst ? BIND_TS_CONST_ENUM : BIND_TS_ENUM,
|
||||
undefined,
|
||||
"typescript enum declaration",
|
||||
);
|
||||
|
||||
this.expect(tt.braceL);
|
||||
node.members = this.tsParseDelimitedList(
|
||||
"EnumMembers",
|
||||
@@ -1126,11 +1156,22 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
tsParseModuleOrNamespaceDeclaration(
|
||||
node: N.TsModuleDeclaration,
|
||||
nested?: boolean = false,
|
||||
): N.TsModuleDeclaration {
|
||||
node.id = this.parseIdentifier();
|
||||
|
||||
if (!nested) {
|
||||
this.checkLVal(
|
||||
node.id,
|
||||
BIND_TS_NAMESPACE,
|
||||
null,
|
||||
"module or namespace declaration",
|
||||
);
|
||||
}
|
||||
|
||||
if (this.eat(tt.dot)) {
|
||||
const inner = this.startNode();
|
||||
this.tsParseModuleOrNamespaceDeclaration(inner);
|
||||
this.tsParseModuleOrNamespaceDeclaration(inner, true);
|
||||
node.body = inner;
|
||||
} else {
|
||||
node.body = this.tsParseModuleBlock();
|
||||
@@ -1260,7 +1301,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
switch (starttype) {
|
||||
case tt._function:
|
||||
return this.parseFunctionStatement(nany);
|
||||
return this.parseFunctionStatement(
|
||||
nany,
|
||||
/* async */ false,
|
||||
/* declarationPosition */ true,
|
||||
);
|
||||
case tt._class:
|
||||
return this.parseClass(
|
||||
nany,
|
||||
@@ -1531,6 +1576,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
super.parseFunctionBodyAndFinish(node, type, isMethod);
|
||||
}
|
||||
|
||||
checkFunctionStatementId(node: N.Function): void {
|
||||
if (!node.body && node.id) {
|
||||
this.checkLVal(node.id, BIND_TS_FN_TYPE, null, "function name");
|
||||
} else {
|
||||
super.checkFunctionStatementId(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
parseSubscript(
|
||||
base: N.Expression,
|
||||
startPos: number,
|
||||
@@ -2214,7 +2267,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
checkLVal(
|
||||
expr: N.Expression,
|
||||
bindingType: ?BindingTypes = BIND_NONE,
|
||||
bindingType: BindingTypes = BIND_NONE,
|
||||
checkClashes: ?{ [key: string]: boolean },
|
||||
contextDescription: string,
|
||||
): void {
|
||||
105
packages/babel-parser/src/plugins/typescript/scope.js
Normal file
105
packages/babel-parser/src/plugins/typescript/scope.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// @flow
|
||||
|
||||
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,
|
||||
} from "../../util/scopeflags";
|
||||
import * as N from "../../types";
|
||||
|
||||
class TypeScriptScope extends Scope {
|
||||
types: string[] = [];
|
||||
|
||||
// enums (which are also in .types)
|
||||
enums: string[] = [];
|
||||
|
||||
// const enums (which are also in .enums and .types)
|
||||
constEnums: string[] = [];
|
||||
|
||||
// classes (which are also in .lexical) and interface (which are also in .types)
|
||||
classes: string[] = [];
|
||||
|
||||
// namespaces and bodyless-functions 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: string[] = [];
|
||||
}
|
||||
|
||||
// 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> {
|
||||
createScope(flags: ScopeFlags): TypeScriptScope {
|
||||
return new TypeScriptScope(flags);
|
||||
}
|
||||
|
||||
declareName(name: string, bindingType: BindingTypes, pos: number) {
|
||||
const scope = this.currentScope();
|
||||
if (bindingType & BIND_FLAGS_TS_EXPORT_ONLY) {
|
||||
this.maybeExportDefined(scope, name);
|
||||
scope.exportOnlyBindings.push(name);
|
||||
return;
|
||||
}
|
||||
|
||||
super.declareName(...arguments);
|
||||
|
||||
if (bindingType & BIND_KIND_TYPE) {
|
||||
if (!(bindingType & BIND_KIND_VALUE)) {
|
||||
// "Value" bindings have already been registered by the superclass.
|
||||
this.checkRedeclarationInScope(scope, name, bindingType, pos);
|
||||
this.maybeExportDefined(scope, name);
|
||||
}
|
||||
scope.types.push(name);
|
||||
}
|
||||
if (bindingType & BIND_FLAGS_TS_ENUM) scope.enums.push(name);
|
||||
if (bindingType & BIND_FLAGS_TS_CONST_ENUM) scope.constEnums.push(name);
|
||||
if (bindingType & BIND_FLAGS_CLASS) scope.classes.push(name);
|
||||
}
|
||||
|
||||
isRedeclaredInScope(
|
||||
scope: TypeScriptScope,
|
||||
name: string,
|
||||
bindingType: BindingTypes,
|
||||
): boolean {
|
||||
if (scope.enums.indexOf(name) > -1) {
|
||||
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.indexOf(name) > -1;
|
||||
return isConst !== wasConst;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (bindingType & BIND_FLAGS_CLASS && scope.classes.indexOf(name) > -1) {
|
||||
if (scope.lexical.indexOf(name) > -1) {
|
||||
// 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.indexOf(name) > -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.isRedeclaredInScope(...arguments);
|
||||
}
|
||||
|
||||
checkLocalExport(id: N.Identifier) {
|
||||
if (
|
||||
this.scopeStack[0].types.indexOf(id.name) === -1 &&
|
||||
this.scopeStack[0].exportOnlyBindings.indexOf(id.name) === -1
|
||||
) {
|
||||
super.checkLocalExport(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user