Skip to content

Commit

Permalink
[ES6] Implemented parse for export Name from Module variants. (#1701)
Browse files Browse the repository at this point in the history
- add `AST_Export` new variants output
- add tests to `test/compress/`
- update `$propdoc` of `AST_Export` ("exported_names" & "module_name")
- add tests for `export ...  as ...` variants
  • Loading branch information
OndrejSpanel authored and alexlamsl committed Mar 30, 2017
1 parent fccefbe commit 5dea522
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 2 deletions.
4 changes: 3 additions & 1 deletion lib/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -789,11 +789,13 @@ var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", {
}
});

var AST_Export = DEFNODE("Export", "exported_definition exported_value is_default", {
var AST_Export = DEFNODE("Export", "exported_definition exported_value is_default exported_names module_name", {
$documentation: "An `export` statement",
$propdoc: {
exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition",
exported_value: "[AST_Node?] An exported value",
exported_names: "[AST_NameImport*?] List of exported names",
module_name: "[AST_String?] Name of the file to load exports from",
is_default: "[Boolean] Whether this is the default exported value of this module"
},
_walk: function (visitor) {
Expand Down
28 changes: 27 additions & 1 deletion lib/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -1277,11 +1277,37 @@ function OutputStream(options) {
output.print("default");
output.space();
}
if (self.exported_value) {
if (self.exported_names) {
output.space();

if (self.exported_names.length === 1 && self.exported_names[0].name.name === "*") {
self.exported_names[0].print(output);
} else {
output.print("{");
self.exported_names.forEach(function (name_import, i) {
output.space();
name_import.print(output);
if (i < self.exported_names.length - 1) {
output.print(",");
output.space();
}
});
output.space();
output.print("}");
}
output.space();
}
else if (self.exported_value) {
self.exported_value.print(output);
} else if (self.exported_definition) {
self.exported_definition.print(output);
}
if (self.module_name) {
output.space();
output.print("from");
output.space();
self.module_name.print(output);
}
output.semicolon();
});

Expand Down
68 changes: 68 additions & 0 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -2237,17 +2237,85 @@ function parse($TEXT, options) {
})
}

function import_nameAsterisk() {
var start = S.token;
var foreign_name;
var name;

next();

var end = prev();

name = new AST_SymbolImport({
name: '*',
start: start,
end: end,
});

foreign_name = new AST_SymbolImportForeign({
name: '*',
start: start,
end: end,
});

return new AST_NameImport({
start: start,
foreign_name: foreign_name,
name: name,
end: end,
})
}

function export_() {
var start = S.token;
var is_default;
var exported_value;
var exported_definition;
var exported_names;

if (is("keyword", "default")) {
is_default = true;
next();
}

if (is("punc", "{")) {
next();
exported_names = [];
while (!is("punc", "}")) {
exported_names.push(import_name());
if (is("punc", ",")) {
next();
}
}
next();
} else if (is("operator", "*")) {
var st = prev();
exported_names = [import_nameAsterisk()];
}

if (exported_names) {
expect_token("name", "from");

var mod_str = S.token;
if (mod_str.type !== 'string') {
unexpected();
}
next();

return new AST_Export({
start: start,
is_default: is_default,
exported_names: exported_names,
module_name: new AST_String({
start: mod_str,
value: mod_str.value,
quote: mod_str.quote,
end: mod_str,
}),
end: prev(),
});
}

var is_definition =
is("keyword", "var") || is("keyword", "let") || is("keyword", "const") ||
is("keyword", "class") || is("keyword", "function");
Expand Down
9 changes: 9 additions & 0 deletions test/compress/harmony.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,15 @@ export_statement: {
expect_exact: "export default 1;export var foo=4;export let foo=6;export const foo=6;export function foo(){};export class foo{};"
}

export_module_statement: {
input: {
export * from "a.js";
export {A} from "a.js";
export {A, B} from "a.js";
}
expect_exact: 'export*from"a.js";export{A}from"a.js";export{A,B}from"a.js";'
}

import_statement_mangling: {
mangle = { toplevel: true };
input: {
Expand Down
40 changes: 40 additions & 0 deletions test/mocha/export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
var assert = require("assert");
var uglify = require("../../");

describe("Export", function() {
it ("Should parse export directives", function() {

var inputs = [
['export * from "a.js"', ['*'], "a.js"],
['export {A} from "a.js"', ['A'], "a.js"],
['export {A as X} from "a.js"', ['X'], "a.js"],
['export {A as Foo, B} from "a.js"', ['Foo', 'B'], "a.js"],
['export {A, B} from "a.js"', ['A', 'B'], "a.js"],
];

var test = function(code) {
return uglify.parse(code, {fromString: true});
};

var extractNames = function(symbols) {
var ret = [];
for (var i = 0; i < symbols.length; i++) {
ret.push(symbols[i].name.name)
}
return ret;
};

for (var i = 0; i < inputs.length; i++) {
var ast = test(inputs[i][0]);
var names = inputs[i][1];
var filename = inputs[i][2];
assert(ast instanceof uglify.AST_Toplevel);
assert.equal(ast.body.length, 1);
var st = ast.body[0];
assert(st instanceof uglify.AST_Export);
var actualNames = extractNames(st.exported_names);
assert.deepEqual(actualNames, names);
assert.equal(st.module_name.value, filename)
}
})
});
35 changes: 35 additions & 0 deletions test/mocha/issue1702.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
var uglify = require('../../');
var assert = require("assert");

describe("For statement", function() {
it("For variable should list enclosing scope in its references (issue #17022)", function() {
var ast = uglify.parse("function f() { for (var a = 0; a < 10; a++) {} }");
ast.figure_out_scope();

var checkWalker = new uglify.TreeWalker(function(node, descend) {
if (node instanceof uglify.AST_VarDef) {
console.log("AST_VarDef");
// one reference should be in the AST_Defun scope - search for it

var walkNode = function (r) {
console.log(r.CTOR.name);
var walker = new uglify.TreeWalker(function(node, descend){
// do not walk into any other scope, it should be listed if needed
console.log(" " + node.CTOR.name);
if (node instanceof uglify.AST_Scope && node != r.scope) return true;
if (node instanceof uglify.AST_For) {
console.log("Great - we found the for statement referencing the variable")
}
return false;
});
r.scope.walk(walker);
r.walk(walker);
};

node.name.thedef.orig.forEach(walkNode);
node.name.thedef.references.forEach(walkNode);
}
});
ast.walk(checkWalker);
});
});

0 comments on commit 5dea522

Please sign in to comment.