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 6f5c8e82d1c kie-issues#1416: On the DMN Editor's Boxed Expression 
Editor, FEEL autocompletion does not suggest the type properties - Part 1 of 2 
(#2509)
6f5c8e82d1c is described below

commit 6f5c8e82d1cdb8ee73e6e14dc66f63dffd4cba30
Author: Daniel José dos Santos <[email protected]>
AuthorDate: Fri Aug 9 11:51:21 2024 -0300

    kie-issues#1416: On the DMN Editor's Boxed Expression Editor, FEEL 
autocompletion does not suggest the type properties - Part 1 of 2 (#2509)
---
 packages/dmn-feel-antlr4-parser/src/index.ts       |   2 +
 .../src/parser/BuiltInTypes.ts                     | 100 +++++++++
 .../src/parser/VariablesRepository.ts              |  13 +-
 packages/feel-input-component/src/FeelInput.tsx    |   2 +-
 packages/feel-input-component/src/index.tsx        |   1 +
 .../tests/semanticTokensProvider.test.ts           | 246 +++++++++++++++++----
 6 files changed, 325 insertions(+), 39 deletions(-)

diff --git a/packages/dmn-feel-antlr4-parser/src/index.ts 
b/packages/dmn-feel-antlr4-parser/src/index.ts
index 8adbba95dd1..cb8a12276bc 100644
--- a/packages/dmn-feel-antlr4-parser/src/index.ts
+++ b/packages/dmn-feel-antlr4-parser/src/index.ts
@@ -25,3 +25,5 @@ export * from "./parser/FeelVariablesParser";
 export * from "./parser/VariablesRepository";
 export * from "./parser/ParsedExpression";
 export * from "./parser/FeelSymbol";
+export * from "./parser/BuiltInTypes";
+export * from "./parser/DataType";
diff --git a/packages/dmn-feel-antlr4-parser/src/parser/BuiltInTypes.ts 
b/packages/dmn-feel-antlr4-parser/src/parser/BuiltInTypes.ts
new file mode 100644
index 00000000000..d399bb38e52
--- /dev/null
+++ b/packages/dmn-feel-antlr4-parser/src/parser/BuiltInTypes.ts
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+import { DataType } from "./DataType";
+
+export class BuiltInTypes {
+  public static readonly Number: DataType = {
+    name: "number",
+    typeRef: "number",
+    properties: new Map([]),
+  };
+
+  public static readonly Boolean: DataType = {
+    name: "boolean",
+    typeRef: "boolean",
+    properties: new Map([]),
+  };
+
+  public static readonly String: DataType = {
+    name: "string",
+    typeRef: "string",
+    properties: new Map([]),
+  };
+
+  public static readonly DaysAndTimeDuration: DataType = {
+    name: "days and time duration",
+    typeRef: "days and time duration",
+    properties: new Map([
+      ["days", BuiltInTypes.Number],
+      ["hours", BuiltInTypes.Number],
+      ["minutes", BuiltInTypes.Number],
+      ["seconds", BuiltInTypes.Number],
+      ["timezone", BuiltInTypes.String],
+    ]),
+  };
+
+  public static readonly DateAndTime: DataType = {
+    name: "date and time",
+    typeRef: "date and time",
+    properties: new Map([
+      ["year", BuiltInTypes.Number],
+      ["month", BuiltInTypes.Number],
+      ["day", BuiltInTypes.Number],
+      ["weekday", BuiltInTypes.Number],
+      ["hour", BuiltInTypes.Number],
+      ["minute", BuiltInTypes.Number],
+      ["second", BuiltInTypes.Number],
+      ["time offset", BuiltInTypes.DaysAndTimeDuration],
+      ["timezone", BuiltInTypes.String],
+    ]),
+  };
+
+  public static readonly YearsAndMonthsDuration: DataType = {
+    name: "years and months duration",
+    typeRef: "years and months duration",
+    properties: new Map([
+      ["years", BuiltInTypes.Number],
+      ["months", BuiltInTypes.Number],
+    ]),
+  };
+
+  public static readonly Time: DataType = {
+    name: "time",
+    typeRef: "time",
+    properties: new Map([
+      ["hour", BuiltInTypes.Number],
+      ["minute", BuiltInTypes.Number],
+      ["second", BuiltInTypes.Number],
+      ["time offset", BuiltInTypes.DaysAndTimeDuration],
+      ["timezone", BuiltInTypes.String],
+    ]),
+  };
+
+  public static readonly Date: DataType = {
+    name: "date",
+    typeRef: "date",
+    properties: new Map([
+      ["year", BuiltInTypes.Number],
+      ["month", BuiltInTypes.Number],
+      ["day", BuiltInTypes.Number],
+      ["weekday", BuiltInTypes.Number],
+    ]),
+  };
+}
diff --git a/packages/dmn-feel-antlr4-parser/src/parser/VariablesRepository.ts 
b/packages/dmn-feel-antlr4-parser/src/parser/VariablesRepository.ts
index 421834fa89a..5f137916a3f 100644
--- a/packages/dmn-feel-antlr4-parser/src/parser/VariablesRepository.ts
+++ b/packages/dmn-feel-antlr4-parser/src/parser/VariablesRepository.ts
@@ -44,6 +44,7 @@ import {
 } from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
 import { Expression } from "./VariableOccurrence";
 import { DmnLatestModel } from "@kie-tools/dmn-marshaller";
+import { BuiltInTypes } from "./BuiltInTypes";
 
 type DmnLiteralExpression = { __$$element: "literalExpression" } & 
DMN15__tLiteralExpression;
 type DmnInvocation = { __$$element: "invocation" } & DMN15__tInvocation;
@@ -75,7 +76,17 @@ export class VariablesRepository {
   private currentUuidPrefix: string;
 
   constructor(dmnDefinitions: DmnDefinitions, externalDefinitions: Map<string, 
DmnLatestModel>) {
-    this.dataTypes = new Map<string, DataType>();
+    this.dataTypes = new Map([
+      [BuiltInTypes.Number.name, BuiltInTypes.Number],
+      [BuiltInTypes.Boolean.name, BuiltInTypes.Boolean],
+      [BuiltInTypes.String.name, BuiltInTypes.String],
+      [BuiltInTypes.DaysAndTimeDuration.name, 
BuiltInTypes.DaysAndTimeDuration],
+      [BuiltInTypes.DateAndTime.name, BuiltInTypes.DateAndTime],
+      [BuiltInTypes.YearsAndMonthsDuration.name, 
BuiltInTypes.YearsAndMonthsDuration],
+      [BuiltInTypes.Time.name, BuiltInTypes.Time],
+      [BuiltInTypes.Date.name, BuiltInTypes.Date],
+    ]);
+
     this.variablesIndexedByUuid = new Map<string, VariableContext>();
     this.expressionsIndexedByUuid = new Map<string, Expression>();
     this.loadImportedVariables(dmnDefinitions, externalDefinitions);
diff --git a/packages/feel-input-component/src/FeelInput.tsx 
b/packages/feel-input-component/src/FeelInput.tsx
index 650d7c26d09..dd5c790812f 100644
--- a/packages/feel-input-component/src/FeelInput.tsx
+++ b/packages/feel-input-component/src/FeelInput.tsx
@@ -285,7 +285,7 @@ export const FeelInput = React.forwardRef<FeelInputRef, 
FeelInputProps>(
       return () => {
         disposable.dispose();
       };
-    }, [enabled, expressionId, feelVariables]);
+    }, [enabled, expressionId, feelVariables, semanticTokensProvider]);
 
     const config = useMemo(() => {
       return feelDefaultConfig(options);
diff --git a/packages/feel-input-component/src/index.tsx 
b/packages/feel-input-component/src/index.tsx
index a583cf91e61..4cade5d58a3 100644
--- a/packages/feel-input-component/src/index.tsx
+++ b/packages/feel-input-component/src/index.tsx
@@ -19,3 +19,4 @@
 
 export * from "./FeelInput";
 export * from "./FeelConfigs";
+export * from "./themes/Element";
diff --git a/packages/feel-input-component/tests/semanticTokensProvider.test.ts 
b/packages/feel-input-component/tests/semanticTokensProvider.test.ts
index 3f3c4e8ec8e..644547c8dac 100644
--- a/packages/feel-input-component/tests/semanticTokensProvider.test.ts
+++ b/packages/feel-input-component/tests/semanticTokensProvider.test.ts
@@ -18,9 +18,10 @@
  */
 
 import { SemanticTokensProvider } from 
"@kie-tools/feel-input-component/dist/semanticTokensProvider";
-import { DmnDefinitions, FeelVariables } from 
"@kie-tools/dmn-feel-antlr4-parser";
+import { BuiltInTypes, DmnDefinitions, FeelVariables } from 
"@kie-tools/dmn-feel-antlr4-parser";
 
 import * as Monaco from "@kie-tools-core/monaco-editor";
+import { Element } from "@kie-tools/feel-input-component/dist/themes/Element";
 
 describe("Semantic Tokens Provider", () => {
   const cancellationTokenMock = {
@@ -34,28 +35,39 @@ describe("Semantic Tokens Provider", () => {
     /**
      * The 'parsedTokens' are the tokens that parser should found in the 
provided 'expression'.
      * The 'expected' are the Monaco Semantic Tokens that we are expecting to 
pass to Monaco to paint it on the screen.
-     *
-     * Each Monaco Semantic Tokens is an array of 5 positions
-     * 0 = The start line of the token RELATIVE TO THE PREVIOUS LINE
-     * 1 = The start index of the token relative to the START of the previous 
token
-     * 2 = The length of the token
-     * 3 = The type of the token (GlobalVariable, Unknown, Function Parameter, 
etc.). It determines the color of the token
-     * 4 = Token modifier. It's always zero since we don't have this feature.
      */
     test.each([
       {
         expression:
           'This is a variable with a very long name to reproduce the issue 
thousand one hundred and seventy-eight + "bar"',
-        expected: [[0, 0, 102, 5, 0]],
+        expected: [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 102,
+          }),
+        ],
       },
       {
         expression: `This is a variable with a very long 
 name to reproduce the issue thousand 
 one hundred and seventy-eight + "bar"`,
         expected: [
-          [0, 0, 36, 5, 0],
-          [1, 0, 37, 5, 0],
-          [1, 0, 29, 5, 0],
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 36,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 37,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 29,
+          }),
         ],
       },
       {
@@ -65,11 +77,31 @@ long name to
  reproduce the issue thousand 
  one hundred and seventy-eight + "bar" + "NICE" + This is a variable with a 
very long name to reproduce the issue thousand one hundred and seventy-eight`,
         expected: [
-          [1, 0, 31, 5, 0],
-          [1, 0, 12, 5, 0],
-          [1, 0, 30, 5, 0],
-          [1, 0, 30, 5, 0],
-          [0, 50, 102, 5, 0],
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 31,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 12,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 30,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 30,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: 50,
+            tokenLength: 102,
+          }),
         ],
       },
       {
@@ -81,44 +113,95 @@ one hundred
 and 
 seventy-eight + "bar`,
         expected: [
-          [0, 0, 44, 5, 0],
-          [1, 0, 10, 5, 0],
-          [1, 0, 10, 5, 0],
-          [1, 0, 9, 5, 0],
-          [1, 0, 12, 5, 0],
-          [1, 0, 4, 5, 0],
-          [1, 0, 13, 5, 0],
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 44,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 10,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 10,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 9,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 12,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 4,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 13,
+          }),
         ],
       },
       {
         expression: `"My " + This is a variable with a                         
very long name to             reproduce
  the issue             thousand             one hundred               and      
            seventy-eight + "bar"`,
         expected: [
-          [0, 8, 89, 5, 0],
-          [1, 0, 104, 5, 0],
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: 8,
+            tokenLength: 89,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 104,
+          }),
         ],
       },
       {
         expression: `This is a variable with a very long name to
 reproduce the issue thousand one hundred and seventy-eight + "bar"`,
         expected: [
-          [0, 0, 43, 5, 0],
-          [1, 0, 58, 5, 0],
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 43,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 58,
+          }),
         ],
       },
       {
         expression: `VeryLongVariableWithoutSpaces
 ThatShouldFailWhenBreakLine`,
         expected: [
-          [0, 0, 29, 7, 0],
-          [1, 0, 27, 7, 0],
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 29,
+            tokenType: Element.UnknownVariable,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 1,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: 27,
+            tokenType: Element.UnknownVariable,
+          }),
         ],
       },
     ])("multiline variables", async ({ expression, expected }) => {
-      const modelMock = {
-        getValue: jest.fn().mockReturnValue(expression),
-        getLinesContent: jest.fn().mockReturnValue(expression.split("\n")),
-      };
+      const modelMock = createModelMockForExpression(expression);
 
       const id = "expressionId";
       const dmnDefinitions = getDmnModelWithContextEntry({
@@ -140,10 +223,66 @@ ThatShouldFailWhenBreakLine`,
         cancellationTokenMock
       );
 
-      const expectedSemanticMonacoTokens = expected.flat();
+      for (let i = 0; i < expected.length; i++) {
+        expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+      }
+    });
+  });
 
-      for (let i = 0; i < expectedSemanticMonacoTokens.length; i++) {
-        
expect(semanticMonacoTokens?.data[i]).toEqual(expectedSemanticMonacoTokens[i]);
+  describe("built-in types", () => {
+    test.each([
+      { type: BuiltInTypes.Number },
+      { type: BuiltInTypes.Boolean },
+      { type: BuiltInTypes.String },
+      { type: BuiltInTypes.DaysAndTimeDuration },
+      { type: BuiltInTypes.DateAndTime },
+      { type: BuiltInTypes.YearsAndMonthsDuration },
+      { type: BuiltInTypes.Time },
+      { type: BuiltInTypes.Date },
+    ])("should recognize built-in type '$type.name' properties as valid", 
async ({ type }) => {
+      const myVariable = "myVar";
+      const id = "someId";
+
+      for (const dataType of type.properties.keys()) {
+        const expression = `${myVariable}.${dataType}`;
+        const modelMock = createModelMockForExpression(expression);
+
+        const expected = [
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: 0,
+            tokenLength: myVariable.length,
+          }),
+          ...getMonacoSemanticToken({
+            startLineRelativeToPreviousLine: 0,
+            startIndexRelativeToPreviousStartIndex: myVariable.length + 1, // 
+1 because of the dot after "myVar"
+            tokenLength: dataType.length,
+          }),
+        ];
+
+        const model = getDmnModelWithContextEntry({
+          entry: {
+            variable: myVariable,
+            type: type.name,
+            expression: {
+              value: expression,
+              id: id,
+            },
+          },
+        });
+
+        const feelVariables = new FeelVariables(model, new Map());
+        const semanticTokensProvider = new 
SemanticTokensProvider(feelVariables, id, () => {});
+
+        const semanticMonacoTokens = await 
semanticTokensProvider.provideDocumentSemanticTokens(
+          modelMock as unknown as Monaco.editor.ITextModel,
+          null,
+          cancellationTokenMock
+        );
+
+        for (let i = 0; i < expected.length; i++) {
+          expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]);
+        }
       }
     });
   });
@@ -154,6 +293,7 @@ function getDmnModelWithContextEntry({
 }: {
   entry: {
     variable: string;
+    type?: string;
     expression: {
       value: string;
       id: string;
@@ -178,6 +318,7 @@ function getDmnModelWithContextEntry({
               variable: {
                 "@_id": "_401F4E2D-442A-4A29-B6B9-906A121C6FC0",
                 "@_name": entry.variable,
+                "@_typeRef": entry.type,
               },
               expression: {
                 __$$element: "literalExpression",
@@ -205,3 +346,34 @@ function getDmnModelWithContextEntry({
 
   return dmnDefinitions;
 }
+
+/**
+ * Create a Monaco Semantic Token, which is an array with 5 positions.
+ * 0 = The start line of the token RELATIVE TO THE PREVIOUS LINE
+ * 1 = The start index of the token relative to the START of the previous token
+ * 2 = The length of the token
+ * 3 = The type of the token (GlobalVariable, Unknown, Function Parameter, 
etc.). It determines the color of the token
+ * 4 = Token modifier. It's always zero since we don't have this feature.
+ * @param args The token values.
+ */
+function getMonacoSemanticToken(args: {
+  startLineRelativeToPreviousLine: number;
+  startIndexRelativeToPreviousStartIndex: number;
+  tokenLength: number;
+  tokenType?: Element;
+}) {
+  return [
+    args.startLineRelativeToPreviousLine,
+    args.startIndexRelativeToPreviousStartIndex,
+    args.tokenLength,
+    args.tokenType ?? Element.Variable,
+    0,
+  ];
+}
+
+function createModelMockForExpression(expression: string) {
+  return {
+    getValue: jest.fn().mockReturnValue(expression),
+    getLinesContent: jest.fn().mockReturnValue(expression.split("\n")),
+  };
+}


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

Reply via email to