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

tiagobento pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git


The following commit(s) were added to refs/heads/main by this push:
     new e87a66faa5a kie-issues#1954: DMN Editor: Question mark character is 
marked as invalid by syntax highlighting (#3126)
e87a66faa5a is described below

commit e87a66faa5ae3d757d352b34f12896d9eacb3c8f
Author: Daniel José dos Santos <[email protected]>
AuthorDate: Mon May 19 10:51:53 2025 -0300

    kie-issues#1954: DMN Editor: Question mark character is marked as invalid 
by syntax highlighting (#3126)
---
 .../src/parser/IdentifiersRepository.ts            |   6 +-
 .../questionMark.dmn                               | 219 +++++++++++++
 .../tests/semanticTokensProvider.test.ts           | 354 +++++++++++++++++++++
 3 files changed, 576 insertions(+), 3 deletions(-)

diff --git 
a/packages/dmn-feel-antlr4-parser/src/parser/IdentifiersRepository.ts 
b/packages/dmn-feel-antlr4-parser/src/parser/IdentifiersRepository.ts
index 8ee1ea69e3f..d8793dd859d 100644
--- a/packages/dmn-feel-antlr4-parser/src/parser/IdentifiersRepository.ts
+++ b/packages/dmn-feel-antlr4-parser/src/parser/IdentifiersRepository.ts
@@ -760,14 +760,14 @@ export class IdentifiersRepository {
   }
 
   private addDecisionTableEntryNode(parent: IdentifierContext, entryNode: 
ExpressionSource) {
+    this.addExpression(parent, entryNode);
     const ruleInputElementNode = this.addIdentifierContext({
       uuid: entryNode["@_id"] ?? "",
-      identifierDefinedByTheContext: "",
-      kind: FeelSyntacticSymbolNature.LocalVariable,
+      identifierDefinedByTheContext: "?",
+      kind: FeelSyntacticSymbolNature.DynamicVariable,
       parentContext: parent,
     });
     parent.children.set(ruleInputElementNode.uuid, ruleInputElementNode);
-    this.addExpression(parent, entryNode);
   }
 
   private addDecisionTableInputEntryNode(parent: IdentifierContext, 
inputEntryNode: DMN15__tInputClause) {
diff --git 
a/packages/feel-input-component/tests-data/variables-inside-decision-tables/questionMark.dmn
 
b/packages/feel-input-component/tests-data/variables-inside-decision-tables/questionMark.dmn
new file mode 100644
index 00000000000..611d74a6578
--- /dev/null
+++ 
b/packages/feel-input-component/tests-data/variables-inside-decision-tables/questionMark.dmn
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~ 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
+  ~
+  ~   http://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.
+-->
+<dmn:definitions
+  xmlns:dmn="https://www.omg.org/spec/DMN/20230324/MODEL/";
+  xmlns="https://kie.apache.org/dmn/_CE3A59D2-54D8-4908-A31C-3B2F6EE550CA";
+  xmlns:feel="https://www.omg.org/spec/DMN/20230324/FEEL/";
+  xmlns:kie="https://kie.org/dmn/extensions/1.0";
+  xmlns:dmndi="https://www.omg.org/spec/DMN/20230324/DMNDI/";
+  xmlns:di="http://www.omg.org/spec/DMN/20180521/DI/";
+  xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/";
+  id="_69E82523-0C6D-47F6-ACDE-5295338676D0"
+  name="Question Mark"
+  typeLanguage="https://www.omg.org/spec/DMN/20230324/FEEL/";
+  namespace="https://kie.apache.org/dmn/_CE3A59D2-54D8-4908-A31C-3B2F6EE550CA";
+>
+  <dmn:extensionElements />
+  <dmn:itemDefinition id="_60E2C86D-5EFC-4226-B2C2-83E87F354182" 
name="tMyType" isCollection="false">
+    <dmn:itemComponent id="_6335ADA6-CBCC-4FDE-8816-B2E4092B6B0B" 
name="favoriteColors" isCollection="true">
+      <dmn:typeRef>string</dmn:typeRef>
+    </dmn:itemComponent>
+    <dmn:itemComponent id="_B00FC6F6-40E7-4282-956E-A560C68C7192" name="name" 
isCollection="false">
+      <dmn:typeRef>string</dmn:typeRef>
+    </dmn:itemComponent>
+  </dmn:itemDefinition>
+  <dmn:inputData id="_281F21FE-2071-47B7-9572-34AD16D69CC1" name="req">
+    <dmn:extensionElements />
+    <dmn:variable id="_F0394E1F-A77B-4293-8948-92CAAB3EA871" name="req" 
typeRef="tMyType" />
+  </dmn:inputData>
+  <dmn:decision id="_5528B743-8C96-44CA-A9F1-45AD1EAD4EE7" name="Decision with 
Decision Table">
+    <dmn:extensionElements />
+    <dmn:variable id="_42EB04BD-1AFB-48CF-B035-8F35011D37E8" name="Decision 
with Decision Table" typeRef="string" />
+    <dmn:informationRequirement id="_93F581DB-43CE-4C8F-A5E4-37031D287137">
+      <dmn:requiredInput href="#_281F21FE-2071-47B7-9572-34AD16D69CC1" />
+    </dmn:informationRequirement>
+    <dmn:decisionTable
+      id="_4D4FFE3E-A684-4980-91DE-796DBAE33EFD"
+      hitPolicy="UNIQUE"
+      preferredOrientation="Rule-as-Row"
+      label="Decision with Decision Table"
+      typeRef="string"
+    >
+      <dmn:input id="_062CDD09-AB9F-4C65-8903-98F71A2743E7">
+        <dmn:inputExpression typeRef="string" 
id="_E11A29B1-5C44-4529-9FD0-F86C58EFD1FF">
+          <dmn:text>req.favoriteColors</dmn:text>
+        </dmn:inputExpression>
+      </dmn:input>
+      <dmn:input id="_66B67FFD-E1C9-4D12-A96A-A2F27D106999">
+        <dmn:inputExpression typeRef="string" 
id="_D4160AF7-B481-4883-9FE1-55C2F091350C">
+          <dmn:text>req.name</dmn:text>
+        </dmn:inputExpression>
+      </dmn:input>
+      <dmn:output id="_57D3070E-5621-4B40-929E-C7332FC40050" />
+      <dmn:annotation name="annotation-1" />
+      <dmn:rule id="_7B425B87-FB86-4C88-BF4C-6C81C023D567">
+        <dmn:inputEntry id="_DACB979A-9CA4-4CC7-A831-D409B08EE061">
+          <dmn:text>contains(??, &quot;blue&quot;)</dmn:text>
+        </dmn:inputEntry>
+        <dmn:inputEntry id="_4EE4BBE9-E0F5-4124-8F68-4CC5BC894BFF">
+          <dmn:text />
+        </dmn:inputEntry>
+        <dmn:outputEntry id="_47574421-7923-48EE-8090-D93F1BA1DBA1">
+          <dmn:text>&quot;Everton&quot;</dmn:text>
+        </dmn:outputEntry>
+        <dmn:annotationEntry>
+          <dmn:text />
+        </dmn:annotationEntry>
+      </dmn:rule>
+      <dmn:rule id="_253BE395-4D58-4AB0-91C0-5863ECDCB7BB">
+        <dmn:inputEntry id="_965ED041-9234-474B-BC50-4A8527B7F722">
+          <dmn:text>-</dmn:text>
+        </dmn:inputEntry>
+        <dmn:inputEntry id="_74A11E3E-92DD-40CB-B783-7F19253ED02F">
+          <dmn:text>-</dmn:text>
+        </dmn:inputEntry>
+        <dmn:outputEntry id="_AD3D7879-F1A9-4E7A-B621-90C4061F662F">
+          <dmn:text>&quot;Other&quot;</dmn:text>
+        </dmn:outputEntry>
+        <dmn:annotationEntry>
+          <dmn:text />
+        </dmn:annotationEntry>
+      </dmn:rule>
+    </dmn:decisionTable>
+  </dmn:decision>
+  <dmn:decision name="Decision With Literal" 
id="_85D75DFC-65B0-45C6-930B-C21161DCE4AB">
+    <dmn:variable name="Decision With Literal" 
id="_F6B4B152-A250-487D-8FCE-3DC1FEDF97EA" />
+    <dmn:literalExpression id="_8A76C5EF-26C1-4522-B1B2-63B64D753799" 
label="Decision With Literal" />
+  </dmn:decision>
+  <dmn:decision name="Decision With Nested Decision Table" 
id="_8D4DF548-956C-49C4-92E9-2C72300FA700">
+    <dmn:variable name="Decision With Nested Decision Table" 
id="_93DF39A3-FA87-4C46-A9FE-BF198478ED2F" />
+    <dmn:context id="_FA91F6F0-AF29-488F-A971-CDF019D751F3" label="Decision 
With Nested Decision Table">
+      <dmn:contextEntry id="_DAF1F88C-9D79-4BFD-AE76-8CDF0296890E">
+        <dmn:variable id="_E5E70802-6AD7-424B-9457-8C3F8F6EAE22" 
name="ContextEntry-1" />
+        <dmn:decisionTable id="_1CF932CB-4CBB-4908-B8D7-E7A4A5473EF3" 
hitPolicy="UNIQUE" label="ContextEntry-1">
+          <dmn:input id="_B7FE38FE-8744-40EF-9FED-BB701CA0F18F">
+            <dmn:inputExpression id="_089C8716-B35D-410E-93DA-F92176F78009">
+              <dmn:text>Input-1</dmn:text>
+            </dmn:inputExpression>
+          </dmn:input>
+          <dmn:output id="_79A8482F-447D-4CD2-AC0D-5884D2723027" />
+          <dmn:annotation name="Annotations" />
+          <dmn:rule id="_73304FAC-CD01-4BD9-9712-9376EA2C42C6">
+            <dmn:inputEntry id="_56D207EF-0BB2-4127-85B1-BE0F0E095CCC">
+              <dmn:text />
+            </dmn:inputEntry>
+            <dmn:outputEntry id="_57A39F96-2671-41F4-8D0D-36893B549CDF">
+              <dmn:text />
+            </dmn:outputEntry>
+            <dmn:annotationEntry>
+              <dmn:text>// Your annotations here</dmn:text>
+            </dmn:annotationEntry>
+          </dmn:rule>
+        </dmn:decisionTable>
+      </dmn:contextEntry>
+      <dmn:contextEntry id="_D0082524-2D80-44B8-8713-C79D84DF3289">
+        <dmn:variable id="_6BE31EB7-4340-4ADB-979B-7F7CAE29E8CA" 
name="ContextEntry-2">
+          <dmn:description />
+        </dmn:variable>
+        <dmn:literalExpression id="_41D275A9-672A-440B-B93A-C44B76583C4E" 
label="ContextEntry-2" />
+      </dmn:contextEntry>
+      <dmn:contextEntry id="_5E9B54FB-81F0-4ED4-A399-DE298FAE385B" />
+    </dmn:context>
+  </dmn:decision>
+  <dmndi:DMNDI>
+    <dmndi:DMNDiagram id="_15ED4075-0AFB-4081-ABD7-5BDB1C18DAFA" name="DRG" 
useAlternativeInputDataShape="false">
+      <di:extension>
+        <kie:ComponentsWidthsExtension>
+          <kie:ComponentWidths 
dmnElementRef="_4D4FFE3E-A684-4980-91DE-796DBAE33EFD">
+            <kie:width>50</kie:width>
+            <kie:width>460</kie:width>
+            <kie:width>380</kie:width>
+            <kie:width>100</kie:width>
+            <kie:width>435</kie:width>
+          </kie:ComponentWidths>
+          <kie:ComponentWidths 
dmnElementRef="_8A76C5EF-26C1-4522-B1B2-63B64D753799">
+            <kie:width>190</kie:width>
+          </kie:ComponentWidths>
+          <kie:ComponentWidths 
dmnElementRef="_FA91F6F0-AF29-488F-A971-CDF019D751F3">
+            <kie:width>120</kie:width>
+          </kie:ComponentWidths>
+          <kie:ComponentWidths 
dmnElementRef="_1CF932CB-4CBB-4908-B8D7-E7A4A5473EF3">
+            <kie:width>60</kie:width>
+            <kie:width>118</kie:width>
+            <kie:width>118</kie:width>
+            <kie:width>240</kie:width>
+          </kie:ComponentWidths>
+          <kie:ComponentWidths 
dmnElementRef="_41D275A9-672A-440B-B93A-C44B76583C4E">
+            <kie:width>190</kie:width>
+          </kie:ComponentWidths>
+        </kie:ComponentsWidthsExtension>
+      </di:extension>
+      <dmndi:DMNShape
+        id="dmnshape-drg-_281F21FE-2071-47B7-9572-34AD16D69CC1"
+        dmnElementRef="_281F21FE-2071-47B7-9572-34AD16D69CC1"
+        isCollapsed="false"
+      >
+        <dmndi:DMNStyle id="_3267F470-7BB8-49B2-8E2A-9F8080A0D86F">
+          <dmndi:FillColor red="255" green="255" blue="255" />
+          <dmndi:StrokeColor red="0" green="0" blue="0" />
+          <dmndi:FontColor red="0" green="0" blue="0" />
+        </dmndi:DMNStyle>
+        <dc:Bounds x="60" y="240" width="100" height="50" />
+        <dmndi:DMNLabel id="_FD8C9A3D-B0DC-4316-9897-0DC9C5044611" />
+      </dmndi:DMNShape>
+      <dmndi:DMNShape
+        id="dmnshape-drg-_5528B743-8C96-44CA-A9F1-45AD1EAD4EE7"
+        dmnElementRef="_5528B743-8C96-44CA-A9F1-45AD1EAD4EE7"
+        isCollapsed="false"
+      >
+        <dmndi:DMNStyle id="_B9BCBD88-EB71-4CCD-BCEF-61D7BC847A26">
+          <dmndi:FillColor red="255" green="255" blue="255" />
+          <dmndi:StrokeColor red="0" green="0" blue="0" />
+          <dmndi:FontColor red="0" green="0" blue="0" />
+        </dmndi:DMNStyle>
+        <dc:Bounds x="60" y="40" width="100" height="50" />
+        <dmndi:DMNLabel id="_F9B159DB-6DB0-4D6A-AEA7-754F2DAA58C7" />
+      </dmndi:DMNShape>
+      <dmndi:DMNEdge
+        id="dmnedge-drg-_93F581DB-43CE-4C8F-A5E4-37031D287137-AUTO-TARGET"
+        dmnElementRef="_93F581DB-43CE-4C8F-A5E4-37031D287137"
+      >
+        <di:waypoint x="110" y="265" />
+        <di:waypoint x="110" y="90" />
+      </dmndi:DMNEdge>
+      <dmndi:DMNShape
+        id="_FD768D1D-607A-42BE-8577-1D5A0743D494"
+        dmnElementRef="_85D75DFC-65B0-45C6-930B-C21161DCE4AB"
+        isCollapsed="false"
+        isListedInputData="false"
+      >
+        <dc:Bounds x="280" y="40" width="160" height="80" />
+      </dmndi:DMNShape>
+      <dmndi:DMNShape
+        id="_DD3DF3BD-CF59-4684-B50A-41546BFB9124"
+        dmnElementRef="_8D4DF548-956C-49C4-92E9-2C72300FA700"
+        isCollapsed="false"
+        isListedInputData="false"
+      >
+        <dc:Bounds x="500" y="40" width="160" height="80" />
+      </dmndi:DMNShape>
+    </dmndi:DMNDiagram>
+  </dmndi:DMNDI>
+</dmn:definitions>
diff --git a/packages/feel-input-component/tests/semanticTokensProvider.test.ts 
b/packages/feel-input-component/tests/semanticTokensProvider.test.ts
index dbc3ca059dc..ab702e188d9 100644
--- a/packages/feel-input-component/tests/semanticTokensProvider.test.ts
+++ b/packages/feel-input-component/tests/semanticTokensProvider.test.ts
@@ -434,6 +434,360 @@ ThatShouldFailWhenBreakLine`,
       }
     });
   });
+
+  describe("Question mark symbol placeholder", () => {
+    const questionMarkTestModelPathRelativeToTheTestFile =
+      "../tests-data/variables-inside-decision-tables/questionMark.dmn";
+    const questionMarkModel = 
getDmnModelFromFilePath(questionMarkTestModelPathRelativeToTheTestFile);
+
+    describe("should recognize question mark placeholder inside Decision 
Tables", () => {
+      const ifExpression = 'if ?="blue" then "#0000FF" else "#000000"';
+      const containsExpression = 'contains(?, "blue")';
+      const everyExpression = 'every item in ? satisfies item = "blue"';
+
+      const feelIdentifiers = new FeelIdentifiers({
+        _readonly_dmnDefinitions: questionMarkModel.definitions,
+      });
+
+      const inputColumnId = "_DACB979A-9CA4-4CC7-A831-D409B08EE061";
+      const outputColumnId = "_4EE4BBE9-E0F5-4124-8F68-4CC5BC894BFF";
+      const inputColumnSemanticTokensProvider = new 
SemanticTokensProvider(feelIdentifiers, inputColumnId, () => {});
+      const outputColumnSemanticTokensProvider = new 
SemanticTokensProvider(feelIdentifiers, outputColumnId, () => {});
+
+      test("should recognize in 'if' inside input columns", async () => {
+        const modelMock = createModelMockForExpression(ifExpression);
+
+        const semanticMonacoTokens = await 
inputColumnSemanticTokensProvider.provideDocumentSemanticTokens(
+          modelMock as unknown as Monaco.editor.ITextModel,
+          null,
+          cancellationTokenMock
+        );
+
+        // Reserved keywords like "if", "then" and "else" are not returned by 
the FeelIdentifiers since they are
+        // reserved keywords used by FEEL handled outside by the Monaco as 
reserved words.
+        // Also, strings are handled by the Monaco.
+        const expected = [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: "if ".length,
+            tokenLength: "?".length,
+            tokenType: Element.DynamicVariable,
+          }),
+        ];
+
+        for (let i = 0; i < expected.length; i++) {
+          expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+        }
+      });
+
+      test("should recognize in 'contains' inside input columns", async () => {
+        const modelMock = createModelMockForExpression(containsExpression);
+
+        const semanticMonacoTokens = await 
inputColumnSemanticTokensProvider.provideDocumentSemanticTokens(
+          modelMock as unknown as Monaco.editor.ITextModel,
+          null,
+          cancellationTokenMock
+        );
+
+        // Reserved keywords like "contains" are not returned by the 
FeelIdentifiers since they are
+        // reserved keywords used by FEEL handled outside by the Monaco as 
reserved words.
+        // Also, strings are handled by the Monaco.
+        const expected = [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: "contains(".length,
+            tokenLength: "?".length,
+            tokenType: Element.DynamicVariable,
+          }),
+        ];
+
+        for (let i = 0; i < expected.length; i++) {
+          expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+        }
+      });
+
+      test("should recognize in 'every' inside input columns", async () => {
+        const modelMock = createModelMockForExpression(everyExpression);
+
+        const semanticMonacoTokens = await 
inputColumnSemanticTokensProvider.provideDocumentSemanticTokens(
+          modelMock as unknown as Monaco.editor.ITextModel,
+          null,
+          cancellationTokenMock
+        );
+
+        // Reserved keywords like "every", "in" and "satisfies" are not 
returned by the FeelIdentifiers since they are
+        // reserved keywords used by FEEL handled outside by the Monaco as 
reserved words.
+        // Also, "item" is not returned here because here is where it being 
declared so there is really nothing
+        // to identify and colorize.
+        const expected = [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: "every item in ".length,
+            tokenLength: "?".length,
+            tokenType: Element.DynamicVariable,
+          }),
+
+          // The declared "item" is identified here.
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: " satisfies ".length + 1,
+            tokenLength: "item".length,
+            tokenType: Element.Variable,
+          }),
+        ];
+
+        for (let i = 0; i < expected.length; i++) {
+          expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+        }
+      });
+
+      test("should recognize in 'if' inside output columns", async () => {
+        const modelMock = createModelMockForExpression(ifExpression);
+
+        const semanticMonacoTokens = await 
outputColumnSemanticTokensProvider.provideDocumentSemanticTokens(
+          modelMock as unknown as Monaco.editor.ITextModel,
+          null,
+          cancellationTokenMock
+        );
+
+        // Reserved keywords like "if", "then" and "else" are not returned by 
the FeelIdentifiers since they are
+        // reserved keywords used by FEEL handled outside by the Monaco as 
reserved words.
+        // Also, strings are handled by the Monaco.
+        const expected = [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: "if ".length,
+            tokenLength: "?".length,
+            tokenType: Element.DynamicVariable,
+          }),
+        ];
+
+        for (let i = 0; i < expected.length; i++) {
+          expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+        }
+      });
+
+      test("should recognize in 'contains' inside output columns", async () => 
{
+        const modelMock = createModelMockForExpression(containsExpression);
+
+        const semanticMonacoTokens = await 
outputColumnSemanticTokensProvider.provideDocumentSemanticTokens(
+          modelMock as unknown as Monaco.editor.ITextModel,
+          null,
+          cancellationTokenMock
+        );
+
+        // Reserved keywords like "contains" are not returned by the 
FeelIdentifiers since they are
+        // reserved keywords used by FEEL handled outside by the Monaco as 
reserved words.
+        // Also, strings are handled by the Monaco.
+        const expected = [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: "contains(".length,
+            tokenLength: "?".length,
+            tokenType: Element.DynamicVariable,
+          }),
+        ];
+
+        for (let i = 0; i < expected.length; i++) {
+          expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+        }
+      });
+
+      test("should recognize in 'every' inside output columns", async () => {
+        const modelMock = createModelMockForExpression(everyExpression);
+
+        const semanticMonacoTokens = await 
outputColumnSemanticTokensProvider.provideDocumentSemanticTokens(
+          modelMock as unknown as Monaco.editor.ITextModel,
+          null,
+          cancellationTokenMock
+        );
+
+        // Reserved keywords like "every", "in" and "satisfies" are not 
returned by the FeelIdentifiers since they are
+        // reserved keywords used by FEEL handled outside by the Monaco as 
reserved words.
+        // Also, "item" is not returned here because here is where it being 
declared so there is really nothing
+        // to identify and colorize.
+        const expected = [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: "every item in ".length,
+            tokenLength: "?".length,
+            tokenType: Element.DynamicVariable,
+          }),
+
+          // The declared "item" is identified here.
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: " satisfies ".length + 1,
+            tokenLength: "item".length,
+            tokenType: Element.Variable,
+          }),
+        ];
+
+        for (let i = 0; i < expected.length; i++) {
+          expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+        }
+      });
+
+      test("should recognize double question mark as an error", async () => {
+        const expression = 'contains(??, "blue")';
+        const modelMock = createModelMockForExpression(expression);
+
+        const semanticMonacoTokens = await 
inputColumnSemanticTokensProvider.provideDocumentSemanticTokens(
+          modelMock as unknown as Monaco.editor.ITextModel,
+          null,
+          cancellationTokenMock
+        );
+
+        const expected = [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: "contains(".length,
+            tokenLength: "??".length,
+            tokenType: Element.UnknownVariable,
+          }),
+        ];
+
+        for (let i = 0; i < expected.length; i++) {
+          expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+        }
+      });
+    });
+
+    test("should not recognize question mark placeholder in Literal 
Expression", async () => {
+      const expression = 'every item in ? satisfies item = "blue"';
+      const literalExpressionId = "_8A76C5EF-26C1-4522-B1B2-63B64D753799";
+      const modelMock = createModelMockForExpression(expression);
+
+      const feelVariables = new FeelIdentifiers({
+        _readonly_dmnDefinitions: questionMarkModel.definitions,
+      });
+
+      const semanticTokensProvider = new SemanticTokensProvider(feelVariables, 
literalExpressionId, () => {});
+
+      const semanticMonacoTokens = await 
semanticTokensProvider.provideDocumentSemanticTokens(
+        modelMock as unknown as Monaco.editor.ITextModel,
+        null,
+        cancellationTokenMock
+      );
+
+      // Reserved keywords like "every", "in" and "satisfies" are not returned 
by the FeelIdentifiers since they are
+      // reserved keywords used by FEEL handled outside by the Monaco as 
reserved words.
+      // Also, "item" is not returned here because here is where it being 
declared so there is really nothing
+      // to identify and colorize.
+      const expected = [
+        ...getMonacoSemanticToken({
+          startLineRelativeToPreviousLine: 0,
+          startIndexRelativeToPreviousStartIndex: "every item in ".length,
+          tokenLength: "?".length,
+          tokenType: Element.UnknownVariable,
+        }),
+
+        // The declared "item" is identified here.
+        ...getMonacoSemanticToken({
+          startLineRelativeToPreviousLine: 0,
+          startIndexRelativeToPreviousStartIndex: " satisfies ".length + 1,
+          tokenLength: "item".length,
+          tokenType: Element.Variable,
+        }),
+      ];
+
+      for (let i = 0; i < expected.length; i++) {
+        expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+      }
+    });
+
+    describe("should recognize question mark placeholder in nested Decision 
Table", () => {
+      const expression = 'every item in ? satisfies item = "blue"';
+      const nestedDecisionTableInputColumnCellId = 
"_56D207EF-0BB2-4127-85B1-BE0F0E095CCC";
+      const nestedDecisionTableOutputColumnCellId = 
"_57A39F96-2671-41F4-8D0D-36893B549CDF";
+
+      test("should recognize question mark placeholder in input column", async 
() => {
+        const modelMock = createModelMockForExpression(expression);
+        const feelVariables = new FeelIdentifiers({
+          _readonly_dmnDefinitions: questionMarkModel.definitions,
+        });
+        const semanticTokensProvider = new SemanticTokensProvider(
+          feelVariables,
+          nestedDecisionTableInputColumnCellId,
+          () => {}
+        );
+
+        const semanticMonacoTokens = await 
semanticTokensProvider.provideDocumentSemanticTokens(
+          modelMock as unknown as Monaco.editor.ITextModel,
+          null,
+          cancellationTokenMock
+        );
+
+        // Reserved keywords like "every", "in" and "satisfies" are not 
returned by the FeelIdentifiers since they are
+        // reserved keywords used by FEEL handled outside by the Monaco as 
reserved words.
+        // Also, "item" is not returned here because here is where it being 
declared so there is really nothing
+        // to identify and colorize.
+        const expected = [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: "every item in ".length,
+            tokenLength: "?".length,
+            tokenType: Element.DynamicVariable,
+          }),
+
+          // The declared "item" is identified here.
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: " satisfies ".length + 1,
+            tokenLength: "item".length,
+            tokenType: Element.Variable,
+          }),
+        ];
+
+        for (let i = 0; i < expected.length; i++) {
+          expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+        }
+      });
+
+      test("should recognize question mark placeholder in output column", 
async () => {
+        const modelMock = createModelMockForExpression(expression);
+        const feelVariables = new FeelIdentifiers({
+          _readonly_dmnDefinitions: questionMarkModel.definitions,
+        });
+        const semanticTokensProvider = new SemanticTokensProvider(
+          feelVariables,
+          nestedDecisionTableOutputColumnCellId,
+          () => {}
+        );
+
+        const semanticMonacoTokens = await 
semanticTokensProvider.provideDocumentSemanticTokens(
+          modelMock as unknown as Monaco.editor.ITextModel,
+          null,
+          cancellationTokenMock
+        );
+
+        // Reserved keywords like "every", "in" and "satisfies" are not 
returned by the FeelIdentifiers since they are
+        // reserved keywords used by FEEL handled outside by the Monaco as 
reserved words.
+        // Also, "item" is not returned here because here is where it being 
declared so there is really nothing
+        // to identify and colorize.
+        const expected = [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: "every item in ".length,
+            tokenLength: "?".length,
+            tokenType: Element.DynamicVariable,
+          }),
+
+          // The declared "item" is identified here.
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: " satisfies ".length + 1,
+            tokenLength: "item".length,
+            tokenType: Element.Variable,
+          }),
+        ];
+
+        for (let i = 0; i < expected.length; i++) {
+          expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+        }
+      });
+    });
+  });
 });
 
 function getDmnModelWithContextEntry({


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to