Repository: quarkusio/quarkus
Issue: Qute: fishy `#if` expressions

I tried several variations of `{#if foo.bar == 'something' || foo.bar == 'other'}` and got mixed results, but not valid results. I think the second boolean operand was ignored, or random.

For some reason, `{#if (foo.bar == 'something') || (foo.bar == 'other')}` got me an error.

Changed files:
independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java
independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java
independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java
independent-projects/qute/core/src/main/java/io/quarkus/qute/Expressions.java
independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java

diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expressions.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expressions.java
index 2600b77e693..4d7d08cf5fc 100644
--- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expressions.java
+++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expressions.java
@@ -71,7 +71,7 @@ public static List<String> splitParts(String value, SplitConfig splitConfig) {
         char separator = 0;
         byte infix = 0;
         byte brackets = 0;
-        ImmutableList.Builder<String> builder = ImmutableList.builder();
+        ImmutableList.Builder<String> parts = ImmutableList.builder();
         StringBuilder buffer = new StringBuilder();
         for (int i = 0; i < value.length(); i++) {
             char c = value.charAt(i);
@@ -82,9 +82,7 @@ public static List<String> splitParts(String value, SplitConfig splitConfig) {
                         if (splitConfig.shouldPrependSeparator(c)) {
                             buffer.append(c);
                         }
-                        if (buffer.length() > 0) {
-                            // Flush the part
-                            builder.add(buffer.toString());
+                        if (addPart(buffer, parts)) {
                             buffer = new StringBuilder();
                         }
                         if (splitConfig.shouldAppendSeparator(c)) {
@@ -110,13 +108,13 @@ public static List<String> splitParts(String value, SplitConfig splitConfig) {
                             // Next infix method
                             infix = 1;
                             buffer.append(RIGHT_BRACKET);
-                            builder.add(buffer.toString());
-                            buffer = new StringBuilder();
+                            if (addPart(buffer, parts)) {
+                                buffer = new StringBuilder();
+                            }
                         } else {
                             // First space - start infix method
                             infix++;
-                            if (buffer.length() > 0) {
-                                builder.add(buffer.toString());
+                            if (addPart(buffer, parts)) {
                                 buffer = new StringBuilder();
                             }
                         }
@@ -138,10 +136,25 @@ public static List<String> splitParts(String value, SplitConfig splitConfig) {
         if (infix > 0) {
             buffer.append(RIGHT_BRACKET);
         }
-        if (buffer.length() > 0) {
-            builder.add(buffer.toString());
+        addPart(buffer, parts);
+        return parts.build();
+    }
+
+    /**
+     * 
+     * @param buffer
+     * @param parts
+     * @return true if a new buffer should be created
+     */
+    private static boolean addPart(StringBuilder buffer, ImmutableList.Builder<String> parts) {
+        if (buffer.length() == 0) {
+            return false;
+        }
+        String val = buffer.toString().trim();
+        if (!val.isEmpty()) {
+            parts.add(val);
         }
-        return builder.build();
+        return true;
     }
 
     private static final SplitConfig DEFAULT_SPLIT_CONFIG = new DefaultSplitConfig();
diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java
index 3cdc5a4f302..1c42d06ef72 100644
--- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java
+++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java
@@ -191,6 +191,11 @@ public Operator getOperator() {
             return operator;
         }
 
+        @Override
+        public String toString() {
+            return "OperandCondition [operator=" + operator + ", expression=" + expression.toOriginalString() + "]";
+        }
+
     }
 
     static class CompositeCondition implements Condition {
@@ -272,6 +277,11 @@ public boolean isEmpty() {
             return conditions.isEmpty();
         }
 
+        @Override
+        public String toString() {
+            return "CompositeCondition [conditions=" + conditions.size() + ", operator=" + operator + "]";
+        }
+
     }
 
     enum Operator {
diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java
index d9a01629402..edc071b12ac 100644
--- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java
+++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java
@@ -486,7 +486,11 @@ private void processParams(String tag, String label, Iterator<String> iter) {
         List<String> paramValues = new ArrayList<>();
 
         while (iter.hasNext()) {
-            paramValues.add(iter.next());
+            // Ignore whitespace strings
+            String val = iter.next().trim();
+            if (!val.isEmpty()) {
+                paramValues.add(val);
+            }
         }
         if (paramValues.size() > factoryParams.size()) {
             LOGGER.debugf("Too many params [label=%s, params=%s, factoryParams=%s]", label, paramValues, factoryParams);
@@ -549,9 +553,12 @@ private void processParams(String tag, String label, Iterator<String> iter) {
      *
      * @param part
      * @return the index of an equals char outside of any string literal,
-     *         <code>-1</code> if no such char is found
+     *         <code>-1</code> if no such char is found or if the part represents a composite param
      */
     static int getFirstDeterminingEqualsCharPosition(String part) {
+        if (!part.isEmpty() && part.charAt(0) == START_COMPOSITE_PARAM) {
+            return -1;
+        }
         boolean stringLiteral = false;
         for (int i = 0; i < part.length(); i++) {
             if (LiteralSupport.isStringLiteralSeparator(part.charAt(i))) {
diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java
index 4ad57315d65..497d3bf6ccd 100644
--- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java
+++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java
@@ -76,6 +76,37 @@ public void testCompositeParameters() {
         assertEquals("OK", engine.parse("{#if (foo || false || true) && (true)}OK{/if}").render());
         assertEquals("NOK", engine.parse("{#if foo || false}OK{#else}NOK{/if}").render());
         assertEquals("OK", engine.parse("{#if false || (foo || (false || true))}OK{#else}NOK{/if}").render());
+        assertEquals("NOK", engine.parse("{#if (true && false)}OK{#else}NOK{/if}").render());
+        assertEquals("OK", engine.parse("{#if true && true}OK{#else}NOK{/if}").render());
+        assertEquals("NOK", engine.parse("{#if true && false}OK{#else}NOK{/if}").render());
+        assertEquals("NOK", engine.parse("{#if false && true}OK{#else}NOK{/if}").render());
+        assertEquals("OK", engine.parse("{#if true and (true or false)}OK{#else}NOK{/if}").render());
+        assertEquals("NOK", engine.parse("{#if true and (true == false)}OK{#else}NOK{/if}").render());
+        assertEquals("OK", engine.parse("{#if true && (false == false)}OK{#else}NOK{/if}").render());
+
+        Map<String, String> foo = new HashMap<>();
+        foo.put("bar", "something");
+        assertEquals("NOK",
+                engine.parse("{#if foo.bar != 'something' && foo.bar != 'other'}OK{#else}NOK{/if}").data("foo", foo).render());
+        assertEquals("OK",
+                engine.parse("{#if foo.bar != 'nothing' && foo.bar != 'other'}OK{#else}NOK{/if}").data("foo", foo).render());
+        assertEquals("OK",
+                engine.parse("{#if foo.bar == 'something' || foo.bar != 'other'}OK{#else}NOK{/if}").data("foo", foo).render());
+        assertEquals("OK", engine.parse("{#if (foo.bar == 'something') || (foo.bar == 'other')}OK{#else}NOK{/if}")
+                .data("foo", foo).render());
+
+        Map<String, String> qual = new HashMap<>();
+        Template template = engine.parse(
+                "{#if qual.name != 'javax.inject.Named' \\n"
+                        + "          && qual.name != 'javax.enterprise.inject.Any'\\n"
+                        + "          && qual.name != 'javax.enterprise.inject.Default'}{qual.name}{/if}");
+        qual.put("name", "org.acme.MyQual");
+        assertEquals("org.acme.MyQual", template
+                .data("qual", qual).render());
+        qual.put("name", "javax.enterprise.inject.Any");
+        assertEquals("", template
+                .data("qual", qual).render());
+
     }
 
     @Test
diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java
index ed042bab6a3..7d06822b1e4 100644
--- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java
+++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java
@@ -170,12 +170,18 @@ public void testSectionParameters() {
                 1);
         assertParserError("{#if (foo || bar}{/}",
                 "Parser error on line 1: unterminated string literal or composite parameter detected for [#if (foo || bar]", 1);
+        assertParams("item.name == 'foo' and item.name is false", "item.name", "==", "'foo'", "and", "item.name", "is",
+                "false");
+        assertParams("(item.name == 'foo') and (item.name is false)", "(item.name == 'foo')", "and", "(item.name is false)");
+        assertParams("(item.name != 'foo') || (item.name == false)", "(item.name != 'foo')", "||", "(item.name == false)");
     }
 
     @Test
     public void testWhitespace() {
         Engine engine = Engine.builder().addDefaults().build();
         assertEquals("Hello world", engine.parse("{#if true  }Hello {name }{/if  }").data("name", "world").render());
+        assertEquals("Hello world", engine.parse("{#if true \\n }Hello {name }{/if  }").data("name", "world").render());
+        assertEquals("Hello world", engine.parse("{#if true \\n || false}Hello {name }{/if  }").data("name", "world").render());
         assertEquals("Hello world", engine.parse("Hello {name ?: 'world'  }").render());
     }