Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ns export when between a default export and exported specifiers. #162

Merged
merged 7 commits into from
May 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions lib/parsers/acorn-extensions/dynamic-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ exports.enable = function (parser) {
};

function parseExprAtom(refDestructuringErrors) {
const node = this.startNode();
const importPos = this.start;
if (this.eat(tt._import)) {
if (this.type !== tt.parenL) {
this.unexpected();
}
return this.finishNode(node, "Import");
return this.finishNode(this.startNodeAt(importPos), "Import");
}
return Pp.parseExprAtom.call(this, refDestructuringErrors);
}

function parseStatement(declaration, topLevel, exports) {
return this.type === tt._import && this.input.charCodeAt(this.pos) === codeOfLeftParen
? this.parseExpressionStatement(this.startNode(), this.parseExpression())
: Pp.parseStatement.call(this, declaration, topLevel, exports);
function parseStatement(declaration, topLevel, exported) {
if (this.type === tt._import &&
this.input.charCodeAt(this.pos) === codeOfLeftParen) {
// import(...)
return this.parseExpressionStatement(this.startNode(), this.parseExpression());
}
return Pp.parseStatement.call(this, declaration, topLevel, exported);
}
162 changes: 63 additions & 99 deletions lib/parsers/acorn-extensions/export.js
Original file line number Diff line number Diff line change
@@ -1,91 +1,100 @@
"use strict";

const acorn = require("acorn");
const dummyParser = new acorn.Parser;
const tt = acorn.tokTypes;

exports.enable = function (parser) {
parser.checkExports = checkExports;
parser.parseExport = parseExport;
};

function checkExports(exported, name) {
if (exported !== void 0) {
exported[name] = true;
}
}

function parseExport(node, exported) {
// export ...
this.next();

if (this.type === tt.star) {
return parseExportNamespaceSpecifiersAndSource(this, node, exported);
}
if (isExportDefaultSpecifier(this)) {
return parseExportDefaultSpecifiersAndSource(this, node, exported);
}
if (this.type === tt._default) {
// ... default function|class|=...;
return parseExportDefaultDeclaration(this, node, exported);
}
if (this.type === tt.star && lookahead(this).isContextual("from")) {
// ... * from '...';
return parseExportNamespace(this, node);
}
if (this.shouldParseExportStatement()) {
// ... var|const|let|function|class ...
return parseExportNamedDeclaration(this, node, exported);
}
return parseExportSpecifiersAndSource(this, node, exported);
}

function isCommaOrFrom(parser) {
return parser.type === tt.comma || parser.isContextual("from");
}
let expectFrom = false;
node.specifiers = [];

do {
if (this.type === tt.name) {
// ... def [, ...]
expectFrom = true;
parseExportDefaultSpecifier(this, node);
} else if (this.type === tt.star) {
// ... * as ns [, ...]
expectFrom = true;
parseExportNamespaceSpecifier(this, node, exported);
} else if (this.type === tt.braceL) {
// ... { x, y as z } [, ...]
parseExportSpecifiers(this, node);
}
}
while (this.eat(tt.comma));

function isExportDefaultSpecifier(parser) {
return parser.type === tt.name && peekNextWith(parser, isCommaOrFrom);
if (expectFrom || this.isContextual("from")) {
// ... [from '...'];
parseExportFrom(this, node, exported);
} else {
this.semicolon();
}

return this.finishNode(node, "ExportNamedDeclaration");
}

function parseExportDefaultDeclaration(parser, node, exported) {
// export default ...;
// ... default function|class|=...;
exported.default = true;
parser.next();

let isAsync;
if (parser.type === tt._function || (isAsync = parser.isAsyncFunction())) {
// Parse a function declaration.
const funcNode = parser.startNode();
if (isAsync) {
parser.next();
}
parser.next();
node.declaration = parser.parseFunction(funcNode, "nullableID", false, isAsync);
} else if (parser.type === tt._class) {
// Parse a class declaration.
node.declaration = parser.parseClass(parser.startNode(), "nullableID");
} else {
// Parse an assignment expression.
node.declaration = parser.parseMaybeAssign();
}
parser.semicolon();
return parser.finishNode(node, "ExportDefaultDeclaration");
}

function parseExportDefaultSpecifiersAndSource(parser, node, exported) {
// export def from '...';
function parseExportDefaultSpecifier(parser, node) {
// ... def
const specifier = parser.startNode();
specifier.exported = parser.parseIdent(true);

node.specifiers = [
parser.finishNode(specifier, "ExportDefaultSpecifier")
];

// export def [, * as ns [, { x, y as z }]] from '...';
parseExportNamespaceSpecifiersMaybe(parser, node, exported);
parseExportSpecifiersMaybe(parser, node);
parseExportFrom(parser, node);
return parser.finishNode(node, "ExportNamedDeclaration");
node.specifiers.push(parser.finishNode(specifier, "ExportDefaultSpecifier"));
}

function parseExportFrom(parser, node) {
// ... from '...';
parser.expectContextual("from");
node.source = parser.type === tt.string ? parser.parseExprAtom() : null;
parser.semicolon();
}

function parseExportNamedDeclaration(parser, node, exported) {
// export var|const|let|function|class ...
// ... var|const|let|function|class ...
node.declaration = parser.parseStatement(true);
node.source = null;
node.specifiers = [];
Expand All @@ -98,77 +107,32 @@ function parseExportNamedDeclaration(parser, node, exported) {
return parser.finishNode(node, "ExportNamedDeclaration");
}

function parseExportNamespaceSpecifiers(parser, node, specifier, exported) {
parser.expectContextual("as");
specifier.exported = parser.parseIdent(true);
node.specifiers.push(
parser.finishNode(specifier, "ExportNamespaceSpecifier")
);

exported[specifier.exported.name] = true;
}

function parseExportNamespaceSpecifiersAndSource(parser, node, exported) {
const star = parser.startNode();
node.specifiers = [];
function parseExportNamespace(parser, node) {
// ... * from '...';
parser.next();

if (! parser.isContextual("as")) {
// export * from '...';
parseExportFrom(parser, node);
return parser.finishNode(node, "ExportAllDeclaration");
}
// export * as ns[, { x, y as z }] from '...';
parseExportNamespaceSpecifiers(parser, node, star, exported);
parseExportSpecifiersMaybe(parser, node);
node.specifiers = [];
parseExportFrom(parser, node);
return parser.finishNode(node, "ExportNamedDeclaration");
}

function parseExportNamespaceSpecifiersMaybe(parser, node, exported) {
if (parser.type === tt.comma && peekNextType(parser) === tt.star) {
parser.next();
const star = parser.startNode();
parser.next();
parseExportNamespaceSpecifiers(parser, node, star, exported);
}
return parser.finishNode(node, "ExportAllDeclaration");
}

function parseExportSpecifiersAndSource(parser, node, exported) {
// export { x, y as z } [from '...'];
node.declaration = null;
node.specifiers = parser.parseExportSpecifiers(exported);

if (parser.isContextual("from")) {
parseExportFrom(parser, node, exported);
} else {
parser.semicolon();
}
return parser.finishNode(node, "ExportNamedDeclaration");
}

function parseExportSpecifiersMaybe(parser, node) {
if (parser.eat(tt.comma)) {
node.specifiers.push.apply(
node.specifiers,
parser.parseExportSpecifiers()
);
}
function parseExportNamespaceSpecifier(parser, node, exported) {
// ... * as ns
const star = parser.startNode();
parser.next();
parser.expectContextual("as");
star.exported = parser.parseIdent(true);
node.specifiers.push(parser.finishNode(star, "ExportNamespaceSpecifier"));
exported[star.exported.name] = true;
}

function peekNextType(parser) {
return peekNextWith(parser, () => parser.type);
function parseExportSpecifiers(parser, node) {
const specifiers = node.specifiers;
specifiers.push.apply(specifiers, parser.parseExportSpecifiers());
}

// Calls the given callback with the state of the parser temporarily advanced
// by calling this.nextToken(), then rolls the parser back to its original state
// and returns the result of the callback.
function peekNextWith(parser, callback) {
const old = Object.assign(Object.create(null), parser);
parser.nextToken();
try {
return callback(parser);
} finally {
Object.assign(parser, old);
}
function lookahead(parser) {
dummyParser.input = parser.input;
dummyParser.pos = parser.pos;
dummyParser.nextToken();
return dummyParser;
}
10 changes: 4 additions & 6 deletions test/babel-plugin-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Object.keys(files).forEach((absPath) => {

// These files fail to transform with es2015 preset due to problems
// unrelated to the functionality of the Reify Babel plugin.
if (relPath === "dynamic-import-tests.js" ||
if (relPath === "export/from-extensions.js" ||
relPath === "export/some.js" ||
relPath === "export-tests.js" ||
relPath === "import-tests.js" ||
Expand All @@ -35,12 +35,13 @@ describe("babel-plugin-transform-es2015-modules-reify", () => {
const ast = parse(code);
delete ast.tokens;
const result = transformFromAst(ast, code, options);
assert.ok(/\bmodule\.(?:watch|importSync|export)\b/.test(result.code));
assert.ok(/\bmodule\.(?:export|import(?:Sync)?|watch)\b/.test(result.code));
return result;
}

Object.keys(filesToTest).forEach((relPath) => {
const code = filesToTest[relPath];
const presets = [es2015Preset];
const plugins = [[reifyPlugin, {
generateLetDeclarations: true
}]];
Expand All @@ -50,10 +51,7 @@ describe("babel-plugin-transform-es2015-modules-reify", () => {
});

it(`compiles ${relPath} with es2015`, () => {
check(code, {
plugins,
presets: [es2015Preset]
});
check(code, { plugins, presets });
});
});
});
17 changes: 12 additions & 5 deletions test/export-tests.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const assert = require("assert");
import assert from "assert";

const isBabylonParser = process.env.REIFY_PARSER === "babylon";

describe("export declarations", () => {
import { Script } from "vm";
Expand Down Expand Up @@ -214,23 +216,26 @@ describe("export declarations", () => {
assert.deepEqual(foo, { a: "a", b: "b", c: "c" });
});

it("should support export-from extensions", () => {
(isBabylonParser ? xit : it)(
"should support export-from extensions", () => {
import {
def1, def2, def3,
ns1, ns2, ns3,
a, b, c, d
def1, def2, def3, def4,
ns1, ns2, ns3, ns4,
a, b, c, d, e
} from "./export/from-extensions";

import def, {
a as _a,
b as _b,
b as _c,
c as _d,
c as _e,
} from "./misc/abc";

assert.strictEqual(def, def1);
assert.strictEqual(def, def2);
assert.strictEqual(def, def3);
assert.strictEqual(def, def4);

function checkNS(ns) {
assert.deepEqual(ns, def);
Expand All @@ -240,11 +245,13 @@ describe("export declarations", () => {
checkNS(ns1);
checkNS(ns2);
checkNS(ns3);
checkNS(ns4);

assert.strictEqual(a, _a);
assert.strictEqual(b, _b);
assert.strictEqual(c, _c);
assert.strictEqual(d, _d);
assert.strictEqual(e, _e);
});

it("should support export { default } from ... syntax", () => {
Expand Down
9 changes: 5 additions & 4 deletions test/export/from-extensions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export def1 from "../misc/abc";
export * as ns1 from "../misc/abc";
export def2, * as ns2 from "../misc/abc";
export def3, { a, b as c } from "../misc/abc";
export * as ns3, { b, c as d } from "../misc/abc";
export * as ns2, { a, b as c } from "../misc/abc";
export def1 from "../misc/abc";
export def2, { b, c as d } from "../misc/abc";
export def3, * as ns3 from "../misc/abc";
export def4, * as ns4, { c as e } from "../misc/abc";
11 changes: 5 additions & 6 deletions test/transform-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,17 @@ describe("compiler.transform", () => {
it("gives the same results as compile with babylon", () => {
check({
ast: true,
parse: require("../lib/parsers/babylon.js").parse
parse: require("../lib/parsers/babylon.js").parse,
filter: (relPath) => {
return relPath !== "export/from-extensions.js";
}
});
}).timeout(5000);

it("gives the same results as compile with acorn", () => {
check({
ast: true,
parse: require("../lib/parsers/acorn.js").parse,
filter: (relPath) => {
// Acorn can't parse this file, so we skip it.
return relPath !== "export/from-extensions.js";
}
parse: require("../lib/parsers/acorn.js").parse
});
}).timeout(5000);
});