diff --git a/build/ConditionsParser.js b/build/ConditionParser.js similarity index 69% rename from build/ConditionsParser.js rename to build/ConditionParser.js index d5f913e..f9c35d9 100644 --- a/build/ConditionsParser.js +++ b/build/ConditionParser.js @@ -4,6 +4,30 @@ "use strict"; + + + function stringOp(op) { + return ["EqualsCaseInsensitive","StartsWith","Equals", "NotEquals", "JavaRegex","MatchesPath","Matches"].includes(op); + } + function numericOp(op) { + return ["Equals", "NotEquals", "GreaterThanOrEquals","GreaterThan","LesserThanOrEquals","LesserThan"].includes(op); + } + + function parseBinaryOperation(t1,op1,v1,error) { + let isStringOp = stringOp(op1), + isNumericOp = numericOp(op1), + vType = Object.prototype.toString.call(v1), + v = (vType=== '[object Array]' ) ? v1.join("") : v1; + if (vType==='[object Number]' && !isNumericOp) { + error("not expecting number on RHS"); + } + if (vType!=='[object Number]' && !isStringOp) { + error("expecting number on RHS"); + } + return { operator:op1, operands:[t1, v]}; + } + + function peg$subclass(child, parent) { function C() { this.constructor = child; } C.prototype = parent.prototype; @@ -194,31 +218,40 @@ function peg$parse(input, options) { var peg$c15 = "notequals"; var peg$c16 = "IsNot"; var peg$c17 = "isnot"; - var peg$c18 = "~/"; - var peg$c19 = "MatchEnd"; - var peg$c20 = "~~"; - var peg$c21 = "JavaRegex"; - var peg$c22 = "~"; - var peg$c23 = "MatchesPath"; - var peg$c24 = "LikePath"; - var peg$c25 = ">="; - var peg$c26 = "GreaterThanOrEquals"; - var peg$c27 = "<="; - var peg$c28 = "LesserThanOrEquals"; - var peg$c29 = ">"; - var peg$c30 = "GreaterThan"; - var peg$c31 = "<"; - var peg$c32 = "LesserThan"; - var peg$c33 = "=|"; - var peg$c34 = "StartsWith"; - var peg$c35 = "\""; - var peg$c36 = "null"; - var peg$c37 = "false"; - var peg$c38 = "true"; - - var peg$r0 = /^[a-zA-Z0-9_.]/; - var peg$r1 = /^[a-zA-Z0-9_.\/]/; - var peg$r2 = /^[ \t]/; + var peg$c18 = ":="; + var peg$c19 = "EqualsCaseInsensitive"; + var peg$c20 = ">"; + var peg$c21 = "GreaterThan"; + var peg$c22 = ">="; + var peg$c23 = "GreaterThanOrEquals"; + var peg$c24 = "<"; + var peg$c25 = "LesserThan"; + var peg$c26 = "<="; + var peg$c27 = "LesserThanOrEquals"; + var peg$c28 = "~~"; + var peg$c29 = "JavaRegex"; + var peg$c30 = "~"; + var peg$c31 = "Matches"; + var peg$c32 = "Like"; + var peg$c33 = "~/"; + var peg$c34 = "MatchesPath"; + var peg$c35 = "LikePath"; + var peg$c36 = "=|"; + var peg$c37 = "StartsWith"; + var peg$c38 = "."; + var peg$c39 = "-"; + var peg$c40 = "0"; + var peg$c41 = "\""; + var peg$c42 = "null"; + var peg$c43 = "false"; + var peg$c44 = "true"; + + var peg$r0 = /^[0-9]/; + var peg$r1 = /^[1-9]/; + var peg$r2 = /^[a-zA-Z]/; + var peg$r3 = /^[\-a-zA-Z0-9_.]/; + var peg$r4 = /^[^"]/; + var peg$r5 = /^[ \t\n]/; var peg$e0 = peg$literalExpectation("!", false); var peg$e1 = peg$literalExpectation("(", false); @@ -238,62 +271,81 @@ function peg$parse(input, options) { var peg$e15 = peg$literalExpectation("notequals", false); var peg$e16 = peg$literalExpectation("IsNot", false); var peg$e17 = peg$literalExpectation("isnot", false); - var peg$e18 = peg$literalExpectation("~/", false); - var peg$e19 = peg$literalExpectation("MatchEnd", false); - var peg$e20 = peg$literalExpectation("~~", false); - var peg$e21 = peg$literalExpectation("JavaRegex", false); - var peg$e22 = peg$literalExpectation("~", false); - var peg$e23 = peg$literalExpectation("MatchesPath", false); - var peg$e24 = peg$literalExpectation("LikePath", false); - var peg$e25 = peg$literalExpectation(">=", false); - var peg$e26 = peg$literalExpectation("GreaterThanOrEquals", false); - var peg$e27 = peg$literalExpectation("<=", false); - var peg$e28 = peg$literalExpectation("LesserThanOrEquals", false); - var peg$e29 = peg$literalExpectation(">", false); - var peg$e30 = peg$literalExpectation("GreaterThan", false); - var peg$e31 = peg$literalExpectation("<", false); - var peg$e32 = peg$literalExpectation("LesserThan", false); - var peg$e33 = peg$literalExpectation("=|", false); - var peg$e34 = peg$literalExpectation("StartsWith", false); - var peg$e35 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "."], false, false); - var peg$e36 = peg$literalExpectation("\"", false); - var peg$e37 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", ".", "/"], false, false); - var peg$e38 = peg$literalExpectation("null", false); - var peg$e39 = peg$literalExpectation("false", false); - var peg$e40 = peg$literalExpectation("true", false); - var peg$e41 = peg$classExpectation([" ", "\t"], false, false); + var peg$e18 = peg$literalExpectation(":=", false); + var peg$e19 = peg$literalExpectation("EqualsCaseInsensitive", false); + var peg$e20 = peg$literalExpectation(">", false); + var peg$e21 = peg$literalExpectation("GreaterThan", false); + var peg$e22 = peg$literalExpectation(">=", false); + var peg$e23 = peg$literalExpectation("GreaterThanOrEquals", false); + var peg$e24 = peg$literalExpectation("<", false); + var peg$e25 = peg$literalExpectation("LesserThan", false); + var peg$e26 = peg$literalExpectation("<=", false); + var peg$e27 = peg$literalExpectation("LesserThanOrEquals", false); + var peg$e28 = peg$literalExpectation("~~", false); + var peg$e29 = peg$literalExpectation("JavaRegex", false); + var peg$e30 = peg$literalExpectation("~", false); + var peg$e31 = peg$literalExpectation("Matches", false); + var peg$e32 = peg$literalExpectation("Like", false); + var peg$e33 = peg$literalExpectation("~/", false); + var peg$e34 = peg$literalExpectation("MatchesPath", false); + var peg$e35 = peg$literalExpectation("LikePath", false); + var peg$e36 = peg$literalExpectation("=|", false); + var peg$e37 = peg$literalExpectation("StartsWith", false); + var peg$e38 = peg$literalExpectation(".", false); + var peg$e39 = peg$literalExpectation("-", false); + var peg$e40 = peg$literalExpectation("0", false); + var peg$e41 = peg$classExpectation([["0", "9"]], false, false); + var peg$e42 = peg$classExpectation([["1", "9"]], false, false); + var peg$e43 = peg$classExpectation([["a", "z"], ["A", "Z"]], false, false); + var peg$e44 = peg$classExpectation(["-", ["a", "z"], ["A", "Z"], ["0", "9"], "_", "."], false, false); + var peg$e45 = peg$literalExpectation("\"", false); + var peg$e46 = peg$classExpectation(["\""], true, false); + var peg$e47 = peg$literalExpectation("null", false); + var peg$e48 = peg$literalExpectation("false", false); + var peg$e49 = peg$literalExpectation("true", false); + var peg$e50 = peg$classExpectation([" ", "\t", "\n"], false, false); var peg$f0 = function(left, op1, right) { return {operator:op1, operands:[left, right] } }; var peg$f1 = function(left, op1, right) { return {operator:op1, operands:[left,right]} }; var peg$f2 = function(operand) { return {operator:"NOT", operands:[operand] } }; var peg$f3 = function(t1, op1, v1) { - var v = ( Object.prototype.toString.call( v1 ) === '[object Array]' ) ? v1.join("") : v1; - return { operator:op1, operands:[t1, v]}; + return parseBinaryOperation(t1,op1,v1,error); }; var peg$f4 = function(t1, op1, v1) { - var v = ( Object.prototype.toString.call( v1 ) === '[object Array]' ) ? v1.join("") : v1; - return { operator:op1, operands:[t1, v]}; + return parseBinaryOperation(t1,op1,v1,error); }; var peg$f5 = function(token1) { return token1; }; var peg$f6 = function(token1) { return token1; }; var peg$f7 = function(stmt1) { return stmt1; }; var peg$f8 = function() {return "AND"; }; var peg$f9 = function() {return "OR"; }; - var peg$f10 = function() {return "StartsWith";}; - var peg$f11 = function() { return "NotEquals"; }; + var peg$f10 = function() { return "EqualsCaseInsensitive"; }; + var peg$f11 = function() {return "StartsWith";}; var peg$f12 = function() { return "Equals"; }; - var peg$f13 = function() {return "RegexMatch";}; - var peg$f14 = function() {return "MatchEnd?";}; - var peg$f15 = function() {return "MatchesPath";}; - var peg$f16 = function() {return "GreaterThanOrEquals";}; - var peg$f17 = function() {return "LesserThanOrEquals";}; - var peg$f18 = function() {return "GreaterThan";}; - var peg$f19 = function() {return "LesserThan";}; - var peg$f20 = function(token) { return token.join(""); }; - var peg$f21 = function(value) { value.unshift("'"); value.push("'"); return value; }; - var peg$f22 = function() { return null; }; - var peg$f23 = function() { return false; }; - var peg$f24 = function() { return true; }; + var peg$f13 = function() { return "NotEquals"; }; + var peg$f14 = function() {return "GreaterThanOrEquals";}; + var peg$f15 = function() {return "GreaterThan";}; + var peg$f16 = function() {return "LesserThanOrEquals";}; + var peg$f17 = function() {return "LesserThan";}; + var peg$f18 = function() {return "JavaRegex";}; + var peg$f19 = function() {return "MatchesPath";}; + var peg$f20 = function() {return "Matches";}; + var peg$f21 = function(int1) { return "." + chars.join(''); }; + var peg$f22 = function() { + return { type: "Literal", value: parseFloat(text()) }; + }; + var peg$f23 = function() { + return { type: "Literal", value: parseFloat(text()) }; + }; + var peg$f24 = function() { + return { type: "Literal", value: parseInt(text()) }; + }; + var peg$f25 = function() { return text(); }; + var peg$f26 = function(value) { value.unshift("'"); value.push("'"); return value; }; + var peg$f27 = function() { return null; }; + var peg$f28 = function() { return false; }; + var peg$f29 = function() { return true; }; + var peg$f30 = function(value) { return value.value; }; var peg$currPos = 0; var peg$savedPos = 0; var peg$posDetailsCache = [{ line: 1, column: 1 }]; @@ -713,42 +765,36 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = []; - s2 = peg$parsews(); - while (s2 !== peg$FAILED) { - s1.push(s2); - s2 = peg$parsews(); - } - s2 = peg$parsetoken(); - if (s2 !== peg$FAILED) { - s3 = []; - s4 = peg$parsews(); - if (s4 !== peg$FAILED) { - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parsews(); + s1 = peg$parsetoken(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsews(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsews(); } } else { - s3 = peg$FAILED; + s2 = peg$FAILED; } - if (s3 !== peg$FAILED) { - s4 = peg$parseoperator(); - if (s4 !== peg$FAILED) { - s5 = []; - s6 = peg$parsews(); - if (s6 !== peg$FAILED) { - while (s6 !== peg$FAILED) { - s5.push(s6); - s6 = peg$parsews(); + if (s2 !== peg$FAILED) { + s3 = peg$parseoperator(); + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parsews(); + if (s5 !== peg$FAILED) { + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parsews(); } } else { - s5 = peg$FAILED; + s4 = peg$FAILED; } - if (s5 !== peg$FAILED) { - s6 = peg$parsevalue(); - if (s6 !== peg$FAILED) { + if (s4 !== peg$FAILED) { + s5 = peg$parsevalue(); + if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f4(s2, s4, s6); + s0 = peg$f4(s1, s3, s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -944,7 +990,7 @@ function peg$parse(input, options) { var s0, s1; s0 = peg$currPos; - s1 = peg$parseop_startswith(); + s1 = peg$parseop_equalsnocase(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f10(); @@ -952,7 +998,7 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = peg$parseop_notequals(); + s1 = peg$parseop_startswith(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f11(); @@ -968,7 +1014,7 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = peg$parseop_regexmatch(); + s1 = peg$parseop_notequals(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f13(); @@ -976,7 +1022,7 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = peg$parseop_matchend(); + s1 = peg$parseop_greatereq(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f14(); @@ -984,7 +1030,7 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = peg$parseop_matchespath(); + s1 = peg$parseop_greater(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f15(); @@ -992,7 +1038,7 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = peg$parseop_greatereq(); + s1 = peg$parseop_lessereq(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f16(); @@ -1000,7 +1046,7 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = peg$parseop_lessereq(); + s1 = peg$parseop_lesser(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f17(); @@ -1008,7 +1054,7 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = peg$parseop_greater(); + s1 = peg$parseop_regexmatch(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f18(); @@ -1016,12 +1062,21 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = peg$parseop_lesser(); + s1 = peg$parseop_matchespath(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f19(); } s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseop_matches(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f20(); + } + s0 = s1; + } } } } @@ -1126,7 +1181,7 @@ function peg$parse(input, options) { return s0; } - function peg$parseop_matchend() { + function peg$parseop_equalsnocase() { var s0; if (input.substr(peg$currPos, 2) === peg$c18) { @@ -1137,9 +1192,9 @@ function peg$parse(input, options) { if (peg$silentFails === 0) { peg$fail(peg$e18); } } if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 8) === peg$c19) { + if (input.substr(peg$currPos, 21) === peg$c19) { s0 = peg$c19; - peg$currPos += 8; + peg$currPos += 21; } else { s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e19); } @@ -1149,20 +1204,20 @@ function peg$parse(input, options) { return s0; } - function peg$parseop_regexmatch() { + function peg$parseop_greater() { var s0; - if (input.substr(peg$currPos, 2) === peg$c20) { + if (input.charCodeAt(peg$currPos) === 62) { s0 = peg$c20; - peg$currPos += 2; + peg$currPos++; } else { s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e20); } } if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 9) === peg$c21) { + if (input.substr(peg$currPos, 11) === peg$c21) { s0 = peg$c21; - peg$currPos += 9; + peg$currPos += 11; } else { s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e21); } @@ -1172,55 +1227,46 @@ function peg$parse(input, options) { return s0; } - function peg$parseop_matchespath() { + function peg$parseop_greatereq() { var s0; - if (input.charCodeAt(peg$currPos) === 126) { + if (input.substr(peg$currPos, 2) === peg$c22) { s0 = peg$c22; - peg$currPos++; + peg$currPos += 2; } else { s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e22); } } if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 11) === peg$c23) { + if (input.substr(peg$currPos, 19) === peg$c23) { s0 = peg$c23; - peg$currPos += 11; + peg$currPos += 19; } else { s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e23); } } - if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 8) === peg$c24) { - s0 = peg$c24; - peg$currPos += 8; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e24); } - } - } } return s0; } - function peg$parseop_greatereq() { + function peg$parseop_lesser() { var s0; - if (input.substr(peg$currPos, 2) === peg$c25) { - s0 = peg$c25; - peg$currPos += 2; + if (input.charCodeAt(peg$currPos) === 60) { + s0 = peg$c24; + peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e25); } + if (peg$silentFails === 0) { peg$fail(peg$e24); } } if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 19) === peg$c26) { - s0 = peg$c26; - peg$currPos += 19; + if (input.substr(peg$currPos, 10) === peg$c25) { + s0 = peg$c25; + peg$currPos += 10; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e25); } } } @@ -1230,73 +1276,82 @@ function peg$parse(input, options) { function peg$parseop_lessereq() { var s0; - if (input.substr(peg$currPos, 2) === peg$c27) { - s0 = peg$c27; + if (input.substr(peg$currPos, 2) === peg$c26) { + s0 = peg$c26; peg$currPos += 2; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e26); } } if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 18) === peg$c28) { - s0 = peg$c28; + if (input.substr(peg$currPos, 18) === peg$c27) { + s0 = peg$c27; peg$currPos += 18; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e27); } } } return s0; } - function peg$parseop_greater() { + function peg$parseop_regexmatch() { var s0; - if (input.charCodeAt(peg$currPos) === 62) { - s0 = peg$c29; - peg$currPos++; + if (input.substr(peg$currPos, 2) === peg$c28) { + s0 = peg$c28; + peg$currPos += 2; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 11) === peg$c30) { - s0 = peg$c30; - peg$currPos += 11; + if (input.substr(peg$currPos, 9) === peg$c29) { + s0 = peg$c29; + peg$currPos += 9; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } } return s0; } - function peg$parseop_lesser() { + function peg$parseop_matches() { var s0; - if (input.charCodeAt(peg$currPos) === 60) { - s0 = peg$c31; + if (input.charCodeAt(peg$currPos) === 126) { + s0 = peg$c30; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e31); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 10) === peg$c32) { - s0 = peg$c32; - peg$currPos += 10; + if (input.substr(peg$currPos, 7) === peg$c31) { + s0 = peg$c31; + peg$currPos += 7; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c32) { + s0 = peg$c32; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e32); } + } } } return s0; } - function peg$parseop_startswith() { + function peg$parseop_matchespath() { var s0; if (input.substr(peg$currPos, 2) === peg$c33) { @@ -1307,49 +1362,305 @@ function peg$parse(input, options) { if (peg$silentFails === 0) { peg$fail(peg$e33); } } if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 10) === peg$c34) { + if (input.substr(peg$currPos, 11) === peg$c34) { s0 = peg$c34; - peg$currPos += 10; + peg$currPos += 11; } else { s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e34); } } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c35) { + s0 = peg$c35; + peg$currPos += 8; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e35); } + } + } } return s0; } - function peg$parsetoken() { + function peg$parseop_startswith() { + var s0; + + if (input.substr(peg$currPos, 2) === peg$c36) { + s0 = peg$c36; + peg$currPos += 2; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e36); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 10) === peg$c37) { + s0 = peg$c37; + peg$currPos += 10; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e37); } + } + } + + return s0; + } + + function peg$parsefrac_string() { var s0, s1, s2; s0 = peg$currPos; - s1 = []; - if (peg$r0.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); + if (input.charCodeAt(peg$currPos) === 46) { + s1 = peg$c38; peg$currPos++; } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e38); } } + if (s1 !== peg$FAILED) { + s2 = peg$parseDecimalIntegerLiteral(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f21(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseDecimalLiteral() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 45) { + s1 = peg$c39; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e39); } + } + if (s1 === peg$FAILED) { + s1 = null; + } + s2 = peg$parseDecimalIntegerLiteral(); if (s2 !== peg$FAILED) { - while (s2 !== peg$FAILED) { - s1.push(s2); - if (peg$r0.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; + if (input.charCodeAt(peg$currPos) === 46) { + s3 = peg$c38; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e38); } + } + if (s3 !== peg$FAILED) { + s4 = peg$currPos; + s5 = []; + s6 = peg$parseDecimalDigit(); + while (s6 !== peg$FAILED) { + s5.push(s6); + if (s5.length >= 10) { + s6 = peg$FAILED; + } else { + s6 = peg$parseDecimalDigit(); + } + } + if (s5.length < 1) { + peg$currPos = s4; + s4 = peg$FAILED; } else { + s4 = s5; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f22(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 46) { + s1 = peg$c38; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e38); } + } + if (s1 !== peg$FAILED) { + s2 = peg$currPos; + s3 = []; + s4 = peg$parseDecimalDigit(); + while (s4 !== peg$FAILED) { + s3.push(s4); + if (s3.length >= 10) { + s4 = peg$FAILED; + } else { + s4 = peg$parseDecimalDigit(); + } + } + if (s3.length < 1) { + peg$currPos = s2; s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + } else { + s2 = s3; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f23(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 45) { + s1 = peg$c39; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e39); } + } + if (s1 === peg$FAILED) { + s1 = null; + } + s2 = peg$parseDecimalIntegerLiteral(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f24(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; } } + } + + return s0; + } + + function peg$parseDecimalIntegerLiteral() { + var s0, s1, s2, s3; + + if (input.charCodeAt(peg$currPos) === 48) { + s0 = peg$c40; + peg$currPos++; } else { - s1 = peg$FAILED; + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e40); } + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseNonZeroDigit(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseDecimalDigit(); + while (s3 !== peg$FAILED) { + s2.push(s3); + if (s2.length >= 9) { + s3 = peg$FAILED; + } else { + s3 = peg$parseDecimalDigit(); + } + } + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } } + + return s0; + } + + function peg$parseDecimalDigit() { + var s0; + + if (peg$r0.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e41); } + } + + return s0; + } + + function peg$parseNonZeroDigit() { + var s0; + + if (peg$r1.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e42); } + } + + return s0; + } + + function peg$parsealpha() { + var s0; + + if (peg$r2.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e43); } + } + + return s0; + } + + function peg$parsetoken() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsealpha(); if (s1 !== peg$FAILED) { + s2 = []; + if (peg$r3.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e44); } + } + while (s3 !== peg$FAILED) { + s2.push(s3); + if (peg$r3.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e44); } + } + } peg$savedPos = s0; - s1 = peg$f20(s1); + s0 = peg$f25(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; } - s0 = s1; return s0; } @@ -1359,41 +1670,41 @@ function peg$parse(input, options) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c35; + s1 = peg$c41; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e36); } + if (peg$silentFails === 0) { peg$fail(peg$e45); } } if (s1 !== peg$FAILED) { s2 = []; - if (peg$r1.test(input.charAt(peg$currPos))) { + if (peg$r4.test(input.charAt(peg$currPos))) { s3 = input.charAt(peg$currPos); peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e37); } + if (peg$silentFails === 0) { peg$fail(peg$e46); } } while (s3 !== peg$FAILED) { s2.push(s3); - if (peg$r1.test(input.charAt(peg$currPos))) { + if (peg$r4.test(input.charAt(peg$currPos))) { s3 = input.charAt(peg$currPos); peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e37); } + if (peg$silentFails === 0) { peg$fail(peg$e46); } } } if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c35; + s3 = peg$c41; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e36); } + if (peg$silentFails === 0) { peg$fail(peg$e45); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f21(s2); + s0 = peg$f26(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1404,46 +1715,55 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c36) { - s1 = peg$c36; + if (input.substr(peg$currPos, 4) === peg$c42) { + s1 = peg$c42; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e38); } + if (peg$silentFails === 0) { peg$fail(peg$e47); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f22(); + s1 = peg$f27(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 5) === peg$c37) { - s1 = peg$c37; + if (input.substr(peg$currPos, 5) === peg$c43) { + s1 = peg$c43; peg$currPos += 5; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e39); } + if (peg$silentFails === 0) { peg$fail(peg$e48); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f23(); + s1 = peg$f28(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c38) { - s1 = peg$c38; + if (input.substr(peg$currPos, 4) === peg$c44) { + s1 = peg$c44; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e40); } + if (peg$silentFails === 0) { peg$fail(peg$e49); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f24(); + s1 = peg$f29(); } s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseDecimalLiteral(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f30(s1); + } + s0 = s1; + } } } } @@ -1454,12 +1774,12 @@ function peg$parse(input, options) { function peg$parsews() { var s0; - if (peg$r2.test(input.charAt(peg$currPos))) { + if (peg$r5.test(input.charAt(peg$currPos))) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e41); } + if (peg$silentFails === 0) { peg$fail(peg$e50); } } return s0; diff --git a/lib/package/plugins/CC007-ConditionSyntax.js b/lib/package/plugins/CC007-ConditionSyntax.js new file mode 100644 index 0000000..0ccbbce --- /dev/null +++ b/lib/package/plugins/CC007-ConditionSyntax.js @@ -0,0 +1,60 @@ +/* + Copyright 2019-2023 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +const ruleId = require("../myUtil.js").getRuleId(), + util = require("util"), + debug = require("debug")("apigeelint:" + ruleId); +const parser = require("../../../build/ConditionParser.js"); + +const plugin = { + ruleId, + name: "Condition Syntax", + fatal: false, + severity: 2, //error + nodeType: "Condition", + enabled: true +}; + +const onCondition = function (condition, cb) { + debug(`onCondition (${condition.getExpression()})`); + let flagged = false; + const expr = condition.getExpression(); + try { + parser.parse(expr); + } catch (e) { + debug(util.format(e)); + flagged = true; + const estr = e.toString(), + loc = e.location.start.column; + condition.addMessage({ + source: condition.getExpression(), + line: condition.getElement().lineNumber, + column: condition.getElement().columnNumber, + plugin, + message: `Condition expression is invalid. Position ${loc}, ${estr}` + }); + } + + if (typeof cb == "function") { + cb(null, flagged); + } + return flagged; +}; + +module.exports = { + plugin, + onCondition +}; diff --git a/src/peggy/Apigee-Condition.pegjs b/lib/peggy/Apigee-Condition.pegjs similarity index 54% rename from src/peggy/Apigee-Condition.pegjs rename to lib/peggy/Apigee-Condition.pegjs index 14b4df2..9145ca1 100644 --- a/src/peggy/Apigee-Condition.pegjs +++ b/lib/peggy/Apigee-Condition.pegjs @@ -20,14 +20,39 @@ // // Examples of conditions that can be parsed by this grammar: // +// proxy.pathsuffix MatchesPath "/authorize" and request.verb = "POST" // (proxy.pathsuffix MatchesPath "/authorize") and (request.verb = "POST") // (proxy.pathsuffix = "/token") and (request.verb = "POST") // (request.verb = "POST") && (proxy.pathsuffix = "/token") // !(request.verb = "POST") // !valid // -// Monday, 6 April 2015, 16:46 -// + +{{ + + function stringOp(op) { + return ["EqualsCaseInsensitive","StartsWith","Equals", "NotEquals", "JavaRegex","MatchesPath","Matches"].includes(op); + } + function numericOp(op) { + return ["Equals", "NotEquals", "GreaterThanOrEquals","GreaterThan","LesserThanOrEquals","LesserThan"].includes(op); + } + + function parseBinaryOperation(t1,op1,v1,error) { + let isStringOp = stringOp(op1), + isNumericOp = numericOp(op1), + vType = Object.prototype.toString.call(v1), + v = (vType=== '[object Array]' ) ? v1.join("") : v1; + if (vType==='[object Number]' && !isNumericOp) { + error("not expecting number on RHS"); + } + if (vType!=='[object Number]' && !isStringOp) { + error("expecting number on RHS"); + } + return { operator:op1, operands:[t1, v]}; + } + +}} + start = boolean_stmt1 @@ -44,15 +69,12 @@ factor = "!" ws* operand:factor { return {operator:"NOT", operands:[operand] } } / primary - primary = "(" ws* t1:token ws+ op1:operator ws+ v1:value ws* ")" { - var v = ( Object.prototype.toString.call( v1 ) === '[object Array]' ) ? v1.join("") : v1; - return { operator:op1, operands:[t1, v]}; + return parseBinaryOperation(t1,op1,v1,error); } - / ws* t1:token ws+ op1:operator ws+ v1:value { - var v = ( Object.prototype.toString.call( v1 ) === '[object Array]' ) ? v1.join("") : v1; - return { operator:op1, operands:[t1, v]}; + / t1:token ws+ op1:operator ws+ v1:value { + return parseBinaryOperation(t1,op1,v1,error); } / token1:token { return token1; } / "(" token1:token ")" { return token1; } @@ -66,47 +88,81 @@ op_and = "and" / "AND" / "&&" op_or = "or" / "OR" / "||" +// Ordering is important. For operators that share a common prefix, +// list the longer, more specific operator, first. + operator - = op_startswith {return "StartsWith";} - / op_notequals { return "NotEquals"; } + = op_equalsnocase { return "EqualsCaseInsensitive"; } + / op_startswith {return "StartsWith";} / op_equals { return "Equals"; } - / op_regexmatch {return "RegexMatch";} - / op_matchend {return "MatchEnd?";} - / op_matchespath {return "MatchesPath";} + / op_notequals { return "NotEquals"; } / op_greatereq {return "GreaterThanOrEquals";} - / op_lessereq {return "LesserThanOrEquals";} / op_greater {return "GreaterThan";} + / op_lessereq {return "LesserThanOrEquals";} / op_lesser {return "LesserThan";} + / op_regexmatch {return "JavaRegex";} + / op_matchespath {return "MatchesPath";} + / op_matches {return "Matches";} op_equals = "=" / "Equals" / "Is" / "is" op_notequals = "!=" / "NotEquals" / "notequals" / "IsNot" / "isnot" -op_matchend = "~/" / "MatchEnd" +op_equalsnocase = ":=" / "EqualsCaseInsensitive" -op_regexmatch = "~~" / "JavaRegex" - -op_matchespath = "~" / "MatchesPath" / "LikePath" +op_greater = ">" / "GreaterThan" op_greatereq = ">=" / "GreaterThanOrEquals" +op_lesser = "<" / "LesserThan" + op_lessereq = "<=" / "LesserThanOrEquals" -op_greater = ">" / "GreaterThan" +op_regexmatch = "~~" / "JavaRegex" -op_lesser = "<" / "LesserThan" +op_matches = "~" / "Matches" / "Like" + +op_matchespath = "~/" / "MatchesPath" / "LikePath" op_startswith = "=|" / "StartsWith" +frac_string + = "." int1:DecimalIntegerLiteral { return "." + chars.join(''); } + +DecimalLiteral + = "-"? DecimalIntegerLiteral "." DecimalDigit|1..10| { + return { type: "Literal", value: parseFloat(text()) }; + } + / "." DecimalDigit|1..10| { + return { type: "Literal", value: parseFloat(text()) }; + } + / "-"? DecimalIntegerLiteral { + return { type: "Literal", value: parseInt(text()) }; + } + +DecimalIntegerLiteral + = "0" + / NonZeroDigit DecimalDigit|0..9| + +DecimalDigit + = [0-9] + +NonZeroDigit + = [1-9] + +alpha + = [a-zA-Z] + token - = token:[a-zA-Z0-9_\.]+ { return token.join(""); } + = alpha [-a-zA-Z0-9_\.]* { return text(); } value - = '"' value:[a-zA-Z0-9_\./]* '"' { value.unshift("'"); value.push("'"); return value; } + = '"' value:[^"]* '"' { value.unshift("'"); value.push("'"); return value; } / "null" { return null; } / "false" { return false; } / "true" { return true; } + / value:DecimalLiteral { return value.value; } ws - = [ \t] + = [ \t\n] diff --git a/out/apigeeLint.json b/out/apigeeLint.json deleted file mode 100644 index cdd696e..0000000 --- a/out/apigeeLint.json +++ /dev/null @@ -1,101 +0,0 @@ - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy - -║ Line │ Column │ Type │ Message │ Rule ID ║ -╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢ -║ 0 │ 0 │ warning │ There are several Statistics Collector policies │ BN009 ║ -║ │ │ │ attached to a step without a condition. If you │ ║ -║ │ │ │ have more than two Statistics Collector policies, │ ║ -║ │ │ │ only the last one in the flow will execute. │ ║ -║ │ │ │ Include a condition to make sure the correct one │ ║ -║ │ │ │ executes. │ ║ - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/proxies/default.xml - -║ Line │ Column │ Type │ Message │ Rule ID ║ -╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢ -║ 17 │ 6 │ error │ Configuration for proxy endpoint default is not │ BN011 ║ -║ │ │ │ well-formed XML (XML declaration allowed only at │ ║ -║ │ │ │ the start of the document.). │ ║ - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/targets/default.xml - -║ Line │ Column │ Type │ Message │ Rule ID ║ -╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢ -║ 17 │ 6 │ error │ Configuration for target endpoint default is not │ BN011 ║ -║ │ │ │ well-formed XML (XML declaration allowed only at │ ║ -║ │ │ │ the start of the document.). │ ║ -║ 0 │ 0 │ warning │ TargetEndpoint (default) is using URL │ TD002 ║ -║ │ │ │ (http://api.foo.com/), using a Target Server │ ║ -║ │ │ │ simplifies CI/CD. │ ║ - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/policies/StatisticsCollector1.xml - -║ Line │ Column │ Type │ Message │ Rule ID ║ -╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢ -║ 0 │ 0 │ warning │ StatisticsCollector1 is attached to a step without │ BN009 ║ -║ │ │ │ a condition. If you have more than two Statistics │ ║ -║ │ │ │ Collector policies, only the last one in the flow │ ║ -║ │ │ │ will execute. Include a condition to make sure │ ║ -║ │ │ │ the correct one executes. │ ║ -║ 17 │ 6 │ error │ Step StatisticsCollector1 configuration is not │ BN011 ║ -║ │ │ │ well-formed XML (XML declaration allowed only at │ ║ -║ │ │ │ the start of the document.). │ ║ -║ 18 │ 80 │ warning │ Non-standard name for policy │ PO007 ║ -║ │ │ │ (StatisticsCollector1). Valid prefixes for the │ ║ -║ │ │ │ StatisticsCollector policy: ["stats","statcoll"] │ ║ - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/policies/StatisticsCollector10.xml - -║ Line │ Column │ Type │ Message │ Rule ID ║ -╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢ -║ 0 │ 0 │ warning │ StatisticsCollector10 is attached to a step │ BN009 ║ -║ │ │ │ without a condition. If you have more than two │ ║ -║ │ │ │ Statistics Collector policies, only the last one │ ║ -║ │ │ │ in the flow will execute. Include a condition to │ ║ -║ │ │ │ make sure the correct one executes. │ ║ -║ 17 │ 6 │ error │ Step StatisticsCollector10 configuration is not │ BN011 ║ -║ │ │ │ well-formed XML (XML declaration allowed only at │ ║ -║ │ │ │ the start of the document.). │ ║ -║ 18 │ 80 │ warning │ Non-standard name for policy │ PO007 ║ -║ │ │ │ (StatisticsCollector10). Valid prefixes for the │ ║ -║ │ │ │ StatisticsCollector policy: ["stats","statcoll"] │ ║ - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/policies/StatisticsCollectorAddress.xml - -║ Line │ Column │ Type │ Message │ Rule ID ║ -╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢ -║ 0 │ 0 │ warning │ StatisticsCollectorAddress is attached to a step │ BN009 ║ -║ │ │ │ without a condition. If you have more than two │ ║ -║ │ │ │ Statistics Collector policies, only the last one │ ║ -║ │ │ │ in the flow will execute. Include a condition to │ ║ -║ │ │ │ make sure the correct one executes. │ ║ -║ 17 │ 6 │ error │ Step StatisticsCollectorAddress configuration is │ BN011 ║ -║ │ │ │ not well-formed XML (XML declaration allowed only │ ║ -║ │ │ │ at the start of the document.). │ ║ -║ 18 │ 80 │ warning │ Non-standard name for policy │ PO007 ║ -║ │ │ │ (StatisticsCollectorAddress). Valid prefixes for │ ║ -║ │ │ │ the StatisticsCollector policy: │ ║ -║ │ │ │ ["stats","statcoll"] │ ║ - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/policies/StatisticsCollectorZip.xml - -║ Line │ Column │ Type │ Message │ Rule ID ║ -╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢ -║ 0 │ 0 │ warning │ StatisticsCollectorZip is attached to a step │ BN009 ║ -║ │ │ │ without a condition. If you have more than two │ ║ -║ │ │ │ Statistics Collector policies, only the last one │ ║ -║ │ │ │ in the flow will execute. Include a condition to │ ║ -║ │ │ │ make sure the correct one executes. │ ║ -║ 17 │ 6 │ error │ Step StatisticsCollectorZip configuration is not │ BN011 ║ -║ │ │ │ well-formed XML (XML declaration allowed only at │ ║ -║ │ │ │ the start of the document.). │ ║ -║ 18 │ 80 │ warning │ Non-standard name for policy │ PO007 ║ -║ │ │ │ (StatisticsCollectorZip). Valid prefixes for the │ ║ -║ │ │ │ StatisticsCollector policy: ["stats","statcoll"] │ ║ - -╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ -║ 6 Errors ║ -╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢ -║ 10 Warnings ║ -╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝ diff --git a/out/apigeelint.out b/out/apigeelint.out deleted file mode 100644 index 8547792..0000000 --- a/out/apigeelint.out +++ /dev/null @@ -1,33 +0,0 @@ - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy - 0:0 warning There are several Statistics Collector policies attached to a step without a condition. If you have more than two Statistics Collector policies, only the last one in the flow will execute. Include a condition to make sure the correct one executes BN009 - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/proxies/default.xml - 17:6 error Configuration for proxy endpoint default is not well-formed XML (XML declaration allowed only at the start of the document.) BN011 - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/targets/default.xml - 17:6 error Configuration for target endpoint default is not well-formed XML (XML declaration allowed only at the start of the document.) BN011 - 0:0 warning TargetEndpoint (default) is using URL (http://api.foo.com/), using a Target Server simplifies CI/CD TD002 - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/policies/StatisticsCollector1.xml - 0:0 warning StatisticsCollector1 is attached to a step without a condition. If you have more than two Statistics Collector policies, only the last one in the flow will execute. Include a condition to make sure the correct one executes BN009 - 17:6 error Step StatisticsCollector1 configuration is not well-formed XML (XML declaration allowed only at the start of the document.) BN011 - 18:80 warning Non-standard name for policy (StatisticsCollector1). Valid prefixes for the StatisticsCollector policy: ["stats","statcoll"] PO007 - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/policies/StatisticsCollector10.xml - 0:0 warning StatisticsCollector10 is attached to a step without a condition. If you have more than two Statistics Collector policies, only the last one in the flow will execute. Include a condition to make sure the correct one executes BN009 - 17:6 error Step StatisticsCollector10 configuration is not well-formed XML (XML declaration allowed only at the start of the document.) BN011 - 18:80 warning Non-standard name for policy (StatisticsCollector10). Valid prefixes for the StatisticsCollector policy: ["stats","statcoll"] PO007 - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/policies/StatisticsCollectorAddress.xml - 0:0 warning StatisticsCollectorAddress is attached to a step without a condition. If you have more than two Statistics Collector policies, only the last one in the flow will execute. Include a condition to make sure the correct one executes BN009 - 17:6 error Step StatisticsCollectorAddress configuration is not well-formed XML (XML declaration allowed only at the start of the document.) BN011 - 18:80 warning Non-standard name for policy (StatisticsCollectorAddress). Valid prefixes for the StatisticsCollector policy: ["stats","statcoll"] PO007 - -/Users/dchiesa/dev/apigeelint/test/fixtures/resources/statistics_collector/multiple_stats_collector_missing_conditions/apiproxy/policies/StatisticsCollectorZip.xml - 0:0 warning StatisticsCollectorZip is attached to a step without a condition. If you have more than two Statistics Collector policies, only the last one in the flow will execute. Include a condition to make sure the correct one executes BN009 - 17:6 error Step StatisticsCollectorZip configuration is not well-formed XML (XML declaration allowed only at the start of the document.) BN011 - 18:80 warning Non-standard name for policy (StatisticsCollectorZip). Valid prefixes for the StatisticsCollector policy: ["stats","statcoll"] PO007 - -✖ 16 problems (6 errors, 10 warnings) - \ No newline at end of file diff --git a/package.json b/package.json index 97d2f1c..e7aee9e 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "scripts": { "badge": "mocha --reporter mocha-badge-generator", "coverage": "npx nyc --reporter=text mocha", - "postinstall": "npx mkdirp build && ./node_modules/peggy/bin/peggy.js -o build/ConditionsParser.js src/peggy/Apigee-Condition.pegjs", + "build-condition-parser": "npx mkdirp build && ./node_modules/peggy/bin/peggy.js -o build/ConditionParser.js lib/peggy/Apigee-Condition.pegjs", + "postinstall": "npm run build-condition-parser", + "pretest": "npm run build-condition-parser", "test": "mocha" }, "repository": { diff --git a/test/specs/CC007-Condition-Syntax-Test.js b/test/specs/CC007-Condition-Syntax-Test.js new file mode 100644 index 0000000..3ec54b5 --- /dev/null +++ b/test/specs/CC007-Condition-Syntax-Test.js @@ -0,0 +1,125 @@ +/* + Copyright 2019-2023 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +/* global describe, it */ + +const testID = "CC007", + assert = require("assert"), + xpath = require("xpath"), + bl = require("../../lib/package/bundleLinter.js"), + plugin = require(bl.resolvePlugin(testID)), + debug = require("debug")("apigeelint:" + testID), + Dom = require("@xmldom/xmldom").DOMParser, + Condition = require("../../lib/package/Condition.js"); + +const escapeXml = (unsafe) => + unsafe.replace(/[<>&]/g, function (c) { + switch (c) { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + } + return c; + }); + +describe(`${testID} - ${plugin.plugin.name}`, function () { + const cases = [ + { expression: "false", expectError: false }, + { expression: "true or false", expectError: false }, + { + expression: "true of false", + expectError: true, + note: "misspelled operator" + }, + { + expression: "proxy.pathsuffix MatchesLike false", + expectError: true, + note: "unknown operator" + }, + { + expression: 'proxy.pathsuffix MatchesPath "/foo/bar"', + expectError: false + }, + { + expression: 'proxy.pathsuffix ~/ "/foo/bar"', + expectError: false + }, + { expression: "A > 20", expectError: false }, + { expression: 'A = "c"', expectError: false }, + { expression: "A = 34", expectError: false }, + { + expression: 'A > "c"', + expectError: true, + notes: "non-numeric on RHS of GT" + }, + { + expression: 'A >= "c"', + expectError: true, + notes: "non-numeric on RHS of GTE" + }, + { + expression: 'A <= "something"', + expectError: true, + notes: "non-numeric on RHS of LTE" + }, + { + expression: '"something" = "something"', + expectError: true, + notes: "non-token on LHS of operator" + }, + { + expression: '20 = "another-string"', + expectError: true, + notes: "non-token on LHS of operator" + }, + { + expression: "20 = 42", + expectError: true, + notes: "non-token on LHS of operator" + }, + { + expression: + 'request.header.content-type = "application/json" AND request.verb = "GET"', + expectError: false + } + ]; + cases.forEach((testcase, i) => { + it(`case ${i}, [${testcase.expression}], expect(${ + testcase.expectError ? "invalid" : "valid" + })`, function () { + const rootElement = new Dom().parseFromString( + `${escapeXml( + testcase.expression + )}` + ); + const cond = xpath.select("/Hello/Condition", rootElement); + const c = new Condition(cond[0], rootElement); + c.addMessage = function (msg) { + debug(msg); + }; + plugin.onCondition(c, function (e, flagged) { + assert.equal(e, undefined); + assert.equal( + flagged, + testcase.expectError, + flagged ? " warning created " : "no warning created" + ); + }); + }); + }); +}); diff --git a/test/specs/ConditionParserTest.js b/test/specs/ConditionParserTest.js new file mode 100644 index 0000000..c00bced --- /dev/null +++ b/test/specs/ConditionParserTest.js @@ -0,0 +1,569 @@ +/* + Copyright 2019-2023 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* global describe, it */ + +const parser = require("../../build/ConditionParser.js"); +const expect = require("chai").expect; +//const debug = require("debug")(`apigeelint:ConditionParser`); + +describe("ConditionParser", function () { + describe("Parens", function () { + it("treats parenthesized and non-parenthesized atoms as equivalent", function () { + const c1 = "valid"; + const c2 = "(valid)"; + const result1 = parser.parse(c1); + const result2 = parser.parse(c2); + expect(JSON.stringify(result1)).to.equal(JSON.stringify(result2)); + expect(JSON.stringify(result1)).to.equal('"valid"'); + }); + it("treats parenthesized and non-parenthesized boolean expressions as equivalent", function () { + const c1 = 'request.verb = "POST"'; + const c2 = '(request.verb = "POST")'; + const result1 = parser.parse(c1); + const result2 = parser.parse(c2); + expect(JSON.stringify(result1)).to.equal(JSON.stringify(result2)); + expect(JSON.stringify(result1)).to.equal( + '{"operator":"Equals","operands":["request.verb","\'POST\'"]}' + ); + }); + }); + + describe("AND statement with MatchesPath verb", function () { + const c1 = + '(proxy.pathsuffix MatchesPath "/auth") and (request.verb = "POST")'; + const c2 = '(proxy.pathsuffix ~/ "/auth") and (request.verb = "POST")'; + it("parses long and short form of MatchesPath the same", function () { + const result1 = parser.parse(c1); + const result2 = parser.parse(c2); + expect(JSON.stringify(result1)).to.equal(JSON.stringify(result2)); + }); + }); + + describe("NOT statements", function () { + it("parses negation of token", function () { + const c1 = "!valid"; + const result1 = parser.parse(c1); + expect(JSON.stringify(result1)).to.equal( + '{"operator":"NOT","operands":["valid"]}' + ); + }); + + it("parses negation of parenthesized expression", function () { + const c1 = "!(valid)"; + const result1 = parser.parse(c1); + expect(JSON.stringify(result1)).to.equal( + '{"operator":"NOT","operands":["valid"]}' + ); + }); + + it("treats parenthesized and non-parenthesized atoms as equivalent", function () { + const c1 = "!valid"; + const c2 = "!(valid)"; + const result1 = parser.parse(c1); + const result2 = parser.parse(c2); + expect(JSON.stringify(result1)).to.equal(JSON.stringify(result2)); + expect(JSON.stringify(result1)).to.equal( + '{"operator":"NOT","operands":["valid"]}' + ); + }); + + it("parses negation of parenthesized compound expression", function () { + const c1 = '!((seven = "5") AND (valid = false))'; + const expected = + '{"operator":"NOT","operands":[{"operator":"AND","operands":[{"operator":"Equals","operands":["seven","\'5\'"]},{"operator":"Equals","operands":["valid",false]}]}]}'; + const result1 = parser.parse(c1); + expect(JSON.stringify(result1)).to.equal(expected); + }); + }); + + describe("AND statements", function () { + it("treats AND and && as synonyms", function () { + const c1 = + '(proxy.pathsuffix ~ "/authorize") and (request.verb = "POST")'; + const result1 = parser.parse(c1); + const c2 = c1.replace("and", "&&"); + const result2 = parser.parse(c2); + expect(JSON.stringify(result1)).to.equal(JSON.stringify(result2)); + }); + + it("parses AND statements with 3 clauses", function () { + const c1 = `(proxy.pathsuffix MatchesPath "/token") and + (request.verb = "POST") and + (request.formparam.grant_type = "authorization_code")`; + const result = parser.parse(c1); + // { + // "operator": "AND", + // "operands": [ + // { + // "operator": "MatchesPath", + // "operands": [ + // "proxy.pathsuffix", + // "'/token'" + // ] + // }, + // { + // "operator": "AND", + // "operands": [ + // { + // "operator": "Equals", + // "operands": [ + // "request.verb", + // "'POST'" + // ] + // }, + // { + // "operator": "Equals", + // "operands": [ + // "request.formparam.grant_type", + // "'authorization_code'" + // ] + // } + // ] + // } + // ] + // } + expect(result.operator).to.equal("AND"); + expect(result.operands.length).to.equal(2); + expect(result.operands[0].operator).to.equal("MatchesPath"); + expect(result.operands[1].operator).to.equal("AND"); + expect(result.operands[1].operands[0].operator).to.equal("Equals"); + expect(result.operands[1].operands[1].operator).to.equal("Equals"); + }); + + it("parses AND statements with 3 clauses, no parens, and newlines", function () { + const c1 = `proxy.pathsuffix MatchesPath "/token" and + request.verb = "POST" and + request.formparam.grant_type = "authorization_code"`; + const result = parser.parse(c1); + expect(result.operator).to.equal("AND"); + expect(result.operands.length).to.equal(2); + expect(result.operands[0].operator).to.equal("MatchesPath"); + expect(result.operands[1].operator).to.equal("AND"); + expect(result.operands[1].operands[0].operator).to.equal("Equals"); + expect(result.operands[1].operands[1].operator).to.equal("Equals"); + }); + }); + + describe("Invalid Operators", function () { + it("rejects double equals as an operator", function () { + const c1 = `request.formparam.grant_type == "authorization_code"`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + expect(e.toString()).to.include('but "="'); + } + try { + parser.parse(c1.replace("==", "=")); + // no error + expect(true); + } catch (e) { + expect.fail(); + } + }); + + it("rejects SeemsLike as an operator", function () { + const c1 = `request.formparam.grant_type SeemsLike "authorization_code"`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + }); + }); + + describe("OR statements", function () { + it("treats OR and || as synonyms", function () { + const c1 = '(request.verb = "PUT") or (request.verb = "POST")'; + const result1 = parser.parse(c1); + const c2 = c1.replace("or", "||"); + const result2 = parser.parse(c2); + expect(JSON.stringify(result1)).to.equal(JSON.stringify(result2)); + }); + }); + + describe("implicit parens", function () { + it("successfully parses compound statements with no parens", function () { + const c1 = 'proxy.pathsuffix ~ "/authorize" and request.verb = "POST"'; + const result1 = parser.parse(c1); + }); + + it("successfully parses implicit parens-2", function () { + const c1 = '(proxy.pathsuffix ~ "/authorize") and request.verb = "POST"'; + parser.parse(c1); // no exception + // TODO: add some expects here + }); + + it("correctly parses compound statements with no parens", function () { + const c1 = 'proxy.pathsuffix ~ "/authorize" and request.verb = "POST"'; + const c2 = + '(proxy.pathsuffix ~ "/authorize") and (request.verb = "POST")'; + const result1 = parser.parse(c1); + const result2 = parser.parse(c2); + expect(JSON.stringify(result1)).to.equal(JSON.stringify(result2)); + }); + + it("correctly parses compound statements with no parens-2", function () { + const c1 = 'proxy.pathsuffix ~ "/authorize" and request.verb = "POST"'; + const c2 = 'proxy.pathsuffix ~ "/authorize" and (request.verb = "POST")'; + const result1 = parser.parse(c1); + const result2 = parser.parse(c2); + expect(JSON.stringify(result1)).to.equal(JSON.stringify(result2)); + }); + + it("correctly parses negated compound statements with no parens", function () { + const c1 = '!((seven = "5") AND (valid = false))'; + const c2 = '!(seven = "5" AND valid = false)'; + const result1 = parser.parse(c1); + const result2 = parser.parse(c2); + expect(JSON.stringify(result1)).to.equal(JSON.stringify(result2)); + }); + }); + + describe("Invalid Syntax", function () { + it("rejects curly braces in place of parens", function () { + const c1 = '{seven = "5"} AND {valid = false}'; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + }); + + it("rejects curly braces on RHS", function () { + const c1 = "variable-name = {5}"; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + }); + + it("rejects a missing double-quote", function () { + const c1 = `request.formparam.grant_type = "authorization_code`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + expect(e.toString()).to.include('Expected "\\""'); + } + try { + parser.parse(c1 + '"'); + // no error + expect(true); + } catch (_e) { + expect.fail(); + } + }); + + it("rejects too many double-quotes", function () { + const c1 = `request.formparam.grant_type = "authorization_code""`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + expect(e.toString()).to.include("Expected [ \\t\\n] or end"); + } + try { + parser.parse(c1.slice(0, -1)); + // no error + expect(true); + } catch (_e) { + expect.fail(); + } + }); + + it("rejects single-quotes", function () { + const c1 = `request.formparam.grant_type = 'authorization_code'`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + try { + parser.parse(c1.replaceAll("'", '"')); + // no error + expect(true); + } catch (_e) { + expect.fail(); + } + }); + + it("rejects a single single-quote", function () { + const c1 = `request.formparam.grant_type = 'authorization_code`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + try { + parser.parse(c1.replaceAll("'", '"') + '"'); + // no error + expect(true); + } catch (_e) { + expect.fail(); + } + }); + + it("rejects symbol on RHS of equals", function () { + const c1 = `request.formparam.grant_type = authorization_code`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + expect(e.toString()).to.include('Expected "-"'); + } + try { + const result = parser.parse( + c1.replace("authorization_code", '"authorization_code"') + ); + // no error + expect(result).to.not.be.null; + } catch (_e) { + expect.fail(); + } + }); + + it("flags a missing close paren", function () { + const c1 = `(request.formparam.grant_type = "authorization_code"`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + expect(e.toString()).to.include('Expected ")"'); + } + try { + const result = parser.parse(c1.replace('code"', 'code")')); + // no error + expect(result).to.not.be.null; + } catch (_e) { + expect.fail(); + } + }); + + it("flags a stray close paren", function () { + const c1 = `request.formparam.grant_type = "authorization_code")`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + try { + const result = parser.parse(c1.slice(0, -1)); + // no error + expect(result).to.not.be.null; + } catch (_e) { + expect.fail(); + } + }); + + it("rejects a missing operand", function () { + const c1 = `request.formparam.grant_type = `; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + try { + const result = parser.parse(c1 + '"foo"'); + // no error + expect(result).to.not.be.null; + } catch (_e) { + expect.fail(); + } + }); + + it("flags a missing clause after conjunction", function () { + const c1 = `request.formparam.grant_type = "client_credentials" and `; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + try { + const result = parser.parse(c1 + "request.header.foo is null"); + // no error + expect(result).to.not.be.null; + } catch (_e) { + expect.fail(); + } + }); + + it("flags a doubled conjunction", function () { + const c1 = `request.formparam.grant_type = "client_credentials" and + and request.header.foo is null`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + try { + const result = parser.parse(c1.replace("and", "")); + // no error + expect(result).to.not.be.null; + } catch (_e) { + expect.fail(); + } + }); + + it("flags a quoted expression", function () { + const c1 = `"request.formparam.grant_type is null"`; + try { + parser.parse(c1); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + try { + const result = parser.parse(c1.replaceAll('"', "")); + // no error + expect(result).to.not.be.null; + } catch (_e) { + expect.fail(); + } + }); + }); + + describe("Operator translation", function () { + const cases = [ + { + expression: 'A := "valid"', + longFormOperator: "EqualsCaseInsensitive" + }, + { expression: 'A = "valid"', longFormOperator: "Equals" }, + { expression: 'A != "valid"', longFormOperator: "NotEquals" }, + { expression: 'A ~~ "foobar[a-z]+"', longFormOperator: "JavaRegex" }, + { expression: 'A ~/ "/foo/bar"', longFormOperator: "MatchesPath" }, + { expression: 'A =| "something"', longFormOperator: "StartsWith" }, + { expression: "A >= 20", longFormOperator: "GreaterThanOrEquals" }, + { expression: "A > 20", longFormOperator: "GreaterThan" }, + { expression: "A <= 20", longFormOperator: "LesserThanOrEquals" }, + { expression: "A < 20", longFormOperator: "LesserThan" } + ]; + cases.forEach((testcase) => { + it(`verifies that ${testcase.expression} is parsed as ${testcase.longFormOperator}`, function () { + try { + const result = parser.parse(testcase.expression); + expect(result.operator).to.equal(testcase.longFormOperator); + } catch (_e) { + expect.fail(_e); + } + }); + }); + }); + + describe("Mismatch between Operator and operand", function () { + const cases = [ + { expression: 'A > "valid"' }, + { expression: "A ~~ 20" }, + { expression: "A ~/ 42" }, + { expression: "A =| 103" }, + { expression: 'A >= "something"' }, + { expression: 'A > "something"' }, + { expression: 'A <= "something"' }, + { expression: 'A < "something"' } + ]; + cases.forEach((testcase) => { + it(`verifies that ${testcase.expression} is rejected as invalid`, function () { + try { + const _result = parser.parse(testcase.expression); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + }); + }); + }); + + describe("Parsing valid numerics", function () { + const cases = [ + { expression: "A > 20" }, + { expression: "A > 20.1" }, + { expression: "A > 20.1392" }, + { expression: "A > -20.1392" }, + { expression: "A > -250" }, + { expression: "A >= 120.1392" }, + { expression: "A < 0.5" }, + { expression: "A < -0.5" } + ]; + cases.forEach((testcase) => { + it(`verifies that ${testcase.expression} is accepted as valid`, function () { + try { + const _result = parser.parse(testcase.expression); + expect(true); + } catch (_e) { + console.log(_e); + expect.fail(); + } + }); + }); + }); + + describe("Parsing invalid numerics", function () { + const cases = [ + { expression: "A > 20.20.20" }, + { expression: "A > .20.1" }, + { expression: "A > ..201392" }, + { expression: "A > -20..1392" }, + { expression: "A > 20..1392" } + ]; + cases.forEach((testcase) => { + it(`verifies that ${testcase.expression} is rejected as invalid`, function () { + try { + const _result = parser.parse(testcase.expression); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + }); + }); + }); + + describe("Non-variables on LHS", function () { + const cases = [ + { expression: '"seventy-two" > 20' }, + { expression: '"a-string" = "another-string"' }, + { expression: '20 = "another-string"' }, + { expression: '20three = "another-string"' }, + { expression: "20 = 42" } + ]; + cases.forEach((testcase) => { + it(`verifies that ${testcase.expression} is rejected as invalid`, function () { + try { + const _result = parser.parse(testcase.expression); + expect.fail(); + } catch (e) { + expect(e.toString()).to.include("SyntaxError"); + } + }); + }); + }); +});