Skip to content

Commit

Permalink
Check exported bindings are defined (#9589)
Browse files Browse the repository at this point in the history
* Check exported bindings are defined

* Add tests

* Update flow whitelist

* Add tests for builtins
  • Loading branch information
danez committed Feb 26, 2019
1 parent e883ff2 commit 43eed1a
Show file tree
Hide file tree
Showing 81 changed files with 1,583 additions and 304 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"strictMode": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ declare export * from 'asd';
declare export { a, b };
declare export {};
declare export { c, d } from 'bar';
var a, b;

declare module B {
declare export type B = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ declare export * from 'asd';
declare export { a, b };
declare export {};
declare export { c, d } from 'bar';
var a, b;
declare module B {
declare export type B = {};
declare export interface Moon {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ import { type Foo12 } from "bar";
import { typeof Foo13 } from "bar";
import { type Foo as Bar1 } from "bar";
import { typeof Foo as Bar2 } from "bar";
export type { foo };
export type { foo1 };
export type { bar } from "bar";
export interface baz { p: number }
export interface qux<T> { p: T }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ import { type Foo12 } from "bar";
import { typeof Foo13 } from "bar";
import { type Foo as Bar1 } from "bar";
import { typeof Foo as Bar2 } from "bar";
export type { foo };
export type { foo1 };
export type { bar } from "bar";
export interface baz {
p: number
Expand Down Expand Up @@ -293,4 +293,4 @@ function foo27(numVal: number = 2) {}

function foo28(numVal?: number = 2) {}

export type * from "foo";
export type * from "foo";
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
var a, c;
export * from "OK"
export { name } from "OK"
export { a as b, c as d } from "hello"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
var a, c;
export * from "OK";
export { name } from "OK";
export { a as b, c as d } from "hello";
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { foo };
var foo;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { foo };
export { foo };
var foo;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { foo, bar };
var foo, bar;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { foo, bar };
export { foo, bar };
var foo, bar;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { foo as bar };
var foo;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { foo as bar };
export { foo as bar };
var foo;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { foo as default };
var foo;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { foo as default };
export { foo as default };
var foo;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { foo as default, bar };
var foo, bar;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { foo as default, bar };
export { foo as default, bar };
var foo, bar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"strictMode": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"strictMode": false
}
2 changes: 1 addition & 1 deletion packages/babel-generator/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ suites.forEach(function(testSuite) {
const actualAst = parse(actualCode, {
filename: actual.loc,
plugins: task.options.plugins || [],
strictMode: false,
strictMode: task.options.strictMode === false ? false : true,
sourceType: "module",
sourceMaps: !!task.sourceMap,
});
Expand Down
18 changes: 16 additions & 2 deletions packages/babel-parser/src/parser/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ export type ExportNamedDeclaration = NodeBase & {
export type ExportSpecifier = NodeBase & {
type: "ExportSpecifier",
exported: Identifier,
local: Identifier,
};

export type ExportDefaultSpecifier = NodeBase & {
Expand Down
19 changes: 19 additions & 0 deletions packages/babel-parser/src/util/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { encrypt as default };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"throws": "Export 'encrypt' is not defined (1:9)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { encrypt as decrypt };
function decrypt() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"throws": "Export 'encrypt' is not defined (1:9)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
function encrypt() {}
}
export { encrypt }
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"throws": "Export 'encrypt' is not defined (4:9)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Object as Obj };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"throws": "Export 'Object' is not defined (1:9)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Object };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"throws": "Export 'Object' is not defined (1:9)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { encrypt };
if (true) function encrypt() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"throws": "In strict mode code, functions can only be declared at top level or inside a block (2:10)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
var encrypt
}
export { encrypt }
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"sourceType": "module"
}

0 comments on commit 43eed1a

Please sign in to comment.