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"
}, AST_Definitions);
var AST_NameImport = DEFNODE("NameImport", "foreign_name name", {
$documentation: "The part of the import statement that imports names from a module.",
var AST_NameMapping = DEFNODE("NameMapping", "foreign_name name", {
$documentation: "The part of the export/import statement that declare names from a module.",
$propdoc: {
foreign_name: "[AST_SymbolImportForeign] The name being imported (as specified in the module)",
name: "[AST_SymbolImport] The name as it becomes available to this module."
foreign_name: "[AST_SymbolExportForeign|AST_SymbolImportForeign] The name being exported/imported (as specified in the module)",
name: "[AST_SymbolExport|AST_SymbolImport] The name as it is visible to this module."
},
_walk: function (visitor) {
return visitor._visit(this, function() {
@ -647,7 +647,7 @@ var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", {
$documentation: "An `import` statement",
$propdoc: {
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",
},
_walk: function(visitor) {
@ -670,7 +670,7 @@ var AST_Export = DEFNODE("Export", "exported_definition exported_value is_defaul
$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",
exported_names: "[AST_NameMapping*?] 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"
},
@ -682,6 +682,14 @@ var AST_Export = DEFNODE("Export", "exported_definition exported_value is_defaul
if (this.exported_value) {
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);
@ -996,7 +1004,7 @@ var AST_SymbolCatch = DEFNODE("SymbolCatch", null, {
}, AST_SymbolBlockDeclaration);
var AST_SymbolImport = DEFNODE("SymbolImport", null, {
$documentation: "Symbol refering to an imported name",
$documentation: "Symbol referring to an imported name",
}, AST_SymbolBlockDeclaration);
var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", null, {
@ -1018,6 +1026,14 @@ var AST_SymbolRef = DEFNODE("SymbolRef", null, {
$documentation: "Reference to some symbol (not definition/declaration)",
}, 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, {
$documentation: "Reference to a label symbol",
}, AST_Symbol);

View File

@ -672,7 +672,9 @@ merge(Compressor.prototype, {
node instanceof AST_DefClass ||
node instanceof AST_Defun ||
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
}
if (node instanceof AST_Definitions && scope === self) {
var in_export = tw.parent() instanceof AST_Export;
node.definitions.forEach(function(def){
if (def.name instanceof AST_SymbolVar) {
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) {
if (node instanceof AST_SymbolDeclaration) {
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.push(def);
}
@ -4037,6 +4040,10 @@ merge(Compressor.prototype, {
return self;
});
OPT(AST_SymbolExport, function(self, compressor){
return self;
});
OPT(AST_SymbolRef, function(self, compressor){
var def = self.resolve_defines(compressor);
if (def) {

View File

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

View File

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

View File

@ -243,9 +243,22 @@ TreeTransformer.prototype = new TreeWalker;
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) {
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_names) do_list(self.exported_names, tw);
if (self.module_name) self.module_name = self.module_name.transform(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;
}
}
}