From: Mihai Bazon Date: Tue, 9 Oct 2012 13:25:45 +0000 (+0300) Subject: added $propdoc to AST nodes and some cleanups X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=1b0aab2ce9e7f429ceeda98ee0cda448085918ec;p=UglifyJS.git added $propdoc to AST nodes and some cleanups hopefully we can make the AST documentation self-generating --- diff --git a/lib/ast.js b/lib/ast.js index 39a84a65..7324e696 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -94,6 +94,11 @@ var AST_Node = DEFNODE("Node", "$self start end", { return new this.CTOR(this); }, $documentation: "Base class of all AST nodes", + $propdoc: { + $self: "[AST_Node] Reference to $self. Not modified by clone(). XXX: this could be removed.", + start: "[AST_Token] The first token of this node", + end: "[AST_Token] The last token of this node" + }, _walk: function(visitor) { return visitor._visit(this); }, @@ -120,10 +125,17 @@ var AST_Debugger = DEFNODE("Debugger", null, { var AST_Directive = DEFNODE("Directive", "value scope", { $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!)", + scope: "[AST_Scope/S] The scope that this directive affects" + }, }, AST_Statement); var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { $documentation: "A statement consisting of an expression, i.e. a = 1 + 2", + $propdoc: { + body: "[AST_Node] an expression node (should not be instanceof AST_Statement)" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.body._walk(visitor); @@ -142,6 +154,9 @@ function walk_body(node, visitor) { var AST_Block = DEFNODE("Block", "body", { $documentation: "A body of statements (usually bracketed)", + $propdoc: { + body: "[AST_Statement*] an array of statements" + }, _walk: function(visitor) { return visitor._visit(this, function(){ walk_body(this, visitor); @@ -162,6 +177,9 @@ var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`", + $propdoc: { + body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.body._walk(visitor); @@ -171,6 +189,9 @@ var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { $documentation: "Statement with a label", + $propdoc: { + label: "[AST_Label] a label definition" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.label._walk(visitor); @@ -181,6 +202,9 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { var AST_DWLoop = DEFNODE("DWLoop", "condition", { $documentation: "Base class for do/while statements", + $propdoc: { + condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.condition._walk(visitor); @@ -190,15 +214,20 @@ var AST_DWLoop = DEFNODE("DWLoop", "condition", { }, AST_StatementWithBody); var AST_Do = DEFNODE("Do", null, { - $documentation: "A `do` statement" + $documentation: "A `do` statement", }, AST_DWLoop); var AST_While = DEFNODE("While", null, { - $documentation: "A `while` statement" + $documentation: "A `while` statement", }, AST_DWLoop); var AST_For = DEFNODE("For", "init condition step", { $documentation: "A `for` statement", + $propdoc: { + init: "[AST_Node?] the `for` initialization code, or null if empty", + condition: "[AST_Node?] the `for` termination clause, or null if empty", + step: "[AST_Node?] the `for` update clause, or null if empty" + }, _walk: function(visitor) { return visitor._visit(this, function(){ if (this.init) this.init._walk(visitor); @@ -211,6 +240,11 @@ var AST_For = DEFNODE("For", "init condition step", { var AST_ForIn = DEFNODE("ForIn", "init name object", { $documentation: "A `for ... in` statement", + $propdoc: { + init: "[AST_Node] the `for/in` initialization code", + name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var", + object: "[AST_Node] the object that we're looping through" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.init._walk(visitor); @@ -222,6 +256,9 @@ var AST_ForIn = DEFNODE("ForIn", "init name object", { var AST_With = DEFNODE("With", "expression", { $documentation: "A `with` statement", + $propdoc: { + expression: "[AST_Node] the `with` expression" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.expression._walk(visitor); @@ -234,10 +271,23 @@ var AST_With = DEFNODE("With", "expression", { 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", + variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope", + functions: "[Object/S] like `variables`, but only lists function declarations", + uses_with: "[boolean/S] tells whether this scope uses the `with` statement", + uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`", + parent_scope: "[AST_Scope?/S] link to the parent scope", + enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes", + cname: "[integer/S] current index for mangling variables (used internally by the mangler)", + }, }, AST_Block); var AST_Toplevel = DEFNODE("Toplevel", "globals", { $documentation: "The toplevel scope", + $propdoc: { + globals: "[Object/S] a map of name -> SymbolDef for all undeclared names", + }, wrap_commonjs: function(name, export_all) { var self = this; if (export_all) { @@ -281,6 +331,11 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", { var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", { $documentation: "Base class for functions", + $propdoc: { + name: "[AST_SymbolDeclaration?] the name of this function", + argnames: "[AST_SymbolFunarg*] array of function arguments", + uses_arguments: "[boolean/S] tells whether this function accesses the arguments array" + }, _walk: function(visitor) { return visitor._visit(this, function(){ if (this.name) this.name._walk(visitor); @@ -308,6 +363,9 @@ var AST_Jump = DEFNODE("Jump", null, { var AST_Exit = DEFNODE("Exit", "value", { $documentation: "Base class for “exits” (`return` and `throw`)", + $propdoc: { + value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return" + }, _walk: function(visitor) { return visitor._visit(this, this.value && function(){ this.value._walk(visitor); @@ -323,8 +381,11 @@ var AST_Throw = DEFNODE("Throw", null, { $documentation: "A `throw` statement" }, AST_Exit); -var AST_LoopControl = DEFNODE("LoopControl", "label loopcontrol_target", { +var AST_LoopControl = DEFNODE("LoopControl", "label", { $documentation: "Base class for loop control statements (`break` and `continue`)", + $propdoc: { + label: "[AST_LabelRef?] the label, or null if none", + }, _walk: function(visitor) { return visitor._visit(this, this.label && function(){ this.label._walk(visitor); @@ -344,6 +405,10 @@ var AST_Continue = DEFNODE("Continue", null, { var AST_If = DEFNODE("If", "condition alternative", { $documentation: "A `if` statement", + $propdoc: { + condition: "[AST_Node] the `if` condition", + alternative: "[AST_Statement?] the `else` part, or null if not present" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.condition._walk(visitor); @@ -355,15 +420,18 @@ var AST_If = DEFNODE("If", "condition alternative", { /* -----[ SWITCH ]----- */ -var AST_Switch = DEFNODE("Switch", "body expression", { +var AST_Switch = DEFNODE("Switch", "expression", { $documentation: "A `switch` statement", + $propdoc: { + expression: "[AST_Node] the `switch` “discriminant”" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.expression._walk(visitor); walk_body(this, visitor); }); } -}, AST_Statement); +}, AST_Block); var AST_SwitchBranch = DEFNODE("SwitchBranch", null, { $documentation: "Base class for `switch` branches", @@ -375,6 +443,9 @@ var AST_Default = DEFNODE("Default", null, { var AST_Case = DEFNODE("Case", "expression", { $documentation: "A `case` switch branch", + $propdoc: { + expression: "[AST_Node] the `case` expression" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.expression._walk(visitor); @@ -387,6 +458,10 @@ var AST_Case = DEFNODE("Case", "expression", { var AST_Try = DEFNODE("Try", "bcatch bfinally", { $documentation: "A `try` statement", + $propdoc: { + bcatch: "[AST_Catch?] the catch block, or null if not present", + bfinally: "[AST_Finally?] the finally block, or null if not present" + }, _walk: function(visitor) { return visitor._visit(this, function(){ walk_body(this, visitor); @@ -404,6 +479,9 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", { // AST_Scope. var AST_Catch = DEFNODE("Catch", "argname", { $documentation: "A `catch` node; only makes sense as part of a `try` statement", + $propdoc: { + argname: "[AST_SymbolCatch] symbol for the exception" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.argname._walk(visitor); @@ -420,6 +498,9 @@ var AST_Finally = DEFNODE("Finally", null, { var AST_Definitions = DEFNODE("Definitions", "definitions", { $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)", + $propdoc: { + definitions: "[AST_VarDef*] array of variable definitions" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.definitions.forEach(function(def){ @@ -439,6 +520,10 @@ var AST_Const = DEFNODE("Const", null, { var AST_VarDef = DEFNODE("VarDef", "name value", { $documentation: "A variable declaration; only appears in a AST_Definitions node", + $propdoc: { + name: "[AST_SymbolVar|AST_SymbolConst] name of the variable", + value: "[AST_Node?] initializer, or null of there's no initializer" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.name._walk(visitor); @@ -451,6 +536,10 @@ var AST_VarDef = DEFNODE("VarDef", "name value", { var AST_Call = DEFNODE("Call", "expression args", { $documentation: "A function call expression", + $propdoc: { + expression: "[AST_Node] expression to invoke as function", + args: "[AST_Node*] array of arguments" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.expression._walk(visitor); @@ -467,6 +556,10 @@ var AST_New = DEFNODE("New", null, { var AST_Seq = DEFNODE("Seq", "car cdr", { $documentation: "A sequence expression (two comma-separated expressions)", + $propdoc: { + car: "[AST_Node] first element in sequence", + cdr: "[AST_Node] second element in sequence" + }, $cons: function(x, y) { var seq = new AST_Seq(x); seq.car = x; @@ -509,7 +602,11 @@ var AST_Seq = DEFNODE("Seq", "car cdr", { }); var AST_PropAccess = DEFNODE("PropAccess", "expression property", { - $documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`" + $documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`", + $propdoc: { + expression: "[AST_Node] the “container” expression", + property: "[AST_Node|string] the property to access. For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node" + } }); var AST_Dot = DEFNODE("Dot", null, { @@ -533,6 +630,10 @@ var AST_Sub = DEFNODE("Sub", null, { var AST_Unary = DEFNODE("Unary", "operator expression", { $documentation: "Base class for unary expressions", + $propdoc: { + operator: "[string] the operator", + expression: "[AST_Node] expression that this unary operator applies to" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.expression._walk(visitor); @@ -550,6 +651,11 @@ var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, { var AST_Binary = DEFNODE("Binary", "left operator right", { $documentation: "Binary expression, i.e. `a + b`", + $propdoc: { + left: "[AST_Node] left-hand side expression", + operator: "[string] the operator", + right: "[AST_Node] right-hand side expression" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.left._walk(visitor); @@ -560,6 +666,11 @@ var AST_Binary = DEFNODE("Binary", "left operator right", { var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", { $documentation: "Conditional expression using the ternary operator, i.e. `a ? b : c`", + $propdoc: { + condition: "[AST_Node]", + consequent: "[AST_Node]", + alternative: "[AST_Node]" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.condition._walk(visitor); @@ -569,7 +680,7 @@ var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", } }); -var AST_Assign = DEFNODE("Assign", "left operator right", { +var AST_Assign = DEFNODE("Assign", null, { $documentation: "An assignment expression — `a = b + 5`", }, AST_Binary); @@ -577,6 +688,9 @@ var AST_Assign = DEFNODE("Assign", "left operator right", { var AST_Array = DEFNODE("Array", "elements", { $documentation: "An array literal", + $propdoc: { + elements: "[AST_Node*] array of elements" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.elements.forEach(function(el){ @@ -588,6 +702,9 @@ var AST_Array = DEFNODE("Array", "elements", { var AST_Object = DEFNODE("Object", "properties", { $documentation: "An object literal", + $propdoc: { + properties: "[AST_ObjectProperty*] array of properties" + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.properties.forEach(function(prop){ @@ -599,6 +716,10 @@ var AST_Object = DEFNODE("Object", "properties", { var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { $documentation: "Base class for literal object properties", + $propdoc: { + key: "[string] the property name; it's always a plain string in our AST, no matter if it was a string, number or identifier in original code", + value: "[AST_Node] property value. For setters and getters this is an AST_Function." + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.value._walk(visitor); @@ -619,11 +740,19 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { }, AST_ObjectProperty); 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)", + thedef: "[SymbolDef/S] the definition of this symbol" + }, $documentation: "Base class for all symbols", }); var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", + $propdoc: { + init: "[AST_Node*/S] array of initializers for this declaration." + } }, AST_Symbol); var AST_SymbolVar = DEFNODE("SymbolVar", null, { @@ -650,8 +779,11 @@ var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { $documentation: "Symbol naming the exception in catch", }, AST_SymbolDeclaration); -var AST_Label = DEFNODE("Label", "references label_target", { +var AST_Label = DEFNODE("Label", "references", { $documentation: "Symbol naming a label (declaration)", + $propdoc: { + references: "[AST_LabelRef*] a list of nodes referring to this label" + } }, AST_SymbolDeclaration); var AST_SymbolRef = DEFNODE("SymbolRef", null, { @@ -675,16 +807,22 @@ var AST_Constant = DEFNODE("Constant", null, { var AST_String = DEFNODE("String", "value", { $documentation: "A string literal", + $propdoc: { + value: "[string] the contents of this string" + } }, AST_Constant); var AST_Number = DEFNODE("Number", "value", { $documentation: "A number literal", + $propdoc: { + value: "[number] the numeric value" + } }, AST_Constant); -var AST_RegExp = DEFNODE("Regexp", "pattern mods", { +var AST_RegExp = DEFNODE("RegExp", "value", { $documentation: "A regexp literal", - initialize: function() { - this.value = new RegExp(this.pattern, this.mods); + $propdoc: { + value: "[RegExp] the actual regexp" } }, AST_Constant); @@ -776,4 +914,23 @@ TreeWalker.prototype = { self = p; } }, + loopcontrol_target: function(label) { + var stack = this.stack; + if (label) { + for (var i = stack.length; --i >= 0;) { + var x = stack[i]; + if (x instanceof AST_LabeledStatement && x.label.name == label.name) { + return x.body.$self; + } + } + } else { + for (var i = stack.length; --i >= 0;) { + var x = stack[i]; + if (x instanceof AST_Switch) return x.$self; + if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) { + return (x.body instanceof AST_BlockStatement ? x.body : x).$self; + } + } + } + } }; diff --git a/lib/compress.js b/lib/compress.js index abb00c29..48a67d41 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -80,7 +80,7 @@ merge(Compressor.prototype, { }, before: function(node, descend, in_list) { if (node._squeezed) return node; - node = node.clone(); + this.stack[this.stack.length - 1] = node = node.clone(); if (node instanceof AST_Scope) { node.drop_unused(this); node = node.hoist_declarations(this); @@ -291,7 +291,7 @@ merge(Compressor.prototype, { var ab = aborts(stat.body); if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda) - || (ab instanceof AST_Continue && self === ab.target()))) { + || (ab instanceof AST_Continue && self.$self === compressor.loopcontrol_target(ab.label)))) { CHANGED = true; var body = as_statement_array(stat.body).slice(0, -1); stat = stat.clone(); @@ -308,7 +308,7 @@ merge(Compressor.prototype, { var ab = aborts(stat.alternative); if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda) - || (ab instanceof AST_Continue && self === ab.target()))) { + || (ab instanceof AST_Continue && self.$self === compressor.loopcontrol_target(ab.label)))) { CHANGED = true; stat = stat.clone(); stat.body = make_node(AST_BlockStatement, stat.body, { @@ -1187,7 +1187,7 @@ merge(Compressor.prototype, { var last_branch = self.body[self.body.length - 1]; if (last_branch) { var stat = last_branch.body[last_branch.body.length - 1]; // last statement - if (stat instanceof AST_Break && !stat.label) + if (stat instanceof AST_Break && compressor.loopcontrol_target(stat.label) === self.$self) last_branch.body.pop(); } return self; diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 70e8fc8d..4db8feff 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -125,9 +125,6 @@ return new (val ? AST_True : AST_False)(args); default: args.value = val; - var m = /\/(.*)\/(.*)/.exec(val+""); - args.pattern = m[1]; - args.mods = m[2]; return new AST_RegExp(args); } }, diff --git a/lib/output.js b/lib/output.js index b0a01613..571dd106 100644 --- a/lib/output.js +++ b/lib/output.js @@ -287,6 +287,7 @@ function OutputStream(options) { last : function() { return last }, semicolon : semicolon, force_semicolon : force_semicolon, + to_ascii : to_ascii, print_name : function(name) { print(make_name(name)) }, print_string : function(str) { print(encode_string(str)) }, next_indent : next_indent, @@ -982,10 +983,10 @@ function OutputStream(options) { output.print(make_num(self.getValue())); }); DEFPRINT(AST_RegExp, function(self, output){ - output.print("/"); - output.print(self.pattern); - output.print("/"); - if (self.mods) output.print(self.mods); + var str = self.getValue().toString(); + if (output.option("ascii_only")) + str = output.to_ascii(str); + output.print(str); }); function force_statement(stat, output) { diff --git a/lib/parse.js b/lib/parse.js index 10a0f1af..a66b32a3 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -528,7 +528,7 @@ function tokenizer($TEXT, filename) { regexp += ch; } var mods = read_name(); - return token("regexp", [ regexp, mods ]); + return token("regexp", new RegExp(regexp, mods)); }); }; @@ -1160,7 +1160,7 @@ function parse($TEXT, options) { ret = new AST_String({ start: tok, end: tok, value: tok.value }); break; case "regexp": - ret = new AST_RegExp({ start: tok, end: tok, pattern: tok.value[0], mods: tok.value[1] }); + ret = new AST_RegExp({ start: tok, end: tok, value: tok.value }); break; case "atom": switch (tok.value) { diff --git a/lib/scope.js b/lib/scope.js index 67ef267b..f705428e 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -113,28 +113,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){ if (node instanceof AST_Label) { node.thedef = node; node.init_scope_vars(); - var p = tw.parent(); // AST_LabeledStatement - var block = p.body; - if (block instanceof AST_StatementWithBody) - block = block.body; - node.label_target = block; } - if (node instanceof AST_LoopControl) { - if (!node.label) { - var a = tw.stack, i = a.length - 1; - while (--i >= 0) { - var p = a[i]; - if (p instanceof AST_For - || p instanceof AST_ForIn - || p instanceof AST_DWLoop - || p instanceof AST_SwitchBranch) { - node.loopcontrol_target = p.body; - break; - } - } - } - } - else if (node instanceof AST_SymbolLambda) { + if (node instanceof AST_SymbolLambda) { scope.def_function(node); node.init.push(tw.parent()); } @@ -347,11 +327,6 @@ AST_Symbol.DEFMETHOD("global", function(){ return this.definition().global; }); -AST_LoopControl.DEFMETHOD("target", function(){ - if (this.label) return this.label.definition().label_target; - return this.loopcontrol_target; -}); - AST_Toplevel.DEFMETHOD("mangle_names", function(options){ options = defaults(options, { sort : false, diff --git a/lib/transform.js b/lib/transform.js index e19210f9..2ba004ee 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -65,7 +65,7 @@ TreeTransformer.prototype = new TreeWalker; x = this; descend(x, tw); } else { - x = this.clone(); + tw.stack[tw.stack - 1] = x = this.clone(); descend(x, tw); y = tw.after(x, in_list); if (y !== undefined) x = y;