Check exported bindings are defined (#9589)
* Check exported bindings are defined * Add tests * Update flow whitelist * Add tests for builtins
This commit is contained in:
@@ -49,6 +49,14 @@ export default class StatementParser extends ExpressionParser {
|
||||
|
||||
this.parseBlockBody(program, true, true, tt.eof);
|
||||
|
||||
if (this.inModule && this.scope.undefinedExports.size > 0) {
|
||||
for (const [name] of Array.from(this.scope.undefinedExports)) {
|
||||
const pos = this.scope.undefinedExports.get(name);
|
||||
// $FlowIssue
|
||||
this.raise(pos, `Export '${name}' is not defined`);
|
||||
}
|
||||
}
|
||||
|
||||
file.program = this.finishNode(program, "Program");
|
||||
file.comments = this.state.comments;
|
||||
|
||||
@@ -1631,7 +1639,7 @@ export default class StatementParser extends ExpressionParser {
|
||||
}
|
||||
|
||||
if (isFromRequired || hasSpecifiers || hasDeclaration) {
|
||||
this.checkExport(node, true);
|
||||
this.checkExport(node, true, false, !!node.source);
|
||||
return this.finishNode(node, "ExportNamedDeclaration");
|
||||
}
|
||||
|
||||
@@ -1845,8 +1853,9 @@ export default class StatementParser extends ExpressionParser {
|
||||
|
||||
checkExport(
|
||||
node: N.ExportNamedDeclaration,
|
||||
checkNames: ?boolean,
|
||||
checkNames?: boolean,
|
||||
isDefault?: boolean,
|
||||
isFrom?: boolean,
|
||||
): void {
|
||||
if (checkNames) {
|
||||
// Check for duplicate exports
|
||||
@@ -1857,6 +1866,11 @@ export default class StatementParser extends ExpressionParser {
|
||||
// Named exports
|
||||
for (const specifier of node.specifiers) {
|
||||
this.checkDuplicateExports(specifier, specifier.exported.name);
|
||||
// check if export is defined
|
||||
// $FlowIgnore
|
||||
if (!isFrom && specifier.local) {
|
||||
this.scope.checkLocalExport(specifier.local);
|
||||
}
|
||||
}
|
||||
} else if (node.declaration) {
|
||||
// Exported declarations
|
||||
|
||||
@@ -831,6 +831,7 @@ export type ExportNamedDeclaration = NodeBase & {
|
||||
export type ExportSpecifier = NodeBase & {
|
||||
type: "ExportSpecifier",
|
||||
exported: Identifier,
|
||||
local: Identifier,
|
||||
};
|
||||
|
||||
export type ExportDefaultSpecifier = NodeBase & {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type BindingTypes,
|
||||
SCOPE_CLASS,
|
||||
} from "./scopeflags";
|
||||
import * as N from "../types";
|
||||
|
||||
// Start an AST node, attaching a start offset.
|
||||
class Scope {
|
||||
@@ -40,6 +41,7 @@ export default class ScopeHandler {
|
||||
scopeStack: Array<Scope> = [];
|
||||
raise: raiseFunction;
|
||||
inModule: boolean;
|
||||
undefinedExports: Map<string, number> = new Map();
|
||||
|
||||
constructor(raise: raiseFunction, inModule: boolean) {
|
||||
this.raise = raise;
|
||||
@@ -95,6 +97,9 @@ export default class ScopeHandler {
|
||||
scope.functions.indexOf(name) > -1 ||
|
||||
scope.var.indexOf(name) > -1;
|
||||
scope.lexical.push(name);
|
||||
if (this.inModule && scope.flags & SCOPE_PROGRAM) {
|
||||
this.undefinedExports.delete(name);
|
||||
}
|
||||
} else if (bindingType === BIND_SIMPLE_CATCH) {
|
||||
const scope = this.currentScope();
|
||||
scope.lexical.push(name);
|
||||
@@ -122,6 +127,10 @@ export default class ScopeHandler {
|
||||
}
|
||||
scope.var.push(name);
|
||||
|
||||
if (this.inModule && scope.flags & SCOPE_PROGRAM) {
|
||||
this.undefinedExports.delete(name);
|
||||
}
|
||||
|
||||
if (scope.flags & SCOPE_VAR) break;
|
||||
}
|
||||
}
|
||||
@@ -130,6 +139,16 @@ export default class ScopeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
checkLocalExport(id: N.Identifier) {
|
||||
// scope.functions must be empty as Module code is always strict.
|
||||
if (
|
||||
this.scopeStack[0].lexical.indexOf(id.name) === -1 &&
|
||||
this.scopeStack[0].var.indexOf(id.name) === -1
|
||||
) {
|
||||
this.undefinedExports.set(id.name, id.start);
|
||||
}
|
||||
}
|
||||
|
||||
currentScope(): Scope {
|
||||
return this.scopeStack[this.scopeStack.length - 1];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user