From 7274ca10a9e1f96a6a8381dc4daa4f5180aeefb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Thu, 4 Apr 2019 22:52:14 +0200 Subject: [PATCH] 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 --- .../babel-parser/src/parser/expression.js | 3 +- packages/babel-parser/src/parser/statement.js | 44 +++-- .../src/plugins/typescript/index.js | 23 ++- .../src/plugins/typescript/scope.js | 20 ++- packages/babel-parser/src/util/scopeflags.js | 31 ++-- .../scope/export-namespace/input.js | 3 + .../scope/export-namespace/output.json | 151 ++++++++++++++++++ .../function-type-before-declaration/index.js | 4 + 8 files changed, 247 insertions(+), 32 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/typescript/scope/export-namespace/input.js create mode 100644 packages/babel-parser/test/fixtures/typescript/scope/export-namespace/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/scope/function-type-before-declaration/index.js diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index f9a75fbe38e4..3978ad226d91 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -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; @@ -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; @@ -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' diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index f569d48e64b3..8b8b9362dac9 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -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; @@ -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; @@ -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). diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index ef1bc9680419..6ed0e79a718a 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -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"; @@ -1186,11 +1188,22 @@ export default (superClass: Class): Class => 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(); @@ -1595,6 +1608,14 @@ export default (superClass: Class): Class => 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, diff --git a/packages/babel-parser/src/plugins/typescript/scope.js b/packages/babel-parser/src/plugins/typescript/scope.js index f0d1ef1526d7..949a87ee86c6 100644 --- a/packages/babel-parser/src/plugins/typescript/scope.js +++ b/packages/babel-parser/src/plugins/typescript/scope.js @@ -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, @@ -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 @@ -30,9 +37,15 @@ export default class TypeScriptScopeHandler extends ScopeHandler