From 688b8a0bfb62af3e9c08ea39530a3815741481ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Fri, 29 Jan 2016 20:47:49 +0000 Subject: [PATCH 1/7] Starting out the import statement --- lib/ast.js | 7 +++++++ lib/output.js | 6 ++++++ lib/parse.js | 20 ++++++++++++++++++-- test/compress/harmony.js | 8 ++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 358862b1..0940647c 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -702,6 +702,13 @@ var AST_Const = DEFNODE("Const", null, { $documentation: "A `const` statement" }, AST_Definitions); +var AST_Import = DEFNODE("Import", "module_name", { + $documentation: "An `import` statement", + $propdoc: { + module_name: "[AST_String] String literal describing where this module came from", + } +}); + var AST_VarDef = DEFNODE("VarDef", "name value", { $documentation: "A variable declaration; only appears in a AST_Definitions node", $propdoc: { diff --git a/lib/output.js b/lib/output.js index c0b86b96..4dc53798 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1037,6 +1037,12 @@ function OutputStream(options) { DEFPRINT(AST_Const, function(self, output){ self._do_print(output, "const"); }); + DEFPRINT(AST_Import, function(self, output) { + output.print("import"); + output.space(); + self.module_name.print(output); + output.semicolon(); + }); function parenthesize_for_noin(node, output, noin) { if (!noin) node.print(output); diff --git a/lib/parse.js b/lib/parse.js index 0a6072af..39bce326 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -44,9 +44,9 @@ "use strict"; -var KEYWORDS = 'break case catch class const continue debugger default delete do else extends finally for function if in instanceof new return switch throw try typeof var let void while with'; +var KEYWORDS = 'break case catch class const continue debugger default delete do else extends finally for function if in instanceof new return switch throw try typeof var let void while with import'; var KEYWORDS_ATOM = 'false null true'; -var RESERVED_WORDS = 'abstract boolean byte char double enum export final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield' +var RESERVED_WORDS = 'abstract boolean byte char double enum export final float goto implements int interface long native package private protected public short static super synchronized this throws transient volatile yield' + " " + KEYWORDS_ATOM + " " + KEYWORDS; var KEYWORDS_BEFORE_EXPRESSION = 'return new delete throw else case'; @@ -909,6 +909,9 @@ function parse($TEXT, options) { body : statement() }); + case "import": + return tmp = import_(), semicolon(), tmp; + default: unexpected(); } @@ -1607,6 +1610,19 @@ function parse($TEXT, options) { } } + function import_() { + return new AST_Import({ + start: prev(), + module_name: new AST_String({ + start : S.token, + value : S.token.value, + quote : S.token.quote, + end : S.token, + }), + end: next(), + }); + } + function as_property_name() { var tmp = S.token; next(); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 622e00a0..4cb3921c 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -305,6 +305,14 @@ number_literals: { } } +import_statement: { + input: { + import "mod-name"; + import "module2"; + } + expect_exact: "import\"mod-name\";import\"module2\";" +} + // Fabio: My patches accidentally caused a crash whenever // there's an extraneous set of parens around an object. regression_cannot_destructure: { From c84306fc9fb52e047e7633b5e23bfa1f6007b25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sun, 21 Feb 2016 17:06:09 +0000 Subject: [PATCH 2/7] Importing names from places --- lib/ast.js | 15 ++++++++++++++- lib/compress.js | 4 ++++ lib/output.js | 6 ++++++ lib/parse.js | 24 ++++++++++++++++++------ test/compress/harmony.js | 16 ++++++++++++++-- 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 0940647c..3457c134 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -702,10 +702,19 @@ var AST_Const = DEFNODE("Const", null, { $documentation: "A `const` statement" }, AST_Definitions); -var AST_Import = DEFNODE("Import", "module_name", { +var AST_Import = DEFNODE("Import", "imported_name module_name", { $documentation: "An `import` statement", $propdoc: { + imported_name: "[AST_SymbolImport] The name of the variable holding the module's default export.", module_name: "[AST_String] String literal describing where this module came from", + }, + _walk: function(visitor) { + return visitor._visit(this, function() { + if (this.imported_name) { + this.imported_name._walk(visitor); + } + this.module_name._walk(visitor); + }); } }); @@ -1072,6 +1081,10 @@ var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { $documentation: "Symbol naming the exception in catch", }, AST_SymbolDeclaration); +var AST_SymbolImport = DEFNODE("SymbolImport", null, { + $documentation: "Symbol refering to an imported name", +}, AST_SymbolDeclaration); + var AST_Label = DEFNODE("Label", "references", { $documentation: "Symbol naming a label (declaration)", $propdoc: { diff --git a/lib/compress.js b/lib/compress.js index 52fbb74d..b1fbdf25 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1775,6 +1775,10 @@ merge(Compressor.prototype, { return self; }); + OPT(AST_Import, function(self, compressor) { + return self; + }); + OPT(AST_Function, function(self, compressor){ self = AST_Lambda.prototype.optimize.call(self, compressor); if (compressor.option("unused") && !compressor.option("keep_fnames")) { diff --git a/lib/output.js b/lib/output.js index 4dc53798..52747603 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1040,6 +1040,12 @@ function OutputStream(options) { DEFPRINT(AST_Import, function(self, output) { output.print("import"); output.space(); + if (self.imported_name) { + self.imported_name.print(output); + output.space(); + output.print("from") + output.space(); + } self.module_name.print(output); output.semicolon(); }); diff --git a/lib/parse.js b/lib/parse.js index 39bce326..93d9836e 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1611,15 +1611,27 @@ function parse($TEXT, options) { } function import_() { + var start = prev(); + var imported_name; + if (is("name")) { + imported_name = as_symbol(AST_SymbolImport); + expect_token("name", "from"); + } + var mod_str = S.token; + if (mod_str.type !== 'string') { + unexpected(); + } + next(); return new AST_Import({ - start: prev(), + start: start, + imported_name: imported_name, module_name: new AST_String({ - start : S.token, - value : S.token.value, - quote : S.token.quote, - end : S.token, + start: mod_str, + value: mod_str.value, + quote: mod_str.quote, + end: mod_str, }), - end: next(), + end: S.token, }); } diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 4cb3921c..dc1c2e4f 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -308,9 +308,21 @@ number_literals: { import_statement: { input: { import "mod-name"; - import "module2"; + import Foo from "bar"; + } + expect_exact: "import\"mod-name\";import Foo from\"bar\";" +} + +import_statement_mangling: { + mangle = { }; + input: { + import Foo from "foo"; + Foo(); + } + expect: { + import a from "foo"; + a(); } - expect_exact: "import\"mod-name\";import\"module2\";" } // Fabio: My patches accidentally caused a crash whenever From d74a8bfab5d7019a99edba1d641696c720f6fc47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Fri, 26 Feb 2016 21:12:19 +0000 Subject: [PATCH 3/7] importing names in the modules, not just default imports --- lib/ast.js | 15 ++++++++++++++- lib/output.js | 31 +++++++++++++++++++++++++++++++ lib/parse.js | 40 ++++++++++++++++++++++++++++++++++++++++ test/compress/harmony.js | 5 ++++- 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 3457c134..768bfc78 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -702,10 +702,19 @@ var AST_Const = DEFNODE("Const", null, { $documentation: "A `const` statement" }, AST_Definitions); -var AST_Import = DEFNODE("Import", "imported_name module_name", { +var AST_NameImport = DEFNODE("NameImport", "foreign_name name", { + $documentation: "The part of the import statement that imports 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." + } +}) + +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", module_name: "[AST_String] String literal describing where this module came from", }, _walk: function(visitor) { @@ -1085,6 +1094,10 @@ var AST_SymbolImport = DEFNODE("SymbolImport", null, { $documentation: "Symbol refering to an imported name", }, AST_SymbolDeclaration); +var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", null, { + $documentation: "A symbol imported from a module, but it is defined in the other module, and its real name is irrelevant for this module's purposes", +}, AST_Symbol); + var AST_Label = DEFNODE("Label", "references", { $documentation: "Symbol naming a label (declaration)", $propdoc: { diff --git a/lib/output.js b/lib/output.js index 52747603..a9c78bbf 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1042,6 +1042,25 @@ function OutputStream(options) { output.space(); if (self.imported_name) { self.imported_name.print(output); + } + if (self.imported_name && self.imported_names) { + output.print(","); + output.space(); + } + if (self.imported_names) { + output.print("{"); + self.imported_names.forEach(function(name_import, i) { + output.space(); + name_import.print(output); + if (i < self.imported_names.length - 1) { + output.print(","); + output.space(); + } + }); + output.space(); + output.print("}"); + } + if (self.imported_name || self.imported_names) { output.space(); output.print("from") output.space(); @@ -1050,6 +1069,18 @@ function OutputStream(options) { output.semicolon(); }); + DEFPRINT(AST_NameImport, function(self, output) { + if (self.foreign_name) { + self.foreign_name.print(output); + output.space(); + output.print("as"); + output.space(); + self.name.print(output); + } else { + self.name.print(output); + } + }); + function parenthesize_for_noin(node, output, noin) { if (!noin) node.print(output); else try { diff --git a/lib/parse.js b/lib/parse.js index 93d9836e..790dba60 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1613,8 +1613,28 @@ function parse($TEXT, options) { function import_() { var start = prev(); var imported_name; + var imported_names; if (is("name")) { imported_name = as_symbol(AST_SymbolImport); + } + + if (is("punc", ",")) { + next(); + } + + if (is("punc", "{")) { + next(); + imported_names = []; + while (!is("punc", "}")) { + imported_names.push(import_name()); + if (is("punc", ",")) { + next(); + } + } + next(); + } + + if (imported_names || imported_name) { expect_token("name", "from"); } var mod_str = S.token; @@ -1625,6 +1645,7 @@ function parse($TEXT, options) { return new AST_Import({ start: start, imported_name: imported_name, + imported_names: imported_names, module_name: new AST_String({ start: mod_str, value: mod_str.value, @@ -1635,6 +1656,25 @@ function parse($TEXT, options) { }); } + function import_name() { + var start = S.token; + var foreign_name; + var name; + + if (peek().value === "as" && peek().type === "name") { + foreign_name = as_symbol(AST_SymbolImportForeign); + next(); // The "as" word + } + name = as_symbol(AST_SymbolImport); + + return new AST_NameImport({ + start: start, + foreign_name: foreign_name, + name: name, + end: prev(), + }) + } + function as_property_name() { var tmp = S.token; next(); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index dc1c2e4f..59c6238a 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -309,8 +309,11 @@ import_statement: { input: { import "mod-name"; import Foo from "bar"; + import { Bar, Baz } from 'lel'; + import Bar, { Foo } from 'lel'; + import { Bar as kex, Baz as food } from 'lel'; } - expect_exact: "import\"mod-name\";import Foo from\"bar\";" + expect_exact: "import\"mod-name\";import Foo from\"bar\";import{Bar,Baz}from\"lel\";import Bar,{Foo}from\"lel\";import{Bar as kex,Baz as food}from\"lel\";" } import_statement_mangling: { From f5c6e28421b9e32668ae9a24c296f11976369bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sat, 27 Feb 2016 12:01:16 +0000 Subject: [PATCH 4/7] Mangling externally imported names by using aliasing --- lib/ast.js | 11 +++++++++++ lib/output.js | 8 ++++++-- lib/parse.js | 8 ++++++++ lib/scope.js | 9 +++++++++ test/compress/harmony.js | 10 ++++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 768bfc78..6a814e35 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -707,6 +707,12 @@ var AST_NameImport = DEFNODE("NameImport", "foreign_name name", { $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." + }, + _walk: function (visitor) { + return visitor._visit(this, function() { + this.foreign_name._walk(visitor); + this.name._walk(visitor); + }); } }) @@ -722,6 +728,11 @@ var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", { if (this.imported_name) { this.imported_name._walk(visitor); } + if (this.imported_names) { + this.imported_names.forEach(function (name_import) { + name_import._walk(visitor); + }); + } this.module_name._walk(visitor); }); } diff --git a/lib/output.js b/lib/output.js index a9c78bbf..e755ec04 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1070,8 +1070,12 @@ function OutputStream(options) { }); DEFPRINT(AST_NameImport, function(self, output) { - if (self.foreign_name) { - self.foreign_name.print(output); + var definition = self.name.definition(); + var names_are_different = + (definition && definition.mangled_name || self.name.name) !== + self.foreign_name.name; + if (names_are_different) { + output.print(self.foreign_name.name); output.space(); output.print("as"); output.space(); diff --git a/lib/parse.js b/lib/parse.js index 790dba60..ccf905fb 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1667,6 +1667,14 @@ function parse($TEXT, options) { } name = as_symbol(AST_SymbolImport); + if (foreign_name === undefined) { + foreign_name = new AST_SymbolImportForeign({ + name: name.name, + start: name.start, + end: name.end, + }); + } + return new AST_NameImport({ start: start, foreign_name: foreign_name, diff --git a/lib/scope.js b/lib/scope.js index 371afb2b..23f70ad1 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -171,6 +171,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ else if (node instanceof AST_SymbolClass) { defun.def_variable(node); } + else if (node instanceof AST_SymbolImport) { + scope.def_variable(node); + } else if (node instanceof AST_SymbolDefClass) { // This deals with the name of the class being available // inside the class. @@ -302,6 +305,12 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol){ this.variables.set(symbol.name, def); def.object_destructuring_arg = symbol.object_destructuring_arg; def.global = !this.parent_scope; + if (symbol instanceof AST_SymbolImport) { + // Imports are not global + def.global = false; + // TODO The real fix comes with block scoping being first class in uglifyJS, + // enabling import definitions to behave like module-level let declarations + } } else { def = this.variables.get(symbol.name); def.orig.push(symbol); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 59c6238a..5ef1416e 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -320,11 +320,21 @@ import_statement_mangling: { mangle = { }; input: { import Foo from "foo"; + import Bar, {Food} from "lel"; + import {What as Whatever} from "lel"; Foo(); + Bar(); + Food(); + Whatever(); } expect: { import a from "foo"; + import b, {Food as c} from "lel"; + import {What as d} from "lel"; a(); + b(); + c(); + d(); } } From cffbe3997092f09fd942ebd5470263d59ca0b5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sat, 27 Feb 2016 12:24:18 +0000 Subject: [PATCH 5/7] Implement the export statement --- lib/ast.js | 9 +++++++++ lib/output.js | 15 +++++++++++++++ lib/parse.js | 35 ++++++++++++++++++++++++++++++++++- test/compress/harmony.js | 12 ++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/lib/ast.js b/lib/ast.js index 6a814e35..e6867205 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -738,6 +738,15 @@ var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", { } }); +var AST_Export = DEFNODE("Export", "exported_definition exported_value is_default", { + $documentation: "An `export` statement", + $propdoc: { + exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition", + exported_value: "[AST_Node?] An exported value", + is_default: "[Boolean] Whether this is the default exported value of this module" + }, +}, AST_Statement); + var AST_VarDef = DEFNODE("VarDef", "name value", { $documentation: "A variable declaration; only appears in a AST_Definitions node", $propdoc: { diff --git a/lib/output.js b/lib/output.js index e755ec04..0dd5dcb9 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1085,6 +1085,21 @@ function OutputStream(options) { } }); + DEFPRINT(AST_Export, function(self, output) { + output.print("export"); + output.space(); + if (self.is_default) { + output.print("default"); + output.space(); + } + if (self.exported_value) { + self.exported_value.print(output); + } else if (self.exported_definition) { + self.exported_definition.print(output); + } + output.semicolon(); + }); + function parenthesize_for_noin(node, output, noin) { if (!noin) node.print(output); else try { diff --git a/lib/parse.js b/lib/parse.js index ccf905fb..1aeab668 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -44,7 +44,7 @@ "use strict"; -var KEYWORDS = 'break case catch class const continue debugger default delete do else extends finally for function if in instanceof new return switch throw try typeof var let void while with import'; +var KEYWORDS = 'break case catch class const continue debugger default delete do else extends finally for function if in instanceof new return switch throw try typeof var let void while with import export'; var KEYWORDS_ATOM = 'false null true'; var RESERVED_WORDS = 'abstract boolean byte char double enum export final float goto implements int interface long native package private protected public short static super synchronized this throws transient volatile yield' + " " + KEYWORDS_ATOM + " " + KEYWORDS; @@ -912,6 +912,9 @@ function parse($TEXT, options) { case "import": return tmp = import_(), semicolon(), tmp; + case "export": + return tmp = export_(), semicolon(), tmp; + default: unexpected(); } @@ -1683,6 +1686,36 @@ function parse($TEXT, options) { }) } + function export_() { + var start = S.token; + var is_default; + var exported_value; + var exported_definition; + + if (is("keyword", "default")) { + is_default = true; + next(); + } + + var is_definition = + is("keyword", "var") || is("keyword", "let") || is("keyword", "const") || + is("keyword", "class") || is("keyword", "function"); + + if (is_definition) { + exported_definition = statement(); + } else { + exported_value = expression(); + } + + return new AST_Export({ + start: start, + is_default: is_default, + exported_value: exported_value, + exported_definition: exported_definition, + end: prev(), + }); + } + function as_property_name() { var tmp = S.token; next(); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 5ef1416e..d27d903b 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -316,6 +316,18 @@ import_statement: { expect_exact: "import\"mod-name\";import Foo from\"bar\";import{Bar,Baz}from\"lel\";import Bar,{Foo}from\"lel\";import{Bar as kex,Baz as food}from\"lel\";" } +export_statement: { + input: { + export default 1; + export var foo = 4; + export let foo = 6; + export const foo = 6; + export function foo() {}; + export class foo { }; + } + expect_exact: "export default 1;export var foo=4;export let foo=6;export const foo=6;export function foo(){};export class foo{};" +} + import_statement_mangling: { mangle = { }; input: { From 4d85e5ef20e9d882b86325fe4f03edac719973e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sat, 27 Feb 2016 12:40:57 +0000 Subject: [PATCH 6/7] Don't mangle exported symbols --- lib/ast.js | 10 ++++++++++ lib/scope.js | 32 ++++++++++++++++++++++---------- test/compress/harmony.js | 16 ++++++++++++++++ 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index e6867205..160b1cc2 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -745,6 +745,16 @@ var AST_Export = DEFNODE("Export", "exported_definition exported_value is_defaul exported_value: "[AST_Node?] An exported value", is_default: "[Boolean] Whether this is the default exported value of this module" }, + _walk: function (visitor) { + visitor._visit(this, function () { + if (this.exported_definition) { + this.exported_definition._walk(visitor); + } + if (this.exported_value) { + this.exported_value._walk(visitor); + } + }); + } }, AST_Statement); var AST_VarDef = DEFNODE("VarDef", "name value", { diff --git a/lib/scope.js b/lib/scope.js index 23f70ad1..f9241f2d 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -49,6 +49,7 @@ function SymbolDef(scope, index, orig) { this.scope = scope; this.references = []; this.global = false; + this.export = false; this.mangled_name = null; this.object_destructuring_arg = false; this.undeclared = false; @@ -61,6 +62,7 @@ SymbolDef.prototype = { if (!options) options = {}; return (this.global && !options.toplevel) + || this.export || this.object_destructuring_arg || this.undeclared || (!options.eval && (this.scope.uses_eval || this.scope.uses_with)) @@ -102,6 +104,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ var defun = null; var nesting = 0; var in_destructuring = null; + var in_export; var tw = new TreeWalker(function(node, descend){ if (options.screw_ie8 && node instanceof AST_Catch) { var save_scope = scope; @@ -131,6 +134,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ labels = save_labels; return true; // don't descend again in TreeWalker } + if (node instanceof AST_Export) { + in_export = true; + descend(); + in_export = false; + } if (node instanceof AST_LabeledStatement) { var l = node.label; if (labels.has(l.name)) { @@ -151,14 +159,14 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ } if (node instanceof AST_SymbolFunarg) { node.object_destructuring_arg = !!in_destructuring; - defun.def_variable(node); + defun.def_variable(node, in_export); } if (node instanceof AST_Label) { node.thedef = node; node.references = []; } if (node instanceof AST_SymbolLambda) { - defun.def_function(node); + defun.def_function(node, in_export); } else if (node instanceof AST_SymbolDefun) { // Careful here, the scope where this should be defined is @@ -166,22 +174,22 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // scope when we encounter the AST_Defun node (which is // instanceof AST_Scope) but we get to the symbol a bit // later. - (node.scope = defun.parent_scope).def_function(node); + (node.scope = defun.parent_scope).def_function(node, in_export); } else if (node instanceof AST_SymbolClass) { - defun.def_variable(node); + defun.def_variable(node, in_export); } else if (node instanceof AST_SymbolImport) { - scope.def_variable(node); + scope.def_variable(node, in_export); } else if (node instanceof AST_SymbolDefClass) { // This deals with the name of the class being available // inside the class. - (node.scope = defun.parent_scope).def_function(node); + (node.scope = defun.parent_scope).def_function(node, in_export); } else if (node instanceof AST_SymbolVar || node instanceof AST_SymbolConst) { - var def = defun.def_variable(node); + var def = defun.def_variable(node, in_export); def.constant = node instanceof AST_SymbolConst; def.destructuring = in_destructuring; def.init = tw.parent().value; @@ -294,11 +302,11 @@ AST_Scope.DEFMETHOD("find_variable", function(name){ || (this.parent_scope && this.parent_scope.find_variable(name)); }); -AST_Scope.DEFMETHOD("def_function", function(symbol){ - this.functions.set(symbol.name, this.def_variable(symbol)); +AST_Scope.DEFMETHOD("def_function", function(symbol, in_export){ + this.functions.set(symbol.name, this.def_variable(symbol, in_export)); }); -AST_Scope.DEFMETHOD("def_variable", function(symbol){ +AST_Scope.DEFMETHOD("def_variable", function(symbol, in_export){ var def; if (!this.variables.has(symbol.name)) { def = new SymbolDef(this, this.variables.size(), symbol); @@ -311,6 +319,10 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol){ // TODO The real fix comes with block scoping being first class in uglifyJS, // enabling import definitions to behave like module-level let declarations } + if (!this.parent_scope && in_export) { + def.global = false; + def.export = true; + } } else { def = this.variables.get(symbol.name); def.orig.push(symbol); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index d27d903b..fec3a835 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -350,6 +350,22 @@ import_statement_mangling: { } } +export_statement_mangling: { + mangle = { }; + input: { + export var foo = 6; + export function bar() { } + export class Baz { } + bar(foo, Baz) + } + expect: { + export var foo = 6; + export function bar() { } + export class Baz { } + bar(foo, Baz) + } +} + // Fabio: My patches accidentally caused a crash whenever // there's an extraneous set of parens around an object. regression_cannot_destructure: { From 1f973a4b5947f322bf2cb5c9576811f9fa644a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sat, 27 Feb 2016 13:27:18 +0000 Subject: [PATCH 7/7] fix crash: Import statements don't abort --- lib/compress.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/compress.js b/lib/compress.js index b1fbdf25..f96fb04a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1025,6 +1025,7 @@ merge(Compressor.prototype, { var n = this.body.length; return n > 0 && aborts(this.body[n - 1]); }; + def(AST_Import, function(){ return null; }); def(AST_BlockStatement, block_aborts); def(AST_SwitchBranch, block_aborts); def(AST_If, function(){