From 1d3fdf21d840041f260d5da730cd697b81f68df2 Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Tue, 20 Jan 2026 11:45:34 +0530 Subject: [PATCH 01/13] Refactor tuple member related info --- .../core/DataMapManager.java | 10 +- .../extension/DataMappingModelTest.java | 239 +++++++++--------- .../data_mapper_model/config/tuple.json | 128 ++++++++++ .../data_mapper_model/config/variable54.json | 8 +- .../data_mapper_model/source/variable50.bal | 23 ++ 5 files changed, 283 insertions(+), 125 deletions(-) create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/variable50.bal diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index 875bf6ed14..790d9950db 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -1267,8 +1267,14 @@ private MappingPort handleTupleType(String id, String name, String typeName, Ref MappingTuplePort tuplePort = new MappingTuplePort(id, name, typeName, "tuple", type.key); tuplePort.typeInfo = isExternalType(type) ? createTypeInfo(type) : null; - for (RefType member : ((RefTupleType) type).memberTypes) { - MappingPort memberPort = getRefMappingPort(id, name, member, visitedTypes, references); + List memberTypes = ((RefTupleType) type).memberTypes; + for (int i = 0; i < memberTypes.size(); i++) { + RefType member = memberTypes.get(i); + String memberName = "[" + i + "]"; + MappingPort memberPort = getRefMappingPort(memberName, name + memberName, member, visitedTypes, references); + if (memberPort.displayName == null) { + memberPort.displayName = name + "[" + i + "]"; + } tuplePort.members.add(memberPort); } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java index 63892132de..2bf4efa987 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java @@ -45,124 +45,125 @@ public class DataMappingModelTest extends AbstractLSTest { @Override protected Object[] getConfigsList() { return new Object[][]{ - {Path.of("query1.json")}, - {Path.of("query2.json")}, - {Path.of("query3.json")}, - {Path.of("query4.json")}, - {Path.of("query5.json")}, - {Path.of("query6.json")}, - {Path.of("query7.json")}, - {Path.of("query8.json")}, - {Path.of("variable1.json")}, - {Path.of("variable2.json")}, - {Path.of("variable3.json")}, - {Path.of("variable4.json")}, - {Path.of("variable5.json")}, - {Path.of("variable6.json")}, - {Path.of("variable7.json")}, - {Path.of("variable8.json")}, - {Path.of("variable9.json")}, - {Path.of("variable10.json")}, - {Path.of("variable11.json")}, - {Path.of("variable12.json")}, - {Path.of("variable13.json")}, - {Path.of("variable14.json")}, - {Path.of("variable15.json")}, - {Path.of("variable16.json")}, - {Path.of("variable17.json")}, - {Path.of("variable18.json")}, - {Path.of("variable19.json")}, - {Path.of("variable20.json")}, - {Path.of("variable21.json")}, - {Path.of("variable22.json")}, - {Path.of("variable23.json")}, - {Path.of("variable24.json")}, - {Path.of("variable25.json")}, - {Path.of("variable26.json")}, - {Path.of("variable27.json")}, - {Path.of("variable28.json")}, - {Path.of("variable29.json")}, - {Path.of("variable30.json")}, - {Path.of("variable31.json")}, - {Path.of("variable32.json")}, - {Path.of("variable33.json")}, - {Path.of("variable34.json")}, - {Path.of("variable35.json")}, - {Path.of("variable36.json")}, - {Path.of("variable37.json")}, - {Path.of("variable38.json")}, - {Path.of("variable39.json")}, - {Path.of("sub_mapping1.json")}, - {Path.of("sub_mapping2.json")}, - {Path.of("sub_mapping3.json")}, - {Path.of("sub_mapping4.json")}, - {Path.of("variable40.json")}, - {Path.of("recursive1.json")}, - {Path.of("recursive2.json")}, - {Path.of("variable41.json")}, - {Path.of("primitiveArray.json")}, - {Path.of("enum.json")}, - {Path.of("enum2.json")}, - {Path.of("union.json")}, - {Path.of("union1.json")}, - {Path.of("const.json")}, - {Path.of("sub_mapping5.json")}, - {Path.of("variable42.json")}, - {Path.of("variable43.json")}, - {Path.of("query9.json")}, - {Path.of("variable44.json")}, - {Path.of("union2.json")}, - {Path.of("query10.json")}, - {Path.of("query11.json")}, - {Path.of("variable45.json")}, - {Path.of("function_def1.json")}, - {Path.of("function_def2.json")}, - {Path.of("function_def3.json")}, - {Path.of("function_def4.json")}, - {Path.of("function_def5.json")}, - {Path.of("sub_mapping6.json")}, - {Path.of("variable46.json")}, - {Path.of("variable47.json")}, - {Path.of("union3.json")}, - {Path.of("union4.json")}, - {Path.of("union5.json")}, - {Path.of("variable48.json")}, - {Path.of("variable49.json")}, - {Path.of("query12.json")}, - {Path.of("query13.json")}, - {Path.of("query14.json")}, - {Path.of("query15.json")}, - {Path.of("function_def6.json")}, - {Path.of("query16.json")}, - {Path.of("query17.json")}, - {Path.of("variable50.json")}, - {Path.of("variable51.json")}, - {Path.of("variable52.json")}, - {Path.of("query18.json")}, - {Path.of("query19.json")}, - {Path.of("query20.json")}, - {Path.of("configurable_variable1.json")}, - {Path.of("configurable_variable2.json")}, - {Path.of("function_def7.json")}, - {Path.of("variable53.json")}, - {Path.of("variable54.json")}, - {Path.of("variable55.json")}, - {Path.of("variable56.json")}, - {Path.of("query21.json")}, - {Path.of("query22.json")}, - {Path.of("variable57.json")}, - {Path.of("focussedView.json")}, - {Path.of("query23.json")}, - {Path.of("query24.json")}, - {Path.of("map.json")}, - {Path.of("sub_mapping7.json")}, - {Path.of("query25.json")}, - {Path.of("query26.json")}, - {Path.of("query27.json")}, - {Path.of("query28.json")}, - {Path.of("query29.json")}, - {Path.of("variable58.json")}, - {Path.of("variable59.json")}, +// {Path.of("query1.json")}, +// {Path.of("query2.json")}, +// {Path.of("query3.json")}, +// {Path.of("query4.json")}, +// {Path.of("query5.json")}, +// {Path.of("query6.json")}, +// {Path.of("query7.json")}, +// {Path.of("query8.json")}, +// {Path.of("variable1.json")}, +// {Path.of("variable2.json")}, +// {Path.of("variable3.json")}, +// {Path.of("variable4.json")}, +// {Path.of("variable5.json")}, +// {Path.of("variable6.json")}, +// {Path.of("variable7.json")}, +// {Path.of("variable8.json")}, +// {Path.of("variable9.json")}, +// {Path.of("variable10.json")}, +// {Path.of("variable11.json")}, +// {Path.of("variable12.json")}, +// {Path.of("variable13.json")}, +// {Path.of("variable14.json")}, +// {Path.of("variable15.json")}, +// {Path.of("variable16.json")}, +// {Path.of("variable17.json")}, +// {Path.of("variable18.json")}, +// {Path.of("variable19.json")}, +// {Path.of("variable20.json")}, +// {Path.of("variable21.json")}, +// {Path.of("variable22.json")}, +// {Path.of("variable23.json")}, +// {Path.of("variable24.json")}, +// {Path.of("variable25.json")}, +// {Path.of("variable26.json")}, +// {Path.of("variable27.json")}, +// {Path.of("variable28.json")}, +// {Path.of("variable29.json")}, +// {Path.of("variable30.json")}, +// {Path.of("variable31.json")}, +// {Path.of("variable32.json")}, +// {Path.of("variable33.json")}, +// {Path.of("variable34.json")}, +// {Path.of("variable35.json")}, +// {Path.of("variable36.json")}, +// {Path.of("variable37.json")}, +// {Path.of("variable38.json")}, +// {Path.of("variable39.json")}, +// {Path.of("sub_mapping1.json")}, +// {Path.of("sub_mapping2.json")}, +// {Path.of("sub_mapping3.json")}, +// {Path.of("sub_mapping4.json")}, +// {Path.of("variable40.json")}, +// {Path.of("recursive1.json")}, +// {Path.of("recursive2.json")}, +// {Path.of("variable41.json")}, +// {Path.of("primitiveArray.json")}, +// {Path.of("enum.json")}, +// {Path.of("enum2.json")}, +// {Path.of("union.json")}, +// {Path.of("union1.json")}, +// {Path.of("const.json")}, +// {Path.of("sub_mapping5.json")}, +// {Path.of("variable42.json")}, +// {Path.of("variable43.json")}, +// {Path.of("query9.json")}, +// {Path.of("variable44.json")}, +// {Path.of("union2.json")}, +// {Path.of("query10.json")}, +// {Path.of("query11.json")}, +// {Path.of("variable45.json")}, +// {Path.of("function_def1.json")}, +// {Path.of("function_def2.json")}, +// {Path.of("function_def3.json")}, +// {Path.of("function_def4.json")}, +// {Path.of("function_def5.json")}, +// {Path.of("sub_mapping6.json")}, +// {Path.of("variable46.json")}, +// {Path.of("variable47.json")}, +// {Path.of("union3.json")}, +// {Path.of("union4.json")}, +// {Path.of("union5.json")}, +// {Path.of("variable48.json")}, +// {Path.of("variable49.json")}, +// {Path.of("query12.json")}, +// {Path.of("query13.json")}, +// {Path.of("query14.json")}, +// {Path.of("query15.json")}, +// {Path.of("function_def6.json")}, +// {Path.of("query16.json")}, +// {Path.of("query17.json")}, +// {Path.of("variable50.json")}, +// {Path.of("variable51.json")}, +// {Path.of("variable52.json")}, +// {Path.of("query18.json")}, +// {Path.of("query19.json")}, +// {Path.of("query20.json")}, +// {Path.of("configurable_variable1.json")}, +// {Path.of("configurable_variable2.json")}, +// {Path.of("function_def7.json")}, +// {Path.of("variable53.json")}, +// {Path.of("variable54.json")}, +// {Path.of("variable55.json")}, +// {Path.of("variable56.json")}, +// {Path.of("query21.json")}, +// {Path.of("query22.json")}, +// {Path.of("variable57.json")}, +// {Path.of("focussedView.json")}, +// {Path.of("query23.json")}, +// {Path.of("query24.json")}, +// {Path.of("map.json")}, +// {Path.of("sub_mapping7.json")}, +// {Path.of("query25.json")}, +// {Path.of("query26.json")}, +// {Path.of("query27.json")}, +// {Path.of("query28.json")}, +// {Path.of("query29.json")}, +// {Path.of("variable58.json")}, +// {Path.of("variable59.json")}, + {Path.of("tuple.json")} }; } @@ -187,7 +188,7 @@ public void test(Path config) throws IOException { TestConfig updateConfig = new TestConfig(testConfig.source(), testConfig.description(), testConfig.codedata(), testConfig.position(), testConfig.propertyKey(), testConfig.targetField(), model); -// updateConfig(configJsonPath, updateConfig); + updateConfig(configJsonPath, updateConfig); compareJsonElements(model, testConfig.model()); Assert.fail(String.format("Failed test: '%s' (%s)", testConfig.description(), configJsonPath)); } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json new file mode 100644 index 0000000000..f8acd341e0 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json @@ -0,0 +1,128 @@ +{ + "source": "variable50.bal", + "description": "Sample diagram node", + "codedata": { + "node": "VARIABLE", + "lineRange": { + "fileName": "variable50.bal", + "startLine": { + "line": 17, + "offset": 8 + }, + "endLine": { + "line": 17, + "offset": 39 + } + }, + "sourceCode": "Bar[] bars = [\n {a: 1, b: 3},\n (from var baz in bazs\n select {a: baz.x, b: baz.y})[0]\n ];" + }, + "position": { + "line": 17, + "offset": 8 + }, + "targetField": "person", + "model": { + "inputs": [ + { + "members": [ + { + "name": "[0]", + "displayName": "data[0]", + "typeName": "int", + "kind": "int" + }, + { + "name": "[1]", + "displayName": "data[1]", + "typeName": "string", + "kind": "string" + }, + { + "name": "[2]", + "displayName": "data[2]", + "typeName": "float", + "kind": "float" + } + ], + "name": "data", + "displayName": "data", + "typeName": "[int, string, float]", + "kind": "tuple", + "category": "local-variable" + } + ], + "output": { + "fields": [], + "name": "person", + "displayName": "person", + "typeName": "Person", + "kind": "record", + "ref": "887620673" + }, + "mappings": [ + { + "output": "person.age", + "inputs": [ + "data" + ], + "expression": "data[0]", + "diagnostics": [], + "elements": [], + "isQueryExpression": false, + "isFunctionCall": false, + "elementAccessIndex": [ + "0" + ] + } + ], + "refs": { + "887620673": { + "fields": [ + { + "name": "name", + "displayName": "name", + "typeName": "string", + "kind": "string", + "optional": false + }, + { + "name": "age", + "displayName": "age", + "typeName": "int", + "kind": "int", + "optional": false + }, + { + "members": [ + { + "name": "[0]", + "displayName": "data[0]", + "typeName": "int", + "kind": "int" + }, + { + "name": "[1]", + "displayName": "data[1]", + "typeName": "string", + "kind": "string" + }, + { + "name": "[2]", + "displayName": "data[2]", + "typeName": "float", + "kind": "float" + } + ], + "name": "data", + "displayName": "data", + "typeName": "", + "kind": "tuple", + "optional": false + } + ], + "typeName": "Person", + "kind": "record" + } + } + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/variable54.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/variable54.json index 04bf11c203..84e2d423e3 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/variable54.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/variable54.json @@ -27,16 +27,16 @@ "members": [ { "fields": [], - "name": "a", - "displayName": "a", + "name": "[0]", + "displayName": "a[0]", "typeName": "Student", "kind": "record", "ref": "545732303" }, { "fields": [], - "name": "a", - "displayName": "a", + "name": "[1]", + "displayName": "a[1]", "typeName": "Person", "kind": "record", "ref": "887620673" diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/variable50.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/variable50.bal new file mode 100644 index 0000000000..423311af3a --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/variable50.bal @@ -0,0 +1,23 @@ +import ballerina/log; + +public type Person record { + string name; + int age; + [int, string, float] data; +}; + +public function main() returns error? { + do { + // [int, string, float] data = [1, "Alice", 99.5]; + // [int, string, float] result = []; + // int[] numbers = [10, 20, 30]; + // Person person = {name: "Alice" + data[1], age: 30}; + // Person updatedPErson = {age: numbers[1]}; + + [int, string, float] data = [1, "Alice", 99.5]; + Person person = {age: data[0]}; + } on fail error e { + log:printError("Error occurred", 'error = e); + return e; + } +} \ No newline at end of file From 7fd16c32a3cebc81ace0c68012a7e34f53f50ac0 Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Tue, 20 Jan 2026 15:26:29 +0530 Subject: [PATCH 02/13] Handle tuple inputs in mappings --- .../core/DataMapManager.java | 22 +++++++++++++++++-- .../data_mapper_model/config/tuple.json | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index 790d9950db..acc31249b7 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -823,7 +823,7 @@ private void genMapping(ListConstructorExpressionNode listCtrExpr, List private void genMapping(Node expr, String name, List elements, SemanticModel semanticModel, Document functionDocument, Document dataMappingDocument, List enumPorts) { List inputs = new ArrayList<>(); - expr.accept(new GenInputsVisitor(inputs, enumPorts)); + expr.accept(new GenInputsVisitor(inputs, enumPorts, semanticModel)); LineRange customFunctionRange = getCustomFunctionRange(expr, functionDocument, dataMappingDocument); List elementAccessIndex = null; if (containsArrayAccess(expr)) { @@ -3207,10 +3207,13 @@ public record TypeInfo(String orgName, String moduleName) { private static class GenInputsVisitor extends NodeVisitor { private final List inputs; private final List enumPorts; + private final SemanticModel semanticModel; - GenInputsVisitor(List inputs, List enumPorts) { + GenInputsVisitor(List inputs, List enumPorts, + SemanticModel semanticModel) { this.inputs = inputs; this.enumPorts = enumPorts; + this.semanticModel = semanticModel; } @Override @@ -3268,6 +3271,12 @@ public void visit(IndexedExpressionNode node) { ExpressionNode containerExpr = node.containerExpression(); SyntaxKind containerKind = containerExpr.kind(); + if (isTupleType(containerExpr)) { + // For tuple element access, use the full indexed expression + addInput(node.toSourceCode().trim()); + return; + } + if (containerKind == SyntaxKind.FIELD_ACCESS || containerKind == SyntaxKind.INDEXED_EXPRESSION) { String source = node.toSourceCode().trim(); String openBraceRemoved = source.replace("[", "."); @@ -3284,6 +3293,15 @@ public void visit(IndexedExpressionNode node) { } } + private boolean isTupleType(ExpressionNode expr) { + if (semanticModel == null) { + return false; + } + return semanticModel.typeOf(expr) + .map(typeSymbol -> CommonUtils.getRawType(typeSymbol).typeKind() == TypeDescKind.TUPLE) + .orElse(false); + } + @Override public void visit(QueryExpressionNode node) { node.queryPipeline().fromClause().accept(this); diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json index f8acd341e0..20c1dbe9ac 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json @@ -63,7 +63,7 @@ { "output": "person.age", "inputs": [ - "data" + "data[0]" ], "expression": "data[0]", "diagnostics": [], From 006f2e107b833278f53a56ffc5935ea143a77e8e Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Wed, 21 Jan 2026 13:16:42 +0530 Subject: [PATCH 03/13] Improve APIs to support inline tuples mappings --- .../core/DataMapManager.java | 172 +++++++++++-- .../extension/DataMapperService.java | 7 +- .../extension/DataMappingDeleteTest.java | 39 +-- .../extension/DataMappingModelTest.java | 236 +++++++++--------- .../extension/DataMappingSourceTest.java | 113 +++++---- .../config/tupleMapping.json | 41 +++ .../config/tupleMapping2.json | 41 +++ .../config/tupleTransformation.json | 41 +++ .../source/tupleMapping.bal | 21 ++ .../source/tupleTransformation.bal | 29 +++ .../delete_mapping/config/tupleMember.json | 36 +++ .../delete_mapping/source/tupleMember.bal | 21 ++ .../diagramutil/RefTypeTest.java | 2 +- .../RefTypeTest/BalProject7/main.bal | 5 + 14 files changed, 584 insertions(+), 220 deletions(-) create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping2.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleTransformation.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleMapping.bal create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleTransformation.bal create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tupleMember.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/source/tupleMember.bal diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index acc31249b7..fee095c196 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -36,6 +36,7 @@ import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; import io.ballerina.compiler.api.symbols.TypeDescKind; import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.api.symbols.TupleTypeSymbol; import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.api.symbols.VariableSymbol; import io.ballerina.compiler.syntax.tree.BinaryExpressionNode; @@ -799,18 +800,24 @@ private void genMapping(ListConstructorExpressionNode listCtrExpr, List } } + // Use bracket notation for tuples, dot notation for arrays + boolean isTuple = semanticModel.typeOf(listCtrExpr) + .map(t -> CommonUtils.getRawType(t).typeKind() == TypeDescKind.TUPLE) + .orElse(false); + List mappingElements = new ArrayList<>(); for (int i = 0; i < size; i++) { List elements = new ArrayList<>(); Node expr = expressions.get(i); + String elementPath = isTuple ? name + "[" + i + "]" : name + "." + i; if (expr.kind() == SyntaxKind.MAPPING_CONSTRUCTOR) { - genMapping((MappingConstructorExpressionNode) expr, elements, name + "." + i, semanticModel, + genMapping((MappingConstructorExpressionNode) expr, elements, elementPath, semanticModel, functionDocument, dataMappingDocument, enumPorts, inputPorts); } else if (expr.kind() == SyntaxKind.LIST_CONSTRUCTOR) { - genMapping((ListConstructorExpressionNode) expr, elements, name + "." + i, semanticModel, + genMapping((ListConstructorExpressionNode) expr, elements, elementPath, semanticModel, functionDocument, dataMappingDocument, enumPorts, inputPorts); } else { - genMapping(expr, name + "." + i, elements, semanticModel, functionDocument, dataMappingDocument, + genMapping(expr, elementPath, elements, semanticModel, functionDocument, dataMappingDocument, enumPorts); } mappingElements.add(new MappingElements(elements)); @@ -1457,7 +1464,8 @@ private boolean isExternalType(RefType refType) { !refType.moduleInfo.packageName.equals(currentModuleInfo.packageName()); } - public JsonElement getSource(Path filePath, JsonElement cd, JsonElement mp, String targetField) { + public JsonElement getSource(SemanticModel semanticModel, Path filePath, JsonElement cd, JsonElement mp, + String targetField) { Codedata codedata = gson.fromJson(cd, Codedata.class); Mapping mapping = gson.fromJson(mp, Mapping.class); NonTerminalNode node = getNode(codedata.lineRange()); @@ -1467,15 +1475,28 @@ public JsonElement getSource(Path filePath, JsonElement cd, JsonElement mp, Stri textEditsMap.put(filePath, textEdits); ExpressionNode expr = null; + TypeSymbol targetTypeSymbol = null; if (node.kind() == SyntaxKind.LOCAL_VAR_DECL) { VariableDeclarationNode varDecl = (VariableDeclarationNode) node; expr = varDecl.initializer().orElseThrow(); + targetTypeSymbol = semanticModel.symbol(varDecl) + .filter(s -> s.kind() == SymbolKind.VARIABLE) + .map(s -> ((VariableSymbol) s).typeDescriptor()) + .orElse(null); } else if (node.kind() == SyntaxKind.MODULE_VAR_DECL) { ModuleVariableDeclarationNode moduleVarDecl = (ModuleVariableDeclarationNode) node; expr = moduleVarDecl.initializer().orElseThrow(); + targetTypeSymbol = semanticModel.symbol(moduleVarDecl) + .filter(s -> s.kind() == SymbolKind.VARIABLE) + .map(s -> ((VariableSymbol) s).typeDescriptor()) + .orElse(null); } else if (node.kind() == SyntaxKind.LET_VAR_DECL) { LetVariableDeclarationNode varDecl = (LetVariableDeclarationNode) node; expr = varDecl.expression(); + targetTypeSymbol = semanticModel.symbol(varDecl) + .filter(s -> s.kind() == SymbolKind.VARIABLE) + .map(s -> ((VariableSymbol) s).typeDescriptor()) + .orElse(null); } else if (node.kind() == SyntaxKind.FUNCTION_DEFINITION) { FunctionDefinitionNode funcDefNode = (FunctionDefinitionNode) node; FunctionBodyNode funcBodyNode = funcDefNode.functionBody(); @@ -1483,6 +1504,15 @@ public JsonElement getSource(Path filePath, JsonElement cd, JsonElement mp, Stri ExpressionFunctionBodyNode exprFuncBodyNode = (ExpressionFunctionBodyNode) funcBodyNode; expr = exprFuncBodyNode.expression(); } + Optional returnTypeSymbol = semanticModel.symbol(node) + .filter(s -> s.kind() == SymbolKind.FUNCTION) + .map(s -> ((FunctionSymbol) s).typeDescriptor().returnTypeDescriptor()) + .orElse(null); + if (Objects.requireNonNull(returnTypeSymbol).isPresent() && + returnTypeSymbol.get().typeKind() == TypeDescKind.TUPLE) { + targetTypeSymbol = returnTypeSymbol.get(); + } + } if (expr != null) { @@ -1490,13 +1520,15 @@ public JsonElement getSource(Path filePath, JsonElement cd, JsonElement mp, Stri expr = ((LetExpressionNode) expr).expression(); } String output = mapping.output(); - String[] splits = output.split(DOT); + // Normalize bracket notation to dot notation (e.g., result[0] -> result.0) + String normalizedOutput = output.replaceAll("\\[(\\d+)]", ".$1"); + String[] splits = normalizedOutput.split(DOT); StringBuilder sb = new StringBuilder(); MatchingNode targetMappingExpr = getTargetMappingExpr(expr, targetField); if (targetMappingExpr != null) { expr = targetMappingExpr.expr(); } - genSource(expr, splits, 1, sb, mapping.expression(), null, textEdits); + genSource(expr, splits, 1, sb, mapping.expression(), null, textEdits, targetTypeSymbol); } setImportStatements(mapping.imports(), textEdits); @@ -1545,22 +1577,26 @@ private ExpressionNode getMappingExpr(Node node) { private void genSource(ExpressionNode expr, String[] names, int idx, StringBuilder stringBuilder, - String mappingExpr, LinePosition position, List textEdits) { + String mappingExpr, LinePosition position, List textEdits, + TypeSymbol targetTypeSymbol) { if (idx == names.length) { textEdits.add(new TextEdit(CommonUtils.toRange(expr.lineRange()), mappingExpr)); } else if (expr == null) { String name = names[idx]; if (name.matches("\\d+")) { - stringBuilder.append(mappingExpr); + // Check if we're creating a tuple literal + TypeSymbol rawType = targetTypeSymbol != null ? CommonUtils.getRawType(targetTypeSymbol) : null; + if (rawType != null && rawType.typeKind() == TypeDescKind.TUPLE) { + int index = Integer.parseInt(name); + stringBuilder.append(generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr)); + } else { + stringBuilder.append(mappingExpr); + } } else { stringBuilder.append(name).append(": "); - for (int i = idx + 1; i < names.length; i++) { - stringBuilder.append("{").append(names[i]).append(": "); - } - stringBuilder.append(mappingExpr); - for (int i = idx + 1; i < names.length; i++) { - stringBuilder.append("}"); - } + // targetTypeSymbol is already the type for this field + String nestedExpr = generateNestedExpression(names, idx + 1, mappingExpr, targetTypeSymbol); + stringBuilder.append(nestedExpr); } textEdits.add(new TextEdit(CommonUtils.toRange(position), stringBuilder.toString())); } else if (expr.kind() == SyntaxKind.MAPPING_CONSTRUCTOR) { @@ -1568,6 +1604,7 @@ private void genSource(ExpressionNode expr, String[] names, int idx, StringBuild MappingConstructorExpressionNode mappingCtrExpr = (MappingConstructorExpressionNode) expr; Map mappingFields = convertMappingFieldsToMap(mappingCtrExpr); SpecificFieldNode mappingFieldNode = mappingFields.get(name); + TypeSymbol fieldType = getFieldType(targetTypeSymbol, name); if (mappingFieldNode == null) { LinePosition insertPosition; if (!mappingFields.isEmpty()) { @@ -1577,17 +1614,24 @@ private void genSource(ExpressionNode expr, String[] names, int idx, StringBuild } else { insertPosition = mappingCtrExpr.closeBrace().lineRange().startLine(); } - genSource(null, names, idx, stringBuilder, mappingExpr, insertPosition, textEdits); + genSource(null, names, idx, stringBuilder, mappingExpr, insertPosition, textEdits, fieldType); } else { genSource(mappingFieldNode.valueExpr().orElseThrow(), names, idx + 1, stringBuilder, mappingExpr, - null, textEdits); + null, textEdits, fieldType); } } else if (expr.kind() == SyntaxKind.LIST_CONSTRUCTOR) { ListConstructorExpressionNode listCtrExpr = (ListConstructorExpressionNode) expr; String name = names[idx]; if (name.matches("\\d+")) { int index = Integer.parseInt(name); - if (index >= listCtrExpr.expressions().size()) { + TypeSymbol rawType = targetTypeSymbol != null ? CommonUtils.getRawType(targetTypeSymbol) : null; + + // Handle tuple types with empty constructor + if (rawType != null && rawType.typeKind() == TypeDescKind.TUPLE + && listCtrExpr.expressions().isEmpty()) { + String tupleExpr = generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr); + textEdits.add(new TextEdit(CommonUtils.toRange(listCtrExpr.lineRange()), tupleExpr)); + } else if (index >= listCtrExpr.expressions().size()) { if (idx > 0) { stringBuilder.append(", "); } @@ -1598,10 +1642,10 @@ private void genSource(ExpressionNode expr, String[] names, int idx, StringBuild } else { insertPosition = listCtrExpr.closeBracket().lineRange().startLine(); } - genSource(null, names, idx, stringBuilder, mappingExpr, insertPosition, textEdits); + genSource(null, names, idx, stringBuilder, mappingExpr, insertPosition, textEdits, null); } else { genSource((ExpressionNode) listCtrExpr.expressions().get(index), names, idx + 1, stringBuilder, - mappingExpr, null, textEdits); + mappingExpr, null, textEdits, null); } } } else if (expr.kind() == SyntaxKind.QUERY_EXPRESSION) { @@ -1611,8 +1655,88 @@ private void genSource(ExpressionNode expr, String[] names, int idx, StringBuild } else { expr = ((CollectClauseNode) clauseNode).expression(); } - genSource(expr, names, idx, stringBuilder, mappingExpr, position, textEdits); + genSource(expr, names, idx, stringBuilder, mappingExpr, position, textEdits, targetTypeSymbol); + } + } + + private String generateTupleExpression(TupleTypeSymbol tupleType, int mappedIndex, String mappingExpr) { + List memberTypes = tupleType.memberTypeDescriptors(); + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < memberTypes.size(); i++) { + if (i > 0) { + sb.append(", "); + } + if (i == mappedIndex) { + sb.append(mappingExpr); + } else { + sb.append(getDefaultValue(memberTypes.get(i))); + } + } + sb.append("]"); + return sb.toString(); + } + + private String getDefaultValue(TypeSymbol typeSymbol) { + TypeSymbol rawType = CommonUtils.getRawType(typeSymbol); + TypeDescKind kind = rawType.typeKind(); + return switch (kind) { + case INT, BYTE -> "0"; + case FLOAT -> "0.0"; + case DECIMAL -> "0d"; + case STRING, STRING_CHAR -> "\"\""; + case BOOLEAN -> "false"; + case NIL -> "()"; + case ARRAY -> "[]"; + case MAP, RECORD -> "{}"; + default -> "()"; + }; + } + + private TypeSymbol getFieldType(TypeSymbol parentType, String fieldName) { + if (parentType == null) { + return null; + } + TypeSymbol rawType = CommonUtils.getRawType(parentType); + if (rawType.typeKind() == TypeDescKind.RECORD) { + RecordFieldSymbol fieldSymbol = ((RecordTypeSymbol) rawType).fieldDescriptors().get(fieldName); + return fieldSymbol != null ? fieldSymbol.typeDescriptor() : null; + } + return null; + } + + private String generateNestedExpression(String[] names, int startIdx, String mappingExpr, TypeSymbol typeSymbol) { + StringBuilder sb = new StringBuilder(); + TypeSymbol currentType = typeSymbol; + + for (int i = startIdx; i < names.length; i++) { + String name = names[i]; + if (name.matches("\\d+")) { + // This is a tuple/array index + TypeSymbol rawType = currentType != null ? CommonUtils.getRawType(currentType) : null; + if (rawType != null && rawType.typeKind() == TypeDescKind.TUPLE) { + int index = Integer.parseInt(name); + sb.append(generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr)); + return sb.toString(); + } else { + // Regular array - just append the expression + sb.append(mappingExpr); + return sb.toString(); + } + } else { + // This is a record field + sb.append("{").append(name).append(": "); + currentType = getFieldType(currentType, name); + } + } + + // If we get here, append the mapping expression and close braces + sb.append(mappingExpr); + for (int i = startIdx; i < names.length; i++) { + if (!names[i].matches("\\d+")) { + sb.append("}"); + } } + return sb.toString(); } private void genDeleteMappingSource(SemanticModel semanticModel, ExpressionNode expr, String[] names, int idx, @@ -1992,13 +2116,13 @@ public JsonElement getQuery(SemanticModel semanticModel, JsonElement cd, JsonEle if (clauseType.equals("collect")) { String query = getQuerySource(mapping.expression(), "collect", targetTypeSymbol); - genSource(expr, mapping.output().split(DOT), 1, new StringBuilder(), query, null, textEdits); + genSource(expr, mapping.output().split(DOT), 1, new StringBuilder(), query, null, textEdits, null); } else { if (targetTypeSymbol.typeKind() == TypeDescKind.ARRAY) { TypeSymbol typeSymbol = CommonUtils.getRawType(((ArrayTypeSymbol) targetTypeSymbol).memberTypeDescriptor()); String query = getQuerySource(mapping.expression(), "select", typeSymbol); - genSource(expr, mapping.output().split(DOT), 1, new StringBuilder(), query, null, textEdits); + genSource(expr, mapping.output().split(DOT), 1, new StringBuilder(), query, null, textEdits, null); } } return gson.toJsonTree(textEditsMap); @@ -2623,7 +2747,7 @@ public JsonElement genMappingFunction(WorkspaceManager workspaceManager, Semanti filePath, functionMetadata, textEditsMap, semanticModel, isCustomFunction); List textEdits = new ArrayList<>(); genSource(targetNode.matchingNode().expr(), mapping.output().split(DOT), 1, new StringBuilder(), - functionName + "(" + mapping.expression() + ")", null, textEdits); + functionName + "(" + mapping.expression() + ")", null, textEdits, null); textEditsMap.computeIfAbsent(filePath, k -> new ArrayList<>()) .addAll(textEdits); return gson.toJsonTree(textEditsMap); diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/DataMapperService.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/DataMapperService.java index aac65f9d3d..adfa404c29 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/DataMapperService.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/DataMapperService.java @@ -142,14 +142,15 @@ public CompletableFuture getSource(DataMapperSourceReq try { Path filePath = Path.of(request.filePath()); this.workspaceManager.loadProject(filePath); + Optional semanticModel = this.workspaceManager.semanticModel(filePath); Optional document = this.workspaceManager.document(filePath); - if (document.isEmpty()) { + if (document.isEmpty() || semanticModel.isEmpty()) { return response; } DataMapManager dataMapManager = new DataMapManager(document.get()); - response.setTextEdits(dataMapManager.getSource(filePath, request.codedata(), request.mapping(), - request.targetField())); + response.setTextEdits(dataMapManager.getSource(semanticModel.get(), filePath, request.codedata(), + request.mapping(), request.targetField())); } catch (Throwable e) { response.setError(e); } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java index cc0f0e41db..7a6f81f426 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java @@ -50,25 +50,26 @@ public class DataMappingDeleteTest extends AbstractLSTest { @Override protected Object[] getConfigsList() { return new Object[][]{ - {Path.of("variable1.json")}, - {Path.of("variable2.json")}, - {Path.of("variable3.json")}, - {Path.of("query1.json")}, - {Path.of("array.json")}, - {Path.of("array2.json")}, - {Path.of("array3.json")}, - {Path.of("variable4.json")}, - {Path.of("variable5.json")}, - {Path.of("function_defn1.json")}, - {Path.of("function_defn2.json")}, - {Path.of("variable6.json")}, - {Path.of("variable7.json")}, - {Path.of("variable8.json")}, - {Path.of("variable9.json")}, - {Path.of("variable10.json")}, - {Path.of("variable11.json")}, - {Path.of("query_collect.json")}, - {Path.of("query_collect2.json")} +// {Path.of("variable1.json")}, +// {Path.of("variable2.json")}, +// {Path.of("variable3.json")}, +// {Path.of("query1.json")}, +// {Path.of("array.json")}, +// {Path.of("array2.json")}, +// {Path.of("array3.json")}, +// {Path.of("variable4.json")}, +// {Path.of("variable5.json")}, +// {Path.of("function_defn1.json")}, +// {Path.of("function_defn2.json")}, +// {Path.of("variable6.json")}, +// {Path.of("variable7.json")}, +// {Path.of("variable8.json")}, +// {Path.of("variable9.json")}, +// {Path.of("variable10.json")}, +// {Path.of("variable11.json")}, +// {Path.of("query_collect.json")}, +// {Path.of("query_collect2.json")}, + {Path.of("tupleMember.json")}, }; } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java index 2bf4efa987..9ffb45f3bc 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java @@ -45,124 +45,124 @@ public class DataMappingModelTest extends AbstractLSTest { @Override protected Object[] getConfigsList() { return new Object[][]{ -// {Path.of("query1.json")}, -// {Path.of("query2.json")}, -// {Path.of("query3.json")}, -// {Path.of("query4.json")}, -// {Path.of("query5.json")}, -// {Path.of("query6.json")}, -// {Path.of("query7.json")}, -// {Path.of("query8.json")}, -// {Path.of("variable1.json")}, -// {Path.of("variable2.json")}, -// {Path.of("variable3.json")}, -// {Path.of("variable4.json")}, -// {Path.of("variable5.json")}, -// {Path.of("variable6.json")}, -// {Path.of("variable7.json")}, -// {Path.of("variable8.json")}, -// {Path.of("variable9.json")}, -// {Path.of("variable10.json")}, -// {Path.of("variable11.json")}, -// {Path.of("variable12.json")}, -// {Path.of("variable13.json")}, -// {Path.of("variable14.json")}, -// {Path.of("variable15.json")}, -// {Path.of("variable16.json")}, -// {Path.of("variable17.json")}, -// {Path.of("variable18.json")}, -// {Path.of("variable19.json")}, -// {Path.of("variable20.json")}, -// {Path.of("variable21.json")}, -// {Path.of("variable22.json")}, -// {Path.of("variable23.json")}, -// {Path.of("variable24.json")}, -// {Path.of("variable25.json")}, -// {Path.of("variable26.json")}, -// {Path.of("variable27.json")}, -// {Path.of("variable28.json")}, -// {Path.of("variable29.json")}, -// {Path.of("variable30.json")}, -// {Path.of("variable31.json")}, -// {Path.of("variable32.json")}, -// {Path.of("variable33.json")}, -// {Path.of("variable34.json")}, -// {Path.of("variable35.json")}, -// {Path.of("variable36.json")}, -// {Path.of("variable37.json")}, -// {Path.of("variable38.json")}, -// {Path.of("variable39.json")}, -// {Path.of("sub_mapping1.json")}, -// {Path.of("sub_mapping2.json")}, -// {Path.of("sub_mapping3.json")}, -// {Path.of("sub_mapping4.json")}, -// {Path.of("variable40.json")}, -// {Path.of("recursive1.json")}, -// {Path.of("recursive2.json")}, -// {Path.of("variable41.json")}, -// {Path.of("primitiveArray.json")}, -// {Path.of("enum.json")}, -// {Path.of("enum2.json")}, -// {Path.of("union.json")}, -// {Path.of("union1.json")}, -// {Path.of("const.json")}, -// {Path.of("sub_mapping5.json")}, -// {Path.of("variable42.json")}, -// {Path.of("variable43.json")}, -// {Path.of("query9.json")}, -// {Path.of("variable44.json")}, -// {Path.of("union2.json")}, -// {Path.of("query10.json")}, -// {Path.of("query11.json")}, -// {Path.of("variable45.json")}, -// {Path.of("function_def1.json")}, -// {Path.of("function_def2.json")}, -// {Path.of("function_def3.json")}, -// {Path.of("function_def4.json")}, -// {Path.of("function_def5.json")}, -// {Path.of("sub_mapping6.json")}, -// {Path.of("variable46.json")}, -// {Path.of("variable47.json")}, -// {Path.of("union3.json")}, -// {Path.of("union4.json")}, -// {Path.of("union5.json")}, -// {Path.of("variable48.json")}, -// {Path.of("variable49.json")}, -// {Path.of("query12.json")}, -// {Path.of("query13.json")}, -// {Path.of("query14.json")}, -// {Path.of("query15.json")}, -// {Path.of("function_def6.json")}, -// {Path.of("query16.json")}, -// {Path.of("query17.json")}, -// {Path.of("variable50.json")}, -// {Path.of("variable51.json")}, -// {Path.of("variable52.json")}, -// {Path.of("query18.json")}, -// {Path.of("query19.json")}, -// {Path.of("query20.json")}, -// {Path.of("configurable_variable1.json")}, -// {Path.of("configurable_variable2.json")}, -// {Path.of("function_def7.json")}, -// {Path.of("variable53.json")}, -// {Path.of("variable54.json")}, -// {Path.of("variable55.json")}, -// {Path.of("variable56.json")}, -// {Path.of("query21.json")}, -// {Path.of("query22.json")}, -// {Path.of("variable57.json")}, -// {Path.of("focussedView.json")}, -// {Path.of("query23.json")}, -// {Path.of("query24.json")}, -// {Path.of("map.json")}, -// {Path.of("sub_mapping7.json")}, -// {Path.of("query25.json")}, -// {Path.of("query26.json")}, -// {Path.of("query27.json")}, -// {Path.of("query28.json")}, -// {Path.of("query29.json")}, -// {Path.of("variable58.json")}, -// {Path.of("variable59.json")}, + {Path.of("query1.json")}, + {Path.of("query2.json")}, + {Path.of("query3.json")}, + {Path.of("query4.json")}, + {Path.of("query5.json")}, + {Path.of("query6.json")}, + {Path.of("query7.json")}, + {Path.of("query8.json")}, + {Path.of("variable1.json")}, + {Path.of("variable2.json")}, + {Path.of("variable3.json")}, + {Path.of("variable4.json")}, + {Path.of("variable5.json")}, + {Path.of("variable6.json")}, + {Path.of("variable7.json")}, + {Path.of("variable8.json")}, + {Path.of("variable9.json")}, + {Path.of("variable10.json")}, + {Path.of("variable11.json")}, + {Path.of("variable12.json")}, + {Path.of("variable13.json")}, + {Path.of("variable14.json")}, + {Path.of("variable15.json")}, + {Path.of("variable16.json")}, + {Path.of("variable17.json")}, + {Path.of("variable18.json")}, + {Path.of("variable19.json")}, + {Path.of("variable20.json")}, + {Path.of("variable21.json")}, + {Path.of("variable22.json")}, + {Path.of("variable23.json")}, + {Path.of("variable24.json")}, + {Path.of("variable25.json")}, + {Path.of("variable26.json")}, + {Path.of("variable27.json")}, + {Path.of("variable28.json")}, + {Path.of("variable29.json")}, + {Path.of("variable30.json")}, + {Path.of("variable31.json")}, + {Path.of("variable32.json")}, + {Path.of("variable33.json")}, + {Path.of("variable34.json")}, + {Path.of("variable35.json")}, + {Path.of("variable36.json")}, + {Path.of("variable37.json")}, + {Path.of("variable38.json")}, + {Path.of("variable39.json")}, + {Path.of("sub_mapping1.json")}, + {Path.of("sub_mapping2.json")}, + {Path.of("sub_mapping3.json")}, + {Path.of("sub_mapping4.json")}, + {Path.of("variable40.json")}, + {Path.of("recursive1.json")}, + {Path.of("recursive2.json")}, + {Path.of("variable41.json")}, + {Path.of("primitiveArray.json")}, + {Path.of("enum.json")}, + {Path.of("enum2.json")}, + {Path.of("union.json")}, + {Path.of("union1.json")}, + {Path.of("const.json")}, + {Path.of("sub_mapping5.json")}, + {Path.of("variable42.json")}, + {Path.of("variable43.json")}, + {Path.of("query9.json")}, + {Path.of("variable44.json")}, + {Path.of("union2.json")}, + {Path.of("query10.json")}, + {Path.of("query11.json")}, + {Path.of("variable45.json")}, + {Path.of("function_def1.json")}, + {Path.of("function_def2.json")}, + {Path.of("function_def3.json")}, + {Path.of("function_def4.json")}, + {Path.of("function_def5.json")}, + {Path.of("sub_mapping6.json")}, + {Path.of("variable46.json")}, + {Path.of("variable47.json")}, + {Path.of("union3.json")}, + {Path.of("union4.json")}, + {Path.of("union5.json")}, + {Path.of("variable48.json")}, + {Path.of("variable49.json")}, + {Path.of("query12.json")}, + {Path.of("query13.json")}, + {Path.of("query14.json")}, + {Path.of("query15.json")}, + {Path.of("function_def6.json")}, + {Path.of("query16.json")}, + {Path.of("query17.json")}, + {Path.of("variable50.json")}, + {Path.of("variable51.json")}, + {Path.of("variable52.json")}, + {Path.of("query18.json")}, + {Path.of("query19.json")}, + {Path.of("query20.json")}, + {Path.of("configurable_variable1.json")}, + {Path.of("configurable_variable2.json")}, + {Path.of("function_def7.json")}, + {Path.of("variable53.json")}, + {Path.of("variable54.json")}, + {Path.of("variable55.json")}, + {Path.of("variable56.json")}, + {Path.of("query21.json")}, + {Path.of("query22.json")}, + {Path.of("variable57.json")}, + {Path.of("focussedView.json")}, + {Path.of("query23.json")}, + {Path.of("query24.json")}, + {Path.of("map.json")}, + {Path.of("sub_mapping7.json")}, + {Path.of("query25.json")}, + {Path.of("query26.json")}, + {Path.of("query27.json")}, + {Path.of("query28.json")}, + {Path.of("query29.json")}, + {Path.of("variable58.json")}, + {Path.of("variable59.json")}, {Path.of("tuple.json")} }; } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java index df1a1133ed..b750ae4d3b 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java @@ -50,61 +50,64 @@ public class DataMappingSourceTest extends AbstractLSTest { @Override protected Object[] getConfigsList() { return new Object[][]{ - {Path.of("variable1.json")}, - {Path.of("variable2.json")}, - {Path.of("variable2_1.json")}, - {Path.of("variable2_2.json")}, - {Path.of("variable3.json")}, - {Path.of("variable3_1.json")}, - {Path.of("variable3_2.json")}, - {Path.of("variable5_array0.json")}, - {Path.of("variable5_array1.json")}, - {Path.of("variable5_array2.json")}, - {Path.of("variable5_array3.json")}, - {Path.of("variable6.json")}, - {Path.of("variable7_new.json")}, - {Path.of("variable7_new1.json")}, - {Path.of("variable8_new.json")}, - {Path.of("variable8_new1.json")}, - {Path.of("variable9.json")}, - {Path.of("variable9_new.json")}, - {Path.of("variable9_new1.json")}, - {Path.of("variable10.json")}, - {Path.of("variable10_new.json")}, - {Path.of("variable11.json")}, - {Path.of("variable11_new.json")}, - {Path.of("variable12.json")}, - {Path.of("variable12_new1.json")}, - {Path.of("variable12_new2.json")}, - {Path.of("variable13.json")}, - {Path.of("variable13_new1.json")}, - {Path.of("variable13_new2.json")}, - {Path.of("variable13_new3.json")}, - {Path.of("variable13_new4.json")}, - {Path.of("variable13_new5.json")}, - {Path.of("variable13_new6.json")}, - {Path.of("variable13_new7.json")}, - {Path.of("variable14.json")}, - {Path.of("variable15.json")}, - {Path.of("variable15_new.json")}, - {Path.of("variable16.json")}, - {Path.of("variable16_new.json")}, - {Path.of("variable16_new1.json")}, - {Path.of("query1.json")}, - {Path.of("query2.json")}, - {Path.of("query3.json")}, - {Path.of("variable17.json")}, - {Path.of("variable18.json")}, - {Path.of("variable19.json")}, - {Path.of("function_defn1.json")}, - {Path.of("function_defn2.json")}, - {Path.of("function_defn3.json")}, - {Path.of("query4.json")}, - {Path.of("query5.json")}, - {Path.of("query6.json")}, - {Path.of("query7.json")}, - {Path.of("query8.json")}, - {Path.of("variable20.json")}, +// {Path.of("variable1.json")}, +// {Path.of("variable2.json")}, +// {Path.of("variable2_1.json")}, +// {Path.of("variable2_2.json")}, +// {Path.of("variable3.json")}, +// {Path.of("variable3_1.json")}, +// {Path.of("variable3_2.json")}, +// {Path.of("variable5_array0.json")}, +// {Path.of("variable5_array1.json")}, +// {Path.of("variable5_array2.json")}, +// {Path.of("variable5_array3.json")}, +// {Path.of("variable6.json")}, +// {Path.of("variable7_new.json")}, +// {Path.of("variable7_new1.json")}, +// {Path.of("variable8_new.json")}, +// {Path.of("variable8_new1.json")}, +// {Path.of("variable9.json")}, +// {Path.of("variable9_new.json")}, +// {Path.of("variable9_new1.json")}, +// {Path.of("variable10.json")}, +// {Path.of("variable10_new.json")}, +// {Path.of("variable11.json")}, +// {Path.of("variable11_new.json")}, +// {Path.of("variable12.json")}, +// {Path.of("variable12_new1.json")}, +// {Path.of("variable12_new2.json")}, +// {Path.of("variable13.json")}, +// {Path.of("variable13_new1.json")}, +// {Path.of("variable13_new2.json")}, +// {Path.of("variable13_new3.json")}, +// {Path.of("variable13_new4.json")}, +// {Path.of("variable13_new5.json")}, +// {Path.of("variable13_new6.json")}, +// {Path.of("variable13_new7.json")}, +// {Path.of("variable14.json")}, +// {Path.of("variable15.json")}, +// {Path.of("variable15_new.json")}, +// {Path.of("variable16.json")}, +// {Path.of("variable16_new.json")}, +// {Path.of("variable16_new1.json")}, +// {Path.of("query1.json")}, +// {Path.of("query2.json")}, +// {Path.of("query3.json")}, +// {Path.of("variable17.json")}, +// {Path.of("variable18.json")}, +// {Path.of("variable19.json")}, +// {Path.of("function_defn1.json")}, +// {Path.of("function_defn2.json")}, +// {Path.of("function_defn3.json")}, +// {Path.of("query4.json")}, +// {Path.of("query5.json")}, +// {Path.of("query6.json")}, +// {Path.of("query7.json")}, +// {Path.of("query8.json")}, +// {Path.of("variable20.json")}, + {Path.of("tupleMapping.json")}, +// {Path.of("tupleMapping2.json")}, +// {Path.of("tupleTransformation.json")} }; } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping.json new file mode 100644 index 0000000000..e29429f2c8 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping.json @@ -0,0 +1,41 @@ +{ + "source": "tupleMapping.bal", + "description": "Tuple mapping", + "codedata": { + "node": "VARIABLE", + "lineRange": { + "fileName": "tupleMapping.bal", + "startLine": { + "line": 13, + "offset": 8 + }, + "endLine": { + "line": 13, + "offset": 41 + } + }, + "sourceCode": "[int, string, float] result = [];" + }, + "mapping": { + "output": "result[0]", + "expression": "data[0]" + }, + "targetField": "result", + "output": { + "tupleMapping.bal": [ + { + "range": { + "start": { + "line": 13, + "character": 38 + }, + "end": { + "line": 13, + "character": 40 + } + }, + "newText": "[data[0], \"\", 0.0]" + } + ] + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping2.json new file mode 100644 index 0000000000..cdd03e53e6 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping2.json @@ -0,0 +1,41 @@ +{ + "source": "tupleMapping.bal", + "description": "Tuple mapping", + "codedata": { + "node": "VARIABLE", + "lineRange": { + "fileName": "tupleMapping.bal", + "startLine": { + "line": 15, + "offset": 8 + }, + "endLine": { + "line": 15, + "offset": 63 + } + }, + "sourceCode": "[int, string, float] result = [];" + }, + "mapping": { + "output": "person2.data[0]", + "expression": "result[0]" + }, + "targetField": "person2", + "output": { + "tupleMapping.bal": [ + { + "range": { + "start": { + "line": 15, + "character": 61 + }, + "end": { + "line": 15, + "character": 61 + } + }, + "newText": ", data: [result[0], \"\", 0.0]" + } + ] + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleTransformation.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleTransformation.json new file mode 100644 index 0000000000..1dad688de8 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleTransformation.json @@ -0,0 +1,41 @@ +{ + "source": "tupleTransformation.bal", + "description": "Tuple transformation mapping", + "codedata": { + "node": "VARIABLE", + "lineRange": { + "fileName": "tupleTransformation.bal", + "startLine": { + "line": 28, + "offset": 0 + }, + "endLine": { + "line": 28, + "offset": 94 + } + }, + "sourceCode": "[int, string, float] result = [];" + }, + "mapping": { + "output": "transform1[2].id", + "expression": "person.id" + }, + "targetField": "transform1", + "output": { + "tupleTransformation.bal": [ + { + "range": { + "start": { + "line": 28, + "character": 91 + }, + "end": { + "line": 28, + "character": 93 + } + }, + "newText": "[0, \"\", person.id]" + } + ] + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleMapping.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleMapping.bal new file mode 100644 index 0000000000..753a8b76bc --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleMapping.bal @@ -0,0 +1,21 @@ +import ballerina/log; + +type TupleType [int, string, float]; + +public type Person record { + string name; + int age; + TupleType data; +}; + +public function main() returns error? { + do { + [int, string, float] data = [1, "Alice", 99.5]; + [int, string, float] result = []; + Person person1 = {name: "Bob", age: 30, data: data}; + Person person2 = {age: data[0], name: person1.data[1]}; //Case 02 + } on fail error e { + log:printError("Error occurred", 'error = e); + return e; + } +} \ No newline at end of file diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleTransformation.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleTransformation.bal new file mode 100644 index 0000000000..13e8c7e3b7 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleTransformation.bal @@ -0,0 +1,29 @@ +type Student record { + string id; + string name; +}; + +type DetailedPerson record { + string id; + string name; +}; + +type CourseDetails record { + string courseId; + string courseName; + decimal credits; + string grade; +}; + +type DetailedStudent record { + Course[] courses; +}; + +type Course record { + string id; + string name; + float credits; +}; + +// Case 01 +function transform1(DetailedPerson person, Course course) returns [int, string, Course] => []; diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tupleMember.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tupleMember.json new file mode 100644 index 0000000000..d610fc7f29 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tupleMember.json @@ -0,0 +1,36 @@ +{ + "source": "tupleMember.bal", + "description": "", + "codedata": { + "node": "VARIABLE", + "lineRange": { + "fileName": "tupleMember.bal", + "startLine": { + "line": 15, + "offset": 8 + }, + "endLine": { + "line": 15, + "offset": 54 + } + } + }, + "mapping": { + "output": "person2.data[0]", + "inputs": [ + "result[0]" + ], + "expression": "result[0]", + "diagnostics": [], + "elements": [], + "isQueryExpression": false, + "isFunctionCall": false, + "elementAccessIndex": [ + "0" + ] + }, + "targetField": "person2", + "output": { + "tupleMember.bal": [] + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/source/tupleMember.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/source/tupleMember.bal new file mode 100644 index 0000000000..2dd6a51288 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/source/tupleMember.bal @@ -0,0 +1,21 @@ +import ballerina/log; + +type TupleType [int, string, float]; + +public type Person record { + string name; + int age; + TupleType data; +}; + +public function main() returns error? { + do { + [int, string, float] data = [1, "Alice", 99.5]; + [int, string, float] result = []; //Case 01 + Person person1 = {name: "Bob", age: 30, data: data}; + Person person2 = {data: [result[0], "", 0.0]}; //Case 02 + } on fail error e { + log:printError("Error occurred", 'error = e); + return e; + } +} \ No newline at end of file diff --git a/misc/diagram-util/src/test/java/org/ballerinalang/diagramutil/RefTypeTest.java b/misc/diagram-util/src/test/java/org/ballerinalang/diagramutil/RefTypeTest.java index a6573ebad9..226c0fd2bc 100644 --- a/misc/diagram-util/src/test/java/org/ballerinalang/diagramutil/RefTypeTest.java +++ b/misc/diagram-util/src/test/java/org/ballerinalang/diagramutil/RefTypeTest.java @@ -86,7 +86,7 @@ public void getRefTypeForSymbol(Path jsonPath) throws IOException { String refTypeJson = gson.toJson(refType).concat(System.lineSeparator()); String expectedRefTypeJson = gson.toJson(jsonObject.get("refType")).concat(System.lineSeparator()); if (!refTypeJson.equals(expectedRefTypeJson)) { -// updateConfig(jsonPath, refTypeJson); + updateConfig(jsonPath, refTypeJson); Assert.fail( String.format("Reference type JSON does not match.\n Expected : %s\n Received %s", expectedRefTypeJson, refTypeJson)); diff --git a/misc/diagram-util/src/test/resources/RefTypeTest/BalProject7/main.bal b/misc/diagram-util/src/test/resources/RefTypeTest/BalProject7/main.bal index ba7c64e1ae..4d78276c9b 100644 --- a/misc/diagram-util/src/test/resources/RefTypeTest/BalProject7/main.bal +++ b/misc/diagram-util/src/test/resources/RefTypeTest/BalProject7/main.bal @@ -11,3 +11,8 @@ public type RecordWithStream record {| string id; SimpleStream data; |}; + +public type PersonWithTuple record {| + string name; + [int, Person] info; +|}; From cf9ddb7d11b4a13769903a5f70f59d77cdb16dcf Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Wed, 21 Jan 2026 14:03:52 +0530 Subject: [PATCH 04/13] Support tuple mappings and visualization in reusable mode --- .../core/DataMapManager.java | 55 ++++-- .../extension/DataMappingModelTest.java | 3 +- .../extension/DataMappingSourceTest.java | 114 +++++------ .../config/tupleBasedTransformation.json | 185 ++++++++++++++++++ .../source/tupleBasedTransformation.bal | 29 +++ .../config/tupleTransformation.json | 4 +- 6 files changed, 317 insertions(+), 73 deletions(-) create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/tupleBasedTransformation.bal diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index fee095c196..79d6c0485e 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -228,8 +228,9 @@ public JsonElement getMappings(SemanticModel semanticModel, JsonElement cd, Line .toList(); Map references = new HashMap<>(); RefType refType; + TypeSymbol targetTypeSymbol; try { - TypeSymbol targetTypeSymbol = targetNode.typeSymbol(); + targetTypeSymbol = targetNode.typeSymbol(); TypeSymbol rawtargetTypeSymbol = CommonUtils.getRawType(targetNode.typeSymbol()); if (rawtargetTypeSymbol.typeKind() == TypeDescKind.UNION) { targetTypeSymbol = @@ -366,7 +367,7 @@ public JsonElement getMappings(SemanticModel semanticModel, JsonElement cd, Line functionDocument, dataMappingDocument, enumPorts, inputPorts); } else if (expr.kind() == SyntaxKind.LIST_CONSTRUCTOR) { genMapping((ListConstructorExpressionNode) expr, mappings, name, semanticModel, functionDocument, - dataMappingDocument, enumPorts, inputPorts); + dataMappingDocument, enumPorts, inputPorts, targetTypeSymbol); } else { genMapping(expr, name, mappings, semanticModel, functionDocument, dataMappingDocument, enumPorts); } @@ -766,7 +767,7 @@ private void genMapping(MappingConstructorExpressionNode mappingCtrExpr, List mappings, String name, SemanticModel semanticModel, Document functionDocument, Document dataMappingDocument, - List enumPorts, List inputPorts) { + List enumPorts, List inputPorts, TypeSymbol targetType) { SeparatedNodeList expressions = listCtrExpr.expressions(); int size = expressions.size(); @@ -801,21 +802,39 @@ private void genMapping(ListConstructorExpressionNode listCtrExpr, List } // Use bracket notation for tuples, dot notation for arrays - boolean isTuple = semanticModel.typeOf(listCtrExpr) - .map(t -> CommonUtils.getRawType(t).typeKind() == TypeDescKind.TUPLE) - .orElse(false); + // First try to use the target type, then fall back to semantic model + boolean isTuple = false; + if (targetType != null) { + TypeSymbol rawTargetType = CommonUtils.getRawType(targetType); + isTuple = rawTargetType.typeKind() == TypeDescKind.TUPLE; + } + if (!isTuple) { + isTuple = semanticModel.typeOf(listCtrExpr) + .map(t -> CommonUtils.getRawType(t).typeKind() == TypeDescKind.TUPLE) + .orElse(false); + } List mappingElements = new ArrayList<>(); + // Get tuple member types if available + List memberTypes = null; + if (isTuple && targetType != null) { + TypeSymbol rawTargetType = CommonUtils.getRawType(targetType); + if (rawTargetType.typeKind() == TypeDescKind.TUPLE) { + memberTypes = ((TupleTypeSymbol) rawTargetType).memberTypeDescriptors(); + } + } + for (int i = 0; i < size; i++) { List elements = new ArrayList<>(); Node expr = expressions.get(i); String elementPath = isTuple ? name + "[" + i + "]" : name + "." + i; + TypeSymbol memberType = (memberTypes != null && i < memberTypes.size()) ? memberTypes.get(i) : null; if (expr.kind() == SyntaxKind.MAPPING_CONSTRUCTOR) { genMapping((MappingConstructorExpressionNode) expr, elements, elementPath, semanticModel, functionDocument, dataMappingDocument, enumPorts, inputPorts); } else if (expr.kind() == SyntaxKind.LIST_CONSTRUCTOR) { genMapping((ListConstructorExpressionNode) expr, elements, elementPath, semanticModel, - functionDocument, dataMappingDocument, enumPorts, inputPorts); + functionDocument, dataMappingDocument, enumPorts, inputPorts, memberType); } else { genMapping(expr, elementPath, elements, semanticModel, functionDocument, dataMappingDocument, enumPorts); @@ -1588,7 +1607,8 @@ private void genSource(ExpressionNode expr, String[] names, int idx, StringBuild TypeSymbol rawType = targetTypeSymbol != null ? CommonUtils.getRawType(targetTypeSymbol) : null; if (rawType != null && rawType.typeKind() == TypeDescKind.TUPLE) { int index = Integer.parseInt(name); - stringBuilder.append(generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr)); + stringBuilder.append(generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr, + names, idx)); } else { stringBuilder.append(mappingExpr); } @@ -1629,7 +1649,8 @@ private void genSource(ExpressionNode expr, String[] names, int idx, StringBuild // Handle tuple types with empty constructor if (rawType != null && rawType.typeKind() == TypeDescKind.TUPLE && listCtrExpr.expressions().isEmpty()) { - String tupleExpr = generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr); + String tupleExpr = generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr, + names, idx); textEdits.add(new TextEdit(CommonUtils.toRange(listCtrExpr.lineRange()), tupleExpr)); } else if (index >= listCtrExpr.expressions().size()) { if (idx > 0) { @@ -1659,7 +1680,8 @@ private void genSource(ExpressionNode expr, String[] names, int idx, StringBuild } } - private String generateTupleExpression(TupleTypeSymbol tupleType, int mappedIndex, String mappingExpr) { + private String generateTupleExpression(TupleTypeSymbol tupleType, int mappedIndex, String mappingExpr, + String[] names, int nameIdx) { List memberTypes = tupleType.memberTypeDescriptors(); StringBuilder sb = new StringBuilder("["); for (int i = 0; i < memberTypes.size(); i++) { @@ -1667,7 +1689,14 @@ private String generateTupleExpression(TupleTypeSymbol tupleType, int mappedInde sb.append(", "); } if (i == mappedIndex) { - sb.append(mappingExpr); + // Check if there are more field names after the index (nested field access) + if (nameIdx + 1 < names.length) { + // Generate nested expression for the tuple member + TypeSymbol memberType = memberTypes.get(i); + sb.append(generateNestedExpression(names, nameIdx + 1, mappingExpr, memberType)); + } else { + sb.append(mappingExpr); + } } else { sb.append(getDefaultValue(memberTypes.get(i))); } @@ -1715,7 +1744,7 @@ private String generateNestedExpression(String[] names, int startIdx, String map TypeSymbol rawType = currentType != null ? CommonUtils.getRawType(currentType) : null; if (rawType != null && rawType.typeKind() == TypeDescKind.TUPLE) { int index = Integer.parseInt(name); - sb.append(generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr)); + sb.append(generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr, names, i)); return sb.toString(); } else { // Regular array - just append the expression diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java index 9ffb45f3bc..fb32a13cc9 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java @@ -163,7 +163,8 @@ protected Object[] getConfigsList() { {Path.of("query29.json")}, {Path.of("variable58.json")}, {Path.of("variable59.json")}, - {Path.of("tuple.json")} + {Path.of("tuple.json")}, + {Path.of("tupleBasedTransformation.json")} }; } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java index b750ae4d3b..3dd0999278 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java @@ -50,64 +50,64 @@ public class DataMappingSourceTest extends AbstractLSTest { @Override protected Object[] getConfigsList() { return new Object[][]{ -// {Path.of("variable1.json")}, -// {Path.of("variable2.json")}, -// {Path.of("variable2_1.json")}, -// {Path.of("variable2_2.json")}, -// {Path.of("variable3.json")}, -// {Path.of("variable3_1.json")}, -// {Path.of("variable3_2.json")}, -// {Path.of("variable5_array0.json")}, -// {Path.of("variable5_array1.json")}, -// {Path.of("variable5_array2.json")}, -// {Path.of("variable5_array3.json")}, -// {Path.of("variable6.json")}, -// {Path.of("variable7_new.json")}, -// {Path.of("variable7_new1.json")}, -// {Path.of("variable8_new.json")}, -// {Path.of("variable8_new1.json")}, -// {Path.of("variable9.json")}, -// {Path.of("variable9_new.json")}, -// {Path.of("variable9_new1.json")}, -// {Path.of("variable10.json")}, -// {Path.of("variable10_new.json")}, -// {Path.of("variable11.json")}, -// {Path.of("variable11_new.json")}, -// {Path.of("variable12.json")}, -// {Path.of("variable12_new1.json")}, -// {Path.of("variable12_new2.json")}, -// {Path.of("variable13.json")}, -// {Path.of("variable13_new1.json")}, -// {Path.of("variable13_new2.json")}, -// {Path.of("variable13_new3.json")}, -// {Path.of("variable13_new4.json")}, -// {Path.of("variable13_new5.json")}, -// {Path.of("variable13_new6.json")}, -// {Path.of("variable13_new7.json")}, -// {Path.of("variable14.json")}, -// {Path.of("variable15.json")}, -// {Path.of("variable15_new.json")}, -// {Path.of("variable16.json")}, -// {Path.of("variable16_new.json")}, -// {Path.of("variable16_new1.json")}, -// {Path.of("query1.json")}, -// {Path.of("query2.json")}, -// {Path.of("query3.json")}, -// {Path.of("variable17.json")}, -// {Path.of("variable18.json")}, -// {Path.of("variable19.json")}, -// {Path.of("function_defn1.json")}, -// {Path.of("function_defn2.json")}, -// {Path.of("function_defn3.json")}, -// {Path.of("query4.json")}, -// {Path.of("query5.json")}, -// {Path.of("query6.json")}, -// {Path.of("query7.json")}, -// {Path.of("query8.json")}, -// {Path.of("variable20.json")}, + {Path.of("variable1.json")}, + {Path.of("variable2.json")}, + {Path.of("variable2_1.json")}, + {Path.of("variable2_2.json")}, + {Path.of("variable3.json")}, + {Path.of("variable3_1.json")}, + {Path.of("variable3_2.json")}, + {Path.of("variable5_array0.json")}, + {Path.of("variable5_array1.json")}, + {Path.of("variable5_array2.json")}, + {Path.of("variable5_array3.json")}, + {Path.of("variable6.json")}, + {Path.of("variable7_new.json")}, + {Path.of("variable7_new1.json")}, + {Path.of("variable8_new.json")}, + {Path.of("variable8_new1.json")}, + {Path.of("variable9.json")}, + {Path.of("variable9_new.json")}, + {Path.of("variable9_new1.json")}, + {Path.of("variable10.json")}, + {Path.of("variable10_new.json")}, + {Path.of("variable11.json")}, + {Path.of("variable11_new.json")}, + {Path.of("variable12.json")}, + {Path.of("variable12_new1.json")}, + {Path.of("variable12_new2.json")}, + {Path.of("variable13.json")}, + {Path.of("variable13_new1.json")}, + {Path.of("variable13_new2.json")}, + {Path.of("variable13_new3.json")}, + {Path.of("variable13_new4.json")}, + {Path.of("variable13_new5.json")}, + {Path.of("variable13_new6.json")}, + {Path.of("variable13_new7.json")}, + {Path.of("variable14.json")}, + {Path.of("variable15.json")}, + {Path.of("variable15_new.json")}, + {Path.of("variable16.json")}, + {Path.of("variable16_new.json")}, + {Path.of("variable16_new1.json")}, + {Path.of("query1.json")}, + {Path.of("query2.json")}, + {Path.of("query3.json")}, + {Path.of("variable17.json")}, + {Path.of("variable18.json")}, + {Path.of("variable19.json")}, + {Path.of("function_defn1.json")}, + {Path.of("function_defn2.json")}, + {Path.of("function_defn3.json")}, + {Path.of("query4.json")}, + {Path.of("query5.json")}, + {Path.of("query6.json")}, + {Path.of("query7.json")}, + {Path.of("query8.json")}, + {Path.of("variable20.json")}, {Path.of("tupleMapping.json")}, -// {Path.of("tupleMapping2.json")}, -// {Path.of("tupleTransformation.json")} + {Path.of("tupleMapping2.json")}, + {Path.of("tupleTransformation.json")} }; } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation.json new file mode 100644 index 0000000000..e68293d48c --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation.json @@ -0,0 +1,185 @@ +{ + "source": "tupleBasedTransformation.bal", + "description": "Sample diagram node", + "codedata": { + "node": "VARIABLE", + "lineRange": { + "fileName": "tupleBasedTransformation.bal", + "startLine": { + "line": 28, + "offset": 0 + }, + "endLine": { + "line": 28, + "offset": 123 + } + }, + "sourceCode": "Bar[] bars = [\n {a: 1, b: 3},\n (from var baz in bazs\n select {a: baz.x, b: baz.y})[0]\n ];" + }, + "position": { + "line": 28, + "offset": 91 + }, + "targetField": "transform1", + "model": { + "inputs": [ + { + "fields": [], + "name": "course", + "displayName": "course", + "typeName": "Course", + "kind": "record", + "category": "parameter", + "ref": "524765447" + }, + { + "fields": [], + "name": "person", + "displayName": "person", + "typeName": "DetailedPerson", + "kind": "record", + "category": "parameter", + "ref": "-692431599" + } + ], + "output": { + "members": [ + { + "name": "[0]", + "displayName": "transform1[0]", + "typeName": "int", + "kind": "int" + }, + { + "name": "[1]", + "displayName": "transform1[1]", + "typeName": "string", + "kind": "string" + }, + { + "fields": [], + "name": "[2]", + "displayName": "transform1[2]", + "typeName": "Course", + "kind": "record", + "ref": "524765447" + } + ], + "name": "transform1", + "displayName": "transform1", + "typeName": "[int, string, Course]", + "kind": "tuple" + }, + "mappings": [ + { + "output": "transform1", + "inputs": [], + "expression": "[0, person.id, {id: person.id}]", + "diagnostics": [ + { + "message": "missing non-defaultable required record field 'credits'", + "code": "BCE2520" + }, + { + "message": "missing non-defaultable required record field 'name'", + "code": "BCE2520" + } + ], + "elements": [ + { + "mappings": [ + { + "output": "transform1[0]", + "inputs": [], + "expression": "0", + "diagnostics": [], + "elements": [], + "isQueryExpression": false, + "isFunctionCall": false + } + ] + }, + { + "mappings": [ + { + "output": "transform1[1]", + "inputs": [ + "person.id" + ], + "expression": "person.id", + "diagnostics": [], + "elements": [], + "isQueryExpression": false, + "isFunctionCall": false + } + ] + }, + { + "mappings": [ + { + "output": "transform1[2].id", + "inputs": [ + "person.id" + ], + "expression": "person.id", + "diagnostics": [], + "elements": [], + "isQueryExpression": false, + "isFunctionCall": false + } + ] + } + ] + } + ], + "refs": { + "-692431599": { + "fields": [ + { + "name": "id", + "displayName": "id", + "typeName": "string", + "kind": "string", + "optional": false + }, + { + "name": "name", + "displayName": "name", + "typeName": "string", + "kind": "string", + "optional": false + } + ], + "typeName": "DetailedPerson", + "kind": "record" + }, + "524765447": { + "fields": [ + { + "name": "id", + "displayName": "id", + "typeName": "string", + "kind": "string", + "optional": false + }, + { + "name": "name", + "displayName": "name", + "typeName": "string", + "kind": "string", + "optional": false + }, + { + "name": "credits", + "displayName": "credits", + "typeName": "float", + "kind": "float", + "optional": false + } + ], + "typeName": "Course", + "kind": "record" + } + } + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/tupleBasedTransformation.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/tupleBasedTransformation.bal new file mode 100644 index 0000000000..2c6987ec6f --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/tupleBasedTransformation.bal @@ -0,0 +1,29 @@ +type Student record { + string id; + string name; +}; + +type DetailedPerson record { + string id; + string name; +}; + +type CourseDetails record { + string courseId; + string courseName; + decimal credits; + string grade; +}; + +type DetailedStudent record { + Course[] courses; +}; + +type Course record { + string id; + string name; + float credits; +}; + +// Case 01 +function transform1(DetailedPerson person, Course course) returns [int, string, Course] => [0, person.id, {id: person.id}]; diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleTransformation.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleTransformation.json index 1dad688de8..8aeb7348ba 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleTransformation.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleTransformation.json @@ -18,7 +18,7 @@ }, "mapping": { "output": "transform1[2].id", - "expression": "person.id" + "expression": "course.id" }, "targetField": "transform1", "output": { @@ -34,7 +34,7 @@ "character": 93 } }, - "newText": "[0, \"\", person.id]" + "newText": "[0, \"\", {id: course.id}]" } ] } From 1aec4a09a3f678cf6bee122d747579af5cd52eb8 Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Wed, 21 Jan 2026 14:20:48 +0530 Subject: [PATCH 05/13] Support tuple member mapping deletion in reusable data mapper --- .../core/DataMapManager.java | 43 +++++++++++++++++-- .../delete_mapping/config/tupleMember.json | 16 ++++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index 79d6c0485e..e3c5a4f9d6 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -1564,7 +1564,9 @@ public JsonElement deleteMapping(SemanticModel semanticModel, Path filePath, Jso List textEdits = new ArrayList<>(); textEditsMap.put(filePath, textEdits); String output = mapping.output(); - String[] splits = output.split(DOT); + // Normalize bracket notation to dot notation (e.g., data[0] -> data.0) + String normalizedOutput = output.replaceAll("\\[(\\d+)]", ".$1"); + String[] splits = normalizedOutput.split(DOT); if (targetNode != null && targetNode.matchingNode != null && targetNode.matchingNode.expr() != null) { ExpressionNode expr = targetNode.matchingNode.expr(); genDeleteMappingSource(semanticModel, expr, splits, 1, textEdits, targetNode.typeSymbol); @@ -1869,7 +1871,38 @@ private void genDeleteMappingSource(SemanticModel semanticModel, ExpressionNode } } - if (expressions.size() == 1) { + // Check if this is a tuple type - if so, replace with default value instead of deleting + // First try semantic model, then fall back to targetSymbol + boolean isTuple = false; + TypeSymbol tupleTypeSymbol = null; + + // Try semantic model first + Optional semanticType = semanticModel.typeOf(listCtrExpr); + if (semanticType.isPresent()) { + TypeSymbol rawType = CommonUtils.getRawType(semanticType.get()); + if (rawType.typeKind() == TypeDescKind.TUPLE) { + isTuple = true; + tupleTypeSymbol = rawType; + } + } + + // Fall back to targetSymbol if semantic model didn't work + if (!isTuple && targetSymbol != null) { + TypeSymbol rawType = CommonUtils.getRawType(targetSymbol); + if (rawType.typeKind() == TypeDescKind.TUPLE) { + isTuple = true; + tupleTypeSymbol = rawType; + } + } + + if (isTuple && tupleTypeSymbol != null) { + // For tuples, replace with default value for the member type + List memberTypes = ((TupleTypeSymbol) tupleTypeSymbol).memberTypeDescriptors(); + if (memberIdx < memberTypes.size()) { + String defaultVal = getDefaultValue(memberTypes.get(memberIdx)); + textEdits.add(new TextEdit(CommonUtils.toRange(expr.lineRange()), defaultVal)); + } + } else if (expressions.size() == 1) { textEdits.add(new TextEdit(CommonUtils.toRange(expr.lineRange()), "")); } else { if (memberIdx + 1 == expressions.size()) { @@ -1936,8 +1969,10 @@ private void genDeleteMappingSource(SemanticModel semanticModel, ExpressionNode Map mappingFields = convertMappingFieldsToMap(mappingCtrExpr); SpecificFieldNode mappingFieldNode = mappingFields.get(name); if (mappingFieldNode != null) { + // Get the field type for the next level + TypeSymbol fieldType = getFieldType(targetSymbol, name); genDeleteMappingSource(semanticModel, mappingFieldNode.valueExpr().orElseThrow(), names, idx + 1, - textEdits, targetSymbol); + textEdits, fieldType); } } else if (expr.kind() == SyntaxKind.LIST_CONSTRUCTOR) { ListConstructorExpressionNode listCtrExpr = (ListConstructorExpressionNode) expr; @@ -1945,6 +1980,8 @@ private void genDeleteMappingSource(SemanticModel semanticModel, ExpressionNode if (name.matches("\\d+")) { int index = Integer.parseInt(name); if (index < listCtrExpr.expressions().size()) { + // For tuples, pass the tuple type (not member type) so the base case can determine + // if it needs to replace with default value genDeleteMappingSource(semanticModel, (ExpressionNode) listCtrExpr.expressions().get(index), names, idx + 1, textEdits, targetSymbol); } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tupleMember.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tupleMember.json index d610fc7f29..8cfa47f441 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tupleMember.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tupleMember.json @@ -31,6 +31,20 @@ }, "targetField": "person2", "output": { - "tupleMember.bal": [] + "tupleMember.bal": [ + { + "range": { + "start": { + "line": 15, + "character": 33 + }, + "end": { + "line": 15, + "character": 42 + } + }, + "newText": "0" + } + ] } } From d605bd577305a2216bb841086d2d82b03ee65b06 Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Wed, 21 Jan 2026 16:23:09 +0530 Subject: [PATCH 06/13] Remove module info data from tuple members --- .../core/DataMapManager.java | 10 + .../extension/DataMappingDeleteTest.java | 38 ++-- .../extension/DataMappingModelTest.java | 2 +- .../data_mapper_model/config/tuple.json | 2 +- .../config/tupleBasedTransformation2.json | 177 ++++++++++++++++++ .../source/tupleBasedTransformation2.bal | 29 +++ .../models/connector/ReferenceType.java | 10 +- 7 files changed, 246 insertions(+), 22 deletions(-) create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation2.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/tupleBasedTransformation2.bal diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index e3c5a4f9d6..02a48c508d 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -3408,6 +3408,16 @@ private static class GenInputsVisitor extends NodeVisitor { @Override public void visit(FieldAccessExpressionNode node) { + ExpressionNode baseExpr = node.expression(); + // Check if the base expression involves a tuple indexed access + if (baseExpr.kind() == SyntaxKind.INDEXED_EXPRESSION) { + IndexedExpressionNode indexedExpr = (IndexedExpressionNode) baseExpr; + if (isTupleType(indexedExpr.containerExpression())) { + // For tuple field access like input[2].id, preserve the full path + addInput(node.toSourceCode().trim()); + return; + } + } String source = node.toSourceCode().trim(); String[] split = source.split("\\["); if (split.length > 1) { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java index 7a6f81f426..d5f109eefc 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java @@ -50,25 +50,25 @@ public class DataMappingDeleteTest extends AbstractLSTest { @Override protected Object[] getConfigsList() { return new Object[][]{ -// {Path.of("variable1.json")}, -// {Path.of("variable2.json")}, -// {Path.of("variable3.json")}, -// {Path.of("query1.json")}, -// {Path.of("array.json")}, -// {Path.of("array2.json")}, -// {Path.of("array3.json")}, -// {Path.of("variable4.json")}, -// {Path.of("variable5.json")}, -// {Path.of("function_defn1.json")}, -// {Path.of("function_defn2.json")}, -// {Path.of("variable6.json")}, -// {Path.of("variable7.json")}, -// {Path.of("variable8.json")}, -// {Path.of("variable9.json")}, -// {Path.of("variable10.json")}, -// {Path.of("variable11.json")}, -// {Path.of("query_collect.json")}, -// {Path.of("query_collect2.json")}, + {Path.of("variable1.json")}, + {Path.of("variable2.json")}, + {Path.of("variable3.json")}, + {Path.of("query1.json")}, + {Path.of("array.json")}, + {Path.of("array2.json")}, + {Path.of("array3.json")}, + {Path.of("variable4.json")}, + {Path.of("variable5.json")}, + {Path.of("function_defn1.json")}, + {Path.of("function_defn2.json")}, + {Path.of("variable6.json")}, + {Path.of("variable7.json")}, + {Path.of("variable8.json")}, + {Path.of("variable9.json")}, + {Path.of("variable10.json")}, + {Path.of("variable11.json")}, + {Path.of("query_collect.json")}, + {Path.of("query_collect2.json")}, {Path.of("tupleMember.json")}, }; diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java index fb32a13cc9..c2c2b44a18 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java @@ -189,7 +189,7 @@ public void test(Path config) throws IOException { TestConfig updateConfig = new TestConfig(testConfig.source(), testConfig.description(), testConfig.codedata(), testConfig.position(), testConfig.propertyKey(), testConfig.targetField(), model); - updateConfig(configJsonPath, updateConfig); +// updateConfig(configJsonPath, updateConfig); compareJsonElements(model, testConfig.model()); Assert.fail(String.format("Failed test: '%s' (%s)", testConfig.description(), configJsonPath)); } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json index 20c1dbe9ac..b4d2840451 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json @@ -115,7 +115,7 @@ ], "name": "data", "displayName": "data", - "typeName": "", + "typeName": "[int, string, float]", "kind": "tuple", "optional": false } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation2.json new file mode 100644 index 0000000000..28c0c1cbc3 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation2.json @@ -0,0 +1,177 @@ +{ + "source": "tupleBasedTransformation2.bal", + "description": "Sample diagram node", + "codedata": { + "node": "VARIABLE", + "lineRange": { + "fileName": "tupleBasedTransformation2.bal", + "startLine": { + "line": 28, + "offset": 0 + }, + "endLine": { + "line": 28, + "offset": 109 + } + }, + "sourceCode": "Bar[] bars = [\n {a: 1, b: 3},\n (from var baz in bazs\n select {a: baz.x, b: baz.y})[0]\n ];" + }, + "position": { + "line": 28, + "offset": 82 + }, + "targetField": "transform1", + "model": { + "inputs": [ + { + "members": [ + { + "name": "[0]", + "displayName": "input[0]", + "typeName": "int", + "kind": "int" + }, + { + "name": "[1]", + "displayName": "input[1]", + "typeName": "string", + "kind": "string" + }, + { + "fields": [], + "name": "[2]", + "displayName": "input[2]", + "typeName": "Course", + "kind": "record", + "ref": "524765447" + } + ], + "name": "input", + "displayName": "input", + "typeName": "[int, string, Course]", + "kind": "tuple", + "category": "parameter" + } + ], + "output": { + "members": [ + { + "name": "[0]", + "displayName": "transform1[0]", + "typeName": "int", + "kind": "int" + }, + { + "name": "[1]", + "displayName": "transform1[1]", + "typeName": "string", + "kind": "string" + }, + { + "fields": [], + "name": "[2]", + "displayName": "transform1[2]", + "typeName": "Course", + "kind": "record", + "ref": "524765447" + } + ], + "name": "transform1", + "displayName": "transform1", + "typeName": "[int, string, Course]", + "kind": "tuple" + }, + "mappings": [ + { + "output": "transform1", + "inputs": [], + "expression": "[0, \"\", {id: input[2].id}]", + "diagnostics": [ + { + "message": "missing non-defaultable required record field 'credits'", + "code": "BCE2520" + }, + { + "message": "missing non-defaultable required record field 'name'", + "code": "BCE2520" + } + ], + "elements": [ + { + "mappings": [ + { + "output": "transform1[0]", + "inputs": [], + "expression": "0", + "diagnostics": [], + "elements": [], + "isQueryExpression": false, + "isFunctionCall": false + } + ] + }, + { + "mappings": [ + { + "output": "transform1[1]", + "inputs": [], + "expression": "\"\"", + "diagnostics": [], + "elements": [], + "isQueryExpression": false, + "isFunctionCall": false + } + ] + }, + { + "mappings": [ + { + "output": "transform1[2].id", + "inputs": [ + "input[2].id" + ], + "expression": "input[2].id", + "diagnostics": [], + "elements": [], + "isQueryExpression": false, + "isFunctionCall": false, + "elementAccessIndex": [ + "2" + ] + } + ] + } + ] + } + ], + "refs": { + "524765447": { + "fields": [ + { + "name": "id", + "displayName": "id", + "typeName": "string", + "kind": "string", + "optional": false + }, + { + "name": "name", + "displayName": "name", + "typeName": "string", + "kind": "string", + "optional": false + }, + { + "name": "credits", + "displayName": "credits", + "typeName": "float", + "kind": "float", + "optional": false + } + ], + "typeName": "Course", + "kind": "record" + } + } + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/tupleBasedTransformation2.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/tupleBasedTransformation2.bal new file mode 100644 index 0000000000..26a3bb99ba --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/tupleBasedTransformation2.bal @@ -0,0 +1,29 @@ +type Student record { + string id; + string name; +}; + +type DetailedPerson record { + string id; + string name; +}; + +type CourseDetails record { + string courseId; + string courseName; + decimal credits; + string grade; +}; + +type DetailedStudent record { + Course[] courses; +}; + +type Course record { + string id; + string name; + float credits; +}; + +// Case 01 +function transform1([int, string, Course] input) returns [int, string, Course] => [0, "", {id: input[2].id}]; diff --git a/misc/diagram-util/src/main/java/org/ballerinalang/diagramutil/connector/models/connector/ReferenceType.java b/misc/diagram-util/src/main/java/org/ballerinalang/diagramutil/connector/models/connector/ReferenceType.java index e96d465fbc..f0e57de447 100644 --- a/misc/diagram-util/src/main/java/org/ballerinalang/diagramutil/connector/models/connector/ReferenceType.java +++ b/misc/diagram-util/src/main/java/org/ballerinalang/diagramutil/connector/models/connector/ReferenceType.java @@ -273,12 +273,20 @@ public static RefType fromSemanticSymbol(TypeSymbol symbol, String name, ModuleI } else if (kind == TypeDescKind.TUPLE) { TupleTypeSymbol typeSymbol = (TupleTypeSymbol) symbol; RefTupleType tupleType = new RefTupleType(name); + StringBuilder tupleNameBuilder = new StringBuilder(); + tupleNameBuilder.append("["); for (TypeSymbol memberTypeSymbol : typeSymbol.memberTypeDescriptors()) { - String memberTypeName = memberTypeSymbol.getName().orElse(""); + String memberTypeName = memberTypeSymbol.getName().orElse(memberTypeSymbol.signature()); ModuleID memberModuleId = getModuleID(memberTypeSymbol, moduleID); RefType refType = fromSemanticSymbol(memberTypeSymbol, memberTypeName, memberModuleId, typeDefSymbols); + if (tupleNameBuilder.length() > 1) { + tupleNameBuilder.append(", ").append(memberTypeName); + } else { + tupleNameBuilder.append(memberTypeName); + } tupleType.memberTypes.add(refType); } + tupleType.name = tupleNameBuilder + "]"; return tupleType; } else if (kind == TypeDescKind.REGEXP) { return new RefType("regexp:RegExp"); From 416f2f4846c44ff219fe7da924915999cf2f9f10 Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Thu, 22 Jan 2026 11:21:06 +0530 Subject: [PATCH 07/13] Resolve checkstyle violation --- .../io/ballerina/flowmodelgenerator/core/DataMapManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index 02a48c508d..c4bf9c0258 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -33,10 +33,10 @@ import io.ballerina.compiler.api.symbols.RecordTypeSymbol; import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.SymbolKind; +import io.ballerina.compiler.api.symbols.TupleTypeSymbol; import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; import io.ballerina.compiler.api.symbols.TypeDescKind; import io.ballerina.compiler.api.symbols.TypeSymbol; -import io.ballerina.compiler.api.symbols.TupleTypeSymbol; import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.api.symbols.VariableSymbol; import io.ballerina.compiler.syntax.tree.BinaryExpressionNode; From e1a76a96709f3f0047f7b0b67bcc6d168cbff727 Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Thu, 22 Jan 2026 12:04:50 +0530 Subject: [PATCH 08/13] Address PR suggestions --- .../core/DataMapManager.java | 26 ++++++++++++++----- .../extension/DataMappingDeleteTest.java | 3 +-- .../extension/DataMappingModelTest.java | 3 ++- .../models/connector/ReferenceType.java | 2 +- .../diagramutil/RefTypeTest.java | 2 +- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index c4bf9c0258..008e2d9073 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -1525,9 +1525,8 @@ public JsonElement getSource(SemanticModel semanticModel, Path filePath, JsonEle } Optional returnTypeSymbol = semanticModel.symbol(node) .filter(s -> s.kind() == SymbolKind.FUNCTION) - .map(s -> ((FunctionSymbol) s).typeDescriptor().returnTypeDescriptor()) - .orElse(null); - if (Objects.requireNonNull(returnTypeSymbol).isPresent() && + .flatMap(s -> ((FunctionSymbol) s).typeDescriptor().returnTypeDescriptor()); + if (returnTypeSymbol.isPresent() && returnTypeSymbol.get().typeKind() == TypeDescKind.TUPLE) { targetTypeSymbol = returnTypeSymbol.get(); } @@ -1608,7 +1607,14 @@ private void genSource(ExpressionNode expr, String[] names, int idx, StringBuild // Check if we're creating a tuple literal TypeSymbol rawType = targetTypeSymbol != null ? CommonUtils.getRawType(targetTypeSymbol) : null; if (rawType != null && rawType.typeKind() == TypeDescKind.TUPLE) { - int index = Integer.parseInt(name); + int index; + try { + index = Integer.parseInt(name); + } catch (NumberFormatException e) { + stringBuilder.append(mappingExpr); + textEdits.add(new TextEdit(CommonUtils.toRange(position), stringBuilder.toString())); + return; + } stringBuilder.append(generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr, names, idx)); } else { @@ -1745,9 +1751,15 @@ private String generateNestedExpression(String[] names, int startIdx, String map // This is a tuple/array index TypeSymbol rawType = currentType != null ? CommonUtils.getRawType(currentType) : null; if (rawType != null && rawType.typeKind() == TypeDescKind.TUPLE) { - int index = Integer.parseInt(name); - sb.append(generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr, names, i)); - return sb.toString(); + try { + int index = Integer.parseInt(name); + sb.append(generateTupleExpression((TupleTypeSymbol) rawType, index, mappingExpr, names, i)); + return sb.toString(); + } catch (NumberFormatException e) { + // Fallback: treat as a regular array access and just append the expression + sb.append(mappingExpr); + return sb.toString(); + } } else { // Regular array - just append the expression sb.append(mappingExpr); diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java index d5f109eefc..4a76c5eb68 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java @@ -69,8 +69,7 @@ protected Object[] getConfigsList() { {Path.of("variable11.json")}, {Path.of("query_collect.json")}, {Path.of("query_collect2.json")}, - {Path.of("tupleMember.json")}, - + {Path.of("tupleMember.json")} }; } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java index c2c2b44a18..1f6c048dc1 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java @@ -164,7 +164,8 @@ protected Object[] getConfigsList() { {Path.of("variable58.json")}, {Path.of("variable59.json")}, {Path.of("tuple.json")}, - {Path.of("tupleBasedTransformation.json")} + {Path.of("tupleBasedTransformation.json")}, + {Path.of("tupleBasedTransformation2.json")}, }; } diff --git a/misc/diagram-util/src/main/java/org/ballerinalang/diagramutil/connector/models/connector/ReferenceType.java b/misc/diagram-util/src/main/java/org/ballerinalang/diagramutil/connector/models/connector/ReferenceType.java index f0e57de447..a9af5f6213 100644 --- a/misc/diagram-util/src/main/java/org/ballerinalang/diagramutil/connector/models/connector/ReferenceType.java +++ b/misc/diagram-util/src/main/java/org/ballerinalang/diagramutil/connector/models/connector/ReferenceType.java @@ -286,7 +286,7 @@ public static RefType fromSemanticSymbol(TypeSymbol symbol, String name, ModuleI } tupleType.memberTypes.add(refType); } - tupleType.name = tupleNameBuilder + "]"; + tupleType.name = tupleNameBuilder.append("]").toString(); return tupleType; } else if (kind == TypeDescKind.REGEXP) { return new RefType("regexp:RegExp"); diff --git a/misc/diagram-util/src/test/java/org/ballerinalang/diagramutil/RefTypeTest.java b/misc/diagram-util/src/test/java/org/ballerinalang/diagramutil/RefTypeTest.java index 226c0fd2bc..a6573ebad9 100644 --- a/misc/diagram-util/src/test/java/org/ballerinalang/diagramutil/RefTypeTest.java +++ b/misc/diagram-util/src/test/java/org/ballerinalang/diagramutil/RefTypeTest.java @@ -86,7 +86,7 @@ public void getRefTypeForSymbol(Path jsonPath) throws IOException { String refTypeJson = gson.toJson(refType).concat(System.lineSeparator()); String expectedRefTypeJson = gson.toJson(jsonObject.get("refType")).concat(System.lineSeparator()); if (!refTypeJson.equals(expectedRefTypeJson)) { - updateConfig(jsonPath, refTypeJson); +// updateConfig(jsonPath, refTypeJson); Assert.fail( String.format("Reference type JSON does not match.\n Expected : %s\n Received %s", expectedRefTypeJson, refTypeJson)); From 08dac6c4968c694fa3fad5073f163af4b257f10e Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Thu, 22 Jan 2026 15:11:19 +0530 Subject: [PATCH 09/13] Fix tuple mapping inconsistencies in queries --- .../core/DataMapManager.java | 12 ++++-- .../extension/DataMappingSourceTest.java | 3 +- .../config/tupleBasedQuery.json | 41 +++++++++++++++++++ .../source/tupleBasedQuery.bal | 26 ++++++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleBasedQuery.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleBasedQuery.bal diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index 008e2d9073..9e5298369c 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -1542,9 +1542,14 @@ public JsonElement getSource(SemanticModel semanticModel, Path filePath, JsonEle String normalizedOutput = output.replaceAll("\\[(\\d+)]", ".$1"); String[] splits = normalizedOutput.split(DOT); StringBuilder sb = new StringBuilder(); - MatchingNode targetMappingExpr = getTargetMappingExpr(expr, targetField); - if (targetMappingExpr != null) { - expr = targetMappingExpr.expr(); + TargetNode targetNode = getTargetNode(node, targetField, semanticModel); + if (targetNode != null && targetNode.matchingNode != null) { + expr = targetNode.matchingNode.expr(); + targetTypeSymbol = targetNode.typeSymbol; + TypeSymbol rawType = CommonUtils.getRawType(targetTypeSymbol); + if (rawType.typeKind() == TypeDescKind.ARRAY) { + targetTypeSymbol = CommonUtils.getRawType(((ArrayTypeSymbol) rawType).memberTypeDescriptor()); + } } genSource(expr, splits, 1, sb, mapping.expression(), null, textEdits, targetTypeSymbol); } @@ -1622,7 +1627,6 @@ private void genSource(ExpressionNode expr, String[] names, int idx, StringBuild } } else { stringBuilder.append(name).append(": "); - // targetTypeSymbol is already the type for this field String nestedExpr = generateNestedExpression(names, idx + 1, mappingExpr, targetTypeSymbol); stringBuilder.append(nestedExpr); } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java index 3dd0999278..46321caf9c 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java @@ -107,7 +107,8 @@ protected Object[] getConfigsList() { {Path.of("variable20.json")}, {Path.of("tupleMapping.json")}, {Path.of("tupleMapping2.json")}, - {Path.of("tupleTransformation.json")} + {Path.of("tupleTransformation.json")}, + {Path.of("tupleBasedQuery.json")} }; } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleBasedQuery.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleBasedQuery.json new file mode 100644 index 0000000000..ac012c58b7 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleBasedQuery.json @@ -0,0 +1,41 @@ +{ + "source": "tupleBasedQuery.bal", + "description": "Tuple mapping", + "codedata": { + "node": "VARIABLE", + "lineRange": { + "fileName": "tupleBasedQuery.bal", + "startLine": { + "line": 14, + "offset": 8 + }, + "endLine": { + "line": 15, + "offset": 22 + } + }, + "sourceCode": "[int, string, float] result = [];" + }, + "mapping": { + "output": "persons2.data[0]", + "expression": "personsItem.data[0]" + }, + "targetField": "persons2.0", + "output": { + "tupleBasedQuery.bal": [ + { + "range": { + "start": { + "line": 15, + "character": 20 + }, + "end": { + "line": 15, + "character": 20 + } + }, + "newText": "data: [personsItem.data[0], \"\", 0.0]" + } + ] + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleBasedQuery.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleBasedQuery.bal new file mode 100644 index 0000000000..abcf231ef1 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleBasedQuery.bal @@ -0,0 +1,26 @@ +import ballerina/log; + +type TupleType [int, string, float]; + +public type Person record { + string name; + int age; + TupleType data; +}; + +public function main() returns error? { + do { + [int, string, float] data = [1, "Alice", 99.5]; + Person[] persons = []; + Person[] persons2 = from var personsItem in persons + select {}; + [int, string, float][] result = []; //Case 01 + [int, string, float][] result2 = from var resultItem in result + select [0, "", 0.0]; //Case 01 + Person person1 = {name: "Bob", age: 30, data: data}; + Person person2 = persons[0]; //Case 02 + } on fail error e { + log:printError("Error occurred", 'error = e); + return e; + } +} From 6b5ef468123179969965fb45f3490a85b2b39d9f Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Thu, 22 Jan 2026 16:07:43 +0530 Subject: [PATCH 10/13] Fix failing test case --- .../core/DataMapManager.java | 20 ++++++++++++------- .../extension/DataMappingSourceTest.java | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index 9e5298369c..4bb2e074bb 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -1542,13 +1542,19 @@ public JsonElement getSource(SemanticModel semanticModel, Path filePath, JsonEle String normalizedOutput = output.replaceAll("\\[(\\d+)]", ".$1"); String[] splits = normalizedOutput.split(DOT); StringBuilder sb = new StringBuilder(); - TargetNode targetNode = getTargetNode(node, targetField, semanticModel); - if (targetNode != null && targetNode.matchingNode != null) { - expr = targetNode.matchingNode.expr(); - targetTypeSymbol = targetNode.typeSymbol; - TypeSymbol rawType = CommonUtils.getRawType(targetTypeSymbol); - if (rawType.typeKind() == TypeDescKind.ARRAY) { - targetTypeSymbol = CommonUtils.getRawType(((ArrayTypeSymbol) rawType).memberTypeDescriptor()); + MatchingNode targetMappingExpr = getTargetMappingExpr(expr, targetField); + if (targetMappingExpr != null) { + expr = targetMappingExpr.expr(); + } + // Get resolved type for targetField navigation (needed for tuple field mappings) + if (targetField != null) { + TargetNode targetNode = getTargetNode(node, targetField, semanticModel); + if (targetNode != null) { + targetTypeSymbol = targetNode.typeSymbol; + TypeSymbol rawType = CommonUtils.getRawType(targetTypeSymbol); + if (rawType.typeKind() == TypeDescKind.ARRAY) { + targetTypeSymbol = CommonUtils.getRawType(((ArrayTypeSymbol) rawType).memberTypeDescriptor()); + } } } genSource(expr, splits, 1, sb, mapping.expression(), null, textEdits, targetTypeSymbol); diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java index 46321caf9c..aa252e746f 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java @@ -153,7 +153,7 @@ public void test(Path config) throws IOException { TestConfig updatedConfig = new TestConfig(testConfig.source(), testConfig.description(), testConfig.codedata(), testConfig.propertyKey(), testConfig.position(), testConfig.mapping(), testConfig.targetField(), newMap); -// updateConfig(configJsonPath, updatedConfig); + updateConfig(configJsonPath, updatedConfig); Assert.fail(String.format("Failed test: '%s' (%s)", testConfig.description(), configJsonPath)); } } From 501c05c3a3535a20e86dce9ecf13a62a0f3d986c Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Fri, 23 Jan 2026 09:04:39 +0530 Subject: [PATCH 11/13] Remove element access index from tuple mappings --- .../core/DataMapManager.java | 19 +++++++++++++++---- .../data_mapper_model/config/tuple.json | 5 +---- .../config/tupleBasedTransformation2.json | 5 +---- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index 4bb2e074bb..54e194122e 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -853,7 +853,7 @@ private void genMapping(Node expr, String name, List elements, Semantic LineRange customFunctionRange = getCustomFunctionRange(expr, functionDocument, dataMappingDocument); List elementAccessIndex = null; if (containsArrayAccess(expr)) { - elementAccessIndex = extractArrayIndices(expr); + elementAccessIndex = extractArrayIndices(expr, semanticModel); } Mapping mapping = new Mapping(name, inputs, expr.toSourceCode(), getDiagnostics(expr.lineRange(), semanticModel), new ArrayList<>(), @@ -931,9 +931,9 @@ private List> getDiagnostics(LineRange lineRange, SemanticMo .toList(); } - private List extractArrayIndices(Node expr) { + private List extractArrayIndices(Node expr, SemanticModel semanticModel) { List indices = new ArrayList<>(); - ArrayIndexExtractorVisitor visitor = new ArrayIndexExtractorVisitor(indices); + ArrayIndexExtractorVisitor visitor = new ArrayIndexExtractorVisitor(indices, semanticModel); expr.accept(visitor); return indices.isEmpty() ? null : indices; } @@ -3582,15 +3582,26 @@ private void addInput(String input) { private static class ArrayIndexExtractorVisitor extends NodeVisitor { private final List indices; + private final SemanticModel semanticModel; - ArrayIndexExtractorVisitor(List indices) { + ArrayIndexExtractorVisitor(List indices, SemanticModel semanticModel) { this.indices = indices; + this.semanticModel = semanticModel; } @Override public void visit(IndexedExpressionNode node) { node.containerExpression().accept(this); + // Skip tuple member access - only include array indices + Optional containerType = semanticModel.typeOf(node.containerExpression()); + if (containerType.isPresent()) { + TypeSymbol rawType = CommonUtils.getRawType(containerType.get()); + if (rawType.typeKind() == TypeDescKind.TUPLE) { + return; + } + } + SeparatedNodeList keyExpressions = node.keyExpression(); for (ExpressionNode keyExpr : keyExpressions) { String indexValue = extractIndexValue(keyExpr); diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json index b4d2840451..a7f0baef10 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple.json @@ -69,10 +69,7 @@ "diagnostics": [], "elements": [], "isQueryExpression": false, - "isFunctionCall": false, - "elementAccessIndex": [ - "0" - ] + "isFunctionCall": false } ], "refs": { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation2.json index 28c0c1cbc3..65138ec41b 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation2.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation2.json @@ -134,10 +134,7 @@ "diagnostics": [], "elements": [], "isQueryExpression": false, - "isFunctionCall": false, - "elementAccessIndex": [ - "2" - ] + "isFunctionCall": false } ] } From 85acd4ac05d6e1b15b8725a31fe40a86bb4d30fb Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Fri, 23 Jan 2026 12:38:00 +0530 Subject: [PATCH 12/13] Add newline at EOF --- .../src/test/resources/data_mapper_model/source/variable50.bal | 2 +- .../test/resources/data_mapper_source/source/tupleMapping.bal | 2 +- .../src/test/resources/delete_mapping/source/tupleMember.bal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/variable50.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/variable50.bal index 423311af3a..0c3035745b 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/variable50.bal +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/source/variable50.bal @@ -20,4 +20,4 @@ public function main() returns error? { log:printError("Error occurred", 'error = e); return e; } -} \ No newline at end of file +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleMapping.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleMapping.bal index 753a8b76bc..2474e9640b 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleMapping.bal +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/source/tupleMapping.bal @@ -18,4 +18,4 @@ public function main() returns error? { log:printError("Error occurred", 'error = e); return e; } -} \ No newline at end of file +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/source/tupleMember.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/source/tupleMember.bal index 2dd6a51288..99e8bbdc50 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/source/tupleMember.bal +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/source/tupleMember.bal @@ -18,4 +18,4 @@ public function main() returns error? { log:printError("Error occurred", 'error = e); return e; } -} \ No newline at end of file +} From a8c8470e65cc5cc4d41f31dbff16f9426d39674e Mon Sep 17 00:00:00 2001 From: pasindufernando1 Date: Fri, 6 Feb 2026 10:13:51 +0530 Subject: [PATCH 13/13] Address PR suggestions --- .../flowmodelgenerator/core/DataMapManager.java | 2 +- .../extension/DataMappingDeleteTest.java | 2 +- .../extension/DataMappingModelTest.java | 4 ++-- .../extension/DataMappingSourceTest.java | 10 +++++----- ...sformation.json => tuple_based_transformation.json} | 0 ...ormation2.json => tuple_based_transformation2.json} | 0 .../{tupleBasedQuery.json => tuple_based_query.json} | 0 .../config/{tupleMapping.json => tuple_mapping.json} | 0 .../config/{tupleMapping2.json => tuple_mapping2.json} | 0 ...leTransformation.json => tuple_transformation.json} | 0 .../config/{tupleMember.json => tuple_member.json} | 0 11 files changed, 9 insertions(+), 9 deletions(-) rename flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/{tupleBasedTransformation.json => tuple_based_transformation.json} (100%) rename flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/{tupleBasedTransformation2.json => tuple_based_transformation2.json} (100%) rename flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/{tupleBasedQuery.json => tuple_based_query.json} (100%) rename flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/{tupleMapping.json => tuple_mapping.json} (100%) rename flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/{tupleMapping2.json => tuple_mapping2.json} (100%) rename flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/{tupleTransformation.json => tuple_transformation.json} (100%) rename flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/{tupleMember.json => tuple_member.json} (100%) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java index 54e194122e..2dd62dd601 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/DataMapManager.java @@ -231,7 +231,7 @@ public JsonElement getMappings(SemanticModel semanticModel, JsonElement cd, Line TypeSymbol targetTypeSymbol; try { targetTypeSymbol = targetNode.typeSymbol(); - TypeSymbol rawtargetTypeSymbol = CommonUtils.getRawType(targetNode.typeSymbol()); + TypeSymbol rawtargetTypeSymbol = CommonUtils.getRawType(targetTypeSymbol); if (rawtargetTypeSymbol.typeKind() == TypeDescKind.UNION) { targetTypeSymbol = filterErrorOrNil(semanticModel, (UnionTypeSymbol) rawtargetTypeSymbol, new ArrayList<>()); diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java index 4a76c5eb68..eefe541dcd 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingDeleteTest.java @@ -69,7 +69,7 @@ protected Object[] getConfigsList() { {Path.of("variable11.json")}, {Path.of("query_collect.json")}, {Path.of("query_collect2.json")}, - {Path.of("tupleMember.json")} + {Path.of("tuple_member.json")} }; } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java index 1f6c048dc1..1e0c1fb78b 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingModelTest.java @@ -164,8 +164,8 @@ protected Object[] getConfigsList() { {Path.of("variable58.json")}, {Path.of("variable59.json")}, {Path.of("tuple.json")}, - {Path.of("tupleBasedTransformation.json")}, - {Path.of("tupleBasedTransformation2.json")}, + {Path.of("tuple_based_transformation.json")}, + {Path.of("tuple_based_transformation2.json")}, }; } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java index aa252e746f..e839a1d664 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/java/io/ballerina/flowmodelgenerator/extension/DataMappingSourceTest.java @@ -105,10 +105,10 @@ protected Object[] getConfigsList() { {Path.of("query7.json")}, {Path.of("query8.json")}, {Path.of("variable20.json")}, - {Path.of("tupleMapping.json")}, - {Path.of("tupleMapping2.json")}, - {Path.of("tupleTransformation.json")}, - {Path.of("tupleBasedQuery.json")} + {Path.of("tuple_mapping.json")}, + {Path.of("tuple_mapping2.json")}, + {Path.of("tuple_transformation.json")}, + {Path.of("tuple_based_query.json")} }; } @@ -153,7 +153,7 @@ public void test(Path config) throws IOException { TestConfig updatedConfig = new TestConfig(testConfig.source(), testConfig.description(), testConfig.codedata(), testConfig.propertyKey(), testConfig.position(), testConfig.mapping(), testConfig.targetField(), newMap); - updateConfig(configJsonPath, updatedConfig); +// updateConfig(configJsonPath, updatedConfig); Assert.fail(String.format("Failed test: '%s' (%s)", testConfig.description(), configJsonPath)); } } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple_based_transformation.json similarity index 100% rename from flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation.json rename to flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple_based_transformation.json diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple_based_transformation2.json similarity index 100% rename from flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tupleBasedTransformation2.json rename to flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_model/config/tuple_based_transformation2.json diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleBasedQuery.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tuple_based_query.json similarity index 100% rename from flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleBasedQuery.json rename to flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tuple_based_query.json diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tuple_mapping.json similarity index 100% rename from flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping.json rename to flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tuple_mapping.json diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tuple_mapping2.json similarity index 100% rename from flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleMapping2.json rename to flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tuple_mapping2.json diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleTransformation.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tuple_transformation.json similarity index 100% rename from flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tupleTransformation.json rename to flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/data_mapper_source/config/tuple_transformation.json diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tupleMember.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tuple_member.json similarity index 100% rename from flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tupleMember.json rename to flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/delete_mapping/config/tuple_member.json