Skip to content

Commit

Permalink
Fix body-less functions and namespaces
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nicolo-ribaudo committed Apr 4, 2019
1 parent c2428b0 commit 7274ca1
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 32 deletions.
3 changes: 2 additions & 1 deletion packages/babel-parser/src/parser/expression.js
Expand Up @@ -1767,6 +1767,7 @@ export default class ExpressionParser extends LValParser {
this.parseFunctionParams((node: any), allowModifiers);
this.checkYieldAwaitInDefaultParams();
this.parseFunctionBodyAndFinish(node, type, true);
this.scope.exit();

this.state.yieldPos = oldYieldPos;
this.state.awaitPos = oldAwaitPos;
Expand Down Expand Up @@ -1795,6 +1796,7 @@ export default class ExpressionParser extends LValParser {
if (params) this.setArrowFunctionParameters(node, params);
this.parseFunctionBody(node, true);

this.scope.exit();
this.state.maybeInArrowParameters = oldMaybeInArrowParameters;
this.state.yieldPos = oldYieldPos;
this.state.awaitPos = oldAwaitPos;
Expand Down Expand Up @@ -1890,7 +1892,6 @@ export default class ExpressionParser extends LValParser {
node.body = this.parseBlock(true, false);
this.state.labels = oldLabels;
}
this.scope.exit();

this.state.inParameters = oldInParameters;
// Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval'
Expand Down
44 changes: 28 additions & 16 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -1043,22 +1043,6 @@ export default class StatementParser extends ExpressionParser {

if (isStatement) {
node.id = this.parseFunctionId(requireId);
if (node.id && !isHangingStatement) {
// If it is a regular function declaration in sloppy mode, then it is
// subject to Annex B semantics (BIND_FUNCTION). Otherwise, the binding
// mode depends on properties of the current scope (see
// treatFunctionsAsVar).
this.checkLVal(
node.id,
this.state.strict || node.generator || node.async
? this.scope.treatFunctionsAsVar
? BIND_VAR
: BIND_LEXICAL
: BIND_FUNCTION,
null,
"function name",
);
}
}

const oldInClassProperty = this.state.inClassProperty;
Expand Down Expand Up @@ -1086,6 +1070,15 @@ export default class StatementParser extends ExpressionParser {
);
});

this.scope.exit();

if (isStatement && !isHangingStatement) {
// We need to validate this _after_ parsing the function body
// because of TypeScript body-less function declarations,
// which shouldn't be added to the scope.
this.checkFunctionStatementId(node);
}

this.state.inClassProperty = oldInClassProperty;
this.state.yieldPos = oldYieldPos;
this.state.awaitPos = oldAwaitPos;
Expand All @@ -1112,6 +1105,25 @@ export default class StatementParser extends ExpressionParser {
this.checkYieldAwaitInDefaultParams();
}

checkFunctionStatementId(node: N.Function): void {
if (!node.id) return;

// If it is a regular function declaration in sloppy mode, then it is
// subject to Annex B semantics (BIND_FUNCTION). Otherwise, the binding
// mode depends on properties of the current scope (see
// treatFunctionsAsVar).
this.checkLVal(
node.id,
this.state.strict || node.generator || node.async
? this.scope.treatFunctionsAsVar
? BIND_VAR
: BIND_LEXICAL
: BIND_FUNCTION,
null,
"function name",
);
}

// Parse a class declaration or literal (depending on the
// `isStatement` parameter).

Expand Down
23 changes: 22 additions & 1 deletion packages/babel-parser/src/plugins/typescript/index.js
Expand Up @@ -13,6 +13,8 @@ import {
BIND_TS_ENUM,
BIND_TS_TYPE,
BIND_TS_INTERFACE,
BIND_TS_FN_TYPE,
BIND_TS_NAMESPACE,
} from "../../util/scopeflags";
import TypeScriptScopeHandler from "./scope";

Expand Down Expand Up @@ -1186,11 +1188,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();
Expand Down Expand Up @@ -1595,6 +1608,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,
Expand Down
20 changes: 18 additions & 2 deletions packages/babel-parser/src/plugins/typescript/scope.js
Expand Up @@ -4,6 +4,7 @@ import ScopeHandler, { Scope } from "../../util/scope";
import {
BIND_KIND_TYPE,
BIND_FLAGS_TS_ENUM,
BIND_FLAGS_TS_EXPORT_ONLY,
BIND_KIND_VALUE,
BIND_FLAGS_CLASS,
type ScopeFlags,
Expand All @@ -19,6 +20,12 @@ class TypeScriptScope extends Scope {

// 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
Expand All @@ -30,9 +37,15 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
}

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);

const scope = this.currentScope();
if (bindingType & BIND_KIND_TYPE) {
if (!(bindingType & BIND_KIND_VALUE)) {
// "Value" bindings have already been registered by the superclass.
Expand Down Expand Up @@ -71,7 +84,10 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
}

checkLocalExport(id: N.Identifier) {
if (this.scopeStack[0].types.indexOf(id.name) === -1) {
if (
this.scopeStack[0].types.indexOf(id.name) === -1 &&
this.scopeStack[0].exportOnlyBindings.indexOf(id.name) === -1
) {
super.checkLocalExport(id);
}
}
Expand Down
31 changes: 19 additions & 12 deletions packages/babel-parser/src/util/scopeflags.js
Expand Up @@ -37,18 +37,19 @@ export function functionFlags(isAsync: boolean, isGenerator: boolean) {

// These flags are meant to be _only_ used inside the Scope class (or subclasses).
// prettier-ignore
export const BIND_KIND_VALUE = 0b000_0000_01,
BIND_KIND_TYPE = 0b000_0000_10,
export const BIND_KIND_VALUE = 0b0000_0000_01,
BIND_KIND_TYPE = 0b0000_0000_10,
// Used in checkLVal and declareName to determine the type of a binding
BIND_SCOPE_VAR = 0b000_0001_00, // Var-style binding
BIND_SCOPE_LEXICAL = 0b000_0010_00, // Let- or const-style binding
BIND_SCOPE_FUNCTION = 0b000_0100_00, // Function declaration
BIND_SCOPE_OUTSIDE = 0b000_1000_00, // Special case for function names as
// bound inside the function
BIND_SCOPE_VAR = 0b0000_0001_00, // Var-style binding
BIND_SCOPE_LEXICAL = 0b0000_0010_00, // Let- or const-style binding
BIND_SCOPE_FUNCTION = 0b0000_0100_00, // Function declaration
BIND_SCOPE_OUTSIDE = 0b0000_1000_00, // Special case for function names as
// bound inside the function
// Misc flags
BIND_FLAGS_NONE = 0b001_0000_00,
BIND_FLAGS_CLASS = 0b010_0000_00,
BIND_FLAGS_TS_ENUM = 0b100_0000_00;
BIND_FLAGS_NONE = 0b0001_0000_00,
BIND_FLAGS_CLASS = 0b0010_0000_00,
BIND_FLAGS_TS_ENUM = 0b0100_0000_00,
BIND_FLAGS_TS_EXPORT_ONLY = 0b1000_0000_00;

// These flags are meant to be _only_ used by Scope consumers
// prettier-ignore
Expand All @@ -60,17 +61,23 @@ export const BIND_CLASS = BIND_KIND_VALUE | BIND_KIND_TYPE | BIND_SCOPE_L
BIND_TS_INTERFACE = 0 | BIND_KIND_TYPE | 0 | BIND_FLAGS_CLASS ,
BIND_TS_TYPE = 0 | BIND_KIND_TYPE | 0 | 0 ,
BIND_TS_ENUM = BIND_KIND_VALUE | BIND_KIND_TYPE | BIND_SCOPE_LEXICAL | BIND_FLAGS_TS_ENUM,
BIND_TS_FN_TYPE = 0 | 0 | 0 | BIND_FLAGS_TS_EXPORT_ONLY,
// These bindings don't introduce anything in the scope. They are used for assignments and
// function expressions IDs.
BIND_NONE = 0 | 0 | 0 | BIND_FLAGS_NONE ,
BIND_OUTSIDE = BIND_KIND_VALUE | 0 | 0 | BIND_FLAGS_NONE ;
BIND_OUTSIDE = BIND_KIND_VALUE | 0 | 0 | BIND_FLAGS_NONE ,

BIND_TS_NAMESPACE = BIND_TS_FN_TYPE;

export type BindingTypes =
| typeof BIND_NONE
| typeof BIND_OUTSIDE
| typeof BIND_VAR
| typeof BIND_LEXICAL
| typeof BIND_CLASS
| typeof BIND_FUNCTION
| typeof BIND_TS_INTERFACE
| typeof BIND_TS_TYPE
| typeof BIND_TS_ENUM;
| typeof BIND_TS_ENUM
| typeof BIND_TS_FN_TYPE
| typeof BIND_TS_NAMESPACE;
@@ -0,0 +1,3 @@
namespace N {}

export { N };
@@ -0,0 +1,151 @@
{
"type": "File",
"start": 0,
"end": 29,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 13
}
},
"program": {
"type": "Program",
"start": 0,
"end": 29,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 13
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TSModuleDeclaration",
"start": 0,
"end": 14,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 14
}
},
"id": {
"type": "Identifier",
"start": 10,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 10
},
"end": {
"line": 1,
"column": 11
},
"identifierName": "N"
},
"name": "N"
},
"body": {
"type": "TSModuleBlock",
"start": 12,
"end": 14,
"loc": {
"start": {
"line": 1,
"column": 12
},
"end": {
"line": 1,
"column": 14
}
},
"body": []
}
},
{
"type": "ExportNamedDeclaration",
"start": 16,
"end": 29,
"loc": {
"start": {
"line": 3,
"column": 0
},
"end": {
"line": 3,
"column": 13
}
},
"specifiers": [
{
"type": "ExportSpecifier",
"start": 25,
"end": 26,
"loc": {
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 10
}
},
"local": {
"type": "Identifier",
"start": 25,
"end": 26,
"loc": {
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 10
},
"identifierName": "N"
},
"name": "N"
},
"exported": {
"type": "Identifier",
"start": 25,
"end": 26,
"loc": {
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 10
},
"identifierName": "N"
},
"name": "N"
}
}
],
"source": null,
"declaration": null
}
],
"directives": []
}
}

0 comments on commit 7274ca1

Please sign in to comment.