From 0f2a2ee11e651a547f97a7255907ceb67ca3eac3 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 20 Feb 2010 20:00:07 -0500 Subject: [PATCH] Improving soaks to avoid uncessesary parentheses. --- lib/nodes.js | 18 +++++++++++++----- src/nodes.coffee | 20 +++++++++++++------- test/test_existence.coffee | 13 +++++++++++++ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/lib/nodes.js b/lib/nodes.js index c05f9cdb..2f2fc7a8 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -99,6 +99,9 @@ var closure, top; this.options = merge(o || {}); this.indent = o.indent; + if (!(this.operation_sensitive())) { + del(this.options, 'operation'); + } top = this.top_sensitive() ? this.options.top : del(this.options, 'top'); closure = this.is_statement() && !this.is_statement_only() && !top && !this.options.returns && !(this instanceof CommentNode) && !this.contains(function(node) { return node.is_statement_only(); @@ -164,6 +167,9 @@ Node.prototype.top_sensitive = function top_sensitive() { return false; }; + Node.prototype.operation_sensitive = function operation_sensitive() { + return false; + }; // A collection of nodes, each one representing an expression. Expressions = (exports.Expressions = inherit(Node, { type: 'Expressions', @@ -326,6 +332,9 @@ this.children.push(prop); return this; }, + operation_sensitive: function operation_sensitive() { + return true; + }, has_properties: function has_properties() { return !!this.properties.length; }, @@ -349,9 +358,10 @@ return this.base.is_statement && this.base.is_statement() && !this.has_properties(); }, compile_node: function compile_node(o) { - var _a, _b, baseline, code, only, part, parts, prop, props, soaked, temp; + var _a, _b, baseline, code, only, op, part, parts, prop, props, soaked, temp; soaked = false; only = del(o, 'only_first'); + op = del(o, 'operation'); props = only ? this.properties.slice(0, this.properties.length - 1) : this.properties; baseline = this.base.compile(o); if (this.base instanceof ObjectNode && this.has_properties()) { @@ -378,10 +388,7 @@ this.last = parts[parts.length - 1]; this.source = parts.length > 1 ? parts.slice(0, (parts.length - 1)).join('') : null; code = parts.join('').replace(/\)\(\)\)/, '()))'); - if (!(soaked)) { - return code; - } - return '(' + code + ')'; + return op && soaked ? '(' + code + ')' : code; } })); // Pass through CoffeeScript comments into JavaScript comments at the @@ -943,6 +950,7 @@ return this.CHAINABLE.indexOf(this.operator) >= 0; }, compile_node: function compile_node(o) { + o.operation = true; if (this.is_chainable() && this.first.unwrap() instanceof OpNode && this.first.unwrap().is_chainable()) { return this.compile_chain(o); } diff --git a/src/nodes.coffee b/src/nodes.coffee index c4a7131c..a7d5a026 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -63,6 +63,7 @@ Node: exports.Node: -> Node::compile: (o) -> @options: merge o or {} @indent: o.indent + del @options, 'operation' unless @operation_sensitive() top: if @top_sensitive() then @options.top else del @options, 'top' closure: @is_statement() and not @is_statement_only() and not top and not @options.returns and not (this instanceof CommentNode) and @@ -95,11 +96,12 @@ Node::toString: (idt) -> '\n' + idt + @type + (child.toString(idt + TAB) for child in @children).join('') # Default implementations of the common node methods. -Node::unwrap: -> this -Node::children: [] -Node::is_statement: -> false -Node::is_statement_only: -> false -Node::top_sensitive: -> false +Node::unwrap: -> this +Node::children: [] +Node::is_statement: -> false +Node::is_statement_only: -> false +Node::top_sensitive: -> false +Node::operation_sensitive: -> false # A collection of nodes, each one representing an expression. Expressions: exports.Expressions: inherit Node, { @@ -239,6 +241,9 @@ ValueNode: exports.ValueNode: inherit Node, { @children.push(prop) this + operation_sensitive: -> + true + has_properties: -> !!@properties.length @@ -264,6 +269,7 @@ ValueNode: exports.ValueNode: inherit Node, { compile_node: (o) -> soaked: false only: del(o, 'only_first') + op: del(o, 'operation') props: if only then @properties[0...@properties.length - 1] else @properties baseline: @base.compile o baseline: '(' + baseline + ')' if @base instanceof ObjectNode and @has_properties() @@ -285,8 +291,7 @@ ValueNode: exports.ValueNode: inherit Node, { @last: parts[parts.length - 1] @source: if parts.length > 1 then parts[0...(parts.length - 1)].join('') else null code: parts.join('').replace(/\)\(\)\)/, '()))') - return code unless soaked - '(' + code + ')' + if op and soaked then '(' + code + ')' else code } @@ -757,6 +762,7 @@ OpNode: exports.OpNode: inherit Node, { @CHAINABLE.indexOf(@operator) >= 0 compile_node: (o) -> + o.operation: true return @compile_chain(o) if @is_chainable() and @first.unwrap() instanceof OpNode and @first.unwrap().is_chainable() return @compile_assignment(o) if @ASSIGNMENT.indexOf(@operator) >= 0 return @compile_unary(o) if @is_unary() diff --git a/test/test_existence.coffee b/test/test_existence.coffee index c08d8151..502e8f6f 100644 --- a/test/test_existence.coffee +++ b/test/test_existence.coffee @@ -55,3 +55,16 @@ ok arr.pop()?.length is 2 ok arr.pop()?.length is undefined ok arr[0]?.length is undefined ok arr.pop()?.length?.non?.existent()?.property is undefined + + +# Soaks method calls safely. +value: undefined +result: value?.toString().toLowerCase() + +ok result is undefined + +value: 10 +result: value?.toString().toLowerCase() + +ok result is '10' +