From c0f3e6763a183a025c8da39fc5486e7725101687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jos=C3=A9=20dos=20Santos?= Date: Wed, 30 Oct 2024 15:14:56 -0300 Subject: [PATCH] kie-issues#2549: Decision Service cannot be recognized in columns of decision table (#2684) --- .../src/parser/VariablesRepository.ts | 32 ++- packages/feel-input-component/package.json | 1 + .../included.dmn | 106 ++++++++++ .../modelWithInclude.dmn | 188 ++++++++++++++++++ .../tests/semanticTokensProvider.test.ts | 150 ++++++++++++++ pnpm-lock.yaml | 3 + 6 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 packages/feel-input-component/tests-data/variables-inside-decision-tables/included.dmn create mode 100644 packages/feel-input-component/tests-data/variables-inside-decision-tables/modelWithInclude.dmn diff --git a/packages/dmn-feel-antlr4-parser/src/parser/VariablesRepository.ts b/packages/dmn-feel-antlr4-parser/src/parser/VariablesRepository.ts index 5f137916a3f..a923a0186f7 100644 --- a/packages/dmn-feel-antlr4-parser/src/parser/VariablesRepository.ts +++ b/packages/dmn-feel-antlr4-parser/src/parser/VariablesRepository.ts @@ -379,6 +379,35 @@ export class VariablesRepository { this.addVariable(id, "", FeelSyntacticSymbolNature.LocalVariable, parent); } + private addDecisionTableEntryNode(parent: VariableContext, entryId: string) { + const ruleInputElementNode = this.addVariable(entryId, "", FeelSyntacticSymbolNature.LocalVariable, parent); + parent.children.set(ruleInputElementNode.uuid, ruleInputElementNode); + this.addVariable(ruleInputElementNode.uuid, "", FeelSyntacticSymbolNature.LocalVariable, ruleInputElementNode); + } + + private addDecisionTable(parent: VariableContext, decisionTable: DmnDecisionTable) { + const variableNode = this.addVariable( + decisionTable["@_id"] ?? "", + "", + FeelSyntacticSymbolNature.LocalVariable, + parent + ); + parent.children.set(variableNode.uuid, variableNode); + + if (decisionTable.rule) { + for (const ruleElement of decisionTable.rule) { + ruleElement.inputEntry?.forEach((ruleInputElement) => + this.addDecisionTableEntryNode(parent, ruleInputElement["@_id"] ?? "") + ); + ruleElement.outputEntry?.forEach((ruleOutputElement) => + this.addDecisionTableEntryNode(parent, ruleOutputElement["@_id"] ?? "") + ); + } + } + + this.addVariable(variableNode.uuid, "", FeelSyntacticSymbolNature.LocalVariable, parent); + } + private addInvocation(parent: VariableContext, element: DmnInvocation) { if (element.binding) { for (const bindingElement of element.binding) { @@ -594,7 +623,8 @@ export class VariablesRepository { break; case "decisionTable": - // Do nothing because DecisionTable does not define variables + // It doesn't define variables but we need it to create its own context to use variables inside of Decision Table. + this.addDecisionTable(parent, expression); break; case "context": diff --git a/packages/feel-input-component/package.json b/packages/feel-input-component/package.json index be2dfee300d..da5dc24036d 100644 --- a/packages/feel-input-component/package.json +++ b/packages/feel-input-component/package.json @@ -30,6 +30,7 @@ "@babel/preset-env": "^7.16.0", "@babel/preset-react": "^7.16.0", "@kie-tools-core/webpack-base": "workspace:*", + "@kie-tools/dmn-marshaller": "workspace:*", "@kie-tools/eslint": "workspace:*", "@kie-tools/jest-base": "workspace:*", "@kie-tools/root-env": "workspace:*", diff --git a/packages/feel-input-component/tests-data/variables-inside-decision-tables/included.dmn b/packages/feel-input-component/tests-data/variables-inside-decision-tables/included.dmn new file mode 100644 index 00000000000..20e48f0279a --- /dev/null +++ b/packages/feel-input-component/tests-data/variables-inside-decision-tables/included.dmn @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + Input + + + + + + + + + + + + + + + + + 190 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/feel-input-component/tests-data/variables-inside-decision-tables/modelWithInclude.dmn b/packages/feel-input-component/tests-data/variables-inside-decision-tables/modelWithInclude.dmn new file mode 100644 index 00000000000..29d96398958 --- /dev/null +++ b/packages/feel-input-component/tests-data/variables-inside-decision-tables/modelWithInclude.dmn @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + Input + + + + + + + - + + + MyIncludedModel.MyDS(LocalInput) + MyIncludedModel.RemoteInput + + + + + + + + - + + + LocalInput + LocalDecision + + + + + + + + - + + + MyIncludedModel.MyDS(LocalInput) + MyIncludedModel.RemoteInput + LocalInput + LocalDecision + + + + + + + + + + + + + + + + + + + 60 + 118 + 773 + 240 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/feel-input-component/tests/semanticTokensProvider.test.ts b/packages/feel-input-component/tests/semanticTokensProvider.test.ts index 644547c8dac..44d2261587c 100644 --- a/packages/feel-input-component/tests/semanticTokensProvider.test.ts +++ b/packages/feel-input-component/tests/semanticTokensProvider.test.ts @@ -22,6 +22,9 @@ import { BuiltInTypes, DmnDefinitions, FeelVariables } from "@kie-tools/dmn-feel import * as Monaco from "@kie-tools-core/monaco-editor"; import { Element } from "@kie-tools/feel-input-component/dist/themes/Element"; +import * as fs from "fs"; +import * as path from "path"; +import { getMarshaller } from "@kie-tools/dmn-marshaller"; describe("Semantic Tokens Provider", () => { const cancellationTokenMock = { @@ -286,6 +289,145 @@ ThatShouldFailWhenBreakLine`, } }); }); + + describe("variables inside Decision Tables", () => { + const dmnModelWithIncludesPosixPathRelativeToTheTestFile = + "../tests-data/variables-inside-decision-tables/modelWithInclude.dmn"; + const includedDmnModelPosixPathRelativeToTheTestFile = + "../tests-data/variables-inside-decision-tables/included.dmn"; + const localModel = getDmnModelFromFilePath(dmnModelWithIncludesPosixPathRelativeToTheTestFile); + const includedModel = getDmnModelFromFilePath(includedDmnModelPosixPathRelativeToTheTestFile); + + test("should recognize local nodes", async () => { + const expression = "LocalInput + LocalDecision"; + const id = "_AEC3EEB0-8436-4767-A214-20FF5E5CB7BE"; + const modelMock = createModelMockForExpression(expression); + + const feelVariables = new FeelVariables( + localModel.definitions, + new Map([[includedModel.definitions["@_namespace"] ?? "", includedModel]]) + ); + + const semanticTokensProvider = new SemanticTokensProvider(feelVariables, id, () => {}); + + const semanticMonacoTokens = await semanticTokensProvider.provideDocumentSemanticTokens( + modelMock as unknown as Monaco.editor.ITextModel, + null, + cancellationTokenMock + ); + + const expected = [ + ...getMonacoSemanticToken({ + startLineRelativeToPreviousLine: 0, + startIndexRelativeToPreviousStartIndex: 0, + tokenLength: "LocalInput".length, + }), + ...getMonacoSemanticToken({ + startLineRelativeToPreviousLine: 0, + startIndexRelativeToPreviousStartIndex: "LocalInput".length + 3, // +3 because of the " + " + tokenLength: "LocalDecision".length, + }), + ]; + + for (let i = 0; i < expected.length; i++) { + expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]); + } + }); + + test("should recognize included nodes", async () => { + const expression = "MyIncludedModel.MyDS(LocalInput) + MyIncludedModel.RemoteInput"; + const id = "_206131ED-0B81-4013-980A-4BB2539A53D0"; + const modelMock = createModelMockForExpression(expression); + + const feelVariables = new FeelVariables( + localModel.definitions, + new Map([[includedModel.definitions["@_namespace"] ?? "", includedModel]]) + ); + + const semanticTokensProvider = new SemanticTokensProvider(feelVariables, id, () => {}); + + const semanticMonacoTokens = await semanticTokensProvider.provideDocumentSemanticTokens( + modelMock as unknown as Monaco.editor.ITextModel, + null, + cancellationTokenMock + ); + + const expected = [ + ...getMonacoSemanticToken({ + startLineRelativeToPreviousLine: 0, + startIndexRelativeToPreviousStartIndex: 0, + tokenLength: "MyIncludedModel.MyDS".length, + tokenType: Element.FunctionCall, + }), + ...getMonacoSemanticToken({ + startLineRelativeToPreviousLine: 0, + startIndexRelativeToPreviousStartIndex: "MyIncludedModel.MyDS".length + 1, // +1 because of the "(" + tokenLength: "LocalInput".length, + }), + ...getMonacoSemanticToken({ + startLineRelativeToPreviousLine: 0, + startIndexRelativeToPreviousStartIndex: "LocalInput".length + ") + ".length, + tokenLength: "MyIncludedModel.RemoteInput".length, + }), + ]; + + for (let i = 0; i < expected.length; i++) { + expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]); + } + }); + + test("should recognize included nodes mixed with included nodes", async () => { + const expression = "MyIncludedModel.MyDS(LocalInput) + MyIncludedModel.RemoteInput + LocalInput + LocalDecision"; + const id = "_18832484-9481-49BC-BD40-927CB9872C6B"; + const modelMock = createModelMockForExpression(expression); + + const feelVariables = new FeelVariables( + localModel.definitions, + new Map([[includedModel.definitions["@_namespace"] ?? "", includedModel]]) + ); + + const semanticTokensProvider = new SemanticTokensProvider(feelVariables, id, () => {}); + + const semanticMonacoTokens = await semanticTokensProvider.provideDocumentSemanticTokens( + modelMock as unknown as Monaco.editor.ITextModel, + null, + cancellationTokenMock + ); + + const expected = [ + ...getMonacoSemanticToken({ + startLineRelativeToPreviousLine: 0, + startIndexRelativeToPreviousStartIndex: 0, + tokenLength: "MyIncludedModel.MyDS".length, + tokenType: Element.FunctionCall, + }), + ...getMonacoSemanticToken({ + startLineRelativeToPreviousLine: 0, + startIndexRelativeToPreviousStartIndex: "MyIncludedModel.MyDS".length + 1, // +1 because of the "(" + tokenLength: "LocalInput".length, + }), + ...getMonacoSemanticToken({ + startLineRelativeToPreviousLine: 0, + startIndexRelativeToPreviousStartIndex: "LocalInput".length + ") + ".length, + tokenLength: "MyIncludedModel.RemoteInput".length, + }), + ...getMonacoSemanticToken({ + startLineRelativeToPreviousLine: 0, + startIndexRelativeToPreviousStartIndex: "MyIncludedModel.RemoteInput".length + " + ".length, + tokenLength: "LocalInput".length, + }), + ...getMonacoSemanticToken({ + startLineRelativeToPreviousLine: 0, + startIndexRelativeToPreviousStartIndex: "LocalInput".length + " + ".length, + tokenLength: "LocalDecision".length, + }), + ]; + + for (let i = 0; i < expected.length; i++) { + expect(semanticMonacoTokens?.data[i]).toEqual(expected[i]); + } + }); + }); }); function getDmnModelWithContextEntry({ @@ -377,3 +519,11 @@ function createModelMockForExpression(expression: string) { getLinesContent: jest.fn().mockReturnValue(expression.split("\n")), }; } + +function getDmnModelFromFilePath(modelFilePosixPathRelativeToTheTestFile: string) { + const { parser } = getMarshaller( + fs.readFileSync(path.join(__dirname, modelFilePosixPathRelativeToTheTestFile), "utf-8"), + { upgradeTo: "latest" } + ); + return parser.parse(); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7dfe20babcc..810747018cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4532,6 +4532,9 @@ importers: '@kie-tools-core/webpack-base': specifier: workspace:* version: link:../webpack-base + '@kie-tools/dmn-marshaller': + specifier: workspace:* + version: link:../dmn-marshaller '@kie-tools/eslint': specifier: workspace:* version: link:../eslint