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]