| | |
| | | } |
| | | if (S.token.value == "import" && !is_token(peek(), "punc", "(") && !is_token(peek(), "punc", ".")) { |
| | | next(); |
| | | var node = import_(); |
| | | var node = import_statement(); |
| | | semicolon(); |
| | | return node; |
| | | } |
| | |
| | | case "export": |
| | | if (!is_token(peek(), "punc", "(")) { |
| | | next(); |
| | | var node = export_(); |
| | | var node = export_statement(); |
| | | if (is("punc", ";")) semicolon(); |
| | | return node; |
| | | } |
| | |
| | | }); |
| | | }; |
| | | |
| | | function track_used_binding_identifiers(is_parameter, strict) { |
| | | var parameters = new Set(); |
| | | var duplicate = false; |
| | | var default_assignment = false; |
| | | var spread = false; |
| | | var strict_mode = !!strict; |
| | | var tracker = { |
| | | add_parameter: function(token) { |
| | | if (parameters.has(token.value)) { |
| | | if (duplicate === false) { |
| | | duplicate = token; |
| | | } |
| | | tracker.check_strict(); |
| | | } else { |
| | | parameters.add(token.value); |
| | | if (is_parameter) { |
| | | switch (token.value) { |
| | | case "arguments": |
| | | case "eval": |
| | | case "yield": |
| | | if (strict_mode) { |
| | | token_error(token, "Unexpected " + token.value + " identifier as parameter inside strict mode"); |
| | | } |
| | | break; |
| | | default: |
| | | if (RESERVED_WORDS.has(token.value)) { |
| | | unexpected(); |
| | | } |
| | | class UsedParametersTracker { |
| | | constructor(is_parameter, strict, duplicates_ok = false) { |
| | | this.is_parameter = is_parameter; |
| | | this.duplicates_ok = duplicates_ok; |
| | | this.parameters = new Set(); |
| | | this.duplicate = null; |
| | | this.default_assignment = false; |
| | | this.spread = false; |
| | | this.strict_mode = !!strict; |
| | | } |
| | | add_parameter(token) { |
| | | if (this.parameters.has(token.value)) { |
| | | if (this.duplicate === null) { |
| | | this.duplicate = token; |
| | | } |
| | | this.check_strict(); |
| | | } else { |
| | | this.parameters.add(token.value); |
| | | if (this.is_parameter) { |
| | | switch (token.value) { |
| | | case "arguments": |
| | | case "eval": |
| | | case "yield": |
| | | if (this.strict_mode) { |
| | | token_error(token, "Unexpected " + token.value + " identifier as parameter inside strict mode"); |
| | | } |
| | | break; |
| | | default: |
| | | if (RESERVED_WORDS.has(token.value)) { |
| | | unexpected(); |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | mark_default_assignment: function(token) { |
| | | if (default_assignment === false) { |
| | | default_assignment = token; |
| | | } |
| | | }, |
| | | mark_spread: function(token) { |
| | | if (spread === false) { |
| | | spread = token; |
| | | } |
| | | }, |
| | | mark_strict_mode: function() { |
| | | strict_mode = true; |
| | | }, |
| | | is_strict: function() { |
| | | return default_assignment !== false || spread !== false || strict_mode; |
| | | }, |
| | | check_strict: function() { |
| | | if (tracker.is_strict() && duplicate !== false) { |
| | | token_error(duplicate, "Parameter " + duplicate.value + " was used already"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | return tracker; |
| | | } |
| | | mark_default_assignment(token) { |
| | | if (this.default_assignment === false) { |
| | | this.default_assignment = token; |
| | | } |
| | | } |
| | | mark_spread(token) { |
| | | if (this.spread === false) { |
| | | this.spread = token; |
| | | } |
| | | } |
| | | mark_strict_mode() { |
| | | this.strict_mode = true; |
| | | } |
| | | is_strict() { |
| | | return this.default_assignment !== false || this.spread !== false || this.strict_mode; |
| | | } |
| | | check_strict() { |
| | | if (this.is_strict() && this.duplicate !== null && !this.duplicates_ok) { |
| | | token_error(this.duplicate, "Parameter " + this.duplicate.value + " was used already"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | function parameters(params) { |
| | | var used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict")); |
| | | var used_parameters = new UsedParametersTracker(true, S.input.has_directive("use strict")); |
| | | |
| | | expect("("); |
| | | |
| | |
| | | var param; |
| | | var expand = false; |
| | | if (used_parameters === undefined) { |
| | | used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict")); |
| | | used_parameters = new UsedParametersTracker(true, S.input.has_directive("use strict")); |
| | | } |
| | | if (is("expand", "...")) { |
| | | expand = S.token; |
| | |
| | | var expand_token; |
| | | var first_token = S.token; |
| | | if (used_parameters === undefined) { |
| | | used_parameters = track_used_binding_identifiers(false, S.input.has_directive("use strict")); |
| | | const strict = S.input.has_directive("use strict"); |
| | | const duplicates_ok = symbol_type === AST_SymbolVar; |
| | | used_parameters = new UsedParametersTracker(false, strict, duplicates_ok); |
| | | } |
| | | symbol_type = symbol_type === undefined ? AST_SymbolFunarg : symbol_type; |
| | | if (is("punc", "[")) { |
| | |
| | | if (is("punc", "{") || is("punc", "[")) { |
| | | def = new AST_VarDef({ |
| | | start: S.token, |
| | | name: binding_element(undefined ,sym_type), |
| | | name: binding_element(undefined, sym_type), |
| | | value: is("operator", "=") ? (expect_token("operator", "="), expression(false, no_in)) : null, |
| | | end: prev() |
| | | }); |
| | |
| | | }; |
| | | |
| | | const is_not_method_start = () => |
| | | !is("punc", "(") && !is("punc", ",") && !is("punc", "}") && !is("operator", "="); |
| | | !is("punc", "(") && !is("punc", ",") && !is("punc", "}") && !is("punc", ";") && !is("operator", "="); |
| | | |
| | | var is_async = false; |
| | | var is_static = false; |
| | |
| | | } |
| | | } |
| | | |
| | | function import_() { |
| | | function maybe_import_assertion() { |
| | | if (is("name", "assert") && !has_newline_before(S.token)) { |
| | | next(); |
| | | return object_or_destructuring_(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | function import_statement() { |
| | | var start = prev(); |
| | | |
| | | var imported_name; |
| | |
| | | unexpected(); |
| | | } |
| | | next(); |
| | | |
| | | const assert_clause = maybe_import_assertion(); |
| | | |
| | | return new AST_Import({ |
| | | start: start, |
| | | imported_name: imported_name, |
| | | imported_names: imported_names, |
| | | start, |
| | | imported_name, |
| | | imported_names, |
| | | module_name: new AST_String({ |
| | | start: mod_str, |
| | | value: mod_str.value, |
| | | quote: mod_str.quote, |
| | | end: mod_str, |
| | | }), |
| | | assert_clause, |
| | | end: S.token, |
| | | }); |
| | | } |
| | |
| | | return names; |
| | | } |
| | | |
| | | function export_() { |
| | | function export_statement() { |
| | | var start = S.token; |
| | | var is_default; |
| | | var exported_names; |
| | |
| | | } |
| | | next(); |
| | | |
| | | const assert_clause = maybe_import_assertion(); |
| | | |
| | | return new AST_Export({ |
| | | start: start, |
| | | is_default: is_default, |
| | |
| | | end: mod_str, |
| | | }), |
| | | end: prev(), |
| | | assert_clause |
| | | }); |
| | | } else { |
| | | return new AST_Export({ |
| | |
| | | exported_value: exported_value, |
| | | exported_definition: exported_definition, |
| | | end: prev(), |
| | | assert_clause: null |
| | | }); |
| | | } |
| | | |
| | |
| | | |
| | | ***********************************************************************/ |
| | | |
| | | function DEFNODE(type, props, methods, base = AST_Node) { |
| | | function DEFNODE(type, props, ctor, methods, base = AST_Node) { |
| | | if (!props) props = []; |
| | | else props = props.split(/\s+/); |
| | | 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;) { |
| | | code += "this." + props[i] + " = props." + props[i] + ";"; |
| | | } |
| | | const proto = base && Object.create(base.prototype); |
| | | if (proto && proto.initialize || (methods && methods.initialize)) |
| | | code += "this.initialize();"; |
| | | code += "}"; |
| | | code += "this.flags = 0;"; |
| | | code += "}"; |
| | | var ctor = new Function(code)(); |
| | | if (proto) { |
| | | ctor.prototype = proto; |
| | | ctor.BASE = base; |
| | |
| | | if (type) { |
| | | ctor.prototype.TYPE = ctor.TYPE = type; |
| | | } |
| | | if (methods) for (i in methods) if (HOP(methods, i)) { |
| | | if (methods) for (let i in methods) if (HOP(methods, i)) { |
| | | if (i[0] === "$") { |
| | | ctor[i.substr(1)] = methods[i]; |
| | | } else { |
| | |
| | | } |
| | | } |
| | | |
| | | var AST_Node = DEFNODE("Node", "start end", { |
| | | var AST_Node = DEFNODE("Node", "start end", function AST_Node(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | _clone: function(deep) { |
| | | if (deep) { |
| | | var self = this.clone(); |
| | |
| | | |
| | | /* -----[ statements ]----- */ |
| | | |
| | | var AST_Statement = DEFNODE("Statement", null, { |
| | | var AST_Statement = DEFNODE("Statement", null, function AST_Statement(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class of all statements", |
| | | }); |
| | | |
| | | var AST_Debugger = DEFNODE("Debugger", null, { |
| | | var AST_Debugger = DEFNODE("Debugger", null, function AST_Debugger(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Represents a debugger statement", |
| | | }, AST_Statement); |
| | | |
| | | var AST_Directive = DEFNODE("Directive", "value quote", { |
| | | var AST_Directive = DEFNODE("Directive", "value quote", function AST_Directive(props) { |
| | | if (props) { |
| | | this.value = props.value; |
| | | this.quote = props.quote; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $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!)", |
| | |
| | | }, |
| | | }, AST_Statement); |
| | | |
| | | var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { |
| | | var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", function AST_SimpleStatement(props) { |
| | | if (props) { |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $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)" |
| | |
| | | return clone; |
| | | } |
| | | |
| | | var AST_Block = DEFNODE("Block", "body block_scope", { |
| | | var AST_Block = DEFNODE("Block", "body block_scope", function AST_Block(props) { |
| | | if (props) { |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A body of statements (usually braced)", |
| | | $propdoc: { |
| | | body: "[AST_Statement*] an array of statements", |
| | |
| | | clone: clone_block_scope |
| | | }, AST_Statement); |
| | | |
| | | var AST_BlockStatement = DEFNODE("BlockStatement", null, { |
| | | var AST_BlockStatement = DEFNODE("BlockStatement", null, function AST_BlockStatement(props) { |
| | | if (props) { |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A block statement", |
| | | }, AST_Block); |
| | | |
| | | var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { |
| | | var AST_EmptyStatement = DEFNODE("EmptyStatement", null, function AST_EmptyStatement(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The empty statement (empty block or simply a semicolon)" |
| | | }, AST_Statement); |
| | | |
| | | var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { |
| | | var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", function AST_StatementWithBody(props) { |
| | | if (props) { |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $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" |
| | | } |
| | | }, AST_Statement); |
| | | |
| | | var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { |
| | | var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", function AST_LabeledStatement(props) { |
| | | if (props) { |
| | | this.label = props.label; |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Statement with a label", |
| | | $propdoc: { |
| | | label: "[AST_Label] a label definition" |
| | |
| | | } |
| | | }, AST_StatementWithBody); |
| | | |
| | | var AST_IterationStatement = DEFNODE("IterationStatement", "block_scope", { |
| | | $documentation: "Internal class. All loops inherit from it.", |
| | | $propdoc: { |
| | | block_scope: "[AST_Scope] the block scope for this iteration statement." |
| | | }, |
| | | clone: clone_block_scope |
| | | }, AST_StatementWithBody); |
| | | var AST_IterationStatement = DEFNODE( |
| | | "IterationStatement", |
| | | "block_scope", |
| | | function AST_IterationStatement(props) { |
| | | if (props) { |
| | | this.block_scope = props.block_scope; |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | var AST_DWLoop = DEFNODE("DWLoop", "condition", { |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $documentation: "Internal class. All loops inherit from it.", |
| | | $propdoc: { |
| | | block_scope: "[AST_Scope] the block scope for this iteration statement." |
| | | }, |
| | | clone: clone_block_scope |
| | | }, |
| | | AST_StatementWithBody |
| | | ); |
| | | |
| | | var AST_DWLoop = DEFNODE("DWLoop", "condition", function AST_DWLoop(props) { |
| | | if (props) { |
| | | this.condition = props.condition; |
| | | this.block_scope = props.block_scope; |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class for do/while statements", |
| | | $propdoc: { |
| | | condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement" |
| | | } |
| | | }, AST_IterationStatement); |
| | | |
| | | var AST_Do = DEFNODE("Do", null, { |
| | | var AST_Do = DEFNODE("Do", null, function AST_Do(props) { |
| | | if (props) { |
| | | this.condition = props.condition; |
| | | this.block_scope = props.block_scope; |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `do` statement", |
| | | _walk: function(visitor) { |
| | | return visitor._visit(this, function() { |
| | |
| | | } |
| | | }, AST_DWLoop); |
| | | |
| | | var AST_While = DEFNODE("While", null, { |
| | | var AST_While = DEFNODE("While", null, function AST_While(props) { |
| | | if (props) { |
| | | this.condition = props.condition; |
| | | this.block_scope = props.block_scope; |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `while` statement", |
| | | _walk: function(visitor) { |
| | | return visitor._visit(this, function() { |
| | |
| | | }, |
| | | }, AST_DWLoop); |
| | | |
| | | var AST_For = DEFNODE("For", "init condition step", { |
| | | var AST_For = DEFNODE("For", "init condition step", function AST_For(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.condition = props.condition; |
| | | this.step = props.step; |
| | | this.block_scope = props.block_scope; |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `for` statement", |
| | | $propdoc: { |
| | | init: "[AST_Node?] the `for` initialization code, or null if empty", |
| | |
| | | }, |
| | | }, AST_IterationStatement); |
| | | |
| | | var AST_ForIn = DEFNODE("ForIn", "init object", { |
| | | var AST_ForIn = DEFNODE("ForIn", "init object", function AST_ForIn(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.object = props.object; |
| | | this.block_scope = props.block_scope; |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `for ... in` statement", |
| | | $propdoc: { |
| | | init: "[AST_Node] the `for/in` initialization code", |
| | |
| | | }, |
| | | }, AST_IterationStatement); |
| | | |
| | | var AST_ForOf = DEFNODE("ForOf", "await", { |
| | | var AST_ForOf = DEFNODE("ForOf", "await", function AST_ForOf(props) { |
| | | if (props) { |
| | | this.await = props.await; |
| | | this.init = props.init; |
| | | this.object = props.object; |
| | | this.block_scope = props.block_scope; |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `for ... of` statement", |
| | | }, AST_ForIn); |
| | | |
| | | var AST_With = DEFNODE("With", "expression", { |
| | | var AST_With = DEFNODE("With", "expression", function AST_With(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `with` statement", |
| | | $propdoc: { |
| | | expression: "[AST_Node] the `with` expression" |
| | |
| | | |
| | | /* -----[ scope and functions ]----- */ |
| | | |
| | | var AST_Scope = DEFNODE("Scope", "variables functions uses_with uses_eval parent_scope enclosed cname", { |
| | | $documentation: "Base class for all statements introducing a lexical scope", |
| | | $propdoc: { |
| | | variables: "[Map/S] a map of name -> SymbolDef for all variables/functions defined in this scope", |
| | | 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)", |
| | | }, |
| | | get_defun_scope: function() { |
| | | var self = this; |
| | | while (self.is_block_scope()) { |
| | | self = self.parent_scope; |
| | | var AST_Scope = DEFNODE( |
| | | "Scope", |
| | | "variables functions uses_with uses_eval parent_scope enclosed cname", |
| | | function AST_Scope(props) { |
| | | if (props) { |
| | | this.variables = props.variables; |
| | | this.functions = props.functions; |
| | | this.uses_with = props.uses_with; |
| | | this.uses_eval = props.uses_eval; |
| | | this.parent_scope = props.parent_scope; |
| | | this.enclosed = props.enclosed; |
| | | this.cname = props.cname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | return self; |
| | | }, |
| | | clone: function(deep, toplevel) { |
| | | var node = this._clone(deep); |
| | | if (deep && this.variables && toplevel && !this._block_scope) { |
| | | node.figure_out_scope({}, { |
| | | toplevel: toplevel, |
| | | parent_scope: this.parent_scope |
| | | }); |
| | | } else { |
| | | if (this.variables) node.variables = new Map(this.variables); |
| | | if (this.enclosed) node.enclosed = this.enclosed.slice(); |
| | | if (this._block_scope) node._block_scope = this._block_scope; |
| | | } |
| | | return node; |
| | | }, |
| | | pinned: function() { |
| | | return this.uses_eval || this.uses_with; |
| | | } |
| | | }, AST_Block); |
| | | |
| | | var AST_Toplevel = DEFNODE("Toplevel", "globals", { |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $documentation: "Base class for all statements introducing a lexical scope", |
| | | $propdoc: { |
| | | variables: "[Map/S] a map of name -> SymbolDef for all variables/functions defined in this scope", |
| | | 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)", |
| | | }, |
| | | get_defun_scope: function() { |
| | | var self = this; |
| | | while (self.is_block_scope()) { |
| | | self = self.parent_scope; |
| | | } |
| | | return self; |
| | | }, |
| | | clone: function(deep, toplevel) { |
| | | var node = this._clone(deep); |
| | | if (deep && this.variables && toplevel && !this._block_scope) { |
| | | node.figure_out_scope({}, { |
| | | toplevel: toplevel, |
| | | parent_scope: this.parent_scope |
| | | }); |
| | | } else { |
| | | if (this.variables) node.variables = new Map(this.variables); |
| | | if (this.enclosed) node.enclosed = this.enclosed.slice(); |
| | | if (this._block_scope) node._block_scope = this._block_scope; |
| | | } |
| | | return node; |
| | | }, |
| | | pinned: function() { |
| | | return this.uses_eval || this.uses_with; |
| | | } |
| | | }, |
| | | AST_Block |
| | | ); |
| | | |
| | | var AST_Toplevel = DEFNODE("Toplevel", "globals", function AST_Toplevel(props) { |
| | | if (props) { |
| | | this.globals = props.globals; |
| | | this.variables = props.variables; |
| | | this.functions = props.functions; |
| | | this.uses_with = props.uses_with; |
| | | this.uses_eval = props.uses_eval; |
| | | this.parent_scope = props.parent_scope; |
| | | this.enclosed = props.enclosed; |
| | | this.cname = props.cname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The toplevel scope", |
| | | $propdoc: { |
| | | globals: "[Map/S] a map of name -> SymbolDef for all undeclared names", |
| | |
| | | } |
| | | }, AST_Scope); |
| | | |
| | | var AST_Expansion = DEFNODE("Expansion", "expression", { |
| | | var AST_Expansion = DEFNODE("Expansion", "expression", function AST_Expansion(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "An expandible argument, such as ...rest, a splat, such as [1,2,...all], or an expansion in a variable declaration, such as var [first, ...rest] = list", |
| | | $propdoc: { |
| | | expression: "[AST_Node] the thing to be expanded" |
| | |
| | | }, |
| | | }); |
| | | |
| | | var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments is_generator async", { |
| | | $documentation: "Base class for functions", |
| | | $propdoc: { |
| | | name: "[AST_SymbolDeclaration?] the name of this function", |
| | | argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion|AST_DefaultAssign*] array of function arguments, destructurings, or expanding arguments", |
| | | uses_arguments: "[boolean/S] tells whether this function accesses the arguments array", |
| | | is_generator: "[boolean] is this a generator method", |
| | | async: "[boolean] is this method async", |
| | | }, |
| | | args_as_names: function () { |
| | | var out = []; |
| | | for (var i = 0; i < this.argnames.length; i++) { |
| | | if (this.argnames[i] instanceof AST_Destructuring) { |
| | | out.push(...this.argnames[i].all_symbols()); |
| | | } else { |
| | | out.push(this.argnames[i]); |
| | | } |
| | | } |
| | | return out; |
| | | }, |
| | | _walk: function(visitor) { |
| | | return visitor._visit(this, function() { |
| | | if (this.name) this.name._walk(visitor); |
| | | var argnames = this.argnames; |
| | | for (var i = 0, len = argnames.length; i < len; i++) { |
| | | argnames[i]._walk(visitor); |
| | | } |
| | | walk_body(this, visitor); |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | let i = this.body.length; |
| | | while (i--) push(this.body[i]); |
| | | |
| | | i = this.argnames.length; |
| | | while (i--) push(this.argnames[i]); |
| | | |
| | | if (this.name) push(this.name); |
| | | }, |
| | | is_braceless() { |
| | | return this.body[0] instanceof AST_Return && this.body[0].value; |
| | | }, |
| | | // Default args and expansion don't count, so .argnames.length doesn't cut it |
| | | length_property() { |
| | | let length = 0; |
| | | |
| | | for (const arg of this.argnames) { |
| | | if (arg instanceof AST_SymbolFunarg || arg instanceof AST_Destructuring) { |
| | | length++; |
| | | } |
| | | var AST_Lambda = DEFNODE( |
| | | "Lambda", |
| | | "name argnames uses_arguments is_generator async", |
| | | function AST_Lambda(props) { |
| | | if (props) { |
| | | this.name = props.name; |
| | | this.argnames = props.argnames; |
| | | this.uses_arguments = props.uses_arguments; |
| | | this.is_generator = props.is_generator; |
| | | this.async = props.async; |
| | | this.variables = props.variables; |
| | | this.functions = props.functions; |
| | | this.uses_with = props.uses_with; |
| | | this.uses_eval = props.uses_eval; |
| | | this.parent_scope = props.parent_scope; |
| | | this.enclosed = props.enclosed; |
| | | this.cname = props.cname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | return length; |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $documentation: "Base class for functions", |
| | | $propdoc: { |
| | | name: "[AST_SymbolDeclaration?] the name of this function", |
| | | argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion|AST_DefaultAssign*] array of function arguments, destructurings, or expanding arguments", |
| | | uses_arguments: "[boolean/S] tells whether this function accesses the arguments array", |
| | | is_generator: "[boolean] is this a generator method", |
| | | async: "[boolean] is this method async", |
| | | }, |
| | | args_as_names: function () { |
| | | var out = []; |
| | | for (var i = 0; i < this.argnames.length; i++) { |
| | | if (this.argnames[i] instanceof AST_Destructuring) { |
| | | out.push(...this.argnames[i].all_symbols()); |
| | | } else { |
| | | out.push(this.argnames[i]); |
| | | } |
| | | } |
| | | return out; |
| | | }, |
| | | _walk: function(visitor) { |
| | | return visitor._visit(this, function() { |
| | | if (this.name) this.name._walk(visitor); |
| | | var argnames = this.argnames; |
| | | for (var i = 0, len = argnames.length; i < len; i++) { |
| | | argnames[i]._walk(visitor); |
| | | } |
| | | walk_body(this, visitor); |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | let i = this.body.length; |
| | | while (i--) push(this.body[i]); |
| | | |
| | | i = this.argnames.length; |
| | | while (i--) push(this.argnames[i]); |
| | | |
| | | if (this.name) push(this.name); |
| | | }, |
| | | is_braceless() { |
| | | return this.body[0] instanceof AST_Return && this.body[0].value; |
| | | }, |
| | | // Default args and expansion don't count, so .argnames.length doesn't cut it |
| | | length_property() { |
| | | let length = 0; |
| | | |
| | | for (const arg of this.argnames) { |
| | | if (arg instanceof AST_SymbolFunarg || arg instanceof AST_Destructuring) { |
| | | length++; |
| | | } |
| | | } |
| | | |
| | | return length; |
| | | } |
| | | }, |
| | | AST_Scope |
| | | ); |
| | | |
| | | var AST_Accessor = DEFNODE("Accessor", null, function AST_Accessor(props) { |
| | | if (props) { |
| | | this.name = props.name; |
| | | this.argnames = props.argnames; |
| | | this.uses_arguments = props.uses_arguments; |
| | | this.is_generator = props.is_generator; |
| | | this.async = props.async; |
| | | this.variables = props.variables; |
| | | this.functions = props.functions; |
| | | this.uses_with = props.uses_with; |
| | | this.uses_eval = props.uses_eval; |
| | | this.parent_scope = props.parent_scope; |
| | | this.enclosed = props.enclosed; |
| | | this.cname = props.cname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | }, AST_Scope); |
| | | |
| | | var AST_Accessor = DEFNODE("Accessor", null, { |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A setter/getter function. The `name` property is always null." |
| | | }, AST_Lambda); |
| | | |
| | | var AST_Function = DEFNODE("Function", null, { |
| | | var AST_Function = DEFNODE("Function", null, function AST_Function(props) { |
| | | if (props) { |
| | | this.name = props.name; |
| | | this.argnames = props.argnames; |
| | | this.uses_arguments = props.uses_arguments; |
| | | this.is_generator = props.is_generator; |
| | | this.async = props.async; |
| | | this.variables = props.variables; |
| | | this.functions = props.functions; |
| | | this.uses_with = props.uses_with; |
| | | this.uses_eval = props.uses_eval; |
| | | this.parent_scope = props.parent_scope; |
| | | this.enclosed = props.enclosed; |
| | | this.cname = props.cname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A function expression" |
| | | }, AST_Lambda); |
| | | |
| | | var AST_Arrow = DEFNODE("Arrow", null, { |
| | | var AST_Arrow = DEFNODE("Arrow", null, function AST_Arrow(props) { |
| | | if (props) { |
| | | this.name = props.name; |
| | | this.argnames = props.argnames; |
| | | this.uses_arguments = props.uses_arguments; |
| | | this.is_generator = props.is_generator; |
| | | this.async = props.async; |
| | | this.variables = props.variables; |
| | | this.functions = props.functions; |
| | | this.uses_with = props.uses_with; |
| | | this.uses_eval = props.uses_eval; |
| | | this.parent_scope = props.parent_scope; |
| | | this.enclosed = props.enclosed; |
| | | this.cname = props.cname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "An ES6 Arrow function ((a) => b)" |
| | | }, AST_Lambda); |
| | | |
| | | var AST_Defun = DEFNODE("Defun", null, { |
| | | var AST_Defun = DEFNODE("Defun", null, function AST_Defun(props) { |
| | | if (props) { |
| | | this.name = props.name; |
| | | this.argnames = props.argnames; |
| | | this.uses_arguments = props.uses_arguments; |
| | | this.is_generator = props.is_generator; |
| | | this.async = props.async; |
| | | this.variables = props.variables; |
| | | this.functions = props.functions; |
| | | this.uses_with = props.uses_with; |
| | | this.uses_eval = props.uses_eval; |
| | | this.parent_scope = props.parent_scope; |
| | | this.enclosed = props.enclosed; |
| | | this.cname = props.cname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A function definition" |
| | | }, AST_Lambda); |
| | | |
| | | /* -----[ DESTRUCTURING ]----- */ |
| | | var AST_Destructuring = DEFNODE("Destructuring", "names is_array", { |
| | | var AST_Destructuring = DEFNODE("Destructuring", "names is_array", function AST_Destructuring(props) { |
| | | if (props) { |
| | | this.names = props.names; |
| | | this.is_array = props.is_array; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A destructuring of several names. Used in destructuring assignment and with destructuring function argument names", |
| | | $propdoc: { |
| | | "names": "[AST_Node*] Array of properties or elements", |
| | |
| | | } |
| | | }); |
| | | |
| | | var AST_PrefixedTemplateString = DEFNODE("PrefixedTemplateString", "template_string prefix", { |
| | | $documentation: "A templatestring with a prefix, such as String.raw`foobarbaz`", |
| | | $propdoc: { |
| | | template_string: "[AST_TemplateString] The template string", |
| | | prefix: "[AST_Node] The prefix, which will get called." |
| | | }, |
| | | _walk: function(visitor) { |
| | | return visitor._visit(this, function () { |
| | | this.prefix._walk(visitor); |
| | | this.template_string._walk(visitor); |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | push(this.template_string); |
| | | push(this.prefix); |
| | | }, |
| | | }); |
| | | var AST_PrefixedTemplateString = DEFNODE( |
| | | "PrefixedTemplateString", |
| | | "template_string prefix", |
| | | function AST_PrefixedTemplateString(props) { |
| | | if (props) { |
| | | this.template_string = props.template_string; |
| | | this.prefix = props.prefix; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | var AST_TemplateString = DEFNODE("TemplateString", "segments", { |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $documentation: "A templatestring with a prefix, such as String.raw`foobarbaz`", |
| | | $propdoc: { |
| | | template_string: "[AST_TemplateString] The template string", |
| | | prefix: "[AST_Node] The prefix, which will get called." |
| | | }, |
| | | _walk: function(visitor) { |
| | | return visitor._visit(this, function () { |
| | | this.prefix._walk(visitor); |
| | | this.template_string._walk(visitor); |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | push(this.template_string); |
| | | push(this.prefix); |
| | | }, |
| | | } |
| | | ); |
| | | |
| | | var AST_TemplateString = DEFNODE("TemplateString", "segments", function AST_TemplateString(props) { |
| | | if (props) { |
| | | this.segments = props.segments; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A template string literal", |
| | | $propdoc: { |
| | | segments: "[AST_Node*] One or more segments, starting with AST_TemplateSegment. AST_Node may follow AST_TemplateSegment, but each AST_Node must be followed by AST_TemplateSegment." |
| | |
| | | } |
| | | }); |
| | | |
| | | var AST_TemplateSegment = DEFNODE("TemplateSegment", "value raw", { |
| | | var AST_TemplateSegment = DEFNODE("TemplateSegment", "value raw", function AST_TemplateSegment(props) { |
| | | if (props) { |
| | | this.value = props.value; |
| | | this.raw = props.raw; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A segment of a template string literal", |
| | | $propdoc: { |
| | | value: "Content of the segment", |
| | |
| | | |
| | | /* -----[ JUMPS ]----- */ |
| | | |
| | | var AST_Jump = DEFNODE("Jump", null, { |
| | | var AST_Jump = DEFNODE("Jump", null, function AST_Jump(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)" |
| | | }, AST_Statement); |
| | | |
| | | var AST_Exit = DEFNODE("Exit", "value", { |
| | | var AST_Exit = DEFNODE("Exit", "value", function AST_Exit(props) { |
| | | if (props) { |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $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" |
| | |
| | | }, |
| | | }, AST_Jump); |
| | | |
| | | var AST_Return = DEFNODE("Return", null, { |
| | | var AST_Return = DEFNODE("Return", null, function AST_Return(props) { |
| | | if (props) { |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `return` statement" |
| | | }, AST_Exit); |
| | | |
| | | var AST_Throw = DEFNODE("Throw", null, { |
| | | var AST_Throw = DEFNODE("Throw", null, function AST_Throw(props) { |
| | | if (props) { |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `throw` statement" |
| | | }, AST_Exit); |
| | | |
| | | var AST_LoopControl = DEFNODE("LoopControl", "label", { |
| | | var AST_LoopControl = DEFNODE("LoopControl", "label", function AST_LoopControl(props) { |
| | | if (props) { |
| | | this.label = props.label; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class for loop control statements (`break` and `continue`)", |
| | | $propdoc: { |
| | | label: "[AST_LabelRef?] the label, or null if none", |
| | |
| | | }, |
| | | }, AST_Jump); |
| | | |
| | | var AST_Break = DEFNODE("Break", null, { |
| | | var AST_Break = DEFNODE("Break", null, function AST_Break(props) { |
| | | if (props) { |
| | | this.label = props.label; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `break` statement" |
| | | }, AST_LoopControl); |
| | | |
| | | var AST_Continue = DEFNODE("Continue", null, { |
| | | var AST_Continue = DEFNODE("Continue", null, function AST_Continue(props) { |
| | | if (props) { |
| | | this.label = props.label; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `continue` statement" |
| | | }, AST_LoopControl); |
| | | |
| | | var AST_Await = DEFNODE("Await", "expression", { |
| | | var AST_Await = DEFNODE("Await", "expression", function AST_Await(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "An `await` statement", |
| | | $propdoc: { |
| | | expression: "[AST_Node] the mandatory expression being awaited", |
| | |
| | | }, |
| | | }); |
| | | |
| | | var AST_Yield = DEFNODE("Yield", "expression is_star", { |
| | | var AST_Yield = DEFNODE("Yield", "expression is_star", function AST_Yield(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.is_star = props.is_star; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `yield` statement", |
| | | $propdoc: { |
| | | expression: "[AST_Node?] the value returned or thrown by this statement; could be null (representing undefined) but only when is_star is set to false", |
| | |
| | | |
| | | /* -----[ IF ]----- */ |
| | | |
| | | var AST_If = DEFNODE("If", "condition alternative", { |
| | | var AST_If = DEFNODE("If", "condition alternative", function AST_If(props) { |
| | | if (props) { |
| | | this.condition = props.condition; |
| | | this.alternative = props.alternative; |
| | | this.body = props.body; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `if` statement", |
| | | $propdoc: { |
| | | condition: "[AST_Node] the `if` condition", |
| | |
| | | |
| | | /* -----[ SWITCH ]----- */ |
| | | |
| | | var AST_Switch = DEFNODE("Switch", "expression", { |
| | | var AST_Switch = DEFNODE("Switch", "expression", function AST_Switch(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `switch` statement", |
| | | $propdoc: { |
| | | expression: "[AST_Node] the `switch` “discriminant”" |
| | |
| | | } |
| | | }, AST_Block); |
| | | |
| | | var AST_SwitchBranch = DEFNODE("SwitchBranch", null, { |
| | | var AST_SwitchBranch = DEFNODE("SwitchBranch", null, function AST_SwitchBranch(props) { |
| | | if (props) { |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class for `switch` branches", |
| | | }, AST_Block); |
| | | |
| | | var AST_Default = DEFNODE("Default", null, { |
| | | var AST_Default = DEFNODE("Default", null, function AST_Default(props) { |
| | | if (props) { |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `default` switch branch", |
| | | }, AST_SwitchBranch); |
| | | |
| | | var AST_Case = DEFNODE("Case", "expression", { |
| | | var AST_Case = DEFNODE("Case", "expression", function AST_Case(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `case` switch branch", |
| | | $propdoc: { |
| | | expression: "[AST_Node] the `case` expression" |
| | |
| | | |
| | | /* -----[ EXCEPTIONS ]----- */ |
| | | |
| | | var AST_Try = DEFNODE("Try", "bcatch bfinally", { |
| | | var AST_Try = DEFNODE("Try", "bcatch bfinally", function AST_Try(props) { |
| | | if (props) { |
| | | this.bcatch = props.bcatch; |
| | | this.bfinally = props.bfinally; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `try` statement", |
| | | $propdoc: { |
| | | bcatch: "[AST_Catch?] the catch block, or null if not present", |
| | |
| | | }, |
| | | }, AST_Block); |
| | | |
| | | var AST_Catch = DEFNODE("Catch", "argname", { |
| | | var AST_Catch = DEFNODE("Catch", "argname", function AST_Catch(props) { |
| | | if (props) { |
| | | this.argname = props.argname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `catch` node; only makes sense as part of a `try` statement", |
| | | $propdoc: { |
| | | argname: "[AST_SymbolCatch|AST_Destructuring|AST_Expansion|AST_DefaultAssign] symbol for the exception" |
| | |
| | | }, |
| | | }, AST_Block); |
| | | |
| | | var AST_Finally = DEFNODE("Finally", null, { |
| | | var AST_Finally = DEFNODE("Finally", null, function AST_Finally(props) { |
| | | if (props) { |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `finally` node; only makes sense as part of a `try` statement" |
| | | }, AST_Block); |
| | | |
| | | /* -----[ VAR/CONST ]----- */ |
| | | |
| | | var AST_Definitions = DEFNODE("Definitions", "definitions", { |
| | | var AST_Definitions = DEFNODE("Definitions", "definitions", function AST_Definitions(props) { |
| | | if (props) { |
| | | this.definitions = props.definitions; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)", |
| | | $propdoc: { |
| | | definitions: "[AST_VarDef*] array of variable definitions" |
| | |
| | | }, |
| | | }, AST_Statement); |
| | | |
| | | var AST_Var = DEFNODE("Var", null, { |
| | | var AST_Var = DEFNODE("Var", null, function AST_Var(props) { |
| | | if (props) { |
| | | this.definitions = props.definitions; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `var` statement" |
| | | }, AST_Definitions); |
| | | |
| | | var AST_Let = DEFNODE("Let", null, { |
| | | var AST_Let = DEFNODE("Let", null, function AST_Let(props) { |
| | | if (props) { |
| | | this.definitions = props.definitions; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `let` statement" |
| | | }, AST_Definitions); |
| | | |
| | | var AST_Const = DEFNODE("Const", null, { |
| | | var AST_Const = DEFNODE("Const", null, function AST_Const(props) { |
| | | if (props) { |
| | | this.definitions = props.definitions; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A `const` statement" |
| | | }, AST_Definitions); |
| | | |
| | | var AST_VarDef = DEFNODE("VarDef", "name value", { |
| | | var AST_VarDef = DEFNODE("VarDef", "name value", function AST_VarDef(props) { |
| | | if (props) { |
| | | this.name = props.name; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A variable declaration; only appears in a AST_Definitions node", |
| | | $propdoc: { |
| | | name: "[AST_Destructuring|AST_SymbolConst|AST_SymbolLet|AST_SymbolVar] name of the variable", |
| | |
| | | }, |
| | | }); |
| | | |
| | | var AST_NameMapping = DEFNODE("NameMapping", "foreign_name name", { |
| | | var AST_NameMapping = DEFNODE("NameMapping", "foreign_name name", function AST_NameMapping(props) { |
| | | if (props) { |
| | | this.foreign_name = props.foreign_name; |
| | | this.name = props.name; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The part of the export/import statement that declare names from a module.", |
| | | $propdoc: { |
| | | foreign_name: "[AST_SymbolExportForeign|AST_SymbolImportForeign] The name being exported/imported (as specified in the 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_NameMapping*] The names of non-default imported variables", |
| | | 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); |
| | | } |
| | | if (this.imported_names) { |
| | | this.imported_names.forEach(function(name_import) { |
| | | name_import._walk(visitor); |
| | | }); |
| | | } |
| | | this.module_name._walk(visitor); |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | push(this.module_name); |
| | | if (this.imported_names) { |
| | | let i = this.imported_names.length; |
| | | while (i--) push(this.imported_names[i]); |
| | | var AST_Import = DEFNODE( |
| | | "Import", |
| | | "imported_name imported_names module_name assert_clause", |
| | | function AST_Import(props) { |
| | | if (props) { |
| | | this.imported_name = props.imported_name; |
| | | this.imported_names = props.imported_names; |
| | | this.module_name = props.module_name; |
| | | this.assert_clause = props.assert_clause; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | if (this.imported_name) push(this.imported_name); |
| | | }, |
| | | }); |
| | | |
| | | var AST_ImportMeta = DEFNODE("ImportMeta", null, { |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $documentation: "An `import` statement", |
| | | $propdoc: { |
| | | imported_name: "[AST_SymbolImport] The name of the variable holding the module's default export.", |
| | | imported_names: "[AST_NameMapping*] The names of non-default imported variables", |
| | | module_name: "[AST_String] String literal describing where this module came from", |
| | | assert_clause: "[AST_Object?] The import assertion" |
| | | }, |
| | | _walk: function(visitor) { |
| | | return visitor._visit(this, function() { |
| | | 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); |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | push(this.module_name); |
| | | if (this.imported_names) { |
| | | let i = this.imported_names.length; |
| | | while (i--) push(this.imported_names[i]); |
| | | } |
| | | if (this.imported_name) push(this.imported_name); |
| | | }, |
| | | } |
| | | ); |
| | | |
| | | var AST_ImportMeta = DEFNODE("ImportMeta", null, function AST_ImportMeta(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A reference to import.meta", |
| | | }); |
| | | |
| | | var AST_Export = DEFNODE("Export", "exported_definition exported_value is_default exported_names module_name", { |
| | | $documentation: "An `export` statement", |
| | | $propdoc: { |
| | | exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition", |
| | | exported_value: "[AST_Node?] An exported value", |
| | | 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" |
| | | }, |
| | | _walk: function (visitor) { |
| | | return visitor._visit(this, function () { |
| | | if (this.exported_definition) { |
| | | this.exported_definition._walk(visitor); |
| | | } |
| | | 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); |
| | | } |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | if (this.module_name) push(this.module_name); |
| | | if (this.exported_names) { |
| | | let i = this.exported_names.length; |
| | | while (i--) push(this.exported_names[i]); |
| | | var AST_Export = DEFNODE( |
| | | "Export", |
| | | "exported_definition exported_value is_default exported_names module_name assert_clause", |
| | | function AST_Export(props) { |
| | | if (props) { |
| | | this.exported_definition = props.exported_definition; |
| | | this.exported_value = props.exported_value; |
| | | this.is_default = props.is_default; |
| | | this.exported_names = props.exported_names; |
| | | this.module_name = props.module_name; |
| | | this.assert_clause = props.assert_clause; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | if (this.exported_value) push(this.exported_value); |
| | | if (this.exported_definition) push(this.exported_definition); |
| | | } |
| | | }, AST_Statement); |
| | | |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $documentation: "An `export` statement", |
| | | $propdoc: { |
| | | exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition", |
| | | exported_value: "[AST_Node?] An exported value", |
| | | 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", |
| | | assert_clause: "[AST_Object?] The import assertion" |
| | | }, |
| | | _walk: function (visitor) { |
| | | return visitor._visit(this, function () { |
| | | if (this.exported_definition) { |
| | | this.exported_definition._walk(visitor); |
| | | } |
| | | 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); |
| | | } |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | if (this.module_name) push(this.module_name); |
| | | if (this.exported_names) { |
| | | let i = this.exported_names.length; |
| | | while (i--) push(this.exported_names[i]); |
| | | } |
| | | if (this.exported_value) push(this.exported_value); |
| | | if (this.exported_definition) push(this.exported_definition); |
| | | } |
| | | }, |
| | | AST_Statement |
| | | ); |
| | | |
| | | /* -----[ OTHER ]----- */ |
| | | |
| | | var AST_Call = DEFNODE("Call", "expression args optional _annotations", { |
| | | $documentation: "A function call expression", |
| | | $propdoc: { |
| | | expression: "[AST_Node] expression to invoke as function", |
| | | args: "[AST_Node*] array of arguments", |
| | | optional: "[boolean] whether this is an optional call (IE ?.() )", |
| | | _annotations: "[number] bitfield containing information about the call" |
| | | }, |
| | | initialize() { |
| | | if (this._annotations == null) this._annotations = 0; |
| | | }, |
| | | _walk(visitor) { |
| | | return visitor._visit(this, function() { |
| | | var args = this.args; |
| | | for (var i = 0, len = args.length; i < len; i++) { |
| | | args[i]._walk(visitor); |
| | | } |
| | | this.expression._walk(visitor); // TODO why do we need to crawl this last? |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | let i = this.args.length; |
| | | while (i--) push(this.args[i]); |
| | | push(this.expression); |
| | | }, |
| | | }); |
| | | var AST_Call = DEFNODE( |
| | | "Call", |
| | | "expression args optional _annotations", |
| | | function AST_Call(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.args = props.args; |
| | | this.optional = props.optional; |
| | | this._annotations = props._annotations; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | this.initialize(); |
| | | } |
| | | |
| | | var AST_New = DEFNODE("New", null, { |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $documentation: "A function call expression", |
| | | $propdoc: { |
| | | expression: "[AST_Node] expression to invoke as function", |
| | | args: "[AST_Node*] array of arguments", |
| | | optional: "[boolean] whether this is an optional call (IE ?.() )", |
| | | _annotations: "[number] bitfield containing information about the call" |
| | | }, |
| | | initialize() { |
| | | if (this._annotations == null) this._annotations = 0; |
| | | }, |
| | | _walk(visitor) { |
| | | return visitor._visit(this, function() { |
| | | var args = this.args; |
| | | for (var i = 0, len = args.length; i < len; i++) { |
| | | args[i]._walk(visitor); |
| | | } |
| | | this.expression._walk(visitor); // TODO why do we need to crawl this last? |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | let i = this.args.length; |
| | | while (i--) push(this.args[i]); |
| | | push(this.expression); |
| | | }, |
| | | } |
| | | ); |
| | | |
| | | var AST_New = DEFNODE("New", null, function AST_New(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.args = props.args; |
| | | this.optional = props.optional; |
| | | this._annotations = props._annotations; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | this.initialize(); |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "An object instantiation. Derives from a function call since it has exactly the same properties" |
| | | }, AST_Call); |
| | | |
| | | var AST_Sequence = DEFNODE("Sequence", "expressions", { |
| | | var AST_Sequence = DEFNODE("Sequence", "expressions", function AST_Sequence(props) { |
| | | if (props) { |
| | | this.expressions = props.expressions; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A sequence expression (comma-separated expressions)", |
| | | $propdoc: { |
| | | expressions: "[AST_Node*] array of expressions (at least two)" |
| | |
| | | }, |
| | | }); |
| | | |
| | | var AST_PropAccess = DEFNODE("PropAccess", "expression property optional", { |
| | | $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 & AST_DotHash this is always a plain string, while for AST_Sub it's an arbitrary AST_Node", |
| | | var AST_PropAccess = DEFNODE( |
| | | "PropAccess", |
| | | "expression property optional", |
| | | function AST_PropAccess(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.property = props.property; |
| | | this.optional = props.optional; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | optional: "[boolean] whether this is an optional property access (IE ?.)" |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $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 & AST_DotHash this is always a plain string, while for AST_Sub it's an arbitrary AST_Node", |
| | | |
| | | optional: "[boolean] whether this is an optional property access (IE ?.)" |
| | | } |
| | | } |
| | | }); |
| | | ); |
| | | |
| | | var AST_Dot = DEFNODE("Dot", "quote", { |
| | | var AST_Dot = DEFNODE("Dot", "quote", function AST_Dot(props) { |
| | | if (props) { |
| | | this.quote = props.quote; |
| | | this.expression = props.expression; |
| | | this.property = props.property; |
| | | this.optional = props.optional; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A dotted property access expression", |
| | | $propdoc: { |
| | | quote: "[string] the original quote character when transformed from AST_Sub", |
| | |
| | | }, |
| | | }, AST_PropAccess); |
| | | |
| | | var AST_DotHash = DEFNODE("DotHash", "", { |
| | | var AST_DotHash = DEFNODE("DotHash", "", function AST_DotHash(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.property = props.property; |
| | | this.optional = props.optional; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A dotted property access to a private property", |
| | | _walk: function(visitor) { |
| | | return visitor._visit(this, function() { |
| | |
| | | }, |
| | | }, AST_PropAccess); |
| | | |
| | | var AST_Sub = DEFNODE("Sub", null, { |
| | | var AST_Sub = DEFNODE("Sub", null, function AST_Sub(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.property = props.property; |
| | | this.optional = props.optional; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Index-style property access, i.e. `a[\"foo\"]`", |
| | | _walk: function(visitor) { |
| | | return visitor._visit(this, function() { |
| | |
| | | }, |
| | | }, AST_PropAccess); |
| | | |
| | | var AST_Chain = DEFNODE("Chain", "expression", { |
| | | var AST_Chain = DEFNODE("Chain", "expression", function AST_Chain(props) { |
| | | if (props) { |
| | | this.expression = props.expression; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A chain expression like a?.b?.(c)?.[d]", |
| | | $propdoc: { |
| | | expression: "[AST_Call|AST_Dot|AST_DotHash|AST_Sub] chain element." |
| | |
| | | }, |
| | | }); |
| | | |
| | | var AST_Unary = DEFNODE("Unary", "operator expression", { |
| | | var AST_Unary = DEFNODE("Unary", "operator expression", function AST_Unary(props) { |
| | | if (props) { |
| | | this.operator = props.operator; |
| | | this.expression = props.expression; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class for unary expressions", |
| | | $propdoc: { |
| | | operator: "[string] the operator", |
| | |
| | | }, |
| | | }); |
| | | |
| | | var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, { |
| | | var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, function AST_UnaryPrefix(props) { |
| | | if (props) { |
| | | this.operator = props.operator; |
| | | this.expression = props.expression; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Unary prefix expression, i.e. `typeof i` or `++i`" |
| | | }, AST_Unary); |
| | | |
| | | var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, { |
| | | var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, function AST_UnaryPostfix(props) { |
| | | if (props) { |
| | | this.operator = props.operator; |
| | | this.expression = props.expression; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Unary postfix expression, i.e. `i++`" |
| | | }, AST_Unary); |
| | | |
| | | var AST_Binary = DEFNODE("Binary", "operator left right", { |
| | | var AST_Binary = DEFNODE("Binary", "operator left right", function AST_Binary(props) { |
| | | if (props) { |
| | | this.operator = props.operator; |
| | | this.left = props.left; |
| | | this.right = props.right; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Binary expression, i.e. `a + b`", |
| | | $propdoc: { |
| | | left: "[AST_Node] left-hand side expression", |
| | |
| | | }, |
| | | }); |
| | | |
| | | 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); |
| | | this.consequent._walk(visitor); |
| | | this.alternative._walk(visitor); |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | push(this.alternative); |
| | | push(this.consequent); |
| | | push(this.condition); |
| | | }, |
| | | }); |
| | | var AST_Conditional = DEFNODE( |
| | | "Conditional", |
| | | "condition consequent alternative", |
| | | function AST_Conditional(props) { |
| | | if (props) { |
| | | this.condition = props.condition; |
| | | this.consequent = props.consequent; |
| | | this.alternative = props.alternative; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | var AST_Assign = DEFNODE("Assign", "logical", { |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $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); |
| | | this.consequent._walk(visitor); |
| | | this.alternative._walk(visitor); |
| | | }); |
| | | }, |
| | | _children_backwards(push) { |
| | | push(this.alternative); |
| | | push(this.consequent); |
| | | push(this.condition); |
| | | }, |
| | | } |
| | | ); |
| | | |
| | | var AST_Assign = DEFNODE("Assign", "logical", function AST_Assign(props) { |
| | | if (props) { |
| | | this.logical = props.logical; |
| | | this.operator = props.operator; |
| | | this.left = props.left; |
| | | this.right = props.right; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "An assignment expression — `a = b + 5`", |
| | | $propdoc: { |
| | | logical: "Whether it's a logical assignment" |
| | | } |
| | | }, AST_Binary); |
| | | |
| | | var AST_DefaultAssign = DEFNODE("DefaultAssign", null, { |
| | | var AST_DefaultAssign = DEFNODE("DefaultAssign", null, function AST_DefaultAssign(props) { |
| | | if (props) { |
| | | this.operator = props.operator; |
| | | this.left = props.left; |
| | | this.right = props.right; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A default assignment expression like in `(a = 3) => a`" |
| | | }, AST_Binary); |
| | | |
| | | /* -----[ LITERALS ]----- */ |
| | | |
| | | var AST_Array = DEFNODE("Array", "elements", { |
| | | var AST_Array = DEFNODE("Array", "elements", function AST_Array(props) { |
| | | if (props) { |
| | | this.elements = props.elements; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "An array literal", |
| | | $propdoc: { |
| | | elements: "[AST_Node*] array of elements" |
| | |
| | | }, |
| | | }); |
| | | |
| | | var AST_Object = DEFNODE("Object", "properties", { |
| | | var AST_Object = DEFNODE("Object", "properties", function AST_Object(props) { |
| | | if (props) { |
| | | this.properties = props.properties; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "An object literal", |
| | | $propdoc: { |
| | | properties: "[AST_ObjectProperty*] array of properties" |
| | |
| | | }, |
| | | }); |
| | | |
| | | var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { |
| | | var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", function AST_ObjectProperty(props) { |
| | | if (props) { |
| | | this.key = props.key; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class for literal object properties", |
| | | $propdoc: { |
| | | key: "[string|AST_Node] property name. For ObjectKeyVal this is a string. For getters, setters and computed property this is an AST_Node.", |
| | |
| | | } |
| | | }); |
| | | |
| | | var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", { |
| | | var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", function AST_ObjectKeyVal(props) { |
| | | if (props) { |
| | | this.quote = props.quote; |
| | | this.key = props.key; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A key: value object property", |
| | | $propdoc: { |
| | | quote: "[string] the original quote character" |
| | |
| | | } |
| | | }, AST_ObjectProperty); |
| | | |
| | | var AST_PrivateSetter = DEFNODE("PrivateSetter", "static", { |
| | | var AST_PrivateSetter = DEFNODE("PrivateSetter", "static", function AST_PrivateSetter(props) { |
| | | if (props) { |
| | | this.static = props.static; |
| | | this.key = props.key; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $propdoc: { |
| | | static: "[boolean] whether this is a static private setter" |
| | | }, |
| | |
| | | } |
| | | }, AST_ObjectProperty); |
| | | |
| | | var AST_PrivateGetter = DEFNODE("PrivateGetter", "static", { |
| | | var AST_PrivateGetter = DEFNODE("PrivateGetter", "static", function AST_PrivateGetter(props) { |
| | | if (props) { |
| | | this.static = props.static; |
| | | this.key = props.key; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $propdoc: { |
| | | static: "[boolean] whether this is a static private getter" |
| | | }, |
| | |
| | | } |
| | | }, AST_ObjectProperty); |
| | | |
| | | var AST_ObjectSetter = DEFNODE("ObjectSetter", "quote static", { |
| | | var AST_ObjectSetter = DEFNODE("ObjectSetter", "quote static", function AST_ObjectSetter(props) { |
| | | if (props) { |
| | | this.quote = props.quote; |
| | | this.static = props.static; |
| | | this.key = props.key; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $propdoc: { |
| | | quote: "[string|undefined] the original quote character, if any", |
| | | static: "[boolean] whether this is a static setter (classes only)" |
| | |
| | | } |
| | | }, AST_ObjectProperty); |
| | | |
| | | var AST_ObjectGetter = DEFNODE("ObjectGetter", "quote static", { |
| | | var AST_ObjectGetter = DEFNODE("ObjectGetter", "quote static", function AST_ObjectGetter(props) { |
| | | if (props) { |
| | | this.quote = props.quote; |
| | | this.static = props.static; |
| | | this.key = props.key; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $propdoc: { |
| | | quote: "[string|undefined] the original quote character, if any", |
| | | static: "[boolean] whether this is a static getter (classes only)" |
| | |
| | | } |
| | | }, AST_ObjectProperty); |
| | | |
| | | var AST_ConciseMethod = DEFNODE("ConciseMethod", "quote static is_generator async", { |
| | | $propdoc: { |
| | | quote: "[string|undefined] the original quote character, if any", |
| | | static: "[boolean] is this method static (classes only)", |
| | | is_generator: "[boolean] is this a generator method", |
| | | async: "[boolean] is this method async", |
| | | }, |
| | | $documentation: "An ES6 concise method inside an object or class", |
| | | computed_key() { |
| | | return !(this.key instanceof AST_SymbolMethod); |
| | | } |
| | | }, AST_ObjectProperty); |
| | | var AST_ConciseMethod = DEFNODE( |
| | | "ConciseMethod", |
| | | "quote static is_generator async", |
| | | function AST_ConciseMethod(props) { |
| | | if (props) { |
| | | this.quote = props.quote; |
| | | this.static = props.static; |
| | | this.is_generator = props.is_generator; |
| | | this.async = props.async; |
| | | this.key = props.key; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | var AST_PrivateMethod = DEFNODE("PrivateMethod", "", { |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $propdoc: { |
| | | quote: "[string|undefined] the original quote character, if any", |
| | | static: "[boolean] is this method static (classes only)", |
| | | is_generator: "[boolean] is this a generator method", |
| | | async: "[boolean] is this method async", |
| | | }, |
| | | $documentation: "An ES6 concise method inside an object or class", |
| | | computed_key() { |
| | | return !(this.key instanceof AST_SymbolMethod); |
| | | } |
| | | }, |
| | | AST_ObjectProperty |
| | | ); |
| | | |
| | | var AST_PrivateMethod = DEFNODE("PrivateMethod", "", function AST_PrivateMethod(props) { |
| | | if (props) { |
| | | this.quote = props.quote; |
| | | this.static = props.static; |
| | | this.is_generator = props.is_generator; |
| | | this.async = props.async; |
| | | this.key = props.key; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A private class method inside a class", |
| | | }, AST_ConciseMethod); |
| | | |
| | | var AST_Class = DEFNODE("Class", "name extends properties", { |
| | | var AST_Class = DEFNODE("Class", "name extends properties", function AST_Class(props) { |
| | | if (props) { |
| | | this.name = props.name; |
| | | this.extends = props.extends; |
| | | this.properties = props.properties; |
| | | this.variables = props.variables; |
| | | this.functions = props.functions; |
| | | this.uses_with = props.uses_with; |
| | | this.uses_eval = props.uses_eval; |
| | | this.parent_scope = props.parent_scope; |
| | | this.enclosed = props.enclosed; |
| | | this.cname = props.cname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $propdoc: { |
| | | name: "[AST_SymbolClass|AST_SymbolDefClass?] optional class name.", |
| | | extends: "[AST_Node]? optional parent class", |
| | |
| | | }, |
| | | }, AST_Scope /* TODO a class might have a scope but it's not a scope */); |
| | | |
| | | var AST_ClassProperty = DEFNODE("ClassProperty", "static quote", { |
| | | var AST_ClassProperty = DEFNODE("ClassProperty", "static quote", function AST_ClassProperty(props) { |
| | | if (props) { |
| | | this.static = props.static; |
| | | this.quote = props.quote; |
| | | this.key = props.key; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A class property", |
| | | $propdoc: { |
| | | static: "[boolean] whether this is a static key", |
| | |
| | | } |
| | | }, AST_ObjectProperty); |
| | | |
| | | var AST_ClassPrivateProperty = DEFNODE("ClassPrivateProperty", "", { |
| | | var AST_ClassPrivateProperty = DEFNODE("ClassPrivateProperty", "", function AST_ClassPrivateProperty(props) { |
| | | if (props) { |
| | | this.static = props.static; |
| | | this.quote = props.quote; |
| | | this.key = props.key; |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A class property for a private property", |
| | | }, AST_ClassProperty); |
| | | |
| | | var AST_DefClass = DEFNODE("DefClass", null, { |
| | | var AST_DefClass = DEFNODE("DefClass", null, function AST_DefClass(props) { |
| | | if (props) { |
| | | this.name = props.name; |
| | | this.extends = props.extends; |
| | | this.properties = props.properties; |
| | | this.variables = props.variables; |
| | | this.functions = props.functions; |
| | | this.uses_with = props.uses_with; |
| | | this.uses_eval = props.uses_eval; |
| | | this.parent_scope = props.parent_scope; |
| | | this.enclosed = props.enclosed; |
| | | this.cname = props.cname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A class definition", |
| | | }, AST_Class); |
| | | |
| | | var AST_ClassExpression = DEFNODE("ClassExpression", null, { |
| | | var AST_ClassExpression = DEFNODE("ClassExpression", null, function AST_ClassExpression(props) { |
| | | if (props) { |
| | | this.name = props.name; |
| | | this.extends = props.extends; |
| | | this.properties = props.properties; |
| | | this.variables = props.variables; |
| | | this.functions = props.functions; |
| | | this.uses_with = props.uses_with; |
| | | this.uses_eval = props.uses_eval; |
| | | this.parent_scope = props.parent_scope; |
| | | this.enclosed = props.enclosed; |
| | | this.cname = props.cname; |
| | | this.body = props.body; |
| | | this.block_scope = props.block_scope; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A class expression." |
| | | }, AST_Class); |
| | | |
| | | var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { |
| | | var AST_Symbol = DEFNODE("Symbol", "scope name thedef", function AST_Symbol(props) { |
| | | if (props) { |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $propdoc: { |
| | | name: "[string] name of this symbol", |
| | | scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)", |
| | |
| | | $documentation: "Base class for all symbols" |
| | | }); |
| | | |
| | | var AST_NewTarget = DEFNODE("NewTarget", null, { |
| | | var AST_NewTarget = DEFNODE("NewTarget", null, function AST_NewTarget(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A reference to new.target" |
| | | }); |
| | | |
| | | var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { |
| | | var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", function AST_SymbolDeclaration(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", |
| | | }, AST_Symbol); |
| | | |
| | | var AST_SymbolVar = DEFNODE("SymbolVar", null, { |
| | | var AST_SymbolVar = DEFNODE("SymbolVar", null, function AST_SymbolVar(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol defining a variable", |
| | | }, AST_SymbolDeclaration); |
| | | |
| | | var AST_SymbolBlockDeclaration = DEFNODE("SymbolBlockDeclaration", null, { |
| | | $documentation: "Base class for block-scoped declaration symbols" |
| | | }, AST_SymbolDeclaration); |
| | | var AST_SymbolBlockDeclaration = DEFNODE( |
| | | "SymbolBlockDeclaration", |
| | | null, |
| | | function AST_SymbolBlockDeclaration(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | var AST_SymbolConst = DEFNODE("SymbolConst", null, { |
| | | this.flags = 0; |
| | | }, |
| | | { |
| | | $documentation: "Base class for block-scoped declaration symbols" |
| | | }, |
| | | AST_SymbolDeclaration |
| | | ); |
| | | |
| | | var AST_SymbolConst = DEFNODE("SymbolConst", null, function AST_SymbolConst(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A constant declaration" |
| | | }, AST_SymbolBlockDeclaration); |
| | | |
| | | var AST_SymbolLet = DEFNODE("SymbolLet", null, { |
| | | var AST_SymbolLet = DEFNODE("SymbolLet", null, function AST_SymbolLet(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A block-scoped `let` declaration" |
| | | }, AST_SymbolBlockDeclaration); |
| | | |
| | | var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, { |
| | | var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, function AST_SymbolFunarg(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol naming a function argument", |
| | | }, AST_SymbolVar); |
| | | |
| | | var AST_SymbolDefun = DEFNODE("SymbolDefun", null, { |
| | | var AST_SymbolDefun = DEFNODE("SymbolDefun", null, function AST_SymbolDefun(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol defining a function", |
| | | }, AST_SymbolDeclaration); |
| | | |
| | | var AST_SymbolMethod = DEFNODE("SymbolMethod", null, { |
| | | var AST_SymbolMethod = DEFNODE("SymbolMethod", null, function AST_SymbolMethod(props) { |
| | | if (props) { |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol in an object defining a method", |
| | | }, AST_Symbol); |
| | | |
| | | var AST_SymbolClassProperty = DEFNODE("SymbolClassProperty", null, { |
| | | var AST_SymbolClassProperty = DEFNODE("SymbolClassProperty", null, function AST_SymbolClassProperty(props) { |
| | | if (props) { |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol for a class property", |
| | | }, AST_Symbol); |
| | | |
| | | var AST_SymbolLambda = DEFNODE("SymbolLambda", null, { |
| | | var AST_SymbolLambda = DEFNODE("SymbolLambda", null, function AST_SymbolLambda(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol naming a function expression", |
| | | }, AST_SymbolDeclaration); |
| | | |
| | | var AST_SymbolDefClass = DEFNODE("SymbolDefClass", null, { |
| | | var AST_SymbolDefClass = DEFNODE("SymbolDefClass", null, function AST_SymbolDefClass(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol naming a class's name in a class declaration. Lexically scoped to its containing scope, and accessible within the class." |
| | | }, AST_SymbolBlockDeclaration); |
| | | |
| | | var AST_SymbolClass = DEFNODE("SymbolClass", null, { |
| | | var AST_SymbolClass = DEFNODE("SymbolClass", null, function AST_SymbolClass(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol naming a class's name. Lexically scoped to the class." |
| | | }, AST_SymbolDeclaration); |
| | | |
| | | var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { |
| | | var AST_SymbolCatch = DEFNODE("SymbolCatch", null, function AST_SymbolCatch(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol naming the exception in catch", |
| | | }, AST_SymbolBlockDeclaration); |
| | | |
| | | var AST_SymbolImport = DEFNODE("SymbolImport", null, { |
| | | var AST_SymbolImport = DEFNODE("SymbolImport", null, function AST_SymbolImport(props) { |
| | | if (props) { |
| | | this.init = props.init; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol referring to an imported name", |
| | | }, AST_SymbolBlockDeclaration); |
| | | |
| | | var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", null, { |
| | | var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", null, function AST_SymbolImportForeign(props) { |
| | | if (props) { |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $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", { |
| | | var AST_Label = DEFNODE("Label", "references", function AST_Label(props) { |
| | | if (props) { |
| | | this.references = props.references; |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | this.initialize(); |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol naming a label (declaration)", |
| | | $propdoc: { |
| | | references: "[AST_LoopControl*] a list of nodes referring to this label" |
| | |
| | | } |
| | | }, AST_Symbol); |
| | | |
| | | var AST_SymbolRef = DEFNODE("SymbolRef", null, { |
| | | var AST_SymbolRef = DEFNODE("SymbolRef", null, function AST_SymbolRef(props) { |
| | | if (props) { |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Reference to some symbol (not definition/declaration)", |
| | | }, AST_Symbol); |
| | | |
| | | var AST_SymbolExport = DEFNODE("SymbolExport", null, { |
| | | var AST_SymbolExport = DEFNODE("SymbolExport", null, function AST_SymbolExport(props) { |
| | | if (props) { |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Symbol referring to a name to export", |
| | | }, AST_SymbolRef); |
| | | |
| | | var AST_SymbolExportForeign = DEFNODE("SymbolExportForeign", null, { |
| | | var AST_SymbolExportForeign = DEFNODE("SymbolExportForeign", null, function AST_SymbolExportForeign(props) { |
| | | if (props) { |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $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, function AST_LabelRef(props) { |
| | | if (props) { |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Reference to a label symbol", |
| | | }, AST_Symbol); |
| | | |
| | | var AST_This = DEFNODE("This", null, { |
| | | var AST_This = DEFNODE("This", null, function AST_This(props) { |
| | | if (props) { |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The `this` symbol", |
| | | }, AST_Symbol); |
| | | |
| | | var AST_Super = DEFNODE("Super", null, { |
| | | var AST_Super = DEFNODE("Super", null, function AST_Super(props) { |
| | | if (props) { |
| | | this.scope = props.scope; |
| | | this.name = props.name; |
| | | this.thedef = props.thedef; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The `super` symbol", |
| | | }, AST_This); |
| | | |
| | | var AST_Constant = DEFNODE("Constant", null, { |
| | | var AST_Constant = DEFNODE("Constant", null, function AST_Constant(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class for all constants", |
| | | getValue: function() { |
| | | return this.value; |
| | | } |
| | | }); |
| | | |
| | | var AST_String = DEFNODE("String", "value quote", { |
| | | var AST_String = DEFNODE("String", "value quote", function AST_String(props) { |
| | | if (props) { |
| | | this.value = props.value; |
| | | this.quote = props.quote; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A string literal", |
| | | $propdoc: { |
| | | value: "[string] the contents of this string", |
| | |
| | | } |
| | | }, AST_Constant); |
| | | |
| | | var AST_Number = DEFNODE("Number", "value raw", { |
| | | var AST_Number = DEFNODE("Number", "value raw", function AST_Number(props) { |
| | | if (props) { |
| | | this.value = props.value; |
| | | this.raw = props.raw; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A number literal", |
| | | $propdoc: { |
| | | value: "[number] the numeric value", |
| | |
| | | } |
| | | }, AST_Constant); |
| | | |
| | | var AST_BigInt = DEFNODE("BigInt", "value", { |
| | | var AST_BigInt = DEFNODE("BigInt", "value", function AST_BigInt(props) { |
| | | if (props) { |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A big int literal", |
| | | $propdoc: { |
| | | value: "[string] big int value" |
| | | } |
| | | }, AST_Constant); |
| | | |
| | | var AST_RegExp = DEFNODE("RegExp", "value", { |
| | | var AST_RegExp = DEFNODE("RegExp", "value", function AST_RegExp(props) { |
| | | if (props) { |
| | | this.value = props.value; |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A regexp literal", |
| | | $propdoc: { |
| | | value: "[RegExp] the actual regexp", |
| | | } |
| | | }, AST_Constant); |
| | | |
| | | var AST_Atom = DEFNODE("Atom", null, { |
| | | var AST_Atom = DEFNODE("Atom", null, function AST_Atom(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class for atoms", |
| | | }, AST_Constant); |
| | | |
| | | var AST_Null = DEFNODE("Null", null, { |
| | | var AST_Null = DEFNODE("Null", null, function AST_Null(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The `null` atom", |
| | | value: null |
| | | }, AST_Atom); |
| | | |
| | | var AST_NaN = DEFNODE("NaN", null, { |
| | | var AST_NaN = DEFNODE("NaN", null, function AST_NaN(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The impossible value", |
| | | value: 0/0 |
| | | }, AST_Atom); |
| | | |
| | | var AST_Undefined = DEFNODE("Undefined", null, { |
| | | var AST_Undefined = DEFNODE("Undefined", null, function AST_Undefined(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The `undefined` value", |
| | | value: (function() {}()) |
| | | }, AST_Atom); |
| | | |
| | | var AST_Hole = DEFNODE("Hole", null, { |
| | | var AST_Hole = DEFNODE("Hole", null, function AST_Hole(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "A hole in an array", |
| | | value: (function() {}()) |
| | | }, AST_Atom); |
| | | |
| | | var AST_Infinity = DEFNODE("Infinity", null, { |
| | | var AST_Infinity = DEFNODE("Infinity", null, function AST_Infinity(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The `Infinity` value", |
| | | value: 1/0 |
| | | }, AST_Atom); |
| | | |
| | | var AST_Boolean = DEFNODE("Boolean", null, { |
| | | var AST_Boolean = DEFNODE("Boolean", null, function AST_Boolean(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "Base class for booleans", |
| | | }, AST_Atom); |
| | | |
| | | var AST_False = DEFNODE("False", null, { |
| | | var AST_False = DEFNODE("False", null, function AST_False(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The `false` atom", |
| | | value: false |
| | | }, AST_Boolean); |
| | | |
| | | var AST_True = DEFNODE("True", null, { |
| | | var AST_True = DEFNODE("True", null, function AST_True(props) { |
| | | if (props) { |
| | | this.start = props.start; |
| | | this.end = props.end; |
| | | } |
| | | |
| | | this.flags = 0; |
| | | }, { |
| | | $documentation: "The `true` atom", |
| | | value: true |
| | | }, AST_Boolean); |
| | |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * Walks an AST node and its children. |
| | | * |
| | | * {cb} can return `walk_abort` to interrupt the walk. |
| | | * |
| | | * @param node |
| | | * @param cb {(node, info: { parent: (nth) => any }) => (boolean | undefined)} |
| | | * |
| | | * @returns {boolean} whether the walk was aborted |
| | | * |
| | | * @example |
| | | * const found_some_cond = walk_parent(my_ast_node, (node, { parent }) => { |
| | | * if (some_cond(node, parent())) return walk_abort |
| | | * }); |
| | | */ |
| | | function walk_parent(node, cb, initial_stack) { |
| | | const to_visit = [node]; |
| | | const push = to_visit.push.bind(to_visit); |
| | |
| | | } |
| | | } |
| | | |
| | | find_scope() { |
| | | for (let i = 0;;i++) { |
| | | const p = this.parent(i); |
| | | if (p instanceof AST_Toplevel) return p; |
| | | if (p instanceof AST_Lambda) return p; |
| | | if (p.block_scope) return p.block_scope; |
| | | } |
| | | } |
| | | |
| | | has_directive(type) { |
| | | var dir = this.directives[type]; |
| | | if (dir) return dir; |
| | |
| | | const _PURE = 0b00000001; |
| | | const _INLINE = 0b00000010; |
| | | const _NOINLINE = 0b00000100; |
| | | |
| | | var ast = /*#__PURE__*/Object.freeze({ |
| | | __proto__: null, |
| | | AST_Accessor: AST_Accessor, |
| | | AST_Array: AST_Array, |
| | | AST_Arrow: AST_Arrow, |
| | | AST_Assign: AST_Assign, |
| | | AST_Atom: AST_Atom, |
| | | AST_Await: AST_Await, |
| | | AST_BigInt: AST_BigInt, |
| | | AST_Binary: AST_Binary, |
| | | AST_Block: AST_Block, |
| | | AST_BlockStatement: AST_BlockStatement, |
| | | AST_Boolean: AST_Boolean, |
| | | AST_Break: AST_Break, |
| | | AST_Call: AST_Call, |
| | | AST_Case: AST_Case, |
| | | AST_Catch: AST_Catch, |
| | | AST_Chain: AST_Chain, |
| | | AST_Class: AST_Class, |
| | | AST_ClassExpression: AST_ClassExpression, |
| | | AST_ClassPrivateProperty: AST_ClassPrivateProperty, |
| | | AST_ClassProperty: AST_ClassProperty, |
| | | AST_ConciseMethod: AST_ConciseMethod, |
| | | AST_Conditional: AST_Conditional, |
| | | AST_Const: AST_Const, |
| | | AST_Constant: AST_Constant, |
| | | AST_Continue: AST_Continue, |
| | | AST_Debugger: AST_Debugger, |
| | | AST_Default: AST_Default, |
| | | AST_DefaultAssign: AST_DefaultAssign, |
| | | AST_DefClass: AST_DefClass, |
| | | AST_Definitions: AST_Definitions, |
| | | AST_Defun: AST_Defun, |
| | | AST_Destructuring: AST_Destructuring, |
| | | AST_Directive: AST_Directive, |
| | | AST_Do: AST_Do, |
| | | AST_Dot: AST_Dot, |
| | | AST_DotHash: AST_DotHash, |
| | | AST_DWLoop: AST_DWLoop, |
| | | AST_EmptyStatement: AST_EmptyStatement, |
| | | AST_Exit: AST_Exit, |
| | | AST_Expansion: AST_Expansion, |
| | | AST_Export: AST_Export, |
| | | AST_False: AST_False, |
| | | AST_Finally: AST_Finally, |
| | | AST_For: AST_For, |
| | | AST_ForIn: AST_ForIn, |
| | | AST_ForOf: AST_ForOf, |
| | | AST_Function: AST_Function, |
| | | AST_Hole: AST_Hole, |
| | | AST_If: AST_If, |
| | | AST_Import: AST_Import, |
| | | AST_ImportMeta: AST_ImportMeta, |
| | | AST_Infinity: AST_Infinity, |
| | | AST_IterationStatement: AST_IterationStatement, |
| | | AST_Jump: AST_Jump, |
| | | AST_Label: AST_Label, |
| | | AST_LabeledStatement: AST_LabeledStatement, |
| | | AST_LabelRef: AST_LabelRef, |
| | | AST_Lambda: AST_Lambda, |
| | | AST_Let: AST_Let, |
| | | AST_LoopControl: AST_LoopControl, |
| | | AST_NameMapping: AST_NameMapping, |
| | | AST_NaN: AST_NaN, |
| | | AST_New: AST_New, |
| | | AST_NewTarget: AST_NewTarget, |
| | | AST_Node: AST_Node, |
| | | AST_Null: AST_Null, |
| | | AST_Number: AST_Number, |
| | | AST_Object: AST_Object, |
| | | AST_ObjectGetter: AST_ObjectGetter, |
| | | AST_ObjectKeyVal: AST_ObjectKeyVal, |
| | | AST_ObjectProperty: AST_ObjectProperty, |
| | | AST_ObjectSetter: AST_ObjectSetter, |
| | | AST_PrefixedTemplateString: AST_PrefixedTemplateString, |
| | | AST_PrivateGetter: AST_PrivateGetter, |
| | | AST_PrivateMethod: AST_PrivateMethod, |
| | | AST_PrivateSetter: AST_PrivateSetter, |
| | | AST_PropAccess: AST_PropAccess, |
| | | AST_RegExp: AST_RegExp, |
| | | AST_Return: AST_Return, |
| | | AST_Scope: AST_Scope, |
| | | AST_Sequence: AST_Sequence, |
| | | AST_SimpleStatement: AST_SimpleStatement, |
| | | AST_Statement: AST_Statement, |
| | | AST_StatementWithBody: AST_StatementWithBody, |
| | | AST_String: AST_String, |
| | | AST_Sub: AST_Sub, |
| | | AST_Super: AST_Super, |
| | | AST_Switch: AST_Switch, |
| | | AST_SwitchBranch: AST_SwitchBranch, |
| | | AST_Symbol: AST_Symbol, |
| | | AST_SymbolBlockDeclaration: AST_SymbolBlockDeclaration, |
| | | AST_SymbolCatch: AST_SymbolCatch, |
| | | AST_SymbolClass: AST_SymbolClass, |
| | | AST_SymbolClassProperty: AST_SymbolClassProperty, |
| | | AST_SymbolConst: AST_SymbolConst, |
| | | AST_SymbolDeclaration: AST_SymbolDeclaration, |
| | | AST_SymbolDefClass: AST_SymbolDefClass, |
| | | AST_SymbolDefun: AST_SymbolDefun, |
| | | AST_SymbolExport: AST_SymbolExport, |
| | | AST_SymbolExportForeign: AST_SymbolExportForeign, |
| | | AST_SymbolFunarg: AST_SymbolFunarg, |
| | | AST_SymbolImport: AST_SymbolImport, |
| | | AST_SymbolImportForeign: AST_SymbolImportForeign, |
| | | AST_SymbolLambda: AST_SymbolLambda, |
| | | AST_SymbolLet: AST_SymbolLet, |
| | | AST_SymbolMethod: AST_SymbolMethod, |
| | | AST_SymbolRef: AST_SymbolRef, |
| | | AST_SymbolVar: AST_SymbolVar, |
| | | AST_TemplateSegment: AST_TemplateSegment, |
| | | AST_TemplateString: AST_TemplateString, |
| | | AST_This: AST_This, |
| | | AST_Throw: AST_Throw, |
| | | AST_Token: AST_Token, |
| | | AST_Toplevel: AST_Toplevel, |
| | | AST_True: AST_True, |
| | | AST_Try: AST_Try, |
| | | AST_Unary: AST_Unary, |
| | | AST_UnaryPostfix: AST_UnaryPostfix, |
| | | AST_UnaryPrefix: AST_UnaryPrefix, |
| | | AST_Undefined: AST_Undefined, |
| | | AST_Var: AST_Var, |
| | | AST_VarDef: AST_VarDef, |
| | | AST_While: AST_While, |
| | | AST_With: AST_With, |
| | | AST_Yield: AST_Yield, |
| | | TreeTransformer: TreeTransformer, |
| | | TreeWalker: TreeWalker, |
| | | walk: walk, |
| | | walk_abort: walk_abort, |
| | | walk_body: walk_body, |
| | | walk_parent: walk_parent, |
| | | _INLINE: _INLINE, |
| | | _NOINLINE: _NOINLINE, |
| | | _PURE: _PURE |
| | | }); |
| | | |
| | | /*********************************************************************** |
| | | |
| | |
| | | return body; |
| | | }; |
| | | |
| | | const assert_clause_from_moz = (assertions) => { |
| | | if (assertions && assertions.length > 0) { |
| | | return new AST_Object({ |
| | | start: my_start_token(assertions), |
| | | end: my_end_token(assertions), |
| | | properties: assertions.map((assertion_kv) => |
| | | new AST_ObjectKeyVal({ |
| | | start: my_start_token(assertion_kv), |
| | | end: my_end_token(assertion_kv), |
| | | key: assertion_kv.key.name || assertion_kv.key.value, |
| | | value: from_moz(assertion_kv.value) |
| | | }) |
| | | ) |
| | | }); |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | var MOZ_TO_ME = { |
| | | Program: function(M) { |
| | | return new AST_Toplevel({ |
| | |
| | | body: normalize_directives(M.body.map(from_moz)) |
| | | }); |
| | | }, |
| | | |
| | | ArrayPattern: function(M) { |
| | | return new AST_Destructuring({ |
| | | start: my_start_token(M), |
| | |
| | | is_array: true |
| | | }); |
| | | }, |
| | | |
| | | ObjectPattern: function(M) { |
| | | return new AST_Destructuring({ |
| | | start: my_start_token(M), |
| | |
| | | is_array: false |
| | | }); |
| | | }, |
| | | |
| | | AssignmentPattern: function(M) { |
| | | return new AST_DefaultAssign({ |
| | | start: my_start_token(M), |
| | |
| | | right: from_moz(M.right) |
| | | }); |
| | | }, |
| | | |
| | | SpreadElement: function(M) { |
| | | return new AST_Expansion({ |
| | | start: my_start_token(M), |
| | |
| | | expression: from_moz(M.argument) |
| | | }); |
| | | }, |
| | | |
| | | RestElement: function(M) { |
| | | return new AST_Expansion({ |
| | | start: my_start_token(M), |
| | |
| | | expression: from_moz(M.argument) |
| | | }); |
| | | }, |
| | | |
| | | TemplateElement: function(M) { |
| | | return new AST_TemplateSegment({ |
| | | start: my_start_token(M), |
| | |
| | | raw: M.value.raw |
| | | }); |
| | | }, |
| | | |
| | | TemplateLiteral: function(M) { |
| | | var segments = []; |
| | | for (var i = 0; i < M.quasis.length; i++) { |
| | |
| | | segments: segments |
| | | }); |
| | | }, |
| | | |
| | | TaggedTemplateExpression: function(M) { |
| | | return new AST_PrefixedTemplateString({ |
| | | start: my_start_token(M), |
| | |
| | | prefix: from_moz(M.tag) |
| | | }); |
| | | }, |
| | | |
| | | FunctionDeclaration: function(M) { |
| | | return new AST_Defun({ |
| | | start: my_start_token(M), |
| | |
| | | body: normalize_directives(from_moz(M.body).body) |
| | | }); |
| | | }, |
| | | |
| | | FunctionExpression: function(M) { |
| | | return new AST_Function({ |
| | | start: my_start_token(M), |
| | |
| | | body: normalize_directives(from_moz(M.body).body) |
| | | }); |
| | | }, |
| | | |
| | | ArrowFunctionExpression: function(M) { |
| | | const body = M.body.type === "BlockStatement" |
| | | ? from_moz(M.body).body |
| | |
| | | async: M.async, |
| | | }); |
| | | }, |
| | | |
| | | ExpressionStatement: function(M) { |
| | | return new AST_SimpleStatement({ |
| | | start: my_start_token(M), |
| | |
| | | body: from_moz(M.expression) |
| | | }); |
| | | }, |
| | | |
| | | TryStatement: function(M) { |
| | | var handlers = M.handlers || [M.handler]; |
| | | if (handlers.length > 1 || M.guardedHandlers && M.guardedHandlers.length) { |
| | |
| | | bfinally : M.finalizer ? new AST_Finally(from_moz(M.finalizer)) : null |
| | | }); |
| | | }, |
| | | |
| | | Property: function(M) { |
| | | var key = M.key; |
| | | var args = { |
| | |
| | | return new AST_ConciseMethod(args); |
| | | } |
| | | }, |
| | | |
| | | MethodDefinition: function(M) { |
| | | var args = { |
| | | start : my_start_token(M), |
| | |
| | | args.async = M.value.async; |
| | | return new AST_ConciseMethod(args); |
| | | }, |
| | | |
| | | FieldDefinition: function(M) { |
| | | let key; |
| | | if (M.computed) { |
| | |
| | | static : M.static, |
| | | }); |
| | | }, |
| | | |
| | | PropertyDefinition: function(M) { |
| | | let key; |
| | | if (M.computed) { |
| | |
| | | static : M.static, |
| | | }); |
| | | }, |
| | | |
| | | ArrayExpression: function(M) { |
| | | return new AST_Array({ |
| | | start : my_start_token(M), |
| | |
| | | }) |
| | | }); |
| | | }, |
| | | |
| | | ObjectExpression: function(M) { |
| | | return new AST_Object({ |
| | | start : my_start_token(M), |
| | |
| | | }) |
| | | }); |
| | | }, |
| | | |
| | | SequenceExpression: function(M) { |
| | | return new AST_Sequence({ |
| | | start : my_start_token(M), |
| | |
| | | expressions: M.expressions.map(from_moz) |
| | | }); |
| | | }, |
| | | |
| | | MemberExpression: function(M) { |
| | | return new (M.computed ? AST_Sub : AST_Dot)({ |
| | | start : my_start_token(M), |
| | |
| | | optional : M.optional || false |
| | | }); |
| | | }, |
| | | |
| | | ChainExpression: function(M) { |
| | | return new AST_Chain({ |
| | | start : my_start_token(M), |
| | |
| | | expression : from_moz(M.expression) |
| | | }); |
| | | }, |
| | | |
| | | SwitchCase: function(M) { |
| | | return new (M.test ? AST_Case : AST_Default)({ |
| | | start : my_start_token(M), |
| | |
| | | body : M.consequent.map(from_moz) |
| | | }); |
| | | }, |
| | | |
| | | VariableDeclaration: function(M) { |
| | | return new (M.kind === "const" ? AST_Const : |
| | | M.kind === "let" ? AST_Let : AST_Var)({ |
| | |
| | | end : my_end_token(M), |
| | | imported_name: imported_name, |
| | | imported_names : imported_names, |
| | | module_name : from_moz(M.source) |
| | | module_name : from_moz(M.source), |
| | | assert_clause: assert_clause_from_moz(M.assertions) |
| | | }); |
| | | }, |
| | | |
| | | ExportAllDeclaration: function(M) { |
| | | return new AST_Export({ |
| | | start: my_start_token(M), |
| | |
| | | foreign_name: new AST_SymbolExportForeign({ name: "*" }) |
| | | }) |
| | | ], |
| | | module_name: from_moz(M.source) |
| | | module_name: from_moz(M.source), |
| | | assert_clause: assert_clause_from_moz(M.assertions) |
| | | }); |
| | | }, |
| | | |
| | | ExportNamedDeclaration: function(M) { |
| | | return new AST_Export({ |
| | | start: my_start_token(M), |
| | |
| | | name: from_moz(specifier.local) |
| | | }); |
| | | }) : null, |
| | | module_name: from_moz(M.source) |
| | | module_name: from_moz(M.source), |
| | | assert_clause: assert_clause_from_moz(M.assertions) |
| | | }); |
| | | }, |
| | | |
| | | ExportDefaultDeclaration: function(M) { |
| | | return new AST_Export({ |
| | | start: my_start_token(M), |
| | |
| | | is_default: true |
| | | }); |
| | | }, |
| | | |
| | | Literal: function(M) { |
| | | var val = M.value, args = { |
| | | start : my_start_token(M), |
| | |
| | | return new (val ? AST_True : AST_False)(args); |
| | | } |
| | | }, |
| | | |
| | | MetaProperty: function(M) { |
| | | if (M.meta.name === "new" && M.property.name === "target") { |
| | | return new AST_NewTarget({ |
| | |
| | | }); |
| | | } |
| | | }, |
| | | |
| | | Identifier: function(M) { |
| | | var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2]; |
| | | return new ( p.type == "LabeledStatement" ? AST_Label |
| | |
| | | name : M.name |
| | | }); |
| | | }, |
| | | |
| | | BigIntLiteral(M) { |
| | | return new AST_BigInt({ |
| | | start : my_start_token(M), |
| | | end : my_end_token(M), |
| | | value : M.value |
| | | }); |
| | | }, |
| | | |
| | | EmptyStatement: function(M) { |
| | | return new AST_EmptyStatement({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M) |
| | | }); |
| | | }, |
| | | |
| | | BlockStatement: function(M) { |
| | | return new AST_BlockStatement({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | body: M.body.map(from_moz) |
| | | }); |
| | | }, |
| | | |
| | | IfStatement: function(M) { |
| | | return new AST_If({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | condition: from_moz(M.test), |
| | | body: from_moz(M.consequent), |
| | | alternative: from_moz(M.alternate) |
| | | }); |
| | | }, |
| | | |
| | | LabeledStatement: function(M) { |
| | | return new AST_LabeledStatement({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | label: from_moz(M.label), |
| | | body: from_moz(M.body) |
| | | }); |
| | | }, |
| | | |
| | | BreakStatement: function(M) { |
| | | return new AST_Break({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | label: from_moz(M.label) |
| | | }); |
| | | }, |
| | | |
| | | ContinueStatement: function(M) { |
| | | return new AST_Continue({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | label: from_moz(M.label) |
| | | }); |
| | | }, |
| | | |
| | | WithStatement: function(M) { |
| | | return new AST_With({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | expression: from_moz(M.object), |
| | | body: from_moz(M.body) |
| | | }); |
| | | }, |
| | | |
| | | SwitchStatement: function(M) { |
| | | return new AST_Switch({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | expression: from_moz(M.discriminant), |
| | | body: M.cases.map(from_moz) |
| | | }); |
| | | }, |
| | | |
| | | ReturnStatement: function(M) { |
| | | return new AST_Return({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | value: from_moz(M.argument) |
| | | }); |
| | | }, |
| | | |
| | | ThrowStatement: function(M) { |
| | | return new AST_Throw({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | value: from_moz(M.argument) |
| | | }); |
| | | }, |
| | | |
| | | WhileStatement: function(M) { |
| | | return new AST_While({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | condition: from_moz(M.test), |
| | | body: from_moz(M.body) |
| | | }); |
| | | }, |
| | | |
| | | DoWhileStatement: function(M) { |
| | | return new AST_Do({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | condition: from_moz(M.test), |
| | | body: from_moz(M.body) |
| | | }); |
| | | }, |
| | | |
| | | ForStatement: function(M) { |
| | | return new AST_For({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | init: from_moz(M.init), |
| | | condition: from_moz(M.test), |
| | | step: from_moz(M.update), |
| | | body: from_moz(M.body) |
| | | }); |
| | | }, |
| | | |
| | | ForInStatement: function(M) { |
| | | return new AST_ForIn({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | init: from_moz(M.left), |
| | | object: from_moz(M.right), |
| | | body: from_moz(M.body) |
| | | }); |
| | | }, |
| | | |
| | | ForOfStatement: function(M) { |
| | | return new AST_ForOf({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | init: from_moz(M.left), |
| | | object: from_moz(M.right), |
| | | body: from_moz(M.body), |
| | | await: M.await |
| | | }); |
| | | }, |
| | | |
| | | AwaitExpression: function(M) { |
| | | return new AST_Await({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | expression: from_moz(M.argument) |
| | | }); |
| | | }, |
| | | |
| | | YieldExpression: function(M) { |
| | | return new AST_Yield({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | expression: from_moz(M.argument), |
| | | is_star: M.delegate |
| | | }); |
| | | }, |
| | | |
| | | DebuggerStatement: function(M) { |
| | | return new AST_Debugger({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M) |
| | | }); |
| | | }, |
| | | |
| | | VariableDeclarator: function(M) { |
| | | return new AST_VarDef({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | name: from_moz(M.id), |
| | | value: from_moz(M.init) |
| | | }); |
| | | }, |
| | | |
| | | CatchClause: function(M) { |
| | | return new AST_Catch({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | argname: from_moz(M.param), |
| | | body: from_moz(M.body).body |
| | | }); |
| | | }, |
| | | |
| | | ThisExpression: function(M) { |
| | | return new AST_This({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M) |
| | | }); |
| | | }, |
| | | |
| | | Super: function(M) { |
| | | return new AST_Super({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M) |
| | | }); |
| | | }, |
| | | |
| | | BinaryExpression: function(M) { |
| | | return new AST_Binary({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | operator: M.operator, |
| | | left: from_moz(M.left), |
| | | right: from_moz(M.right) |
| | | }); |
| | | }, |
| | | |
| | | LogicalExpression: function(M) { |
| | | return new AST_Binary({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | operator: M.operator, |
| | | left: from_moz(M.left), |
| | | right: from_moz(M.right) |
| | | }); |
| | | }, |
| | | |
| | | AssignmentExpression: function(M) { |
| | | return new AST_Assign({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | operator: M.operator, |
| | | left: from_moz(M.left), |
| | | right: from_moz(M.right) |
| | | }); |
| | | }, |
| | | |
| | | ConditionalExpression: function(M) { |
| | | return new AST_Conditional({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | condition: from_moz(M.test), |
| | | consequent: from_moz(M.consequent), |
| | | alternative: from_moz(M.alternate) |
| | | }); |
| | | }, |
| | | |
| | | NewExpression: function(M) { |
| | | return new AST_New({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | expression: from_moz(M.callee), |
| | | args: M.arguments.map(from_moz) |
| | | }); |
| | | }, |
| | | |
| | | CallExpression: function(M) { |
| | | return new AST_Call({ |
| | | start: my_start_token(M), |
| | | end: my_end_token(M), |
| | | expression: from_moz(M.callee), |
| | | optional: M.optional, |
| | | args: M.arguments.map(from_moz) |
| | | }); |
| | | } |
| | | }; |
| | |
| | | }); |
| | | }; |
| | | |
| | | map("EmptyStatement", AST_EmptyStatement); |
| | | map("BlockStatement", AST_BlockStatement, "body@body"); |
| | | map("IfStatement", AST_If, "test>condition, consequent>body, alternate>alternative"); |
| | | map("LabeledStatement", AST_LabeledStatement, "label>label, body>body"); |
| | | map("BreakStatement", AST_Break, "label>label"); |
| | | map("ContinueStatement", AST_Continue, "label>label"); |
| | | map("WithStatement", AST_With, "object>expression, body>body"); |
| | | map("SwitchStatement", AST_Switch, "discriminant>expression, cases@body"); |
| | | map("ReturnStatement", AST_Return, "argument>value"); |
| | | map("ThrowStatement", AST_Throw, "argument>value"); |
| | | map("WhileStatement", AST_While, "test>condition, body>body"); |
| | | map("DoWhileStatement", AST_Do, "test>condition, body>body"); |
| | | map("ForStatement", AST_For, "init>init, test>condition, update>step, body>body"); |
| | | map("ForInStatement", AST_ForIn, "left>init, right>object, body>body"); |
| | | map("ForOfStatement", AST_ForOf, "left>init, right>object, body>body, await=await"); |
| | | map("AwaitExpression", AST_Await, "argument>expression"); |
| | | map("YieldExpression", AST_Yield, "argument>expression, delegate=is_star"); |
| | | map("DebuggerStatement", AST_Debugger); |
| | | map("VariableDeclarator", AST_VarDef, "id>name, init>value"); |
| | | map("CatchClause", AST_Catch, "param>argname, body%body"); |
| | | def_to_moz(AST_EmptyStatement, function To_Moz_EmptyStatement() { |
| | | return { |
| | | type: "EmptyStatement" |
| | | }; |
| | | }); |
| | | def_to_moz(AST_BlockStatement, function To_Moz_BlockStatement(M) { |
| | | return { |
| | | type: "BlockStatement", |
| | | body: M.body.map(to_moz) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_If, function To_Moz_IfStatement(M) { |
| | | return { |
| | | type: "IfStatement", |
| | | test: to_moz(M.condition), |
| | | consequent: to_moz(M.body), |
| | | alternate: to_moz(M.alternative) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_LabeledStatement, function To_Moz_LabeledStatement(M) { |
| | | return { |
| | | type: "LabeledStatement", |
| | | label: to_moz(M.label), |
| | | body: to_moz(M.body) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Break, function To_Moz_BreakStatement(M) { |
| | | return { |
| | | type: "BreakStatement", |
| | | label: to_moz(M.label) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Continue, function To_Moz_ContinueStatement(M) { |
| | | return { |
| | | type: "ContinueStatement", |
| | | label: to_moz(M.label) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_With, function To_Moz_WithStatement(M) { |
| | | return { |
| | | type: "WithStatement", |
| | | object: to_moz(M.expression), |
| | | body: to_moz(M.body) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Switch, function To_Moz_SwitchStatement(M) { |
| | | return { |
| | | type: "SwitchStatement", |
| | | discriminant: to_moz(M.expression), |
| | | cases: M.body.map(to_moz) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Return, function To_Moz_ReturnStatement(M) { |
| | | return { |
| | | type: "ReturnStatement", |
| | | argument: to_moz(M.value) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Throw, function To_Moz_ThrowStatement(M) { |
| | | return { |
| | | type: "ThrowStatement", |
| | | argument: to_moz(M.value) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_While, function To_Moz_WhileStatement(M) { |
| | | return { |
| | | type: "WhileStatement", |
| | | test: to_moz(M.condition), |
| | | body: to_moz(M.body) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Do, function To_Moz_DoWhileStatement(M) { |
| | | return { |
| | | type: "DoWhileStatement", |
| | | test: to_moz(M.condition), |
| | | body: to_moz(M.body) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_For, function To_Moz_ForStatement(M) { |
| | | return { |
| | | type: "ForStatement", |
| | | init: to_moz(M.init), |
| | | test: to_moz(M.condition), |
| | | update: to_moz(M.step), |
| | | body: to_moz(M.body) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_ForIn, function To_Moz_ForInStatement(M) { |
| | | return { |
| | | type: "ForInStatement", |
| | | left: to_moz(M.init), |
| | | right: to_moz(M.object), |
| | | body: to_moz(M.body) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_ForOf, function To_Moz_ForOfStatement(M) { |
| | | return { |
| | | type: "ForOfStatement", |
| | | left: to_moz(M.init), |
| | | right: to_moz(M.object), |
| | | body: to_moz(M.body), |
| | | await: M.await |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Await, function To_Moz_AwaitExpression(M) { |
| | | return { |
| | | type: "AwaitExpression", |
| | | argument: to_moz(M.expression) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Yield, function To_Moz_YieldExpression(M) { |
| | | return { |
| | | type: "YieldExpression", |
| | | argument: to_moz(M.expression), |
| | | delegate: M.is_star |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Debugger, function To_Moz_DebuggerStatement() { |
| | | return { |
| | | type: "DebuggerStatement" |
| | | }; |
| | | }); |
| | | def_to_moz(AST_VarDef, function To_Moz_VariableDeclarator(M) { |
| | | return { |
| | | type: "VariableDeclarator", |
| | | id: to_moz(M.name), |
| | | init: to_moz(M.value) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Catch, function To_Moz_CatchClause(M) { |
| | | return { |
| | | type: "CatchClause", |
| | | param: to_moz(M.argname), |
| | | body: to_moz_block(M) |
| | | }; |
| | | }); |
| | | |
| | | map("ThisExpression", AST_This); |
| | | map("Super", AST_Super); |
| | | map("BinaryExpression", AST_Binary, "operator=operator, left>left, right>right"); |
| | | map("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right"); |
| | | map("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right"); |
| | | map("ConditionalExpression", AST_Conditional, "test>condition, consequent>consequent, alternate>alternative"); |
| | | map("NewExpression", AST_New, "callee>expression, arguments@args"); |
| | | map("CallExpression", AST_Call, "callee>expression, optional=optional, arguments@args"); |
| | | def_to_moz(AST_This, function To_Moz_ThisExpression() { |
| | | return { |
| | | type: "ThisExpression" |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Super, function To_Moz_Super() { |
| | | return { |
| | | type: "Super" |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Binary, function To_Moz_BinaryExpression(M) { |
| | | return { |
| | | type: "BinaryExpression", |
| | | operator: M.operator, |
| | | left: to_moz(M.left), |
| | | right: to_moz(M.right) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Binary, function To_Moz_LogicalExpression(M) { |
| | | return { |
| | | type: "LogicalExpression", |
| | | operator: M.operator, |
| | | left: to_moz(M.left), |
| | | right: to_moz(M.right) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Assign, function To_Moz_AssignmentExpression(M) { |
| | | return { |
| | | type: "AssignmentExpression", |
| | | operator: M.operator, |
| | | left: to_moz(M.left), |
| | | right: to_moz(M.right) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Conditional, function To_Moz_ConditionalExpression(M) { |
| | | return { |
| | | type: "ConditionalExpression", |
| | | test: to_moz(M.condition), |
| | | consequent: to_moz(M.consequent), |
| | | alternate: to_moz(M.alternative) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_New, function To_Moz_NewExpression(M) { |
| | | return { |
| | | type: "NewExpression", |
| | | callee: to_moz(M.expression), |
| | | arguments: M.args.map(to_moz) |
| | | }; |
| | | }); |
| | | def_to_moz(AST_Call, function To_Moz_CallExpression(M) { |
| | | return { |
| | | type: "CallExpression", |
| | | callee: to_moz(M.expression), |
| | | optional: M.optional, |
| | | arguments: M.args.map(to_moz) |
| | | }; |
| | | }); |
| | | |
| | | def_to_moz(AST_Toplevel, function To_Moz_Program(M) { |
| | | return to_moz_scope("Program", M); |
| | |
| | | }; |
| | | }); |
| | | |
| | | const assert_clause_to_moz = assert_clause => { |
| | | const assertions = []; |
| | | if (assert_clause) { |
| | | for (const { key, value } of assert_clause.properties) { |
| | | const key_moz = is_basic_identifier_string(key) |
| | | ? { type: "Identifier", name: key } |
| | | : { type: "Literal", value: key, raw: JSON.stringify(key) }; |
| | | assertions.push({ |
| | | type: "ImportAttribute", |
| | | key: key_moz, |
| | | value: to_moz(value) |
| | | }); |
| | | } |
| | | } |
| | | return assertions; |
| | | }; |
| | | |
| | | def_to_moz(AST_Export, function To_Moz_ExportDeclaration(M) { |
| | | if (M.exported_names) { |
| | | if (M.exported_names[0].name.name === "*") { |
| | | return { |
| | | type: "ExportAllDeclaration", |
| | | source: to_moz(M.module_name) |
| | | source: to_moz(M.module_name), |
| | | assertions: assert_clause_to_moz(M.assert_clause) |
| | | }; |
| | | } |
| | | return { |
| | |
| | | }; |
| | | }), |
| | | declaration: to_moz(M.exported_definition), |
| | | source: to_moz(M.module_name) |
| | | source: to_moz(M.module_name), |
| | | assertions: assert_clause_to_moz(M.assert_clause) |
| | | }; |
| | | } |
| | | return { |
| | |
| | | return { |
| | | type: "ImportDeclaration", |
| | | specifiers: specifiers, |
| | | source: to_moz(M.module_name) |
| | | source: to_moz(M.module_name), |
| | | assertions: assert_clause_to_moz(M.assert_clause) |
| | | }; |
| | | }); |
| | | |
| | |
| | | ); |
| | | } |
| | | |
| | | function map(moztype, mytype, propmap) { |
| | | var moz_to_me = "function From_Moz_" + moztype + "(M){\n"; |
| | | moz_to_me += "return new U2." + mytype.name + "({\n" + |
| | | "start: my_start_token(M),\n" + |
| | | "end: my_end_token(M)"; |
| | | |
| | | var me_to_moz = "function To_Moz_" + moztype + "(M){\n"; |
| | | me_to_moz += "return {\n" + |
| | | "type: " + JSON.stringify(moztype); |
| | | |
| | | if (propmap) propmap.split(/\s*,\s*/).forEach(function(prop) { |
| | | var m = /([a-z0-9$_]+)([=@>%])([a-z0-9$_]+)/i.exec(prop); |
| | | if (!m) throw new Error("Can't understand property map: " + prop); |
| | | var moz = m[1], how = m[2], my = m[3]; |
| | | moz_to_me += ",\n" + my + ": "; |
| | | me_to_moz += ",\n" + moz + ": "; |
| | | switch (how) { |
| | | case "@": |
| | | moz_to_me += "M." + moz + ".map(from_moz)"; |
| | | me_to_moz += "M." + my + ".map(to_moz)"; |
| | | break; |
| | | case ">": |
| | | moz_to_me += "from_moz(M." + moz + ")"; |
| | | me_to_moz += "to_moz(M." + my + ")"; |
| | | break; |
| | | case "=": |
| | | moz_to_me += "M." + moz; |
| | | me_to_moz += "M." + my; |
| | | break; |
| | | case "%": |
| | | moz_to_me += "from_moz(M." + moz + ").body"; |
| | | me_to_moz += "to_moz_block(M)"; |
| | | break; |
| | | default: |
| | | throw new Error("Can't understand operator in propmap: " + prop); |
| | | } |
| | | }); |
| | | |
| | | moz_to_me += "\n})\n}"; |
| | | me_to_moz += "\n}\n}"; |
| | | |
| | | moz_to_me = new Function("U2", "my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")( |
| | | ast, my_start_token, my_end_token, from_moz |
| | | ); |
| | | me_to_moz = new Function("to_moz", "to_moz_block", "to_moz_scope", "return(" + me_to_moz + ")")( |
| | | to_moz, to_moz_block, to_moz_scope |
| | | ); |
| | | MOZ_TO_ME[moztype] = moz_to_me; |
| | | def_to_moz(mytype, me_to_moz); |
| | | } |
| | | |
| | | var FROM_MOZ_STACK = null; |
| | | |
| | | function from_moz(node) { |
| | |
| | | // multiline comment |
| | | return ( |
| | | (comment.type === "comment2" || comment.type === "comment1") |
| | | && /@preserve|@lic|@cc_on|^\**!/i.test(comment.value) |
| | | && /@preserve|@copyright|@lic|@cc_on|^\**!/i.test(comment.value) |
| | | ); |
| | | } |
| | | |
| | | class Rope { |
| | | constructor() { |
| | | this.committed = ""; |
| | | this.current = ""; |
| | | } |
| | | |
| | | append(str) { |
| | | this.current += str; |
| | | } |
| | | |
| | | insertAt(char, index) { |
| | | const { committed, current } = this; |
| | | if (index < committed.length) { |
| | | this.committed = committed.slice(0, index) + char + committed.slice(index); |
| | | } else if (index === committed.length) { |
| | | this.committed += char; |
| | | } else { |
| | | index -= committed.length; |
| | | this.committed += current.slice(0, index) + char; |
| | | this.current = current.slice(index); |
| | | } |
| | | } |
| | | |
| | | charAt(index) { |
| | | const { committed } = this; |
| | | if (index < committed.length) return committed[index]; |
| | | return this.current[index - committed.length]; |
| | | } |
| | | |
| | | curLength() { |
| | | return this.current.length; |
| | | } |
| | | |
| | | length() { |
| | | return this.committed.length + this.current.length; |
| | | } |
| | | |
| | | toString() { |
| | | return this.committed + this.current; |
| | | } |
| | | } |
| | | |
| | | function OutputStream(options) { |
| | |
| | | var current_col = 0; |
| | | var current_line = 1; |
| | | var current_pos = 0; |
| | | var OUTPUT = ""; |
| | | var OUTPUT = new Rope(); |
| | | let printed_comments = new Set(); |
| | | |
| | | var to_utf8 = options.ascii_only ? function(str, identifier) { |
| | | if (options.ecma >= 2015 && !options.safari10) { |
| | | var to_utf8 = options.ascii_only ? function(str, identifier = false, regexp = false) { |
| | | if (options.ecma >= 2015 && !options.safari10 && !regexp) { |
| | | str = str.replace(/[\ud800-\udbff][\udc00-\udfff]/g, function(ch) { |
| | | var code = get_full_char_code(ch, 0).toString(16); |
| | | return "\\u{" + code + "}"; |
| | |
| | | var ensure_line_len = options.max_line_len ? function() { |
| | | if (current_col > options.max_line_len) { |
| | | if (might_add_newline) { |
| | | var left = OUTPUT.slice(0, might_add_newline); |
| | | var right = OUTPUT.slice(might_add_newline); |
| | | OUTPUT.insertAt("\n", might_add_newline); |
| | | const curLength = OUTPUT.curLength(); |
| | | if (mappings) { |
| | | var delta = right.length - current_col; |
| | | var delta = curLength - current_col; |
| | | mappings.forEach(function(mapping) { |
| | | mapping.line++; |
| | | mapping.col += delta; |
| | | }); |
| | | } |
| | | OUTPUT = left + "\n" + right; |
| | | current_line++; |
| | | current_pos++; |
| | | current_col = right.length; |
| | | current_col = curLength; |
| | | } |
| | | } |
| | | if (might_add_newline) { |
| | |
| | | |
| | | if (prev === ":" && ch === "}" || (!ch || !";}".includes(ch)) && prev !== ";") { |
| | | if (options.semicolons || requireSemicolonChars.has(ch)) { |
| | | OUTPUT += ";"; |
| | | OUTPUT.append(";"); |
| | | current_col++; |
| | | current_pos++; |
| | | } else { |
| | | ensure_line_len(); |
| | | if (current_col > 0) { |
| | | OUTPUT += "\n"; |
| | | OUTPUT.append("\n"); |
| | | current_pos++; |
| | | current_line++; |
| | | current_col = 0; |
| | |
| | | || (ch == "/" && ch == prev) |
| | | || ((ch == "+" || ch == "-") && ch == last) |
| | | ) { |
| | | OUTPUT += " "; |
| | | OUTPUT.append(" "); |
| | | current_col++; |
| | | current_pos++; |
| | | } |
| | |
| | | if (!might_add_newline) do_add_mapping(); |
| | | } |
| | | |
| | | OUTPUT += str; |
| | | OUTPUT.append(str); |
| | | has_parens = str[str.length - 1] == "("; |
| | | current_pos += str.length; |
| | | var a = str.split(/\r?\n/), n = a.length - 1; |
| | |
| | | |
| | | var newline = options.beautify ? function() { |
| | | if (newline_insert < 0) return print("\n"); |
| | | if (OUTPUT[newline_insert] != "\n") { |
| | | OUTPUT = OUTPUT.slice(0, newline_insert) + "\n" + OUTPUT.slice(newline_insert); |
| | | if (OUTPUT.charAt(newline_insert) != "\n") { |
| | | OUTPUT.insertAt("\n", newline_insert); |
| | | current_pos++; |
| | | current_line++; |
| | | } |
| | | newline_insert++; |
| | | } : options.max_line_len ? function() { |
| | | ensure_line_len(); |
| | | might_add_newline = OUTPUT.length; |
| | | might_add_newline = OUTPUT.length(); |
| | | } : noop; |
| | | |
| | | var semicolon = options.beautify ? function() { |
| | |
| | | if (might_add_newline) { |
| | | ensure_line_len(); |
| | | } |
| | | return OUTPUT; |
| | | return OUTPUT.toString(); |
| | | } |
| | | |
| | | function has_nlb() { |
| | | let n = OUTPUT.length - 1; |
| | | const output = OUTPUT.toString(); |
| | | let n = output.length - 1; |
| | | while (n >= 0) { |
| | | const code = OUTPUT.charCodeAt(n); |
| | | const code = output.charCodeAt(n); |
| | | if (code === CODE_LINE_BREAK) { |
| | | return true; |
| | | } |
| | |
| | | !/comment[134]/.test(c.type) |
| | | ))) return; |
| | | printed_comments.add(comments); |
| | | var insert = OUTPUT.length; |
| | | var insert = OUTPUT.length(); |
| | | comments.filter(comment_filter, node).forEach(function(c, i) { |
| | | if (printed_comments.has(c)) return; |
| | | printed_comments.add(c); |
| | |
| | | need_space = true; |
| | | } |
| | | }); |
| | | if (OUTPUT.length > insert) newline_insert = insert; |
| | | if (OUTPUT.length() > insert) newline_insert = insert; |
| | | } |
| | | |
| | | var stack = []; |
| | |
| | | var encoded = encode_string(str, quote); |
| | | if (escape_directive === true && !encoded.includes("\\")) { |
| | | // Insert semicolons to break directive prologue |
| | | if (!EXPECT_DIRECTIVE.test(OUTPUT)) { |
| | | if (!EXPECT_DIRECTIVE.test(OUTPUT.toString())) { |
| | | force_semicolon(); |
| | | } |
| | | force_semicolon(); |
| | |
| | | var p = output.parent(); |
| | | if (this.args.length === 0 |
| | | && (p instanceof AST_PropAccess // (new Date).getTime(), (new Date)["getTime"]() |
| | | || p instanceof AST_Call && p.expression === this)) // (new foo)(bar) |
| | | || p instanceof AST_Call && p.expression === this |
| | | || p instanceof AST_PrefixedTemplateString && p.prefix === this)) // (new foo)(bar) |
| | | return true; |
| | | }); |
| | | |
| | |
| | | output.space(); |
| | | } |
| | | self.module_name.print(output); |
| | | if (self.assert_clause) { |
| | | output.print("assert"); |
| | | self.assert_clause.print(output); |
| | | } |
| | | output.semicolon(); |
| | | }); |
| | | DEFPRINT(AST_ImportMeta, function(self, output) { |
| | |
| | | output.print("from"); |
| | | output.space(); |
| | | self.module_name.print(output); |
| | | } |
| | | if (self.assert_clause) { |
| | | output.print("assert"); |
| | | self.assert_clause.print(output); |
| | | } |
| | | if (self.exported_value |
| | | && !(self.exported_value instanceof AST_Defun || |
| | |
| | | flags = flags ? sort_regexp_flags(flags) : ""; |
| | | source = source.replace(r_slash_script, slash_script_replace); |
| | | |
| | | output.print(output.to_utf8(`/${source}/${flags}`)); |
| | | output.print(output.to_utf8(`/${source}/${flags}`, false, true)); |
| | | |
| | | const parent = output.parent(); |
| | | if ( |
| | |
| | | return walk_1_state.length == 0 && walk_2_state.length == 0; |
| | | }; |
| | | |
| | | // Creates a shallow compare function |
| | | const mkshallow = (props) => { |
| | | const comparisons = Object |
| | | .keys(props) |
| | | .map(key => { |
| | | if (props[key] === "eq") { |
| | | return `this.${key} === other.${key}`; |
| | | } else if (props[key] === "exist") { |
| | | return `(this.${key} == null ? other.${key} == null : this.${key} === other.${key})`; |
| | | } else { |
| | | throw new Error(`mkshallow: Unexpected instruction: ${props[key]}`); |
| | | } |
| | | }) |
| | | .join(" && "); |
| | | |
| | | return new Function("other", "return " + comparisons); |
| | | }; |
| | | |
| | | const pass_through = () => true; |
| | | |
| | | AST_Node.prototype.shallow_cmp = function () { |
| | |
| | | |
| | | AST_Debugger.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_Directive.prototype.shallow_cmp = mkshallow({ value: "eq" }); |
| | | AST_Directive.prototype.shallow_cmp = function(other) { |
| | | return this.value === other.value; |
| | | }; |
| | | |
| | | AST_SimpleStatement.prototype.shallow_cmp = pass_through; |
| | | |
| | |
| | | |
| | | AST_EmptyStatement.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_LabeledStatement.prototype.shallow_cmp = mkshallow({ "label.name": "eq" }); |
| | | AST_LabeledStatement.prototype.shallow_cmp = function(other) { |
| | | return this.label.name === other.label.name; |
| | | }; |
| | | |
| | | AST_Do.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_While.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_For.prototype.shallow_cmp = mkshallow({ |
| | | init: "exist", |
| | | condition: "exist", |
| | | step: "exist" |
| | | }); |
| | | AST_For.prototype.shallow_cmp = function(other) { |
| | | return (this.init == null ? other.init == null : this.init === other.init) && (this.condition == null ? other.condition == null : this.condition === other.condition) && (this.step == null ? other.step == null : this.step === other.step); |
| | | }; |
| | | |
| | | AST_ForIn.prototype.shallow_cmp = pass_through; |
| | | |
| | |
| | | |
| | | AST_Expansion.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_Lambda.prototype.shallow_cmp = mkshallow({ |
| | | is_generator: "eq", |
| | | async: "eq" |
| | | }); |
| | | AST_Lambda.prototype.shallow_cmp = function(other) { |
| | | return this.is_generator === other.is_generator && this.async === other.async; |
| | | }; |
| | | |
| | | AST_Destructuring.prototype.shallow_cmp = mkshallow({ |
| | | is_array: "eq" |
| | | }); |
| | | AST_Destructuring.prototype.shallow_cmp = function(other) { |
| | | return this.is_array === other.is_array; |
| | | }; |
| | | |
| | | AST_PrefixedTemplateString.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_TemplateString.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_TemplateSegment.prototype.shallow_cmp = mkshallow({ |
| | | "value": "eq" |
| | | }); |
| | | AST_TemplateSegment.prototype.shallow_cmp = function(other) { |
| | | return this.value === other.value; |
| | | }; |
| | | |
| | | AST_Jump.prototype.shallow_cmp = pass_through; |
| | | |
| | |
| | | |
| | | AST_Await.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_Yield.prototype.shallow_cmp = mkshallow({ |
| | | is_star: "eq" |
| | | }); |
| | | AST_Yield.prototype.shallow_cmp = function(other) { |
| | | return this.is_star === other.is_star; |
| | | }; |
| | | |
| | | AST_If.prototype.shallow_cmp = mkshallow({ |
| | | alternative: "exist" |
| | | }); |
| | | AST_If.prototype.shallow_cmp = function(other) { |
| | | return this.alternative == null ? other.alternative == null : this.alternative === other.alternative; |
| | | }; |
| | | |
| | | AST_Switch.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_SwitchBranch.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_Try.prototype.shallow_cmp = mkshallow({ |
| | | bcatch: "exist", |
| | | bfinally: "exist" |
| | | }); |
| | | AST_Try.prototype.shallow_cmp = function(other) { |
| | | return (this.bcatch == null ? other.bcatch == null : this.bcatch === other.bcatch) && (this.bfinally == null ? other.bfinally == null : this.bfinally === other.bfinally); |
| | | }; |
| | | |
| | | AST_Catch.prototype.shallow_cmp = mkshallow({ |
| | | argname: "exist" |
| | | }); |
| | | AST_Catch.prototype.shallow_cmp = function(other) { |
| | | return this.argname == null ? other.argname == null : this.argname === other.argname; |
| | | }; |
| | | |
| | | AST_Finally.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_Definitions.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_VarDef.prototype.shallow_cmp = mkshallow({ |
| | | value: "exist" |
| | | }); |
| | | AST_VarDef.prototype.shallow_cmp = function(other) { |
| | | return this.value == null ? other.value == null : this.value === other.value; |
| | | }; |
| | | |
| | | AST_NameMapping.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_Import.prototype.shallow_cmp = mkshallow({ |
| | | imported_name: "exist", |
| | | imported_names: "exist" |
| | | }); |
| | | AST_Import.prototype.shallow_cmp = function(other) { |
| | | return (this.imported_name == null ? other.imported_name == null : this.imported_name === other.imported_name) && (this.imported_names == null ? other.imported_names == null : this.imported_names === other.imported_names); |
| | | }; |
| | | |
| | | AST_ImportMeta.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_Export.prototype.shallow_cmp = mkshallow({ |
| | | exported_definition: "exist", |
| | | exported_value: "exist", |
| | | exported_names: "exist", |
| | | module_name: "eq", |
| | | is_default: "eq", |
| | | }); |
| | | AST_Export.prototype.shallow_cmp = function(other) { |
| | | return (this.exported_definition == null ? other.exported_definition == null : this.exported_definition === other.exported_definition) && (this.exported_value == null ? other.exported_value == null : this.exported_value === other.exported_value) && (this.exported_names == null ? other.exported_names == null : this.exported_names === other.exported_names) && this.module_name === other.module_name && this.is_default === other.is_default; |
| | | }; |
| | | |
| | | AST_Call.prototype.shallow_cmp = pass_through; |
| | | |
| | |
| | | |
| | | AST_Chain.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_Dot.prototype.shallow_cmp = mkshallow({ |
| | | property: "eq" |
| | | }); |
| | | AST_Dot.prototype.shallow_cmp = function(other) { |
| | | return this.property === other.property; |
| | | }; |
| | | |
| | | AST_DotHash.prototype.shallow_cmp = mkshallow({ |
| | | property: "eq" |
| | | }); |
| | | AST_DotHash.prototype.shallow_cmp = function(other) { |
| | | return this.property === other.property; |
| | | }; |
| | | |
| | | AST_Unary.prototype.shallow_cmp = mkshallow({ |
| | | operator: "eq" |
| | | }); |
| | | AST_Unary.prototype.shallow_cmp = function(other) { |
| | | return this.operator === other.operator; |
| | | }; |
| | | |
| | | AST_Binary.prototype.shallow_cmp = mkshallow({ |
| | | operator: "eq" |
| | | }); |
| | | AST_Binary.prototype.shallow_cmp = function(other) { |
| | | return this.operator === other.operator; |
| | | }; |
| | | |
| | | AST_Conditional.prototype.shallow_cmp = pass_through; |
| | | |
| | |
| | | |
| | | AST_ObjectProperty.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_ObjectKeyVal.prototype.shallow_cmp = mkshallow({ |
| | | key: "eq" |
| | | }); |
| | | AST_ObjectKeyVal.prototype.shallow_cmp = function(other) { |
| | | return this.key === other.key; |
| | | }; |
| | | |
| | | AST_ObjectSetter.prototype.shallow_cmp = mkshallow({ |
| | | static: "eq" |
| | | }); |
| | | AST_ObjectSetter.prototype.shallow_cmp = function(other) { |
| | | return this.static === other.static; |
| | | }; |
| | | |
| | | AST_ObjectGetter.prototype.shallow_cmp = mkshallow({ |
| | | static: "eq" |
| | | }); |
| | | AST_ObjectGetter.prototype.shallow_cmp = function(other) { |
| | | return this.static === other.static; |
| | | }; |
| | | |
| | | AST_ConciseMethod.prototype.shallow_cmp = mkshallow({ |
| | | static: "eq", |
| | | is_generator: "eq", |
| | | async: "eq", |
| | | }); |
| | | AST_ConciseMethod.prototype.shallow_cmp = function(other) { |
| | | return this.static === other.static && this.is_generator === other.is_generator && this.async === other.async; |
| | | }; |
| | | |
| | | AST_Class.prototype.shallow_cmp = mkshallow({ |
| | | name: "exist", |
| | | extends: "exist", |
| | | }); |
| | | AST_Class.prototype.shallow_cmp = function(other) { |
| | | return (this.name == null ? other.name == null : this.name === other.name) && (this.extends == null ? other.extends == null : this.extends === other.extends); |
| | | }; |
| | | |
| | | AST_ClassProperty.prototype.shallow_cmp = mkshallow({ |
| | | static: "eq" |
| | | }); |
| | | AST_ClassProperty.prototype.shallow_cmp = function(other) { |
| | | return this.static === other.static; |
| | | }; |
| | | |
| | | AST_Symbol.prototype.shallow_cmp = mkshallow({ |
| | | name: "eq" |
| | | }); |
| | | AST_Symbol.prototype.shallow_cmp = function(other) { |
| | | return this.name === other.name; |
| | | }; |
| | | |
| | | AST_NewTarget.prototype.shallow_cmp = pass_through; |
| | | |
| | |
| | | |
| | | AST_Super.prototype.shallow_cmp = pass_through; |
| | | |
| | | AST_String.prototype.shallow_cmp = mkshallow({ |
| | | value: "eq" |
| | | }); |
| | | AST_String.prototype.shallow_cmp = function(other) { |
| | | return this.value === other.value; |
| | | }; |
| | | |
| | | AST_Number.prototype.shallow_cmp = mkshallow({ |
| | | value: "eq" |
| | | }); |
| | | AST_Number.prototype.shallow_cmp = function(other) { |
| | | return this.value === other.value; |
| | | }; |
| | | |
| | | AST_BigInt.prototype.shallow_cmp = mkshallow({ |
| | | value: "eq" |
| | | }); |
| | | AST_BigInt.prototype.shallow_cmp = function(other) { |
| | | return this.value === other.value; |
| | | }; |
| | | |
| | | AST_RegExp.prototype.shallow_cmp = function (other) { |
| | | return ( |
| | |
| | | |
| | | let function_defs = null; |
| | | let unmangleable_names = null; |
| | | /** |
| | | * When defined, there is a function declaration somewhere that's inside of a block. |
| | | * See https://tc39.es/ecma262/multipage/additional-ecmascript-features-for-web-browsers.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics |
| | | */ |
| | | let scopes_with_block_defuns = null; |
| | | |
| | | class SymbolDef { |
| | | constructor(scope, orig, init) { |
| | |
| | | }); |
| | | |
| | | function next_mangled(scope, options) { |
| | | let defun_scope; |
| | | if ( |
| | | scopes_with_block_defuns |
| | | && (defun_scope = scope.get_defun_scope()) |
| | | && scopes_with_block_defuns.has(defun_scope) |
| | | ) { |
| | | scope = defun_scope; |
| | | } |
| | | |
| | | var ext = scope.enclosed; |
| | | var nth_identifier = options.nth_identifier; |
| | | out: while (true) { |
| | |
| | | } |
| | | |
| | | const mangled_names = this.mangled_names = new Set(); |
| | | unmangleable_names = new Set(); |
| | | |
| | | if (options.cache) { |
| | | this.globals.forEach(collect); |
| | | if (options.cache.props) { |
| | |
| | | descend(); |
| | | lname = save_nesting; |
| | | return true; // don't descend again in TreeWalker |
| | | } |
| | | if ( |
| | | node instanceof AST_Defun |
| | | && !(tw.parent() instanceof AST_Scope) |
| | | ) { |
| | | scopes_with_block_defuns = scopes_with_block_defuns || new Set(); |
| | | scopes_with_block_defuns.add(node.parent_scope.get_defun_scope()); |
| | | } |
| | | if (node instanceof AST_Scope) { |
| | | node.variables.forEach(collect); |
| | |
| | | this.walk(tw); |
| | | |
| | | if (options.keep_fnames || options.keep_classnames) { |
| | | unmangleable_names = new Set(); |
| | | // Collect a set of short names which are unmangleable, |
| | | // for use in avoiding collisions in next_mangled. |
| | | to_mangle.forEach(def => { |
| | |
| | | |
| | | function_defs = null; |
| | | unmangleable_names = null; |
| | | scopes_with_block_defuns = null; |
| | | |
| | | function collect(symbol) { |
| | | const should_mangle = !options.reserved.has(symbol.name) |
| | | && !(symbol.export & MASK_EXPORT_DONT_MANGLE); |
| | | if (should_mangle) { |
| | | if (symbol.export & MASK_EXPORT_DONT_MANGLE) { |
| | | unmangleable_names.add(symbol.name); |
| | | } else if (!options.reserved.has(symbol.name)) { |
| | | to_mangle.push(symbol); |
| | | } |
| | | } |
| | |
| | | return 2 + this.value.length; |
| | | }; |
| | | |
| | | /** Count commas/semicolons necessary to show a list of expressions/statements */ |
| | | const list_overhead = (array) => array.length && array.length - 1; |
| | | |
| | | AST_Block.prototype._size = function () { |
| | |
| | | && this.argnames[0] instanceof AST_Symbol |
| | | ) |
| | | ) { |
| | | args_and_arrow += 2; |
| | | args_and_arrow += 2; // parens around the args |
| | | } |
| | | |
| | | const body_overhead = this.is_braceless() ? 0 : list_overhead(this.body) + 2; |
| | |
| | | return 7 + list_overhead(this.body); |
| | | }; |
| | | |
| | | /*#__INLINE__*/ |
| | | const def_size = (size, def) => size + list_overhead(def.definitions); |
| | | |
| | | AST_Var.prototype._size = function () { |
| | | return def_size(4, this); |
| | | return 4 + list_overhead(this.definitions); |
| | | }; |
| | | |
| | | AST_Let.prototype._size = function () { |
| | | return def_size(4, this); |
| | | return 4 + list_overhead(this.definitions); |
| | | }; |
| | | |
| | | AST_Const.prototype._size = function () { |
| | | return def_size(6, this); |
| | | return 6 + list_overhead(this.definitions); |
| | | }; |
| | | |
| | | AST_VarDef.prototype._size = function () { |
| | |
| | | |
| | | AST_Undefined.prototype._size = () => 6; // "void 0" |
| | | |
| | | AST_Hole.prototype._size = () => 0; // comma is taken into account |
| | | AST_Hole.prototype._size = () => 0; // comma is taken into account by list_overhead() |
| | | |
| | | AST_Infinity.prototype._size = () => 8; |
| | | |
| | |
| | | AST_Await.prototype._size = () => 6; |
| | | |
| | | AST_Yield.prototype._size = () => 6; |
| | | |
| | | /*********************************************************************** |
| | | |
| | | A JavaScript tokenizer / parser / beautifier / compressor. |
| | | https://github.com/mishoo/UglifyJS2 |
| | | |
| | | -------------------------------- (C) --------------------------------- |
| | | |
| | | Author: Mihai Bazon |
| | | <mihai.bazon@gmail.com> |
| | | http://mihai.bazon.net/blog |
| | | |
| | | Distributed under the BSD license: |
| | | |
| | | Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> |
| | | |
| | | Redistribution and use in source and binary forms, with or without |
| | | modification, are permitted provided that the following conditions |
| | | are met: |
| | | |
| | | * Redistributions of source code must retain the above |
| | | copyright notice, this list of conditions and the following |
| | | disclaimer. |
| | | |
| | | * Redistributions in binary form must reproduce the above |
| | | copyright notice, this list of conditions and the following |
| | | disclaimer in the documentation and/or other materials |
| | | provided with the distribution. |
| | | |
| | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY |
| | | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| | | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE |
| | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, |
| | | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| | | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| | | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR |
| | | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF |
| | | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| | | SUCH DAMAGE. |
| | | |
| | | ***********************************************************************/ |
| | | |
| | | // bitfield flags to be stored in node.flags. |
| | | // These are set and unset during compression, and store information in the node without requiring multiple fields. |
| | | const UNUSED = 0b00000001; |
| | | const TRUTHY = 0b00000010; |
| | | const FALSY = 0b00000100; |
| | | const UNDEFINED = 0b00001000; |
| | | const INLINED = 0b00010000; |
| | | |
| | | // Nodes to which values are ever written. Used when keep_assign is part of the unused option string. |
| | | const WRITE_ONLY = 0b00100000; |
| | | |
| | | // information specific to a single compression pass |
| | | const SQUEEZED = 0b0000000100000000; |
| | | const OPTIMIZED = 0b0000001000000000; |
| | | const TOP = 0b0000010000000000; |
| | | const CLEAR_BETWEEN_PASSES = SQUEEZED | OPTIMIZED | TOP; |
| | | |
| | | const has_flag = (node, flag) => node.flags & flag; |
| | | const set_flag = (node, flag) => { node.flags |= flag; }; |
| | | const clear_flag = (node, flag) => { node.flags &= ~flag; }; |
| | | |
| | | /*********************************************************************** |
| | | |
| | |
| | | return node.expression instanceof AST_Function || is_iife_call(node.expression); |
| | | } |
| | | |
| | | function is_empty(thing) { |
| | | if (thing === null) return true; |
| | | if (thing instanceof AST_EmptyStatement) return true; |
| | | if (thing instanceof AST_BlockStatement) return thing.body.length == 0; |
| | | return false; |
| | | } |
| | | |
| | | const identifier_atom = makePredicate("Infinity NaN undefined"); |
| | | function is_identifier_atom(node) { |
| | | return node instanceof AST_Infinity |
| | |
| | | throw new Error("Can't convert thing to statement array"); |
| | | } |
| | | |
| | | function is_reachable(scope_node, defs) { |
| | | const find_ref = node => { |
| | | if (node instanceof AST_SymbolRef && defs.includes(node.definition())) { |
| | | return walk_abort; |
| | | } |
| | | }; |
| | | |
| | | return walk_parent(scope_node, (node, info) => { |
| | | if (node instanceof AST_Scope && node !== scope_node) { |
| | | var parent = info.parent(); |
| | | |
| | | if ( |
| | | parent instanceof AST_Call |
| | | && parent.expression === node |
| | | // Async/Generators aren't guaranteed to sync evaluate all of |
| | | // their body steps, so it's possible they close over the variable. |
| | | && !(node.async || node.is_generator) |
| | | ) { |
| | | return; |
| | | } |
| | | |
| | | if (walk(node, find_ref)) return walk_abort; |
| | | |
| | | return true; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** Check if a ref refers to the name of a function/class it's defined within */ |
| | | function is_recursive_ref(compressor, def) { |
| | | var node; |
| | |
| | | return false; |
| | | } |
| | | |
| | | /*********************************************************************** |
| | | |
| | | A JavaScript tokenizer / parser / beautifier / compressor. |
| | | https://github.com/mishoo/UglifyJS2 |
| | | |
| | | -------------------------------- (C) --------------------------------- |
| | | |
| | | Author: Mihai Bazon |
| | | <mihai.bazon@gmail.com> |
| | | http://mihai.bazon.net/blog |
| | | |
| | | Distributed under the BSD license: |
| | | |
| | | Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> |
| | | |
| | | Redistribution and use in source and binary forms, with or without |
| | | modification, are permitted provided that the following conditions |
| | | are met: |
| | | |
| | | * Redistributions of source code must retain the above |
| | | copyright notice, this list of conditions and the following |
| | | disclaimer. |
| | | |
| | | * Redistributions in binary form must reproduce the above |
| | | copyright notice, this list of conditions and the following |
| | | disclaimer in the documentation and/or other materials |
| | | provided with the distribution. |
| | | |
| | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY |
| | | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| | | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE |
| | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, |
| | | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| | | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| | | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR |
| | | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF |
| | | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| | | SUCH DAMAGE. |
| | | |
| | | ***********************************************************************/ |
| | | |
| | | // bitfield flags to be stored in node.flags. |
| | | // These are set and unset during compression, and store information in the node without requiring multiple fields. |
| | | const UNUSED = 0b00000001; |
| | | const TRUTHY = 0b00000010; |
| | | const FALSY = 0b00000100; |
| | | const UNDEFINED = 0b00001000; |
| | | const INLINED = 0b00010000; |
| | | |
| | | // Nodes to which values are ever written. Used when keep_assign is part of the unused option string. |
| | | const WRITE_ONLY = 0b00100000; |
| | | |
| | | // information specific to a single compression pass |
| | | const SQUEEZED = 0b0000000100000000; |
| | | const OPTIMIZED = 0b0000001000000000; |
| | | const TOP = 0b0000010000000000; |
| | | const CLEAR_BETWEEN_PASSES = SQUEEZED | OPTIMIZED | TOP; |
| | | |
| | | const has_flag = (node, flag) => node.flags & flag; |
| | | const set_flag = (node, flag) => { node.flags |= flag; }; |
| | | const clear_flag = (node, flag) => { node.flags &= ~flag; }; |
| | | // TODO this only works with AST_Defun, shouldn't it work for other ways of defining functions? |
| | | function retain_top_func(fn, compressor) { |
| | | return compressor.top_retain |
| | | && fn instanceof AST_Defun |
| | | && has_flag(fn, TOP) |
| | | && fn.name |
| | | && compressor.top_retain(fn.name); |
| | | } |
| | | |
| | | /*********************************************************************** |
| | | |
| | |
| | | "isExtensible", |
| | | "isFrozen", |
| | | "isSealed", |
| | | "hasOwn", |
| | | "keys", |
| | | ], |
| | | String: [ |
| | |
| | | def_eval(AST_BigInt, return_this); |
| | | |
| | | def_eval(AST_RegExp, function (compressor) { |
| | | let evaluated = compressor.evaluated_regexps.get(this); |
| | | let evaluated = compressor.evaluated_regexps.get(this.value); |
| | | if (evaluated === undefined) { |
| | | try { |
| | | evaluated = (0, eval)(this.print_to_string()); |
| | | const { source, flags } = this.value; |
| | | evaluated = new RegExp(source, flags); |
| | | } catch (e) { |
| | | evaluated = null; |
| | | } |
| | | compressor.evaluated_regexps.set(this, evaluated); |
| | | compressor.evaluated_regexps.set(this.value, evaluated); |
| | | } |
| | | return evaluated || this; |
| | | }); |
| | |
| | | }); |
| | | } |
| | | |
| | | // Tighten a bunch of statements together, and perform statement-level optimization. |
| | | /** Tighten a bunch of statements together, and perform statement-level optimization. */ |
| | | function tighten_body(statements, compressor) { |
| | | var in_loop, in_try; |
| | | var scope = compressor.find_parent(AST_Scope).get_defun_scope(); |
| | |
| | | |
| | | ***********************************************************************/ |
| | | |
| | | |
| | | function within_array_or_object_literal(compressor) { |
| | | var node, level = 0; |
| | | while (node = compressor.parent(level++)) { |
| | | if (node instanceof AST_Statement) return false; |
| | | if (node instanceof AST_Array |
| | | || node instanceof AST_ObjectKeyVal |
| | | || node instanceof AST_Object) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | function scope_encloses_variables_in_this_scope(scope, pulled_scope) { |
| | | for (const enclosed of pulled_scope.enclosed) { |
| | | if (pulled_scope.variables.has(enclosed.name)) { |
| | | continue; |
| | | } |
| | | const looked_up = scope.find_variable(enclosed.name); |
| | | if (looked_up) { |
| | | if (looked_up === enclosed) continue; |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | function inline_into_symbolref(self, compressor) { |
| | | if ( |
| | | !compressor.option("ie8") |
| | | && is_undeclared_ref(self) |
| | | && !compressor.find_parent(AST_With) |
| | | ) { |
| | | switch (self.name) { |
| | | case "undefined": |
| | | return make_node(AST_Undefined, self).optimize(compressor); |
| | | case "NaN": |
| | | return make_node(AST_NaN, self).optimize(compressor); |
| | | case "Infinity": |
| | | return make_node(AST_Infinity, self).optimize(compressor); |
| | | } |
| | | } |
| | | |
| | | const parent = compressor.parent(); |
| | | if (compressor.option("reduce_vars") && is_lhs(self, parent) !== self) { |
| | | const def = self.definition(); |
| | | const nearest_scope = compressor.find_scope(); |
| | | if (compressor.top_retain && def.global && compressor.top_retain(def)) { |
| | | def.fixed = false; |
| | | def.single_use = false; |
| | | return self; |
| | | } |
| | | |
| | | let fixed = self.fixed_value(); |
| | | let single_use = def.single_use |
| | | && !(parent instanceof AST_Call |
| | | && (parent.is_callee_pure(compressor)) |
| | | || has_annotation(parent, _NOINLINE)) |
| | | && !(parent instanceof AST_Export |
| | | && fixed instanceof AST_Lambda |
| | | && fixed.name); |
| | | |
| | | if (single_use && fixed instanceof AST_Node) { |
| | | single_use = |
| | | !fixed.has_side_effects(compressor) |
| | | && !fixed.may_throw(compressor); |
| | | } |
| | | |
| | | if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) { |
| | | if (retain_top_func(fixed, compressor)) { |
| | | single_use = false; |
| | | } else if (def.scope !== self.scope |
| | | && (def.escaped == 1 |
| | | || has_flag(fixed, INLINED) |
| | | || within_array_or_object_literal(compressor) |
| | | || !compressor.option("reduce_funcs"))) { |
| | | single_use = false; |
| | | } else if (is_recursive_ref(compressor, def)) { |
| | | single_use = false; |
| | | } else if (def.scope !== self.scope || def.orig[0] instanceof AST_SymbolFunarg) { |
| | | single_use = fixed.is_constant_expression(self.scope); |
| | | if (single_use == "f") { |
| | | var scope = self.scope; |
| | | do { |
| | | if (scope instanceof AST_Defun || is_func_expr(scope)) { |
| | | set_flag(scope, INLINED); |
| | | } |
| | | } while (scope = scope.parent_scope); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (single_use && fixed instanceof AST_Lambda) { |
| | | single_use = |
| | | def.scope === self.scope |
| | | && !scope_encloses_variables_in_this_scope(nearest_scope, fixed) |
| | | || parent instanceof AST_Call |
| | | && parent.expression === self |
| | | && !scope_encloses_variables_in_this_scope(nearest_scope, fixed) |
| | | && !(fixed.name && fixed.name.definition().recursive_refs > 0); |
| | | } |
| | | |
| | | if (single_use && fixed) { |
| | | if (fixed instanceof AST_DefClass) { |
| | | set_flag(fixed, SQUEEZED); |
| | | fixed = make_node(AST_ClassExpression, fixed, fixed); |
| | | } |
| | | if (fixed instanceof AST_Defun) { |
| | | set_flag(fixed, SQUEEZED); |
| | | fixed = make_node(AST_Function, fixed, fixed); |
| | | } |
| | | if (def.recursive_refs > 0 && fixed.name instanceof AST_SymbolDefun) { |
| | | const defun_def = fixed.name.definition(); |
| | | let lambda_def = fixed.variables.get(fixed.name.name); |
| | | let name = lambda_def && lambda_def.orig[0]; |
| | | if (!(name instanceof AST_SymbolLambda)) { |
| | | name = make_node(AST_SymbolLambda, fixed.name, fixed.name); |
| | | name.scope = fixed; |
| | | fixed.name = name; |
| | | lambda_def = fixed.def_function(name); |
| | | } |
| | | walk(fixed, node => { |
| | | if (node instanceof AST_SymbolRef && node.definition() === defun_def) { |
| | | node.thedef = lambda_def; |
| | | lambda_def.references.push(node); |
| | | } |
| | | }); |
| | | } |
| | | if ( |
| | | (fixed instanceof AST_Lambda || fixed instanceof AST_Class) |
| | | && fixed.parent_scope !== nearest_scope |
| | | ) { |
| | | fixed = fixed.clone(true, compressor.get_toplevel()); |
| | | |
| | | nearest_scope.add_child_scope(fixed); |
| | | } |
| | | return fixed.optimize(compressor); |
| | | } |
| | | |
| | | // multiple uses |
| | | if (fixed) { |
| | | let replace; |
| | | |
| | | if (fixed instanceof AST_This) { |
| | | if (!(def.orig[0] instanceof AST_SymbolFunarg) |
| | | && def.references.every((ref) => |
| | | def.scope === ref.scope |
| | | )) { |
| | | replace = fixed; |
| | | } |
| | | } else { |
| | | var ev = fixed.evaluate(compressor); |
| | | if ( |
| | | ev !== fixed |
| | | && (compressor.option("unsafe_regexp") || !(ev instanceof RegExp)) |
| | | ) { |
| | | replace = make_node_from_constant(ev, fixed); |
| | | } |
| | | } |
| | | |
| | | if (replace) { |
| | | const name_length = self.size(compressor); |
| | | const replace_size = replace.size(compressor); |
| | | |
| | | let overhead = 0; |
| | | if (compressor.option("unused") && !compressor.exposed(def)) { |
| | | overhead = |
| | | (name_length + 2 + replace_size) / |
| | | (def.references.length - def.assignments); |
| | | } |
| | | |
| | | if (replace_size <= name_length + overhead) { |
| | | return replace; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return self; |
| | | } |
| | | |
| | | function inline_into_call(self, fn, compressor) { |
| | | var exp = self.expression; |
| | | var simple_args = self.args.every((arg) => !(arg instanceof AST_Expansion)); |
| | | |
| | | if (compressor.option("reduce_vars") |
| | | && fn instanceof AST_SymbolRef |
| | | && !has_annotation(self, _NOINLINE) |
| | | ) { |
| | | const fixed = fn.fixed_value(); |
| | | if (!retain_top_func(fixed, compressor)) { |
| | | fn = fixed; |
| | | } |
| | | } |
| | | |
| | | var is_func = fn instanceof AST_Lambda; |
| | | |
| | | var stat = is_func && fn.body[0]; |
| | | var is_regular_func = is_func && !fn.is_generator && !fn.async; |
| | | var can_inline = is_regular_func && compressor.option("inline") && !self.is_callee_pure(compressor); |
| | | if (can_inline && stat instanceof AST_Return) { |
| | | let returned = stat.value; |
| | | if (!returned || returned.is_constant_expression()) { |
| | | if (returned) { |
| | | returned = returned.clone(true); |
| | | } else { |
| | | returned = make_node(AST_Undefined, self); |
| | | } |
| | | const args = self.args.concat(returned); |
| | | return make_sequence(self, args).optimize(compressor); |
| | | } |
| | | |
| | | // optimize identity function |
| | | if ( |
| | | fn.argnames.length === 1 |
| | | && (fn.argnames[0] instanceof AST_SymbolFunarg) |
| | | && self.args.length < 2 |
| | | && returned instanceof AST_SymbolRef |
| | | && returned.name === fn.argnames[0].name |
| | | ) { |
| | | const replacement = |
| | | (self.args[0] || make_node(AST_Undefined)).optimize(compressor); |
| | | |
| | | let parent; |
| | | if ( |
| | | replacement instanceof AST_PropAccess |
| | | && (parent = compressor.parent()) instanceof AST_Call |
| | | && parent.expression === self |
| | | ) { |
| | | // identity function was being used to remove `this`, like in |
| | | // |
| | | // id(bag.no_this)(...) |
| | | // |
| | | // Replace with a larger but more effish (0, bag.no_this) wrapper. |
| | | |
| | | return make_sequence(self, [ |
| | | make_node(AST_Number, self, { value: 0 }), |
| | | replacement |
| | | ]); |
| | | } |
| | | // replace call with first argument or undefined if none passed |
| | | return replacement; |
| | | } |
| | | } |
| | | |
| | | if (can_inline) { |
| | | var scope, in_loop, level = -1; |
| | | let def; |
| | | let returned_value; |
| | | let nearest_scope; |
| | | if (simple_args |
| | | && !fn.uses_arguments |
| | | && !(compressor.parent() instanceof AST_Class) |
| | | && !(fn.name && fn instanceof AST_Function) |
| | | && (returned_value = can_flatten_body(stat)) |
| | | && (exp === fn |
| | | || has_annotation(self, _INLINE) |
| | | || compressor.option("unused") |
| | | && (def = exp.definition()).references.length == 1 |
| | | && !is_recursive_ref(compressor, def) |
| | | && fn.is_constant_expression(exp.scope)) |
| | | && !has_annotation(self, _PURE | _NOINLINE) |
| | | && !fn.contains_this() |
| | | && can_inject_symbols() |
| | | && (nearest_scope = compressor.find_scope()) |
| | | && !scope_encloses_variables_in_this_scope(nearest_scope, fn) |
| | | && !(function in_default_assign() { |
| | | // Due to the fact function parameters have their own scope |
| | | // which can't use `var something` in the function body within, |
| | | // we simply don't inline into DefaultAssign. |
| | | let i = 0; |
| | | let p; |
| | | while ((p = compressor.parent(i++))) { |
| | | if (p instanceof AST_DefaultAssign) return true; |
| | | if (p instanceof AST_Block) break; |
| | | } |
| | | return false; |
| | | })() |
| | | && !(scope instanceof AST_Class) |
| | | ) { |
| | | set_flag(fn, SQUEEZED); |
| | | nearest_scope.add_child_scope(fn); |
| | | return make_sequence(self, flatten_fn(returned_value)).optimize(compressor); |
| | | } |
| | | } |
| | | |
| | | if (can_inline && has_annotation(self, _INLINE)) { |
| | | set_flag(fn, SQUEEZED); |
| | | fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn); |
| | | fn = fn.clone(true); |
| | | fn.figure_out_scope({}, { |
| | | parent_scope: compressor.find_scope(), |
| | | toplevel: compressor.get_toplevel() |
| | | }); |
| | | |
| | | return make_node(AST_Call, self, { |
| | | expression: fn, |
| | | args: self.args, |
| | | }).optimize(compressor); |
| | | } |
| | | |
| | | const can_drop_this_call = is_regular_func && compressor.option("side_effects") && fn.body.every(is_empty); |
| | | if (can_drop_this_call) { |
| | | var args = self.args.concat(make_node(AST_Undefined, self)); |
| | | return make_sequence(self, args).optimize(compressor); |
| | | } |
| | | |
| | | if (compressor.option("negate_iife") |
| | | && compressor.parent() instanceof AST_SimpleStatement |
| | | && is_iife_call(self)) { |
| | | return self.negate(compressor, true); |
| | | } |
| | | |
| | | var ev = self.evaluate(compressor); |
| | | if (ev !== self) { |
| | | ev = make_node_from_constant(ev, self).optimize(compressor); |
| | | return best_of(compressor, ev, self); |
| | | } |
| | | |
| | | return self; |
| | | |
| | | function return_value(stat) { |
| | | if (!stat) return make_node(AST_Undefined, self); |
| | | if (stat instanceof AST_Return) { |
| | | if (!stat.value) return make_node(AST_Undefined, self); |
| | | return stat.value.clone(true); |
| | | } |
| | | if (stat instanceof AST_SimpleStatement) { |
| | | return make_node(AST_UnaryPrefix, stat, { |
| | | operator: "void", |
| | | expression: stat.body.clone(true) |
| | | }); |
| | | } |
| | | } |
| | | |
| | | function can_flatten_body(stat) { |
| | | var body = fn.body; |
| | | var len = body.length; |
| | | if (compressor.option("inline") < 3) { |
| | | return len == 1 && return_value(stat); |
| | | } |
| | | stat = null; |
| | | for (var i = 0; i < len; i++) { |
| | | var line = body[i]; |
| | | if (line instanceof AST_Var) { |
| | | if (stat && !line.definitions.every((var_def) => |
| | | !var_def.value |
| | | )) { |
| | | return false; |
| | | } |
| | | } else if (stat) { |
| | | return false; |
| | | } else if (!(line instanceof AST_EmptyStatement)) { |
| | | stat = line; |
| | | } |
| | | } |
| | | return return_value(stat); |
| | | } |
| | | |
| | | function can_inject_args(block_scoped, safe_to_inject) { |
| | | for (var i = 0, len = fn.argnames.length; i < len; i++) { |
| | | var arg = fn.argnames[i]; |
| | | if (arg instanceof AST_DefaultAssign) { |
| | | if (has_flag(arg.left, UNUSED)) continue; |
| | | return false; |
| | | } |
| | | if (arg instanceof AST_Destructuring) return false; |
| | | if (arg instanceof AST_Expansion) { |
| | | if (has_flag(arg.expression, UNUSED)) continue; |
| | | return false; |
| | | } |
| | | if (has_flag(arg, UNUSED)) continue; |
| | | if (!safe_to_inject |
| | | || block_scoped.has(arg.name) |
| | | || identifier_atom.has(arg.name) |
| | | || scope.conflicting_def(arg.name)) { |
| | | return false; |
| | | } |
| | | if (in_loop) in_loop.push(arg.definition()); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | function can_inject_vars(block_scoped, safe_to_inject) { |
| | | var len = fn.body.length; |
| | | for (var i = 0; i < len; i++) { |
| | | var stat = fn.body[i]; |
| | | if (!(stat instanceof AST_Var)) continue; |
| | | if (!safe_to_inject) return false; |
| | | for (var j = stat.definitions.length; --j >= 0;) { |
| | | var name = stat.definitions[j].name; |
| | | if (name instanceof AST_Destructuring |
| | | || block_scoped.has(name.name) |
| | | || identifier_atom.has(name.name) |
| | | || scope.conflicting_def(name.name)) { |
| | | return false; |
| | | } |
| | | if (in_loop) in_loop.push(name.definition()); |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | function can_inject_symbols() { |
| | | var block_scoped = new Set(); |
| | | do { |
| | | scope = compressor.parent(++level); |
| | | if (scope.is_block_scope() && scope.block_scope) { |
| | | // TODO this is sometimes undefined during compression. |
| | | // But it should always have a value! |
| | | scope.block_scope.variables.forEach(function (variable) { |
| | | block_scoped.add(variable.name); |
| | | }); |
| | | } |
| | | if (scope instanceof AST_Catch) { |
| | | // TODO can we delete? AST_Catch is a block scope. |
| | | if (scope.argname) { |
| | | block_scoped.add(scope.argname.name); |
| | | } |
| | | } else if (scope instanceof AST_IterationStatement) { |
| | | in_loop = []; |
| | | } else if (scope instanceof AST_SymbolRef) { |
| | | if (scope.fixed_value() instanceof AST_Scope) return false; |
| | | } |
| | | } while (!(scope instanceof AST_Scope)); |
| | | |
| | | var safe_to_inject = !(scope instanceof AST_Toplevel) || compressor.toplevel.vars; |
| | | var inline = compressor.option("inline"); |
| | | if (!can_inject_vars(block_scoped, inline >= 3 && safe_to_inject)) return false; |
| | | if (!can_inject_args(block_scoped, inline >= 2 && safe_to_inject)) return false; |
| | | return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop); |
| | | } |
| | | |
| | | function append_var(decls, expressions, name, value) { |
| | | var def = name.definition(); |
| | | |
| | | // Name already exists, only when a function argument had the same name |
| | | const already_appended = scope.variables.has(name.name); |
| | | if (!already_appended) { |
| | | scope.variables.set(name.name, def); |
| | | scope.enclosed.push(def); |
| | | decls.push(make_node(AST_VarDef, name, { |
| | | name: name, |
| | | value: null |
| | | })); |
| | | } |
| | | |
| | | var sym = make_node(AST_SymbolRef, name, name); |
| | | def.references.push(sym); |
| | | if (value) expressions.push(make_node(AST_Assign, self, { |
| | | operator: "=", |
| | | logical: false, |
| | | left: sym, |
| | | right: value.clone() |
| | | })); |
| | | } |
| | | |
| | | function flatten_args(decls, expressions) { |
| | | var len = fn.argnames.length; |
| | | for (var i = self.args.length; --i >= len;) { |
| | | expressions.push(self.args[i]); |
| | | } |
| | | for (i = len; --i >= 0;) { |
| | | var name = fn.argnames[i]; |
| | | var value = self.args[i]; |
| | | if (has_flag(name, UNUSED) || !name.name || scope.conflicting_def(name.name)) { |
| | | if (value) expressions.push(value); |
| | | } else { |
| | | var symbol = make_node(AST_SymbolVar, name, name); |
| | | name.definition().orig.push(symbol); |
| | | if (!value && in_loop) value = make_node(AST_Undefined, self); |
| | | append_var(decls, expressions, symbol, value); |
| | | } |
| | | } |
| | | decls.reverse(); |
| | | expressions.reverse(); |
| | | } |
| | | |
| | | function flatten_vars(decls, expressions) { |
| | | var pos = expressions.length; |
| | | for (var i = 0, lines = fn.body.length; i < lines; i++) { |
| | | var stat = fn.body[i]; |
| | | if (!(stat instanceof AST_Var)) continue; |
| | | for (var j = 0, defs = stat.definitions.length; j < defs; j++) { |
| | | var var_def = stat.definitions[j]; |
| | | var name = var_def.name; |
| | | append_var(decls, expressions, name, var_def.value); |
| | | if (in_loop && fn.argnames.every((argname) => |
| | | argname.name != name.name |
| | | )) { |
| | | var def = fn.variables.get(name.name); |
| | | var sym = make_node(AST_SymbolRef, name, name); |
| | | def.references.push(sym); |
| | | expressions.splice(pos++, 0, make_node(AST_Assign, var_def, { |
| | | operator: "=", |
| | | logical: false, |
| | | left: sym, |
| | | right: make_node(AST_Undefined, name) |
| | | })); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | function flatten_fn(returned_value) { |
| | | var decls = []; |
| | | var expressions = []; |
| | | flatten_args(decls, expressions); |
| | | flatten_vars(decls, expressions); |
| | | expressions.push(returned_value); |
| | | |
| | | if (decls.length) { |
| | | const i = scope.body.indexOf(compressor.parent(level - 1)) + 1; |
| | | scope.body.splice(i, 0, make_node(AST_Var, fn, { |
| | | definitions: decls |
| | | })); |
| | | } |
| | | |
| | | return expressions.map(exp => exp.clone(true)); |
| | | } |
| | | } |
| | | |
| | | /*********************************************************************** |
| | | |
| | | A JavaScript tokenizer / parser / beautifier / compressor. |
| | | https://github.com/mishoo/UglifyJS2 |
| | | |
| | | -------------------------------- (C) --------------------------------- |
| | | |
| | | Author: Mihai Bazon |
| | | <mihai.bazon@gmail.com> |
| | | http://mihai.bazon.net/blog |
| | | |
| | | Distributed under the BSD license: |
| | | |
| | | Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> |
| | | |
| | | Redistribution and use in source and binary forms, with or without |
| | | modification, are permitted provided that the following conditions |
| | | are met: |
| | | |
| | | * Redistributions of source code must retain the above |
| | | copyright notice, this list of conditions and the following |
| | | disclaimer. |
| | | |
| | | * Redistributions in binary form must reproduce the above |
| | | copyright notice, this list of conditions and the following |
| | | disclaimer in the documentation and/or other materials |
| | | provided with the distribution. |
| | | |
| | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY |
| | | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| | | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE |
| | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, |
| | | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| | | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| | | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR |
| | | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF |
| | | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| | | SUCH DAMAGE. |
| | | |
| | | ***********************************************************************/ |
| | | |
| | | class Compressor extends TreeWalker { |
| | | constructor(options, { false_by_default = false, mangle_options = false }) { |
| | | super(); |
| | |
| | | return orig.length == 1 && orig[0] instanceof AST_SymbolLambda; |
| | | }); |
| | | |
| | | function find_scope(tw) { |
| | | for (let i = 0;;i++) { |
| | | const p = tw.parent(i); |
| | | if (p instanceof AST_Toplevel) return p; |
| | | if (p instanceof AST_Lambda) return p; |
| | | if (p.block_scope) return p.block_scope; |
| | | } |
| | | } |
| | | |
| | | function find_variable(compressor, name) { |
| | | var scope, i = 0; |
| | | while (scope = compressor.parent(i++)) { |
| | |
| | | } |
| | | } |
| | | return scope.find_variable(name); |
| | | } |
| | | |
| | | function is_empty(thing) { |
| | | if (thing === null) return true; |
| | | if (thing instanceof AST_EmptyStatement) return true; |
| | | if (thing instanceof AST_BlockStatement) return thing.body.length == 0; |
| | | return false; |
| | | } |
| | | |
| | | var global_names = makePredicate("Array Boolean clearInterval clearTimeout console Date decodeURI decodeURIComponent encodeURI encodeURIComponent Error escape eval EvalError Function isFinite isNaN JSON Math Number parseFloat parseInt RangeError ReferenceError RegExp Object setInterval setTimeout String SyntaxError TypeError unescape URIError"); |
| | |
| | | const defs = new Map(); |
| | | const assignments = []; |
| | | value.properties.forEach(({ key, value }) => { |
| | | const scope = find_scope(hoister); |
| | | const scope = hoister.find_scope(); |
| | | const symbol = self.create_symbol(sym.CTOR, { |
| | | source: sym, |
| | | scope, |
| | |
| | | // that way the next micro-optimization will merge them. |
| | | // ** bail micro-optimization if not a simple switch case with breaks |
| | | if (body.every((branch, i) => |
| | | (branch === default_or_exact || !branch.expression.has_side_effects(compressor)) |
| | | (branch === default_or_exact || branch.expression instanceof AST_Constant) |
| | | && (branch.body.length === 0 || aborts(branch) || body.length - 1 === i)) |
| | | ) { |
| | | for (let i = 0; i < body.length; i++) { |
| | |
| | | |
| | | |
| | | // Prune side-effect free branches that fall into default. |
| | | if (default_or_exact) { |
| | | DEFAULT: if (default_or_exact) { |
| | | let default_index = body.indexOf(default_or_exact); |
| | | let default_body_index = default_index; |
| | | for (; default_body_index < body.length - 1; default_body_index++) { |
| | | if (!is_inert_body(body[default_body_index])) break; |
| | | } |
| | | if (default_body_index < body.length - 1) { |
| | | break DEFAULT; |
| | | } |
| | | |
| | | let side_effect_index = body.length - 1; |
| | | for (; side_effect_index >= 0; side_effect_index--) { |
| | | let branch = body[side_effect_index]; |
| | |
| | | return self; |
| | | }); |
| | | |
| | | // TODO this only works with AST_Defun, shouldn't it work for other ways of defining functions? |
| | | function retain_top_func(fn, compressor) { |
| | | return compressor.top_retain |
| | | && fn instanceof AST_Defun |
| | | && has_flag(fn, TOP) |
| | | && fn.name |
| | | && compressor.top_retain(fn.name); |
| | | } |
| | | |
| | | def_optimize(AST_Call, function(self, compressor) { |
| | | var exp = self.expression; |
| | | var fn = exp; |
| | |
| | | } |
| | | } |
| | | |
| | | var stat = is_func && fn.body[0]; |
| | | var is_regular_func = is_func && !fn.is_generator && !fn.async; |
| | | var can_inline = is_regular_func && compressor.option("inline") && !self.is_callee_pure(compressor); |
| | | if (can_inline && stat instanceof AST_Return) { |
| | | let returned = stat.value; |
| | | if (!returned || returned.is_constant_expression()) { |
| | | if (returned) { |
| | | returned = returned.clone(true); |
| | | } else { |
| | | returned = make_node(AST_Undefined, self); |
| | | } |
| | | const args = self.args.concat(returned); |
| | | return make_sequence(self, args).optimize(compressor); |
| | | } |
| | | |
| | | // optimize identity function |
| | | if ( |
| | | fn.argnames.length === 1 |
| | | && (fn.argnames[0] instanceof AST_SymbolFunarg) |
| | | && self.args.length < 2 |
| | | && returned instanceof AST_SymbolRef |
| | | && returned.name === fn.argnames[0].name |
| | | ) { |
| | | const replacement = |
| | | (self.args[0] || make_node(AST_Undefined)).optimize(compressor); |
| | | |
| | | let parent; |
| | | if ( |
| | | replacement instanceof AST_PropAccess |
| | | && (parent = compressor.parent()) instanceof AST_Call |
| | | && parent.expression === self |
| | | ) { |
| | | // identity function was being used to remove `this`, like in |
| | | // |
| | | // id(bag.no_this)(...) |
| | | // |
| | | // Replace with a larger but more effish (0, bag.no_this) wrapper. |
| | | |
| | | return make_sequence(self, [ |
| | | make_node(AST_Number, self, { value: 0 }), |
| | | replacement |
| | | ]); |
| | | } |
| | | // replace call with first argument or undefined if none passed |
| | | return replacement; |
| | | } |
| | | } |
| | | |
| | | if (can_inline) { |
| | | var scope, in_loop, level = -1; |
| | | let def; |
| | | let returned_value; |
| | | let nearest_scope; |
| | | if (simple_args |
| | | && !fn.uses_arguments |
| | | && !(compressor.parent() instanceof AST_Class) |
| | | && !(fn.name && fn instanceof AST_Function) |
| | | && (returned_value = can_flatten_body(stat)) |
| | | && (exp === fn |
| | | || has_annotation(self, _INLINE) |
| | | || compressor.option("unused") |
| | | && (def = exp.definition()).references.length == 1 |
| | | && !is_recursive_ref(compressor, def) |
| | | && fn.is_constant_expression(exp.scope)) |
| | | && !has_annotation(self, _PURE | _NOINLINE) |
| | | && !fn.contains_this() |
| | | && can_inject_symbols() |
| | | && (nearest_scope = find_scope(compressor)) |
| | | && !scope_encloses_variables_in_this_scope(nearest_scope, fn) |
| | | && !(function in_default_assign() { |
| | | // Due to the fact function parameters have their own scope |
| | | // which can't use `var something` in the function body within, |
| | | // we simply don't inline into DefaultAssign. |
| | | let i = 0; |
| | | let p; |
| | | while ((p = compressor.parent(i++))) { |
| | | if (p instanceof AST_DefaultAssign) return true; |
| | | if (p instanceof AST_Block) break; |
| | | } |
| | | return false; |
| | | })() |
| | | && !(scope instanceof AST_Class) |
| | | ) { |
| | | set_flag(fn, SQUEEZED); |
| | | nearest_scope.add_child_scope(fn); |
| | | return make_sequence(self, flatten_fn(returned_value)).optimize(compressor); |
| | | } |
| | | } |
| | | |
| | | if (can_inline && has_annotation(self, _INLINE)) { |
| | | set_flag(fn, SQUEEZED); |
| | | fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn); |
| | | fn = fn.clone(true); |
| | | fn.figure_out_scope({}, { |
| | | parent_scope: find_scope(compressor), |
| | | toplevel: compressor.get_toplevel() |
| | | }); |
| | | |
| | | return make_node(AST_Call, self, { |
| | | expression: fn, |
| | | args: self.args, |
| | | }).optimize(compressor); |
| | | } |
| | | |
| | | const can_drop_this_call = is_regular_func && compressor.option("side_effects") && fn.body.every(is_empty); |
| | | if (can_drop_this_call) { |
| | | var args = self.args.concat(make_node(AST_Undefined, self)); |
| | | return make_sequence(self, args).optimize(compressor); |
| | | } |
| | | |
| | | if (compressor.option("negate_iife") |
| | | && compressor.parent() instanceof AST_SimpleStatement |
| | | && is_iife_call(self)) { |
| | | return self.negate(compressor, true); |
| | | } |
| | | |
| | | var ev = self.evaluate(compressor); |
| | | if (ev !== self) { |
| | | ev = make_node_from_constant(ev, self).optimize(compressor); |
| | | return best_of(compressor, ev, self); |
| | | } |
| | | |
| | | return self; |
| | | |
| | | function return_value(stat) { |
| | | if (!stat) return make_node(AST_Undefined, self); |
| | | if (stat instanceof AST_Return) { |
| | | if (!stat.value) return make_node(AST_Undefined, self); |
| | | return stat.value.clone(true); |
| | | } |
| | | if (stat instanceof AST_SimpleStatement) { |
| | | return make_node(AST_UnaryPrefix, stat, { |
| | | operator: "void", |
| | | expression: stat.body.clone(true) |
| | | }); |
| | | } |
| | | } |
| | | |
| | | function can_flatten_body(stat) { |
| | | var body = fn.body; |
| | | var len = body.length; |
| | | if (compressor.option("inline") < 3) { |
| | | return len == 1 && return_value(stat); |
| | | } |
| | | stat = null; |
| | | for (var i = 0; i < len; i++) { |
| | | var line = body[i]; |
| | | if (line instanceof AST_Var) { |
| | | if (stat && !line.definitions.every((var_def) => |
| | | !var_def.value |
| | | )) { |
| | | return false; |
| | | } |
| | | } else if (stat) { |
| | | return false; |
| | | } else if (!(line instanceof AST_EmptyStatement)) { |
| | | stat = line; |
| | | } |
| | | } |
| | | return return_value(stat); |
| | | } |
| | | |
| | | function can_inject_args(block_scoped, safe_to_inject) { |
| | | for (var i = 0, len = fn.argnames.length; i < len; i++) { |
| | | var arg = fn.argnames[i]; |
| | | if (arg instanceof AST_DefaultAssign) { |
| | | if (has_flag(arg.left, UNUSED)) continue; |
| | | return false; |
| | | } |
| | | if (arg instanceof AST_Destructuring) return false; |
| | | if (arg instanceof AST_Expansion) { |
| | | if (has_flag(arg.expression, UNUSED)) continue; |
| | | return false; |
| | | } |
| | | if (has_flag(arg, UNUSED)) continue; |
| | | if (!safe_to_inject |
| | | || block_scoped.has(arg.name) |
| | | || identifier_atom.has(arg.name) |
| | | || scope.conflicting_def(arg.name)) { |
| | | return false; |
| | | } |
| | | if (in_loop) in_loop.push(arg.definition()); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | function can_inject_vars(block_scoped, safe_to_inject) { |
| | | var len = fn.body.length; |
| | | for (var i = 0; i < len; i++) { |
| | | var stat = fn.body[i]; |
| | | if (!(stat instanceof AST_Var)) continue; |
| | | if (!safe_to_inject) return false; |
| | | for (var j = stat.definitions.length; --j >= 0;) { |
| | | var name = stat.definitions[j].name; |
| | | if (name instanceof AST_Destructuring |
| | | || block_scoped.has(name.name) |
| | | || identifier_atom.has(name.name) |
| | | || scope.conflicting_def(name.name)) { |
| | | return false; |
| | | } |
| | | if (in_loop) in_loop.push(name.definition()); |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | function can_inject_symbols() { |
| | | var block_scoped = new Set(); |
| | | do { |
| | | scope = compressor.parent(++level); |
| | | if (scope.is_block_scope() && scope.block_scope) { |
| | | // TODO this is sometimes undefined during compression. |
| | | // But it should always have a value! |
| | | scope.block_scope.variables.forEach(function (variable) { |
| | | block_scoped.add(variable.name); |
| | | }); |
| | | } |
| | | if (scope instanceof AST_Catch) { |
| | | // TODO can we delete? AST_Catch is a block scope. |
| | | if (scope.argname) { |
| | | block_scoped.add(scope.argname.name); |
| | | } |
| | | } else if (scope instanceof AST_IterationStatement) { |
| | | in_loop = []; |
| | | } else if (scope instanceof AST_SymbolRef) { |
| | | if (scope.fixed_value() instanceof AST_Scope) return false; |
| | | } |
| | | } while (!(scope instanceof AST_Scope)); |
| | | |
| | | var safe_to_inject = !(scope instanceof AST_Toplevel) || compressor.toplevel.vars; |
| | | var inline = compressor.option("inline"); |
| | | if (!can_inject_vars(block_scoped, inline >= 3 && safe_to_inject)) return false; |
| | | if (!can_inject_args(block_scoped, inline >= 2 && safe_to_inject)) return false; |
| | | return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop); |
| | | } |
| | | |
| | | function append_var(decls, expressions, name, value) { |
| | | var def = name.definition(); |
| | | |
| | | // Name already exists, only when a function argument had the same name |
| | | const already_appended = scope.variables.has(name.name); |
| | | if (!already_appended) { |
| | | scope.variables.set(name.name, def); |
| | | scope.enclosed.push(def); |
| | | decls.push(make_node(AST_VarDef, name, { |
| | | name: name, |
| | | value: null |
| | | })); |
| | | } |
| | | |
| | | var sym = make_node(AST_SymbolRef, name, name); |
| | | def.references.push(sym); |
| | | if (value) expressions.push(make_node(AST_Assign, self, { |
| | | operator: "=", |
| | | logical: false, |
| | | left: sym, |
| | | right: value.clone() |
| | | })); |
| | | } |
| | | |
| | | function flatten_args(decls, expressions) { |
| | | var len = fn.argnames.length; |
| | | for (var i = self.args.length; --i >= len;) { |
| | | expressions.push(self.args[i]); |
| | | } |
| | | for (i = len; --i >= 0;) { |
| | | var name = fn.argnames[i]; |
| | | var value = self.args[i]; |
| | | if (has_flag(name, UNUSED) || !name.name || scope.conflicting_def(name.name)) { |
| | | if (value) expressions.push(value); |
| | | } else { |
| | | var symbol = make_node(AST_SymbolVar, name, name); |
| | | name.definition().orig.push(symbol); |
| | | if (!value && in_loop) value = make_node(AST_Undefined, self); |
| | | append_var(decls, expressions, symbol, value); |
| | | } |
| | | } |
| | | decls.reverse(); |
| | | expressions.reverse(); |
| | | } |
| | | |
| | | function flatten_vars(decls, expressions) { |
| | | var pos = expressions.length; |
| | | for (var i = 0, lines = fn.body.length; i < lines; i++) { |
| | | var stat = fn.body[i]; |
| | | if (!(stat instanceof AST_Var)) continue; |
| | | for (var j = 0, defs = stat.definitions.length; j < defs; j++) { |
| | | var var_def = stat.definitions[j]; |
| | | var name = var_def.name; |
| | | append_var(decls, expressions, name, var_def.value); |
| | | if (in_loop && fn.argnames.every((argname) => |
| | | argname.name != name.name |
| | | )) { |
| | | var def = fn.variables.get(name.name); |
| | | var sym = make_node(AST_SymbolRef, name, name); |
| | | def.references.push(sym); |
| | | expressions.splice(pos++, 0, make_node(AST_Assign, var_def, { |
| | | operator: "=", |
| | | logical: false, |
| | | left: sym, |
| | | right: make_node(AST_Undefined, name) |
| | | })); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | function flatten_fn(returned_value) { |
| | | var decls = []; |
| | | var expressions = []; |
| | | flatten_args(decls, expressions); |
| | | flatten_vars(decls, expressions); |
| | | expressions.push(returned_value); |
| | | |
| | | if (decls.length) { |
| | | const i = scope.body.indexOf(compressor.parent(level - 1)) + 1; |
| | | scope.body.splice(i, 0, make_node(AST_Var, fn, { |
| | | definitions: decls |
| | | })); |
| | | } |
| | | |
| | | return expressions.map(exp => exp.clone(true)); |
| | | } |
| | | return inline_into_call(self, fn, compressor); |
| | | }); |
| | | |
| | | def_optimize(AST_New, function(self, compressor) { |
| | |
| | | |
| | | def_optimize(AST_UnaryPrefix, function(self, compressor) { |
| | | var e = self.expression; |
| | | if (self.operator == "delete" |
| | | && !(e instanceof AST_SymbolRef |
| | | || e instanceof AST_PropAccess |
| | | || is_identifier_atom(e))) { |
| | | if (e instanceof AST_Sequence) { |
| | | const exprs = e.expressions.slice(); |
| | | exprs.push(make_node(AST_True, self)); |
| | | return make_sequence(self, exprs).optimize(compressor); |
| | | } |
| | | return make_sequence(self, [ e, make_node(AST_True, self) ]).optimize(compressor); |
| | | if ( |
| | | self.operator == "delete" && |
| | | !( |
| | | e instanceof AST_SymbolRef || |
| | | e instanceof AST_PropAccess || |
| | | e instanceof AST_Chain || |
| | | is_identifier_atom(e) |
| | | ) |
| | | ) { |
| | | return make_sequence(self, [e, make_node(AST_True, self)]).optimize(compressor); |
| | | } |
| | | var seq = self.lift_sequences(compressor); |
| | | if (seq !== self) { |
| | |
| | | return self; |
| | | }); |
| | | |
| | | function within_array_or_object_literal(compressor) { |
| | | var node, level = 0; |
| | | while (node = compressor.parent(level++)) { |
| | | if (node instanceof AST_Statement) return false; |
| | | if (node instanceof AST_Array |
| | | || node instanceof AST_ObjectKeyVal |
| | | || node instanceof AST_Object) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | def_optimize(AST_SymbolRef, function(self, compressor) { |
| | | if ( |
| | | !compressor.option("ie8") |
| | |
| | | |
| | | const parent = compressor.parent(); |
| | | if (compressor.option("reduce_vars") && is_lhs(self, parent) !== self) { |
| | | const def = self.definition(); |
| | | const nearest_scope = find_scope(compressor); |
| | | if (compressor.top_retain && def.global && compressor.top_retain(def)) { |
| | | def.fixed = false; |
| | | def.single_use = false; |
| | | return self; |
| | | } |
| | | |
| | | let fixed = self.fixed_value(); |
| | | let single_use = def.single_use |
| | | && !(parent instanceof AST_Call |
| | | && (parent.is_callee_pure(compressor)) |
| | | || has_annotation(parent, _NOINLINE)) |
| | | && !(parent instanceof AST_Export |
| | | && fixed instanceof AST_Lambda |
| | | && fixed.name); |
| | | |
| | | if (single_use && fixed instanceof AST_Node) { |
| | | single_use = |
| | | !fixed.has_side_effects(compressor) |
| | | && !fixed.may_throw(compressor); |
| | | } |
| | | |
| | | if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) { |
| | | if (retain_top_func(fixed, compressor)) { |
| | | single_use = false; |
| | | } else if (def.scope !== self.scope |
| | | && (def.escaped == 1 |
| | | || has_flag(fixed, INLINED) |
| | | || within_array_or_object_literal(compressor) |
| | | || !compressor.option("reduce_funcs"))) { |
| | | single_use = false; |
| | | } else if (is_recursive_ref(compressor, def)) { |
| | | single_use = false; |
| | | } else if (def.scope !== self.scope || def.orig[0] instanceof AST_SymbolFunarg) { |
| | | single_use = fixed.is_constant_expression(self.scope); |
| | | if (single_use == "f") { |
| | | var scope = self.scope; |
| | | do { |
| | | if (scope instanceof AST_Defun || is_func_expr(scope)) { |
| | | set_flag(scope, INLINED); |
| | | } |
| | | } while (scope = scope.parent_scope); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (single_use && fixed instanceof AST_Lambda) { |
| | | single_use = |
| | | def.scope === self.scope |
| | | && !scope_encloses_variables_in_this_scope(nearest_scope, fixed) |
| | | || parent instanceof AST_Call |
| | | && parent.expression === self |
| | | && !scope_encloses_variables_in_this_scope(nearest_scope, fixed) |
| | | && !(fixed.name && fixed.name.definition().recursive_refs > 0); |
| | | } |
| | | |
| | | if (single_use && fixed) { |
| | | if (fixed instanceof AST_DefClass) { |
| | | set_flag(fixed, SQUEEZED); |
| | | fixed = make_node(AST_ClassExpression, fixed, fixed); |
| | | } |
| | | if (fixed instanceof AST_Defun) { |
| | | set_flag(fixed, SQUEEZED); |
| | | fixed = make_node(AST_Function, fixed, fixed); |
| | | } |
| | | if (def.recursive_refs > 0 && fixed.name instanceof AST_SymbolDefun) { |
| | | const defun_def = fixed.name.definition(); |
| | | let lambda_def = fixed.variables.get(fixed.name.name); |
| | | let name = lambda_def && lambda_def.orig[0]; |
| | | if (!(name instanceof AST_SymbolLambda)) { |
| | | name = make_node(AST_SymbolLambda, fixed.name, fixed.name); |
| | | name.scope = fixed; |
| | | fixed.name = name; |
| | | lambda_def = fixed.def_function(name); |
| | | } |
| | | walk(fixed, node => { |
| | | if (node instanceof AST_SymbolRef && node.definition() === defun_def) { |
| | | node.thedef = lambda_def; |
| | | lambda_def.references.push(node); |
| | | } |
| | | }); |
| | | } |
| | | if ( |
| | | (fixed instanceof AST_Lambda || fixed instanceof AST_Class) |
| | | && fixed.parent_scope !== nearest_scope |
| | | ) { |
| | | fixed = fixed.clone(true, compressor.get_toplevel()); |
| | | |
| | | nearest_scope.add_child_scope(fixed); |
| | | } |
| | | return fixed.optimize(compressor); |
| | | } |
| | | |
| | | // multiple uses |
| | | if (fixed) { |
| | | let replace; |
| | | |
| | | if (fixed instanceof AST_This) { |
| | | if (!(def.orig[0] instanceof AST_SymbolFunarg) |
| | | && def.references.every((ref) => |
| | | def.scope === ref.scope |
| | | )) { |
| | | replace = fixed; |
| | | } |
| | | } else { |
| | | var ev = fixed.evaluate(compressor); |
| | | if ( |
| | | ev !== fixed |
| | | && (compressor.option("unsafe_regexp") || !(ev instanceof RegExp)) |
| | | ) { |
| | | replace = make_node_from_constant(ev, fixed); |
| | | } |
| | | } |
| | | |
| | | if (replace) { |
| | | const name_length = self.size(compressor); |
| | | const replace_size = replace.size(compressor); |
| | | |
| | | let overhead = 0; |
| | | if (compressor.option("unused") && !compressor.exposed(def)) { |
| | | overhead = |
| | | (name_length + 2 + replace_size) / |
| | | (def.references.length - def.assignments); |
| | | } |
| | | |
| | | if (replace_size <= name_length + overhead) { |
| | | return replace; |
| | | } |
| | | } |
| | | } |
| | | return inline_into_symbolref(self, compressor); |
| | | } else { |
| | | return self; |
| | | } |
| | | |
| | | return self; |
| | | }); |
| | | |
| | | function scope_encloses_variables_in_this_scope(scope, pulled_scope) { |
| | | for (const enclosed of pulled_scope.enclosed) { |
| | | if (pulled_scope.variables.has(enclosed.name)) { |
| | | continue; |
| | | } |
| | | const looked_up = scope.find_variable(enclosed.name); |
| | | if (looked_up) { |
| | | if (looked_up === enclosed) continue; |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | function is_atomic(lhs, self) { |
| | | return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE; |
| | |
| | | return self; |
| | | }); |
| | | |
| | | function is_reachable(self, defs) { |
| | | const find_ref = node => { |
| | | if (node instanceof AST_SymbolRef && member(node.definition(), defs)) { |
| | | return walk_abort; |
| | | } |
| | | }; |
| | | |
| | | return walk_parent(self, (node, info) => { |
| | | if (node instanceof AST_Scope && node !== self) { |
| | | var parent = info.parent(); |
| | | |
| | | if (parent instanceof AST_Call && parent.expression === node) return; |
| | | |
| | | if (walk(node, find_ref)) return walk_abort; |
| | | |
| | | return true; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | const ASSIGN_OPS = makePredicate("+ - / * % >> << >>> | ^ &"); |
| | | const ASSIGN_OPS_COMMUTATIVE = makePredicate("* | ^ &"); |
| | | def_optimize(AST_Assign, function(self, compressor) { |
| | |
| | | } |
| | | |
| | | var def; |
| | | // x = x ---> x |
| | | if ( |
| | | self.operator === "=" |
| | | && self.left instanceof AST_SymbolRef |
| | | && self.left.name !== "arguments" |
| | | && !(def = self.left.definition()).undeclared |
| | | && self.right.equivalent_to(self.left) |
| | | ) { |
| | | return self.right; |
| | | } |
| | | |
| | | if (compressor.option("dead_code") |
| | | && self.left instanceof AST_SymbolRef |
| | | && (def = self.left.definition()).scope === compressor.find_parent(AST_Lambda)) { |
| | |
| | | || parent instanceof AST_Sequence && parent.tail_node() === node); |
| | | } |
| | | self = self.lift_sequences(compressor); |
| | | |
| | | if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) { |
| | | // x = expr1 OP expr2 |
| | | if (self.right.left instanceof AST_SymbolRef |
| | |
| | | }); |
| | | |
| | | def_optimize(AST_Chain, function (self, compressor) { |
| | | if (is_nullish(self.expression, compressor)) return make_node(AST_Undefined, self); |
| | | if (is_nullish(self.expression, compressor)) { |
| | | let parent = compressor.parent(); |
| | | // It's valid to delete a nullish optional chain, but if we optimized |
| | | // this to `delete undefined` then it would appear to be a syntax error |
| | | // when we try to optimize the delete. Thankfully, `delete 0` is fine. |
| | | if (parent instanceof AST_UnaryPrefix && parent.operator === "delete") { |
| | | return make_node_from_constant(0, self); |
| | | } |
| | | return make_node(AST_Undefined, self); |
| | | } |
| | | return self; |
| | | }); |
| | | |
| | |
| | | "applyElement", |
| | | "arc", |
| | | "arcTo", |
| | | "architecture", |
| | | "archive", |
| | | "areas", |
| | | "arguments", |
| | |
| | | "bindTexture", |
| | | "bindTransformFeedback", |
| | | "bindVertexArray", |
| | | "bitness", |
| | | "blendColor", |
| | | "blendEquation", |
| | | "blendEquationSeparate", |
| | |
| | | "boxDecorationBreak", |
| | | "boxShadow", |
| | | "boxSizing", |
| | | "brand", |
| | | "brands", |
| | | "break-after", |
| | | "break-before", |
| | | "break-inside", |
| | |
| | | "fround", |
| | | "fullPath", |
| | | "fullScreen", |
| | | "fullVersionList", |
| | | "fullscreen", |
| | | "fullscreenElement", |
| | | "fullscreenEnabled", |
| | |
| | | "getFrequencyResponse", |
| | | "getFullYear", |
| | | "getGamepads", |
| | | "getHighEntropyValues", |
| | | "getHitTestResults", |
| | | "getHitTestResultsForTransientInput", |
| | | "getHours", |
| | |
| | | "mix-blend-mode", |
| | | "mixBlendMode", |
| | | "mm", |
| | | "mobile", |
| | | "mode", |
| | | "model", |
| | | "modify", |
| | | "mount", |
| | | "move", |
| | |
| | | "placeItems", |
| | | "placeSelf", |
| | | "placeholder", |
| | | "platformVersion", |
| | | "platform", |
| | | "platforms", |
| | | "play", |
| | |
| | | "user-select", |
| | | "userActivation", |
| | | "userAgent", |
| | | "userAgentData", |
| | | "userChoice", |
| | | "userHandle", |
| | | "userHint", |
| | |
| | | "wordSpacing", |
| | | "wordWrap", |
| | | "workerStart", |
| | | "wow64", |
| | | "wrap", |
| | | "wrapKey", |
| | | "writable", |
| | |
| | | var global_ref = typeof global === "object" ? global : self; |
| | | |
| | | new_globals.forEach(function (new_global) { |
| | | objects[new_global] = global_ref[new_global] || new Function(); |
| | | objects[new_global] = global_ref[new_global] || function() {}; |
| | | }); |
| | | |
| | | [ |
| | |
| | | }; |
| | | } |
| | | |
| | | async function minify(files, options) { |
| | | function log_input(files, options, fs, debug_folder) { |
| | | if (!(fs && fs.writeFileSync && fs.mkdirSync)) { |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | fs.mkdirSync(debug_folder); |
| | | } catch (e) { |
| | | if (e.code !== "EEXIST") throw e; |
| | | } |
| | | |
| | | const log_path = `${debug_folder}/terser-debug-${(Math.random() * 9999999) | 0}.log`; |
| | | |
| | | options = options || {}; |
| | | |
| | | const options_str = JSON.stringify(options, (_key, thing) => { |
| | | if (typeof thing === "function") return "[Function " + thing.toString() + "]"; |
| | | if (thing instanceof RegExp) return "[RegExp " + thing.toString() + "]"; |
| | | return thing; |
| | | }, 4); |
| | | |
| | | const files_str = (file) => { |
| | | if (typeof file === "object" && options.parse && options.parse.spidermonkey) { |
| | | return JSON.stringify(file, null, 2); |
| | | } else if (typeof file === "object") { |
| | | return Object.keys(file) |
| | | .map((key) => key + ": " + files_str(file[key])) |
| | | .join("\n\n"); |
| | | } else if (typeof file === "string") { |
| | | return "```\n" + file + "\n```"; |
| | | } else { |
| | | return file; // What do? |
| | | } |
| | | }; |
| | | |
| | | fs.writeFileSync(log_path, "Options: \n" + options_str + "\n\nInput files:\n\n" + files_str(files) + "\n"); |
| | | } |
| | | |
| | | async function minify(files, options, _fs_module) { |
| | | if ( |
| | | _fs_module |
| | | && typeof process === "object" |
| | | && process.env |
| | | && typeof process.env.TERSER_DEBUG_DIR === "string" |
| | | ) { |
| | | log_input(files, options, _fs_module, process.env.TERSER_DEBUG_DIR); |
| | | } |
| | | |
| | | options = defaults(options, { |
| | | compress: {}, |
| | | ecma: undefined, |
| | |
| | | warnings: false, |
| | | wrap: false, |
| | | }, true); |
| | | |
| | | var timings = options.timings && { |
| | | start: Date.now() |
| | | }; |
| | |
| | | |
| | | let result; |
| | | try { |
| | | result = await minify(files, options); |
| | | result = await minify(files, options, fs); |
| | | } catch (ex) { |
| | | if (ex.name == "SyntaxError") { |
| | | print_error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col); |
| | |
| | | }, 2)); |
| | | } else if (program.output == "spidermonkey") { |
| | | try { |
| | | const minified = await minify(result.code, { |
| | | compress: false, |
| | | mangle: false, |
| | | format: { |
| | | ast: true, |
| | | code: false |
| | | } |
| | | }); |
| | | const minified = await minify( |
| | | result.code, |
| | | { |
| | | compress: false, |
| | | mangle: false, |
| | | format: { |
| | | ast: true, |
| | | code: false |
| | | } |
| | | }, |
| | | fs |
| | | ); |
| | | console.log(JSON.stringify(minified.ast.to_mozilla_ast(), null, 2)); |
| | | } catch (ex) { |
| | | fatal(ex); |