diff --git a/lib/lexer.js b/lib/lexer.js index c5c04538..dbe42699 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -305,7 +305,7 @@ return true; }; Lexer.prototype.newlineToken = function(newlines) { - if (!(this.tag() === 'TERMINATOR')) { + if (this.tag() !== 'TERMINATOR') { this.token('TERMINATOR', "\n"); } return true; @@ -523,7 +523,7 @@ if (pi < i && pi < str.length - 1) { tokens.push(['STRING', quote + str.substring(pi, i) + quote]); } - if (!(tokens[0][0] === 'STRING')) { + if (tokens[0][0] !== 'STRING') { tokens.unshift(['STRING', '""']); } interpolated = tokens.length > 1; diff --git a/lib/nodes.js b/lib/nodes.js index ed2783cf..674f2c49 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -1242,6 +1242,10 @@ '==': '===', '!=': '!==' }; + OpNode.prototype.INVERSIONS = { + '!==': '===', + '===': '!==' + }; OpNode.prototype.CHAINABLE = ['<', '>', '>=', '<=', '===', '!==']; OpNode.prototype.ASSIGNMENT = ['||=', '&&=', '?=']; OpNode.prototype.PREFIX_OPERATORS = ['typeof', 'delete']; @@ -1250,13 +1254,19 @@ OpNode.prototype.isUnary = function() { return !this.second; }; - OpNode.prototype.isMutator = function() { + OpNode.prototype.isInvertable = function() { var _b; - return ends(this.operator, '=') && !('===' === (_b = this.operator) || '!==' === _b); + return ('===' === (_b = this.operator) || '!==' === _b); + }; + OpNode.prototype.isMutator = function() { + return ends(this.operator, '=') && !this.isInvertable(); }; OpNode.prototype.isChainable = function() { return include(this.CHAINABLE, this.operator); }; + OpNode.prototype.invert = function() { + return (this.operator = this.INVERSIONS[this.operator]); + }; OpNode.prototype.toString = function(idt) { return OpNode.__superClass__.toString.call(this, idt, this["class"] + ' ' + this.operator); }; @@ -1637,7 +1647,11 @@ this.condition = _b; this.tags || (this.tags = {}); if (this.tags.invert) { - this.condition = new OpNode('!', new ParentheticalNode(this.condition)); + if (this.condition instanceof OpNode && this.condition.isInvertable()) { + this.condition.invert(); + } else { + this.condition = new OpNode('!', new ParentheticalNode(this.condition)); + } } this.elseBody = null; this.isChain = false; diff --git a/lib/rewriter.js b/lib/rewriter.js index ee049802..62219d54 100644 --- a/lib/rewriter.js +++ b/lib/rewriter.js @@ -63,7 +63,7 @@ Rewriter.prototype.adjustComments = function() { return this.scanTokens(function(token, i) { var _c, _d, after, before, post, prev; - if (!(token[0] === 'HERECOMMENT')) { + if (token[0] !== 'HERECOMMENT') { return 1; } _c = [this.tokens[i - 2], this.tokens[i - 1], this.tokens[i + 1], this.tokens[i + 2]]; diff --git a/src/nodes.coffee b/src/nodes.coffee index 391cd2c6..db6913fc 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1059,6 +1059,11 @@ exports.OpNode = class OpNode extends BaseNode '==': '===' '!=': '!==' + # The map of invertable operators. + INVERSIONS: + '!==': '===' + '===': '!==' + # The list of operators for which we perform # [Python-style comparison chaining](http://docs.python.org/reference/expressions.html#notin). CHAINABLE: ['<', '>', '>=', '<=', '===', '!=='] @@ -1081,12 +1086,18 @@ exports.OpNode = class OpNode extends BaseNode isUnary: -> not @second + isInvertable: -> + @operator in ['===', '!=='] + isMutator: -> - ends(@operator, '=') and @operator not in ['===', '!=='] + ends(@operator, '=') and not @isInvertable() isChainable: -> include(@CHAINABLE, @operator) + invert: -> + @operator = @INVERSIONS[@operator] + toString: (idt) -> super(idt, @class + ' ' + @operator) @@ -1369,7 +1380,11 @@ exports.IfNode = class IfNode extends BaseNode constructor: (@condition, @body, @tags) -> @tags or= {} - @condition = new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert + if @tags.invert + if @condition instanceof OpNode and @condition.isInvertable() + @condition.invert() + else + @condition = new OpNode '!', new ParentheticalNode @condition @elseBody = null @isChain = false