From 51e944b7dad40a5b934d6b47aa288c9a9b072b19 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Tue, 11 Aug 2015 20:26:14 +0200 Subject: [PATCH] Add a method of dumping the AST --- bin/uglifyjs | 5 ++++- lib/ast.js | 35 ++++++++++++++++++++++++++--------- lib/utils.js | 21 +++++++++++++++++++++ 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 3f0c8254..10436405 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -76,6 +76,7 @@ You need to pass an argument to this option to specify the name that your module .describe("name-cache", "File to hold mangled names mappings") .describe("pure-funcs", "List of functions that can be safely removed if their return value is not used") .describe("dump-spidermonkey-ast", "Dump SpiderMonkey AST to stdout.") + .describe("dump-ast", "Dump the AST instead of generating output") .alias("p", "prefix") .alias("o", "output") @@ -130,6 +131,7 @@ You need to pass an argument to this option to specify the name that your module .boolean("bare-returns") .boolean("keep-fnames") .boolean("reserve-domprops") + .boolean("dump-ast") .wrap(80) @@ -476,7 +478,8 @@ async.eachLimit(files, 1, function (file, cb) { print(JSON.stringify(TOPLEVEL.to_mozilla_ast(), null, 2)); } else { time_it("generate", function(){ - TOPLEVEL.print(output); + if (ARGS.dump_ast) output.print(TOPLEVEL.dump()); + else TOPLEVEL.print(output); }); output = output.get(); diff --git a/lib/ast.js b/lib/ast.js index 42506cb2..7127bbee 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -47,11 +47,24 @@ function DEFNODE(type, props, methods, base) { if (arguments.length < 4) base = AST_Node; if (!props) props = []; else props = props.split(/\s+/); + var dump_props = []; + props = props.map(function(prop){ + if (/!$/.test(prop)) { + prop = prop.slice(0, -1); + } else { + dump_props.push(prop); + } + return prop; + }); var self_props = props; - if (base && base.PROPS) - props = props.concat(base.PROPS); - var code = "return function AST_" + type + "(props){ if (props) { "; - for (var i = props.length; --i >= 0;) { + if (base) { + if (base.PROPS) props = props.concat(base.PROPS); + if (base.DUMP_PROPS) dump_props = dump_props.concat(base.DUMP_PROPS); + } + var code = "return function AST_" + type + "(props){"; + if (type) { code += "this._class = 'AST_" + type + "';"; } + code += " if (props) { "; + for (var i = 0; i < props.length; ++i) { code += "this." + props[i] + " = props." + props[i] + ";"; } var proto = base && new base; @@ -66,6 +79,7 @@ function DEFNODE(type, props, methods, base) { if (base) base.SUBCLASSES.push(ctor); ctor.prototype.CTOR = ctor; ctor.PROPS = props || null; + ctor.DUMP_PROPS = dump_props.sort(); ctor.SELF_PROPS = self_props; ctor.SUBCLASSES = []; if (type) { @@ -88,7 +102,7 @@ function DEFNODE(type, props, methods, base) { var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file raw", { }, null); -var AST_Node = DEFNODE("Node", "start end", { +var AST_Node = DEFNODE("Node", "start! end!", { clone: function() { return new this.CTOR(this); }, @@ -102,6 +116,9 @@ var AST_Node = DEFNODE("Node", "start end", { }, walk: function(visitor) { return this._walk(visitor); // not sure the indirection will be any help + }, + dump: function () { + return JSON.stringify(dump_internal(this), null, 2); } }, null); @@ -121,7 +138,7 @@ var AST_Debugger = DEFNODE("Debugger", null, { $documentation: "Represents a debugger statement", }, AST_Statement); -var AST_Directive = DEFNODE("Directive", "value scope quote", { +var AST_Directive = DEFNODE("Directive", "value scope! quote", { $documentation: "Represents a directive, like \"use strict\";", $propdoc: { value: "[string] The value of this directive as a plain string (it's not an AST_String!)", @@ -278,7 +295,7 @@ var AST_With = DEFNODE("With", "expression", { /* -----[ scope and functions ]----- */ -var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_eval parent_scope enclosed cname", { +var AST_Scope = DEFNODE("Scope", "directives variables! functions! uses_with! uses_eval! parent_scope! enclosed! cname!", { $documentation: "Base class for all statements introducing a lexical scope", $propdoc: { directives: "[string*/S] an array of directives declared in this scope", @@ -292,7 +309,7 @@ var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_ }, }, AST_Block); -var AST_Toplevel = DEFNODE("Toplevel", "globals", { +var AST_Toplevel = DEFNODE("Toplevel", "globals!", { $documentation: "The toplevel scope", $propdoc: { globals: "[Object/S] a map of name -> SymbolDef for all undeclared names", @@ -789,7 +806,7 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { $documentation: "An object getter property", }, AST_ObjectProperty); -var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { +var AST_Symbol = DEFNODE("Symbol", "scope! name thedef!", { $propdoc: { name: "[string] name of this symbol", scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)", diff --git a/lib/utils.js b/lib/utils.js index 8ef61936..4181cec8 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -214,6 +214,27 @@ function set_intersection(a, b) { }); }; +function dump_internal(val) { + if (val) { + if (typeof val.map == 'function') { + return val.filter(function (x) { + return x != null; + }).map(dump_internal); + } + + if (val.CTOR) { + var out = {_class: val._class}; + val.CTOR.DUMP_PROPS.forEach(function(prop){ + if (val[prop] != null) { + out[prop] = dump_internal(val[prop]); + } + }); + return out; + } + } + return val; +}; + // this function is taken from Acorn [1], written by Marijn Haverbeke // [1] https://github.com/marijnh/acorn function makePredicate(words) {