From c6872c4c629c151de935c30644c9c8a11244bdb4 Mon Sep 17 00:00:00 2001 From: Peter Wright Date: Tue, 27 Feb 2024 10:05:53 +0000 Subject: [PATCH] test: add coalesce operator tests --- .../nawforce/apexparser/ApexParserTest.java | 47 ++++++++++++++ npm/src/__tests__/ApexParserTest.ts | 65 ++++++++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/jvm/src/test/java/com/nawforce/apexparser/ApexParserTest.java b/jvm/src/test/java/com/nawforce/apexparser/ApexParserTest.java index f374ca1..8633c03 100644 --- a/jvm/src/test/java/com/nawforce/apexparser/ApexParserTest.java +++ b/jvm/src/test/java/com/nawforce/apexparser/ApexParserTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test; +import java.util.List; import java.util.Map; import static com.nawforce.apexparser.SyntaxErrorCounter.createParser; @@ -40,6 +41,52 @@ void testExpression() { assertEquals(2, ((ApexParser.Arth1ExpressionContext) context).expression().size()); } + @Test + void testCoalesceExpression() { + Map.Entry parserAndCounter = createParser("a ?? 5"); + ApexParser.ExpressionContext context = parserAndCounter.getKey().expression(); + assertTrue(context instanceof ApexParser.CoalExpressionContext); + assertEquals(2, ((ApexParser.CoalExpressionContext) context).expression().size()); + } + + @Test + void testCoalescePrecedence() { + Map.Entry parserAndCounter = createParser("top ?? 100 - bottom ?? 0"); + ApexParser.ExpressionContext context = parserAndCounter.getKey().expression(); + assertTrue(context instanceof ApexParser.CoalExpressionContext); + + List outer = ((ApexParser.CoalExpressionContext) context).expression(); + assertEquals(2, outer.size()); + + assertTrue(outer.get(0) instanceof ApexParser.CoalExpressionContext); + List inner = ((ApexParser.CoalExpressionContext) outer.get(0)).expression(); + assertEquals(2, inner.size()); + + assertTrue(inner.get(0) instanceof ApexParser.PrimaryExpressionContext); + assertTrue(inner.get(1) instanceof ApexParser.Arth2ExpressionContext); + + assertTrue(outer.get(1) instanceof ApexParser.PrimaryExpressionContext); + } + + @Test + void testCoalescePrecedenceBoolean() { + Map.Entry parserAndCounter = createParser("a ?? false || b ?? false"); + ApexParser.ExpressionContext context = parserAndCounter.getKey().expression(); + assertTrue(context instanceof ApexParser.CoalExpressionContext); + + List outer = ((ApexParser.CoalExpressionContext) context).expression(); + assertEquals(2, outer.size()); + + assertTrue(outer.get(0) instanceof ApexParser.CoalExpressionContext); + List inner = ((ApexParser.CoalExpressionContext) outer.get(0)).expression(); + assertEquals(2, inner.size()); + + assertTrue(inner.get(0) instanceof ApexParser.PrimaryExpressionContext); + assertTrue(inner.get(1) instanceof ApexParser.LogOrExpressionContext); + + assertTrue(outer.get(1) instanceof ApexParser.PrimaryExpressionContext); + } + @Test void testClass() { Map.Entry parserAndCounter = createParser("public class Hello {}"); diff --git a/npm/src/__tests__/ApexParserTest.ts b/npm/src/__tests__/ApexParserTest.ts index 310d1f4..6851844 100644 --- a/npm/src/__tests__/ApexParserTest.ts +++ b/npm/src/__tests__/ApexParserTest.ts @@ -12,8 +12,16 @@ derived from this software without specific prior written permission. */ import { - LiteralContext, Arth1ExpressionContext, CompilationUnitContext, - StatementContext, TriggerUnitContext, QueryContext + LiteralContext, + Arth1ExpressionContext, + CompilationUnitContext, + StatementContext, + TriggerUnitContext, + QueryContext, + CoalExpressionContext, + PrimaryExpressionContext, + Arth2ExpressionContext, + LogOrExpressionContext } from "../ApexParser"; import { ThrowingErrorListener, SyntaxException } from "../ThrowingErrorListener"; import { createParser } from "./SyntaxErrorCounter"; @@ -39,6 +47,59 @@ test('Expression', () => { expect(arthExpression.expression().length).toBe(2) }) +test("Coalesce Expression", () => { + const [parser, errorCounter] = createParser("a ?? 5"); + const context = parser.expression(); + + expect(errorCounter.getNumErrors()).toEqual(0); + expect(context).toBeInstanceOf(CoalExpressionContext); + const coalExpression = context as CoalExpressionContext; + expect(coalExpression.expression().length).toBe(2); +}); + +test("Coalesce Precedence - Arithmetic", () => { + // Based on the example in release notes / docs + // should NOT evaluate to (top ?? 100) - (bottom ?? 0) as you want + // + // left assoc = (top ?? (100 - bottom)) ?? 0 + // right assoc = top ?? ((100 - bottom) ?? 0) + const [parser, errorCounter] = createParser("top ?? 100 - bottom ?? 0"); + const context = parser.expression(); + + expect(errorCounter.getNumErrors()).toEqual(0); + expect(context).toBeInstanceOf(CoalExpressionContext); + const outer = (context as CoalExpressionContext).expression(); + expect(outer.length).toBe(2); + expect(outer[0]).toBeInstanceOf(CoalExpressionContext); + + const inner = (outer[0] as CoalExpressionContext).expression(); // top ?? 100 - bottom + expect(inner.length).toBe(2); + expect(inner[0]).toBeInstanceOf(PrimaryExpressionContext); // top + expect(inner[1]).toBeInstanceOf(Arth2ExpressionContext); // 100 - bottom + + expect(outer[1]).toBeInstanceOf(PrimaryExpressionContext); // 0 +}); + +test("Coalesce Precedence - Boolean", () => { + // This is more nonsense but using a much lower precedence op + // should NOT evaluate to (a ?? false) || (b ?? false) + const [parser, errorCounter] = createParser("a ?? false || b ?? false"); + const context = parser.expression(); + + expect(errorCounter.getNumErrors()).toEqual(0); + expect(context).toBeInstanceOf(CoalExpressionContext); + const outer = (context as CoalExpressionContext).expression(); + expect(outer.length).toBe(2); + expect(outer[0]).toBeInstanceOf(CoalExpressionContext); + + const inner = (outer[0] as CoalExpressionContext).expression(); // a ?? false || b + expect(inner.length).toBe(2); + expect(inner[0]).toBeInstanceOf(PrimaryExpressionContext); // a + expect(inner[1]).toBeInstanceOf(LogOrExpressionContext); // false || b + + expect(outer[1]).toBeInstanceOf(PrimaryExpressionContext); // false +}); + test('Compilation Unit', () => { const [parser, errorCounter] = createParser("public class Hello {}")