You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2025/09/01 13:00:18 UTC

(commons-jexl) branch master updated: JEXL-440: moving tests in dedicated class; - refined parsing exception error reporting;

This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new 8461d83e JEXL-440: moving tests in dedicated class; - refined parsing exception error reporting;
8461d83e is described below

commit 8461d83ed53b90637c94c29492cc5dd72f839d84
Author: Henrib <hb...@gmail.com>
AuthorDate: Mon Sep 1 15:00:08 2025 +0200

    JEXL-440: moving tests in dedicated class;
    - refined parsing exception error reporting;
---
 .../apache/commons/jexl3/internal/Interpreter.java |  15 +-
 .../org/apache/commons/jexl3/parser/Parser.jjt     |   6 +-
 .../org/apache/commons/jexl3/Issues400Test.java    |  75 +--------
 .../java/org/apache/commons/jexl3/JexlTest.java    |  29 ++--
 .../java/org/apache/commons/jexl3/SwitchTest.java  | 174 +++++++++++++++++++++
 5 files changed, 199 insertions(+), 100 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index 486e2e12..4046f761 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -977,7 +977,7 @@ public class Interpreter extends InterpreterBase {
      * Short-circuit evaluation of logical expression.
      * @param check the fuse value that will stop evaluation, true for OR, false for AND
      * @param node a ASTAndNode or a ASTOrNode
-     * @param data
+     * @param data the data, usually null and unused
      * @return true or false if boolean logical option is true, the last evaluated argument otherwise
      */
     private Object shortCircuit(final boolean check, final JexlNode node, final Object data) {
@@ -2016,7 +2016,13 @@ public class Interpreter extends InterpreterBase {
     protected Object visit(final ASTSwitchExpression node, final Object data) {
         final Object value = node.jjtGetChild(0).jjtAccept(this, data);
         final int index = node.switchIndex(value);
-        return index >= 0 ? node.jjtGetChild(index).jjtAccept(this, data) : null;
+        if (index >= 0) {
+          return node.jjtGetChild(index).jjtAccept(this, data);
+        }
+        if (isStrictEngine()) {
+          throw new JexlException(node, "no case in switch expression for: " + value);
+        }
+        return null;
     }
 
     @Override
@@ -2037,6 +2043,9 @@ public class Interpreter extends InterpreterBase {
             }
             return value;
         }
+        if (isStrictEngine()) {
+            throw new JexlException(node, "no case in switch statement for: " + value);
+        }
         return null;
     }
 
@@ -2288,7 +2297,7 @@ public class Interpreter extends InterpreterBase {
     /**
      * Runs a node.
      * @param node the node
-     * @param data the usual data
+     * @param data the usual data, always null
      * @return the return value
      */
     protected Object visitLexicalNode(final JexlNode node, final Object data) {
diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
index 5537abc9..1e11a37b 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -92,11 +92,7 @@ public final class Parser extends JexlParser
             throw new JexlException.Tokenization(info, xtme).clean();
         } catch (ParseException xparse) {
             Token errortok = errorToken(jj_lastpos, jj_scanpos, token.next, token);
-            String msg = xparse.getMessage();
-            if (msg.endsWith(";")) {
-              msg = errortok.image;
-            }
-            throw new JexlException.Parsing(info.at(errortok.beginLine, errortok.beginColumn), msg).clean();
+            throw new JexlException.Parsing(info.at(errortok.beginLine, errortok.beginColumn), errortok.image).clean();
         } finally {
             token_source.defaultLexState = DEFAULT;
             cleanup(previous);
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index efc84ae4..3c5eb7ad 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -192,7 +192,7 @@ public class Issues400Test {
         final Map<String, Object> b = Collections.singletonMap("c", 42);
         final Map<String, Object> a = Collections.singletonMap("b", b);
         JexlScript script;
-        Object result = -42;
+        Object result;
         script = jexl.createScript("a?['B']?['C']", "a");
         result = script.execute(null, a);
         assertEquals(script.getSourceText(), script.getParsedText());
@@ -769,79 +769,6 @@ public class Issues400Test {
         Assertions.assertFalse((boolean) sqle.createScript("a != 25", "a").execute(null, 25));
     }
 
-    @Test
-    void test440a() {
-        JexlFeatures f = JexlFeatures.createDefault().ambiguousStatement(true);
-        JexlEngine jexl = new JexlBuilder().features(f).safe(false).strict(true).create();
-        String src = "let y = switch (x) { case 10,11 -> 3 case 20, 21 -> 4\n" +
-                "default -> { let z = 4; z + x } } y";
-        JexlScript script = jexl.createScript(src, "x");
-        assertNotNull(script);
-        String dbgStr = script.getParsedText();
-        assertNotNull(dbgStr);
-
-        Object result = script.execute(null, 10);
-        Assertions.assertEquals(3, result);
-        result = script.execute(null, 11);
-        Assertions.assertEquals(3, result);
-        result = script.execute(null, 20);
-        Assertions.assertEquals(4, result);
-        result = script.execute(null, 21);
-        Assertions.assertEquals(4, result);
-        result = script.execute(null, 38);
-        Assertions.assertEquals(42, result);
-        src = "let y = switch (x) { case 10,11 -> break; case 20, 21 -> 4; } y";
-        try {
-            script = jexl.createScript(src, "x");
-            fail("should not be able to create script with break in switch");
-        } catch (JexlException.Parsing xparse) {
-            assertTrue(xparse.getMessage().contains("break"));
-        }
-        assertNotNull(script);
-    }
-    @Test
-    void test440b() {
-        JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
-        final String src =
-"switch (x) { case 10 : return 3; case 20 : case 21 : return 4; case 32: break; default : return x + 4; } 169";
-        final JexlScript script = jexl.createScript(src, "x");
-        assertNotNull(script);
-        String dbgStr = script.getParsedText();
-        assertNotNull(dbgStr);
-
-        Object result = script.execute(null, 10);
-        Assertions.assertEquals(3, result);
-        result = script.execute(null, 20);
-        Assertions.assertEquals(4, result);
-        result = script.execute(null, 21);
-        Assertions.assertEquals(4, result);
-        result = script.execute(null, 32);
-        Assertions.assertEquals(169, result);
-        result = script.execute(null, 38);
-        Assertions.assertEquals(42, result);
-    }
-
-    public enum Scope440 {
-        UNDEFINED, UNDECLARED, GLOBAL, LOCAL, THIS, SUPER;
-    }
-
-    @Test
-    void test440c() {
-        JexlEngine jexl = new JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
-        final String src = "let s = switch (x) { case Scope440.UNDEFINED -> 'undefined'; case Scope440.THIS -> 'this'; default -> 'OTHER'; } s";
-        final JexlScript script = jexl.createScript(src, "x");
-        assertNotNull(script);
-        String dbgStr = script.getParsedText();
-        assertNotNull(dbgStr);
-
-        Object result = script.execute(null, Scope440.UNDEFINED);
-        Assertions.assertEquals("undefined", result);
-        result = script.execute(null, Scope440.THIS);
-        Assertions.assertEquals("this", result);
-        result = script.execute(null, 21);
-        Assertions.assertEquals("OTHER", result);
-    }
-
     @Test
     void testIssue441() {
         JexlEngine jexl = new JexlBuilder().create();
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTest.java b/src/test/java/org/apache/commons/jexl3/JexlTest.java
index 0b94a95f..35de2b5b 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTest.java
@@ -92,7 +92,7 @@ public final class JexlTest extends JexlTestCase {
      * Asserts that the given expression returns the given value when applied to the
      * given context
      */
-    protected void assertExpression(final JexlContext jc, final String expression, final Object expected) {
+    private void assertExpression(final JexlContext jc, final String expression, final Object expected) {
         final JexlExpression e = JEXL.createExpression(expression);
         final Object actual = e.evaluate(jc);
         assertEquals(expected, actual, expression);
@@ -136,7 +136,6 @@ public final class JexlTest extends JexlTestCase {
 
     /**
      * Test assignment.
-     * @throws Exception
      */
     @Test
     void testAssignment() {
@@ -151,14 +150,12 @@ public final class JexlTest extends JexlTestCase {
         assertEquals("world", jc.get("hello"), "hello variable not changed");
         assertExpression(jc, "result = 1 + 1", Integer.valueOf(2));
         assertEquals(Integer.valueOf(2), jc.get("result"), "result variable not changed");
-        // todo: make sure properties can be assigned to, fall back to flat var if no property
-        // assertExpression(jc, "foo.property1 = '99'", "99");
-        // assertEquals("property not set", "99", foo.getProperty1());
+        assertExpression(jc, "foo.property1 = '99'", "99");
+        assertEquals("99", foo.getProperty1(), "property not set");
     }
 
     /**
      * Make sure bad syntax throws ParseException
-     * @throws Exception on errors
      */
     @Test
     void testBadParse() {
@@ -196,7 +193,6 @@ public final class JexlTest extends JexlTestCase {
 
     /**
      * Test that 'and' only evaluates the second item if needed
-     * @throws Exception if there are errors
      */
     @Test
     void testBooleanShortCircuitAnd() {
@@ -218,7 +214,6 @@ public final class JexlTest extends JexlTestCase {
 
     /**
      * Test that 'or' only evaluates the second item if needed
-     * @throws Exception if there are errors
      */
     @Test
     void testBooleanShortCircuitOr() {
@@ -255,7 +250,7 @@ public final class JexlTest extends JexlTestCase {
         assertExpression(jc, "stringy + 2", "thingy2");
 
         /*
-         * test new null coersion
+         * test new null coercion
          */
         jc.set("imanull", null);
         assertExpression(jc, "imanull + 2", Integer.valueOf(2));
@@ -267,7 +262,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     /**
-     * Attempts to recreate bug https://jira.werken.com/ViewIssue.jspa?key=JELLY-8
+     * Attempts to recreate bug <a href="https://jira.werken.com/ViewIssue.jspa?key=JELLY-8">...</a>
      */
     @Test
     void testCharAtBug() {
@@ -284,7 +279,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    void testCoercionWithComparisionOperators() {
+    void testCoercionWithComparisonOperators() {
         final JexlContext jc = new MapContext();
 
         assertExpression(jc, "'2' > 1", Boolean.TRUE);
@@ -304,7 +299,6 @@ public final class JexlTest extends JexlTestCase {
 
     /**
      * Test the ## comment in a string
-     * @throws Exception
      */
     @Test
     void testComment() {
@@ -414,7 +408,7 @@ public final class JexlTest extends JexlTestCase {
         jc.set("longstring", "thingthing");
 
         /*
-         *  I can't believe anyone thinks this is a syntax.. :)
+         *  I can't believe anyone thinks this is a syntax... :)
          */
         assertExpression(jc, "empty nullthing", Boolean.TRUE);
         assertExpression(jc, "empty string", Boolean.TRUE);
@@ -454,7 +448,7 @@ public final class JexlTest extends JexlTestCase {
         jc.set("b", Boolean.FALSE);
         jc.set("num", Integer.valueOf(5));
         jc.set("now", Calendar.getInstance().getTime());
-        final GregorianCalendar gc = new GregorianCalendar(5000, 11, 20);
+        final GregorianCalendar gc = new GregorianCalendar(5000, Calendar.DECEMBER, 20);
         jc.set("now2", gc.getTime());
         jc.set("bdec", new BigDecimal("7"));
         jc.set("bint", new BigInteger("7"));
@@ -513,7 +507,7 @@ public final class JexlTest extends JexlTestCase {
     void testIntProperty() {
         final Foo foo = new Foo();
 
-        // lets check the square function first.
+        // let's check the square function first.
         assertEquals(4, foo.square(2));
         assertEquals(4, foo.square(-2));
 
@@ -574,7 +568,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     /**
-     * test the new function e.g constructor invocation
+     * Test the new function e.g. constructor invocation.
      */
     @Test
     void testNew() {
@@ -762,8 +756,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     /**
-     * Simple test of '+' as a string concatenation operator
-     * @throws Exception
+     * Simple test of '+' as a string concatenation operator.
      */
     @Test
     void testStringConcatenation() {
diff --git a/src/test/java/org/apache/commons/jexl3/SwitchTest.java b/src/test/java/org/apache/commons/jexl3/SwitchTest.java
new file mode 100644
index 00000000..986bc477
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/SwitchTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.commons.jexl3;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Tests switch/case statement and expression.
+ */
+@SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"})
+public class SwitchTest extends JexlTestCase {
+
+  public SwitchTest() {
+    super("SwitchTest");
+  }
+
+  @Test
+  void testSwitchExpression() {
+    JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
+    final JexlScript e = jexl.createScript("var j = switch(i) { case 1 -> 2; case 2 -> 3; default -> 4; }; j", "i");
+    final JexlContext jc = new MapContext();
+    final Object o = e.execute(jc, 1);
+    assertEquals(2, o);
+  }
+
+  @Test
+  void testBrokenSwitchExpression0() {
+    JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
+    try {
+      final JexlScript e = jexl.createScript("var j = switch(i) { case 1 -> return 2;  }; j", "i");
+      fail("should not be able to create script with return in switch expression");
+    } catch (JexlException.Parsing xparse) {
+      assertTrue(xparse.getMessage().contains("return"));
+    }
+    try {
+      final JexlScript e = jexl.createScript("var j = switch(i) { case 1 -> break; }; j", "i");
+      fail("should not be able to create script with break in switch expression");
+    } catch (JexlException.Parsing xparse) {
+      assertTrue(xparse.getMessage().contains("break"));
+    }
+  }
+
+  @Test
+  void testSwitchStatement() {
+    JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
+    final JexlScript e = jexl.createScript("switch(i) { case 1: i += 2; case 2: i += 3; default: i += 4; }; i + 33", "i");
+    final JexlContext jc = new MapContext();
+    final Object o = e.execute(jc, 2);
+    assertEquals(42, o);
+  }
+
+  @Test
+  void test440a() {
+    JexlFeatures f = JexlFeatures.createDefault().ambiguousStatement(true);
+    JexlEngine jexl = new JexlBuilder().features(f).safe(false).strict(true).create();
+    String src =
+            "let y = switch (x) { case 10,11 -> 3 case 20, 21 -> 4\n" + "default -> { let z = 4; z + x } } y";
+    JexlScript script = jexl.createScript(src, "x");
+    assertNotNull(script);
+    String dbgStr = script.getParsedText();
+    assertNotNull(dbgStr);
+
+    Object result = script.execute(null, 10);
+    Assertions.assertEquals(3, result);
+    result = script.execute(null, 11);
+    Assertions.assertEquals(3, result);
+    result = script.execute(null, 20);
+    Assertions.assertEquals(4, result);
+    result = script.execute(null, 21);
+    Assertions.assertEquals(4, result);
+    result = script.execute(null, 38);
+    Assertions.assertEquals(42, result);
+    src = "let y = switch (x) { case 10,11 -> break; case 20, 21 -> 4; } y";
+    try {
+      script = jexl.createScript(src, "x");
+      fail("should not be able to create script with break in switch");
+    } catch (JexlException.Parsing xparse) {
+      assertTrue(xparse.getMessage().contains("break"));
+    }
+    assertNotNull(script);
+  }
+
+  @Test
+  void test440b() {
+    JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
+    final String src =
+            "switch (x) { case 10 : return 3; case 20 : case 21 : return 4; case 32: break; default : return x + 4; } 169";
+    final JexlScript script = jexl.createScript(src, "x");
+    assertNotNull(script);
+    String dbgStr = script.getParsedText();
+    assertNotNull(dbgStr);
+
+    Object result = script.execute(null, 10);
+    Assertions.assertEquals(3, result);
+    result = script.execute(null, 20);
+    Assertions.assertEquals(4, result);
+    result = script.execute(null, 21);
+    Assertions.assertEquals(4, result);
+    result = script.execute(null, 32);
+    Assertions.assertEquals(169, result);
+    result = script.execute(null, 38);
+    Assertions.assertEquals(42, result);
+  }
+
+  public enum Scope440 {
+    UNDEFINED, UNDECLARED, GLOBAL, LOCAL, THIS, SUPER;
+  }
+
+  @Test
+  void test440c() {
+    JexlEngine jexl = new JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
+    final String src =
+            "let s = switch (x) { case Scope440.UNDEFINED -> 'undefined'; case Scope440.THIS -> 'this'; default -> 'OTHER'; } s";
+    final JexlScript script = jexl.createScript(src, "x");
+    assertNotNull(script);
+    String dbgStr = script.getParsedText();
+    assertNotNull(dbgStr);
+
+    Object result = script.execute(null, Scope440.UNDEFINED);
+    Assertions.assertEquals("undefined", result);
+    result = script.execute(null, Scope440.THIS);
+    Assertions.assertEquals("this", result);
+    result = script.execute(null, 21);
+    Assertions.assertEquals("OTHER", result);
+  }
+
+  @Test
+  void test440d() {
+    JexlEngine jexl = new JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
+    final String src = "let s = switch (x) { case Scope440.UNDEFINED -> 'undefined'; } s";
+    final JexlScript script = jexl.createScript(src, "x");
+    try {
+      script.execute(null, Scope440.THIS);
+      fail("should not be able to execute script with switch expression with no default");
+    } catch (JexlException xjexl) {
+      assertTrue(xjexl.getMessage().contains("switch"));
+    }
+  }
+
+  @Test
+  void test440e() {
+    JexlEngine jexl = new JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
+    final String src = "function f(x) { switch (x) { case Scope440.UNDEFINED : return 'undefined'; } } f(x)";
+    final JexlScript script = jexl.createScript(src, "x");
+    Object result = script.execute(null, Scope440.UNDEFINED);
+    Assertions.assertEquals("undefined", result);
+    try {
+      script.execute(null, Scope440.THIS);
+      fail("should not be able to execute script with switch expression with no default");
+    } catch (JexlException xjexl) {
+      assertTrue(xjexl.getMessage().contains("switch"));
+    }
+  }
+}