diff --git a/.travis.yml b/.travis.yml index c730046..2e103bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: node_js node_js: - "0.12" - - "iojs" + - "4" + - "5" addons: code_climate: repo_token: @@ -10,3 +11,4 @@ script: node_modules/.bin/gulp coverage-test after_script: - npm install -g codeclimate-test-reporter - cat coverage/lcov.info | codeclimate + diff --git a/CHANGELOG.md b/CHANGELOG.md index e5584d5..d67c173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Development -Nothing yet! +### Fixed +- Binary operators after nested identifiers were not balanced properly, +resulting in a broken expression/AST +- Gulp (or one of its plugins) had a breaking change in a minor release, +preventing the frontend build from running. This build method will be +removed from the next major version of Jexl. For now, Jexl is now version- +locked to the original gulp+plugins that worked. ## [v1.1.2] ### Changed diff --git a/dist/jexl.min.js b/dist/jexl.min.js index 4ad5a31..c1227fa 100644 --- a/dist/jexl.min.js +++ b/dist/jexl.min.js @@ -1 +1 @@ -require=function e(t,r,n){function a(i,s){if(!r[i]){if(!t[i]){var p="function"==typeof require&&require;if(!s&&p)return p(i,!0);if(o)return o(i,!0);var u=new Error("Cannot find module '"+i+"'");throw u.code="MODULE_NOT_FOUND",u}var c=r[i]={exports:{}};t[i][0].call(c.exports,function(e){var r=t[i][1][e];return a(r?r:e)},c,c.exports,e,t,r,n)}return r[i].exports}for(var o="function"==typeof require&&require,i=0;i":{type:"binaryOp",precedence:20,eval:function(e,t){return e>t}},">=":{type:"binaryOp",precedence:20,eval:function(e,t){return e>=t}},"<":{type:"binaryOp",precedence:20,eval:function(e,t){return t>e}},"<=":{type:"binaryOp",precedence:20,eval:function(e,t){return t>=e}},"&&":{type:"binaryOp",precedence:10,eval:function(e,t){return e&&t}},"||":{type:"binaryOp",precedence:10,eval:function(e,t){return e||t}},"in":{type:"binaryOp",precedence:20,eval:function(e,t){return"string"==typeof t?-1!==t.indexOf(e):Array.isArray(t)?t.some(function(t){return t==e}):!1}},"!":{type:"unaryOp",precedence:1/0,eval:function(e){return!e}}}},{}],5:[function(e,t){function r(e,t,r){this._grammar=e,this._state="expectOperand",this._tree=null,this._exprStr=t||"",this._relative=!1,this._stopMap=r||{}}var n=e("./handlers"),a=e("./states").states;r.prototype.addToken=function(e){if("complete"==this._state)throw new Error("Cannot add a new token to a completed Parser");var t=a[this._state],r=this._exprStr;if(this._exprStr+=e.raw,t.subHandler){this._subParser||this._startSubExpression(r);var o=this._subParser.addToken(e);if(o){if(this._endSubExpression(),this._parentStop)return o;this._state=o}}else{if(!t.tokenTypes[e.type]){if(this._stopMap[e.type])return this._stopMap[e.type];throw new Error("Token "+e.raw+" ("+e.type+") unexpected in expression: "+this._exprStr)}var i=t.tokenTypes[e.type],s=n[e.type];i.handler&&(s=i.handler),s&&s.call(this,e),i.toState&&(this._state=i.toState)}return!1},r.prototype.addTokens=function(e){e.forEach(this.addToken,this)},r.prototype.complete=function(){if(this._cursor&&!a[this._state].completable)throw new Error("Unexpected end of expression: "+this._exprStr);return this._subParser&&this._endSubExpression(),this._state="complete",this._cursor?this._tree:null},r.prototype.isRelative=function(){return this._relative},r.prototype._endSubExpression=function(){a[this._state].subHandler.call(this,this._subParser.complete()),this._subParser=null},r.prototype._placeAtCursor=function(e){this._cursor?(this._cursor.right=e,this._setParent(e,this._cursor)):this._tree=e,this._cursor=e},r.prototype._placeBeforeCursor=function(e){this._cursor=this._cursor._parent,this._placeAtCursor(e)},r.prototype._setParent=function(e,t){Object.defineProperty(e,"_parent",{value:t,writable:!0})},r.prototype._startSubExpression=function(e){var t=a[this._state].endStates;t||(this._parentStop=!0,t=this._stopMap),this._subParser=new r(this._grammar,e,t)},t.exports=r},{"./handlers":6,"./states":7}],6:[function(e,t,r){r.argVal=function(e){this._cursor.args.push(e)},r.arrayStart=function(){this._placeAtCursor({type:"ArrayLiteral",value:[]})},r.arrayVal=function(e){e&&this._cursor.value.push(e)},r.binaryOp=function(e){for(var t=this._grammar[e.value].precedence||0,r=this._cursor._parent;r&&r.operator&&this._grammar[r.operator].precedence>=t;)this._cursor=r,r=r._parent;var n={type:"BinaryExpression",operator:e.value,left:this._cursor};this._setParent(this._cursor,n),this._cursor=r,this._placeAtCursor(n)},r.dot=function(){this._nextIdentEncapsulate=this._cursor&&("BinaryExpression"!=this._cursor.type||"BinaryExpression"==this._cursor.type&&this._cursor.right)&&"UnaryExpression"!=this._cursor.type,this._nextIdentRelative=!this._cursor||this._cursor&&!this._nextIdentEncapsulate,this._nextIdentRelative&&(this._relative=!0)},r.filter=function(e){this._placeBeforeCursor({type:"FilterExpression",expr:e,relative:this._subParser.isRelative(),subject:this._cursor})},r.identifier=function(e){var t={type:"Identifier",value:e.value};this._nextIdentEncapsulate?(t.from=this._cursor,this._placeBeforeCursor(t)):(this._nextIdentRelative&&(t.relative=!0),this._placeAtCursor(t))},r.literal=function(e){this._placeAtCursor({type:"Literal",value:e.value})},r.objKey=function(e){this._curObjKey=e.value},r.objStart=function(){this._placeAtCursor({type:"ObjectLiteral",value:{}})},r.objVal=function(e){this._cursor.value[this._curObjKey]=e},r.subExpression=function(e){this._placeAtCursor(e)},r.ternaryEnd=function(e){this._cursor.alternate=e},r.ternaryMid=function(e){this._cursor.consequent=e},r.ternaryStart=function(){this._tree={type:"ConditionalExpression",test:this._tree},this._cursor=this._tree},r.transform=function(e){this._placeBeforeCursor({type:"Transform",name:e.value,args:[],subject:this._cursor})},r.unaryOp=function(e){this._placeAtCursor({type:"UnaryExpression",operator:e.value})}},{}],7:[function(e,t,r){var n=e("./handlers");r.states={expectOperand:{tokenTypes:{literal:{toState:"expectBinOp"},identifier:{toState:"identifier"},unaryOp:{},openParen:{toState:"subExpression"},openCurl:{toState:"expectObjKey",handler:n.objStart},dot:{toState:"traverse"},openBracket:{toState:"arrayVal",handler:n.arrayStart}}},expectBinOp:{tokenTypes:{binaryOp:{toState:"expectOperand"},pipe:{toState:"expectTransform"},dot:{toState:"traverse"},question:{toState:"ternaryMid",handler:n.ternaryStart}},completable:!0},expectTransform:{tokenTypes:{identifier:{toState:"postTransform",handler:n.transform}}},expectObjKey:{tokenTypes:{identifier:{toState:"expectKeyValSep",handler:n.objKey},closeCurl:{toState:"expectBinOp"}}},expectKeyValSep:{tokenTypes:{colon:{toState:"objVal"}}},postTransform:{tokenTypes:{openParen:{toState:"argVal"},binaryOp:{toState:"expectOperand"},dot:{toState:"traverse"},openBracket:{toState:"filter"},pipe:{toState:"expectTransform"}},completable:!0},postTransformArgs:{tokenTypes:{binaryOp:{toState:"expectOperand"},dot:{toState:"traverse"},openBracket:{toState:"filter"},pipe:{toState:"expectTransform"}},completable:!0},identifier:{tokenTypes:{binaryOp:{toState:"expectOperand"},dot:{toState:"traverse"},openBracket:{toState:"filter"},pipe:{toState:"expectTransform"},question:{toState:"ternaryMid",handler:n.ternaryStart}},completable:!0},traverse:{tokenTypes:{identifier:{toState:"identifier"}}},filter:{subHandler:n.filter,endStates:{closeBracket:"identifier"}},subExpression:{subHandler:n.subExpression,endStates:{closeParen:"expectBinOp"}},argVal:{subHandler:n.argVal,endStates:{comma:"argVal",closeParen:"postTransformArgs"}},objVal:{subHandler:n.objVal,endStates:{comma:"expectObjKey",closeCurl:"expectBinOp"}},arrayVal:{subHandler:n.arrayVal,endStates:{comma:"arrayVal",closeBracket:"expectBinOp"}},ternaryMid:{subHandler:n.ternaryMid,endStates:{colon:"ternaryEnd"}},ternaryEnd:{subHandler:n.ternaryEnd,completable:!0}}},{"./handlers":6}],Jexl:[function(e,t){function r(){this._customGrammar=null,this._lexer=null,this._transforms={}}var n=e("./evaluator/Evaluator"),a=e("./Lexer"),o=e("./parser/Parser"),i=e("./grammar").elements;r.prototype.addBinaryOp=function(e,t,r){this._addGrammarElement(e,{type:"binaryOp",precedence:t,eval:r})},r.prototype.addUnaryOp=function(e,t){this._addGrammarElement(e,{type:"unaryOp",weight:1/0,eval:t})},r.prototype.addTransform=function(e,t){this._transforms[e]=t},r.prototype.addTransforms=function(e){for(var t in e)e.hasOwnProperty(t)&&(this._transforms[t]=e[t])},r.prototype.getTransform=function(e){return this._transforms[e]},r.prototype.eval=function(e,t,r){"function"==typeof t?(r=t,t={}):t||(t={});var n=this._eval(e,t);if(r){var a=!1;return n.then(function(e){a=!0,setTimeout(r.bind(null,null,e),0)})["catch"](function(e){a||setTimeout(r.bind(null,e),0)})}return n},r.prototype.removeOp=function(e){var t=this._getCustomGrammar();!t[e]||"binaryOp"!=t[e].type&&"unaryOp"!=t[e].type||(delete t[e],this._lexer=null)},r.prototype._addGrammarElement=function(e,t){var r=this._getCustomGrammar();r[e]=t,this._lexer=null},r.prototype._eval=function(e,t){var r=this,a=this._getGrammar(),i=new o(a),s=new n(a,this._transforms,t);return Promise.resolve().then(function(){return i.addTokens(r._getLexer().tokenize(e)),s.eval(i.complete())})},r.prototype._getCustomGrammar=function(){if(!this._customGrammar){this._customGrammar={};for(var e in i)i.hasOwnProperty(e)&&(this._customGrammar[e]=i[e])}return this._customGrammar},r.prototype._getGrammar=function(){return this._customGrammar||i},r.prototype._getLexer=function(){return this._lexer||(this._lexer=new a(this._getGrammar())),this._lexer},t.exports=new r,t.exports.Jexl=r},{"./Lexer":1,"./evaluator/Evaluator":2,"./grammar":4,"./parser/Parser":5}]},{},[]); \ No newline at end of file +require=function e(t,r,n){function a(i,s){if(!r[i]){if(!t[i]){var p="function"==typeof require&&require;if(!s&&p)return p(i,!0);if(o)return o(i,!0);var u=new Error("Cannot find module '"+i+"'");throw u.code="MODULE_NOT_FOUND",u}var c=r[i]={exports:{}};t[i][0].call(c.exports,function(e){var r=t[i][1][e];return a(r?r:e)},c,c.exports,e,t,r,n)}return r[i].exports}for(var o="function"==typeof require&&require,i=0;i":{type:"binaryOp",precedence:20,eval:function(e,t){return e>t}},">=":{type:"binaryOp",precedence:20,eval:function(e,t){return e>=t}},"<":{type:"binaryOp",precedence:20,eval:function(e,t){return t>e}},"<=":{type:"binaryOp",precedence:20,eval:function(e,t){return t>=e}},"&&":{type:"binaryOp",precedence:10,eval:function(e,t){return e&&t}},"||":{type:"binaryOp",precedence:10,eval:function(e,t){return e||t}},"in":{type:"binaryOp",precedence:20,eval:function(e,t){return"string"==typeof t?-1!==t.indexOf(e):Array.isArray(t)?t.some(function(t){return t==e}):!1}},"!":{type:"unaryOp",precedence:1/0,eval:function(e){return!e}}}},{}],5:[function(e,t){function r(e,t,r){this._grammar=e,this._state="expectOperand",this._tree=null,this._exprStr=t||"",this._relative=!1,this._stopMap=r||{}}var n=e("./handlers"),a=e("./states").states;r.prototype.addToken=function(e){if("complete"==this._state)throw new Error("Cannot add a new token to a completed Parser");var t=a[this._state],r=this._exprStr;if(this._exprStr+=e.raw,t.subHandler){this._subParser||this._startSubExpression(r);var o=this._subParser.addToken(e);if(o){if(this._endSubExpression(),this._parentStop)return o;this._state=o}}else{if(!t.tokenTypes[e.type]){if(this._stopMap[e.type])return this._stopMap[e.type];throw new Error("Token "+e.raw+" ("+e.type+") unexpected in expression: "+this._exprStr)}var i=t.tokenTypes[e.type],s=n[e.type];i.handler&&(s=i.handler),s&&s.call(this,e),i.toState&&(this._state=i.toState)}return!1},r.prototype.addTokens=function(e){e.forEach(this.addToken,this)},r.prototype.complete=function(){if(this._cursor&&!a[this._state].completable)throw new Error("Unexpected end of expression: "+this._exprStr);return this._subParser&&this._endSubExpression(),this._state="complete",this._cursor?this._tree:null},r.prototype.isRelative=function(){return this._relative},r.prototype._endSubExpression=function(){a[this._state].subHandler.call(this,this._subParser.complete()),this._subParser=null},r.prototype._placeAtCursor=function(e){this._cursor?(this._cursor.right=e,this._setParent(e,this._cursor)):this._tree=e,this._cursor=e},r.prototype._placeBeforeCursor=function(e){this._cursor=this._cursor._parent,this._placeAtCursor(e)},r.prototype._setParent=function(e,t){Object.defineProperty(e,"_parent",{value:t,writable:!0})},r.prototype._startSubExpression=function(e){var t=a[this._state].endStates;t||(this._parentStop=!0,t=this._stopMap),this._subParser=new r(this._grammar,e,t)},t.exports=r},{"./handlers":6,"./states":7}],6:[function(e,t,r){r.argVal=function(e){this._cursor.args.push(e)},r.arrayStart=function(){this._placeAtCursor({type:"ArrayLiteral",value:[]})},r.arrayVal=function(e){e&&this._cursor.value.push(e)},r.binaryOp=function(e){for(var t=this._grammar[e.value].precedence||0,r=this._cursor._parent;r&&r.operator&&this._grammar[r.operator].precedence>=t;)this._cursor=r,r=r._parent;var n={type:"BinaryExpression",operator:e.value,left:this._cursor};this._setParent(this._cursor,n),this._cursor=r,this._placeAtCursor(n)},r.dot=function(){this._nextIdentEncapsulate=this._cursor&&("BinaryExpression"!=this._cursor.type||"BinaryExpression"==this._cursor.type&&this._cursor.right)&&"UnaryExpression"!=this._cursor.type,this._nextIdentRelative=!this._cursor||this._cursor&&!this._nextIdentEncapsulate,this._nextIdentRelative&&(this._relative=!0)},r.filter=function(e){this._placeBeforeCursor({type:"FilterExpression",expr:e,relative:this._subParser.isRelative(),subject:this._cursor})},r.identifier=function(e){var t={type:"Identifier",value:e.value};this._nextIdentEncapsulate?(t.from=this._cursor,this._placeBeforeCursor(t),this._nextIdentEncapsulate=!1):(this._nextIdentRelative&&(t.relative=!0),this._placeAtCursor(t))},r.literal=function(e){this._placeAtCursor({type:"Literal",value:e.value})},r.objKey=function(e){this._curObjKey=e.value},r.objStart=function(){this._placeAtCursor({type:"ObjectLiteral",value:{}})},r.objVal=function(e){this._cursor.value[this._curObjKey]=e},r.subExpression=function(e){this._placeAtCursor(e)},r.ternaryEnd=function(e){this._cursor.alternate=e},r.ternaryMid=function(e){this._cursor.consequent=e},r.ternaryStart=function(){this._tree={type:"ConditionalExpression",test:this._tree},this._cursor=this._tree},r.transform=function(e){this._placeBeforeCursor({type:"Transform",name:e.value,args:[],subject:this._cursor})},r.unaryOp=function(e){this._placeAtCursor({type:"UnaryExpression",operator:e.value})}},{}],7:[function(e,t,r){var n=e("./handlers");r.states={expectOperand:{tokenTypes:{literal:{toState:"expectBinOp"},identifier:{toState:"identifier"},unaryOp:{},openParen:{toState:"subExpression"},openCurl:{toState:"expectObjKey",handler:n.objStart},dot:{toState:"traverse"},openBracket:{toState:"arrayVal",handler:n.arrayStart}}},expectBinOp:{tokenTypes:{binaryOp:{toState:"expectOperand"},pipe:{toState:"expectTransform"},dot:{toState:"traverse"},question:{toState:"ternaryMid",handler:n.ternaryStart}},completable:!0},expectTransform:{tokenTypes:{identifier:{toState:"postTransform",handler:n.transform}}},expectObjKey:{tokenTypes:{identifier:{toState:"expectKeyValSep",handler:n.objKey},closeCurl:{toState:"expectBinOp"}}},expectKeyValSep:{tokenTypes:{colon:{toState:"objVal"}}},postTransform:{tokenTypes:{openParen:{toState:"argVal"},binaryOp:{toState:"expectOperand"},dot:{toState:"traverse"},openBracket:{toState:"filter"},pipe:{toState:"expectTransform"}},completable:!0},postTransformArgs:{tokenTypes:{binaryOp:{toState:"expectOperand"},dot:{toState:"traverse"},openBracket:{toState:"filter"},pipe:{toState:"expectTransform"}},completable:!0},identifier:{tokenTypes:{binaryOp:{toState:"expectOperand"},dot:{toState:"traverse"},openBracket:{toState:"filter"},pipe:{toState:"expectTransform"},question:{toState:"ternaryMid",handler:n.ternaryStart}},completable:!0},traverse:{tokenTypes:{identifier:{toState:"identifier"}}},filter:{subHandler:n.filter,endStates:{closeBracket:"identifier"}},subExpression:{subHandler:n.subExpression,endStates:{closeParen:"expectBinOp"}},argVal:{subHandler:n.argVal,endStates:{comma:"argVal",closeParen:"postTransformArgs"}},objVal:{subHandler:n.objVal,endStates:{comma:"expectObjKey",closeCurl:"expectBinOp"}},arrayVal:{subHandler:n.arrayVal,endStates:{comma:"arrayVal",closeBracket:"expectBinOp"}},ternaryMid:{subHandler:n.ternaryMid,endStates:{colon:"ternaryEnd"}},ternaryEnd:{subHandler:n.ternaryEnd,completable:!0}}},{"./handlers":6}],Jexl:[function(e,t){function r(){this._customGrammar=null,this._lexer=null,this._transforms={}}var n=e("./evaluator/Evaluator"),a=e("./Lexer"),o=e("./parser/Parser"),i=e("./grammar").elements;r.prototype.addBinaryOp=function(e,t,r){this._addGrammarElement(e,{type:"binaryOp",precedence:t,eval:r})},r.prototype.addUnaryOp=function(e,t){this._addGrammarElement(e,{type:"unaryOp",weight:1/0,eval:t})},r.prototype.addTransform=function(e,t){this._transforms[e]=t},r.prototype.addTransforms=function(e){for(var t in e)e.hasOwnProperty(t)&&(this._transforms[t]=e[t])},r.prototype.getTransform=function(e){return this._transforms[e]},r.prototype.eval=function(e,t,r){"function"==typeof t?(r=t,t={}):t||(t={});var n=this._eval(e,t);if(r){var a=!1;return n.then(function(e){a=!0,setTimeout(r.bind(null,null,e),0)})["catch"](function(e){a||setTimeout(r.bind(null,e),0)})}return n},r.prototype.removeOp=function(e){var t=this._getCustomGrammar();!t[e]||"binaryOp"!=t[e].type&&"unaryOp"!=t[e].type||(delete t[e],this._lexer=null)},r.prototype._addGrammarElement=function(e,t){var r=this._getCustomGrammar();r[e]=t,this._lexer=null},r.prototype._eval=function(e,t){var r=this,a=this._getGrammar(),i=new o(a),s=new n(a,this._transforms,t);return Promise.resolve().then(function(){return i.addTokens(r._getLexer().tokenize(e)),s.eval(i.complete())})},r.prototype._getCustomGrammar=function(){if(!this._customGrammar){this._customGrammar={};for(var e in i)i.hasOwnProperty(e)&&(this._customGrammar[e]=i[e])}return this._customGrammar},r.prototype._getGrammar=function(){return this._customGrammar||i},r.prototype._getLexer=function(){return this._lexer||(this._lexer=new a(this._getGrammar())),this._lexer},t.exports=new r,t.exports.Jexl=r},{"./Lexer":1,"./evaluator/Evaluator":2,"./grammar":4,"./parser/Parser":5}]},{},[]); \ No newline at end of file diff --git a/lib/parser/handlers.js b/lib/parser/handlers.js index a06f31a..3f98e41 100644 --- a/lib/parser/handlers.js +++ b/lib/parser/handlers.js @@ -96,6 +96,7 @@ exports.identifier = function(token) { if (this._nextIdentEncapsulate) { node.from = this._cursor; this._placeBeforeCursor(node); + this._nextIdentEncapsulate = false; } else { if (this._nextIdentRelative) diff --git a/package.json b/package.json index 7d8397d..0d4c211 100644 --- a/package.json +++ b/package.json @@ -32,17 +32,17 @@ }, "homepage": "https://github.com/TechnologyAdvice/jexl", "devDependencies": { - "browserify": "^9.0.3", + "browserify": "=9.0.3", "chai": "^2.0.0", "chai-as-promised": "^4.2.0", - "gulp": "^3.8.11", + "gulp": "=3.8.11", "gulp-istanbul": "^0.6.0", "gulp-istanbul-enforcer": "^1.0.3", "gulp-mocha": "^2.0.0", - "gulp-rename": "^1.2.0", - "gulp-uglify": "^1.1.0", + "gulp-rename": "=1.2.0", + "gulp-uglify": "=1.1.0", "istanbul": "^0.3.8", "mocha": "^2.1.0", - "vinyl-transform": "^1.0.0" + "vinyl-transform": "=1.0.0" } } diff --git a/test/parser/Parser.js b/test/parser/Parser.js index b000798..554f686 100644 --- a/test/parser/Parser.js +++ b/test/parser/Parser.js @@ -406,4 +406,22 @@ describe('Parser', function() { alternate: {type: 'Literal', value: 'baz'} }); }); + it('should correctly balance a binary op between complex identifiers', function() { + inst.addTokens(lexer.tokenize('a.b == c.d')); + inst.complete().should.deep.equal({ + type: 'BinaryExpression', + operator: '==', + left: { + type: 'Identifier', + value: 'b', + from: {type: 'Identifier', value: 'a'} + }, + right: { + type: 'Identifier', + value: 'd', + from: {type: 'Identifier', value: 'c'} + } + }); + }); }); +