support export statements properly

fixes #2038
fixes #2124
This commit is contained in:
alexlamsl 2017-06-20 15:01:01 +08:00
parent 849ba79dee
commit 0f80bc42d0
6 changed files with 139 additions and 32 deletions

View File

@ -629,11 +629,11 @@ var AST_Const = DEFNODE("Const", null, {
$documentation: "A `const` statement" $documentation: "A `const` statement"
}, AST_Definitions); }, AST_Definitions);
var AST_NameImport = DEFNODE("NameImport", "foreign_name name", { var AST_NameMapping = DEFNODE("NameMapping", "foreign_name name", {
$documentation: "The part of the import statement that imports names from a module.", $documentation: "The part of the export/import statement that declare names from a module.",
$propdoc: { $propdoc: {
foreign_name: "[AST_SymbolImportForeign] The name being imported (as specified in the module)", foreign_name: "[AST_SymbolExportForeign|AST_SymbolImportForeign] The name being exported/imported (as specified in the module)",
name: "[AST_SymbolImport] The name as it becomes available to this module." name: "[AST_SymbolExport|AST_SymbolImport] The name as it is visible to this module."
}, },
_walk: function (visitor) { _walk: function (visitor) {
return visitor._visit(this, function() { return visitor._visit(this, function() {
@ -647,7 +647,7 @@ var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", {
$documentation: "An `import` statement", $documentation: "An `import` statement",
$propdoc: { $propdoc: {
imported_name: "[AST_SymbolImport] The name of the variable holding the module's default export.", imported_name: "[AST_SymbolImport] The name of the variable holding the module's default export.",
imported_names: "[AST_NameImport*] The names of non-default imported variables", imported_names: "[AST_NameMapping*] The names of non-default imported variables",
module_name: "[AST_String] String literal describing where this module came from", module_name: "[AST_String] String literal describing where this module came from",
}, },
_walk: function(visitor) { _walk: function(visitor) {
@ -670,7 +670,7 @@ var AST_Export = DEFNODE("Export", "exported_definition exported_value is_defaul
$propdoc: { $propdoc: {
exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition", exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition",
exported_value: "[AST_Node?] An exported value", exported_value: "[AST_Node?] An exported value",
exported_names: "[AST_NameImport*?] List of exported names", exported_names: "[AST_NameMapping*?] List of exported names",
module_name: "[AST_String?] Name of the file to load exports from", 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" is_default: "[Boolean] Whether this is the default exported value of this module"
}, },
@ -682,6 +682,14 @@ var AST_Export = DEFNODE("Export", "exported_definition exported_value is_defaul
if (this.exported_value) { if (this.exported_value) {
this.exported_value._walk(visitor); this.exported_value._walk(visitor);
} }
if (this.exported_names) {
this.exported_names.forEach(function(name_export) {
name_export._walk(visitor);
});
}
if (this.module_name) {
this.module_name._walk(visitor);
}
}); });
} }
}, AST_Statement); }, AST_Statement);
@ -996,7 +1004,7 @@ var AST_SymbolCatch = DEFNODE("SymbolCatch", null, {
}, AST_SymbolBlockDeclaration); }, AST_SymbolBlockDeclaration);
var AST_SymbolImport = DEFNODE("SymbolImport", null, { var AST_SymbolImport = DEFNODE("SymbolImport", null, {
$documentation: "Symbol refering to an imported name", $documentation: "Symbol referring to an imported name",
}, AST_SymbolBlockDeclaration); }, AST_SymbolBlockDeclaration);
var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", null, { var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", null, {
@ -1018,6 +1026,14 @@ var AST_SymbolRef = DEFNODE("SymbolRef", null, {
$documentation: "Reference to some symbol (not definition/declaration)", $documentation: "Reference to some symbol (not definition/declaration)",
}, AST_Symbol); }, AST_Symbol);
var AST_SymbolExport = DEFNODE("SymbolExport", null, {
$documentation: "Symbol referring to a name to export",
}, AST_SymbolRef);
var AST_SymbolExportForeign = DEFNODE("SymbolExportForeign", null, {
$documentation: "A symbol exported from this module, but it is used in the other module, and its real name is irrelevant for this module's purposes",
}, AST_Symbol);
var AST_LabelRef = DEFNODE("LabelRef", null, { var AST_LabelRef = DEFNODE("LabelRef", null, {
$documentation: "Reference to a label symbol", $documentation: "Reference to a label symbol",
}, AST_Symbol); }, AST_Symbol);

View File

@ -672,7 +672,9 @@ merge(Compressor.prototype, {
node instanceof AST_DefClass || node instanceof AST_DefClass ||
node instanceof AST_Defun || node instanceof AST_Defun ||
node instanceof AST_Let || node instanceof AST_Let ||
node instanceof AST_Const node instanceof AST_Const ||
node instanceof AST_Export ||
node instanceof AST_Import
); );
} }
@ -2099,15 +2101,16 @@ merge(Compressor.prototype, {
return true; // don't go in nested scopes return true; // don't go in nested scopes
} }
if (node instanceof AST_Definitions && scope === self) { if (node instanceof AST_Definitions && scope === self) {
var in_export = tw.parent() instanceof AST_Export;
node.definitions.forEach(function(def){ node.definitions.forEach(function(def){
if (def.name instanceof AST_SymbolVar) { if (def.name instanceof AST_SymbolVar) {
var_defs_by_id.add(def.name.definition().id, def); var_defs_by_id.add(def.name.definition().id, def);
} }
if (!drop_vars) { if (in_export || !drop_vars) {
def.name.walk(new TreeWalker(function(node) { def.name.walk(new TreeWalker(function(node) {
if (node instanceof AST_SymbolDeclaration) { if (node instanceof AST_SymbolDeclaration) {
var def = node.definition(); var def = node.definition();
if (def.global && !(def.id in in_use_ids)) { if ((in_export || def.global) && !(def.id in in_use_ids)) {
in_use_ids[def.id] = true; in_use_ids[def.id] = true;
in_use.push(def); in_use.push(def);
} }
@ -4037,6 +4040,10 @@ merge(Compressor.prototype, {
return self; return self;
}); });
OPT(AST_SymbolExport, function(self, compressor){
return self;
});
OPT(AST_SymbolRef, function(self, compressor){ OPT(AST_SymbolRef, function(self, compressor){
var def = self.resolve_defines(compressor); var def = self.resolve_defines(compressor);
if (def) { if (def) {

View File

@ -1296,7 +1296,7 @@ function OutputStream(options) {
output.semicolon(); output.semicolon();
}); });
DEFPRINT(AST_NameImport, function(self, output) { DEFPRINT(AST_NameMapping, function(self, output) {
var definition = self.name.definition(); var definition = self.name.definition();
var names_are_different = var names_are_different =
(definition && definition.mangled_name || self.name.name) !== (definition && definition.mangled_name || self.name.name) !==
@ -1326,9 +1326,9 @@ function OutputStream(options) {
self.exported_names[0].print(output); self.exported_names[0].print(output);
} else { } else {
output.print("{"); output.print("{");
self.exported_names.forEach(function (name_import, i) { self.exported_names.forEach(function(name_export, i) {
output.space(); output.space();
name_import.print(output); name_export.print(output);
if (i < self.exported_names.length - 1) { if (i < self.exported_names.length - 1) {
output.print(","); output.print(",");
output.space(); output.space();

View File

@ -2302,7 +2302,7 @@ function parse($TEXT, options) {
next(); next();
} }
imported_names = import_names(true); imported_names = map_names(true);
if (imported_names || imported_name) { if (imported_names || imported_name) {
expect_token("name", "from"); expect_token("name", "from");
@ -2326,26 +2326,28 @@ function parse($TEXT, options) {
}); });
} }
function import_name() { function map_name(is_import) {
var foreign_type = is_import ? AST_SymbolImportForeign : AST_SymbolExportForeign;
var type = is_import ? AST_SymbolImport : AST_SymbolExport;
var start = S.token; var start = S.token;
var foreign_name; var foreign_name;
var name; var name;
if (peek().value === "as" && peek().type === "name") { if (peek().value === "as" && peek().type === "name") {
foreign_name = as_symbol(AST_SymbolImportForeign); foreign_name = as_symbol(foreign_type);
next(); // The "as" word next(); // The "as" word
} }
name = as_symbol(AST_SymbolImport); name = as_symbol(type);
if (foreign_name === undefined) { if (foreign_name === undefined) {
foreign_name = new AST_SymbolImportForeign({ foreign_name = new foreign_type({
name: name.name, name: name.name,
start: name.start, start: name.start,
end: name.end, end: name.end,
}); });
} }
return new AST_NameImport({ return new AST_NameMapping({
start: start, start: start,
foreign_name: foreign_name, foreign_name: foreign_name,
name: name, name: name,
@ -2353,26 +2355,26 @@ function parse($TEXT, options) {
}) })
} }
function import_nameAsterisk(name) { function map_nameAsterisk(is_import, name) {
var foreign_type = is_import ? AST_SymbolImportForeign : AST_SymbolExportForeign;
var type = is_import ? AST_SymbolImport : AST_SymbolExport;
var start = S.token; var start = S.token;
var foreign_name; var foreign_name;
var end = prev(); var end = prev();
name = name || new AST_SymbolImport({ name = name || new type({
name: '*', name: '*',
start: start, start: start,
end: end, end: end,
}); });
foreign_name = new AST_SymbolImportForeign({ foreign_name = new foreign_type({
name: '*', name: '*',
start: start, start: start,
end: end, end: end,
}); });
return new AST_NameImport({ return new AST_NameMapping({
start: start, start: start,
foreign_name: foreign_name, foreign_name: foreign_name,
name: name, name: name,
@ -2380,13 +2382,13 @@ function parse($TEXT, options) {
}) })
} }
function import_names(allow_as) { function map_names(is_import) {
var names; var names;
if (is("punc", "{")) { if (is("punc", "{")) {
next(); next();
names = []; names = [];
while (!is("punc", "}")) { while (!is("punc", "}")) {
names.push(import_name()); names.push(map_name(is_import));
if (is("punc", ",")) { if (is("punc", ",")) {
next(); next();
} }
@ -2395,11 +2397,11 @@ function parse($TEXT, options) {
} else if (is("operator", "*")) { } else if (is("operator", "*")) {
var name; var name;
next(); next();
if (allow_as && is("name", "as")) { if (is_import && is("name", "as")) {
next(); // The "as" word next(); // The "as" word
name = as_symbol(AST_SymbolImportForeign); name = as_symbol(AST_SymbolImportForeign);
} }
names = [import_nameAsterisk(name)]; names = [map_nameAsterisk(is_import, name)];
} }
return names; return names;
} }
@ -2415,7 +2417,7 @@ function parse($TEXT, options) {
is_default = true; is_default = true;
next(); next();
} else { } else {
exported_names = import_names(false); exported_names = map_names(false);
if (exported_names) { if (exported_names) {
if (is("name", "from")) { if (is("name", "from")) {

View File

@ -243,9 +243,22 @@ TreeTransformer.prototype = new TreeWalker;
self.expression = self.expression.transform(tw); self.expression = self.expression.transform(tw);
}); });
_(AST_NameMapping, function(self, tw) {
self.foreign_name = self.foreign_name.transform(tw);
self.name = self.name.transform(tw);
});
_(AST_Import, function(self, tw) {
if (self.imported_name) self.imported_name = self.imported_name.transform(tw);
if (self.imported_names) do_list(self.imported_names, tw);
self.module_name = self.module_name.transform(tw);
});
_(AST_Export, function(self, tw) { _(AST_Export, function(self, tw) {
if (self.exported_definition) self.exported_definition = self.exported_definition.transform(tw); if (self.exported_definition) self.exported_definition = self.exported_definition.transform(tw);
if (self.exported_value) self.exported_value = self.exported_value.transform(tw); if (self.exported_value) self.exported_value = self.exported_value.transform(tw);
if (self.exported_names) do_list(self.exported_names, tw);
if (self.module_name) self.module_name = self.module_name.transform(tw);
}); });
_(AST_TemplateString, function(self, tw) { _(AST_TemplateString, function(self, tw) {

69
test/compress/export.js Normal file
View File

@ -0,0 +1,69 @@
issue_2038_1: {
options = {
toplevel: true,
unused: true,
}
mangle = {
toplevel: true,
}
input: {
export var V = 1;
export let L = 2;
export const C = 3;
}
expect: {
export var V = 1;
export let L = 2;
export const C = 3;
}
}
issue_2038_2: {
options = {
toplevel: true,
unused: true,
}
mangle = {
toplevel: true,
}
input: {
let LET = 1;
const CONST = 2;
var VAR = 3;
export { LET, CONST, VAR };
}
expect: {
let a = 1;
const c = 2;
var n = 3;
export { LET as a, CONST as c, VAR as n };
}
}
issue_2124: {
options = {
unused: true,
}
input: {
{
export var V = 1;
}
{
export let L = 2;
}
{
export const C = 3;
}
}
expect: {
{
export var V = 1;
}
{
export let L = 2;
}
{
export const C = 3;
}
}
}