From 375405eb5db872e2836535d1e053535e6310f288 Mon Sep 17 00:00:00 2001 From: LakshanWeerasinghe Date: Thu, 29 Jan 2026 11:22:57 +0530 Subject: [PATCH 1/3] Add imports when creating configs --- .../extension/ConfigEditorV2Service.java | 57 +++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ConfigEditorV2Service.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ConfigEditorV2Service.java index d4afaed2f3..5ea24000ef 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ConfigEditorV2Service.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ConfigEditorV2Service.java @@ -38,6 +38,7 @@ import io.ballerina.compiler.api.symbols.TypeSymbol; import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.ImportDeclarationNode; import io.ballerina.compiler.syntax.tree.ListConstructorExpressionNode; import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; import io.ballerina.compiler.syntax.tree.MappingFieldNode; @@ -45,6 +46,7 @@ import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.ModuleVariableDeclarationNode; import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.NodeParser; import io.ballerina.compiler.syntax.tree.SpecificFieldNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; @@ -102,17 +104,20 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import static io.ballerina.flowmodelgenerator.core.model.Property.CONFIG_VALUE_KEY; import static io.ballerina.flowmodelgenerator.core.model.Property.CONFIG_VAR_DOC_KEY; import static io.ballerina.flowmodelgenerator.core.model.Property.DEFAULT_VALUE_KEY; import static io.ballerina.flowmodelgenerator.core.model.Property.VARIABLE_KEY; +import static io.ballerina.modelgenerator.commons.CommonUtils.importExists; /** * Provides extended services for viewing and editing Ballerina configuration variables. @@ -140,6 +145,8 @@ public class ConfigEditorV2Service implements ExtendedLanguageServerService { private static final String COLON_SPACE = ": "; private static final String DOUBLE_QUOTE = "\""; private static final String EQUALS_SIGN_SPACED = " = "; + private static final String LS = System.lineSeparator(); + private static final String IMPORT_STMT_TEMPLATE = "%nimport %s/%s;%n"; // TOML and Config Statement Format Constants private static final String CONFIG_STATEMENT_FORMAT = "configurable %s %s = %s;"; @@ -318,11 +325,13 @@ private Map> constructSourceTextEdits(Project rootProject, SyntaxTree syntaxTree = document.get().syntaxTree(); ModulePartNode modulePartNode = syntaxTree.rootNode(); LinePosition startPos = LinePosition.from(modulePartNode.lineRange().endLine().line() + 1, 0); - textEdits.add(new TextEdit(CommonUtils.toRange(startPos), configStatement + System.lineSeparator())); + textEdits.add(new TextEdit(CommonUtils.toRange(startPos), configStatement + LS)); + addImportEdits(variable, document.get(), textEdits); } else if (isDelete) { textEdits.add(new TextEdit(CommonUtils.toRange(lineRange), EMPTY_STRING)); } else { textEdits.add(new TextEdit(CommonUtils.toRange(lineRange), configStatement)); + addImportEdits(variable, document.get(), textEdits); } textEditsMap.put(variableFilePath, textEdits); @@ -332,13 +341,53 @@ private Map> constructSourceTextEdits(Project rootProject, } } + /** + * Adds necessary import statements to the text edits for a configuration variable. + * + * @param node The configuration variable flow node. + * @param document The document containing the configuration variable. + * @param textEdits The list of text edits to which import statements will be added. + */ + private static void addImportEdits(FlowNode node, Document document, List textEdits) { + Map propImports = node.properties().get(Property.TYPE_KEY).imports(); + if (propImports == null || propImports.isEmpty()) { + return; + } + + ModulePartNode rootNode = document.syntaxTree().rootNode(); + LineRange startLineRange = rootNode.lineRange(); + + Set imports = new HashSet<>(); + NodeList importNodes = rootNode.imports(); + if (!importNodes.isEmpty()) { + ImportDeclarationNode lastImportNode = importNodes.get(importNodes.size() - 1); + startLineRange = lastImportNode.lineRange(); + LinePosition linePosition = LinePosition.from(startLineRange.endLine().line() + 1, 0); + startLineRange = LineRange.from(startLineRange.fileName(), linePosition, linePosition); + } + + propImports.values().forEach(moduleId -> { + String[] importParts = moduleId.split("/"); + String orgName = importParts[0]; + String moduleName = importParts[1].split(":")[0]; + if (!importExists(rootNode, orgName, moduleName)) { + imports.add(String.format(IMPORT_STMT_TEMPLATE, orgName, moduleName)); + } + }); + + if (!imports.isEmpty()) { + String importsStmts = String.join(LS, imports); + textEdits.addFirst(new TextEdit(CommonUtils.toRange(startLineRange), importsStmts)); + } + } + /** * Constructs the Ballerina source code statement for a configuration variable. */ private static String constructConfigStatement(FlowNode node) { String defaultValue = node.properties().get(DEFAULT_VALUE_KEY).toSourceCode(); String variableDoc = node.properties().get(CONFIG_VAR_DOC_KEY).toSourceCode(); - List docLines = Arrays.stream(variableDoc.split(System.lineSeparator())).toList(); + List docLines = Arrays.stream(variableDoc.split(LS)).toList(); StringBuilder configStatementBuilder = new StringBuilder(); docLines.forEach(docLine -> { @@ -346,7 +395,7 @@ private static String constructConfigStatement(FlowNode node) { configStatementBuilder .append(HASH_COMMENT_PREFIX) .append(docLine) - .append(System.lineSeparator()); + .append(LS); } } ); @@ -933,7 +982,7 @@ private Map> constructConfigTomlTextEdits(Project project, } } LinePosition startPos = LinePosition.from(0, 0); - textEdits.add(new TextEdit(CommonUtils.toRange(startPos), newContent + System.lineSeparator())); + textEdits.add(new TextEdit(CommonUtils.toRange(startPos), newContent + LS)); } } From a69485f8156ab8c826ebdcce308ce4d6c95d00be Mon Sep 17 00:00:00 2001 From: LakshanWeerasinghe Date: Thu, 29 Jan 2026 11:33:33 +0530 Subject: [PATCH 2/3] Add test for adding configs --- .../config/config10.json | 162 ++++++++++++++++++ .../config/config11.json | 149 ++++++++++++++++ .../config_with_import.bal | 3 + 3 files changed, 314 insertions(+) create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/config/config10.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/config/config11.json create mode 100644 flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/source/config_update_test_project/config_with_import.bal diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/config/config10.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/config/config10.json new file mode 100644 index 0000000000..d1d16dd184 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/config/config10.json @@ -0,0 +1,162 @@ +{ + "description": "Add config with its type being from an imported module", + "project": "config_update_test_project", + "request": { + "packageName": "config_tests/config_update_test_project", + "moduleName": "", + "configFilePath": "config.bal", + "configVariable": { + "id": "32790", + "metadata": { + "label": "Config", + "description": "Create a configurable variable" + }, + "codedata": { + "node": "CONFIG_VARIABLE", + "lineRange": { + "fileName": "config.bal", + "startLine": { + "line": 1, + "offset": 0 + }, + "endLine": { + "line": 1, + "offset": 54 + } + } + }, + "returning": false, + "properties": { + "variable": { + "metadata": { + "label": "Variable Name", + "description": "Name of the variable" + }, + "types": [ + { + "fieldType": "IDENTIFIER", + "scope": "Global", + "selected": true + } + ], + "optional": false, + "editable": true, + "advanced": false, + "hidden": false, + "modified": false, + "value": "token" + }, + "type": { + "metadata": { + "label": "Variable Type", + "description": "Type of the variable" + }, + "types": [ + { + "fieldType": "TYPE", + "selected": true + } + ], + "value": "http:BearerTokenConfig", + "placeholder": "var", + "optional": false, + "editable": true, + "advanced": false, + "hidden": false, + "modified": true, + "codedata": {}, + "imports": { + "http": "ballerina/http:2.14.7" + } + }, + "configValue": { + "metadata": { + "label": "Config Value", + "description": "Config value of the variable, to be used in Config.toml" + }, + "types": [ + { + "fieldType": "EXPRESSION", + "selected": true + } + ], + "value": "", + "optional": true, + "editable": true, + "advanced": false, + "hidden": true, + "modified": false + }, + "defaultValue": { + "metadata": { + "label": "Default Value", + "description": "Default value of the variable" + }, + "types": [ + { + "fieldType": "EXPRESSION", + "selected": true + } + ], + "value": "", + "optional": true, + "editable": true, + "advanced": false, + "hidden": false, + "modified": true + }, + "documentation": { + "metadata": { + "label": "Documentation", + "description": "Variable documentation in Markdown format" + }, + "types": [ + { + "fieldType": "TEXT", + "selected": true + } + ], + "value": "", + "optional": true, + "editable": true, + "advanced": false, + "hidden": false, + "modified": false + } + }, + "flags": 0 + } + }, + "response": { + "textEdits": { + "config.bal": [ + { + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 5, + "character": 39 + } + }, + "newText": "\nimport ballerina/http;\n" + }, + { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 54 + } + }, + "newText": "configurable http:BearerTokenConfig token = ?;" + } + ] + } + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/config/config11.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/config/config11.json new file mode 100644 index 0000000000..bb34899aba --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/config/config11.json @@ -0,0 +1,149 @@ +{ + "description": "Add config with its type being from an imported module", + "project": "config_update_test_project", + "request": { + "packageName": "config_tests/config_update_test_project", + "moduleName": "", + "configFilePath": "config_with_import.bal", + "configVariable": { + "id": "32790", + "metadata": { + "label": "Config", + "description": "Create a configurable variable" + }, + "codedata": { + "node": "CONFIG_VARIABLE", + "lineRange": { + "fileName": "config_with_import.bal", + "startLine": { + "line": 1, + "offset": 0 + }, + "endLine": { + "line": 1, + "offset": 54 + } + } + }, + "returning": false, + "properties": { + "variable": { + "metadata": { + "label": "Variable Name", + "description": "Name of the variable" + }, + "types": [ + { + "fieldType": "IDENTIFIER", + "scope": "Global", + "selected": true + } + ], + "optional": false, + "editable": true, + "advanced": false, + "hidden": false, + "modified": false, + "value": "token" + }, + "type": { + "metadata": { + "label": "Variable Type", + "description": "Type of the variable" + }, + "types": [ + { + "fieldType": "TYPE", + "selected": true + } + ], + "value": "http:CredentialsConfig", + "placeholder": "var", + "optional": false, + "editable": true, + "advanced": false, + "hidden": false, + "modified": true, + "codedata": {}, + "imports": { + "http": "ballerina/http:2.14.7" + } + }, + "configValue": { + "metadata": { + "label": "Config Value", + "description": "Config value of the variable, to be used in Config.toml" + }, + "types": [ + { + "fieldType": "EXPRESSION", + "selected": true + } + ], + "value": "", + "optional": true, + "editable": true, + "advanced": false, + "hidden": true, + "modified": false + }, + "defaultValue": { + "metadata": { + "label": "Default Value", + "description": "Default value of the variable" + }, + "types": [ + { + "fieldType": "EXPRESSION", + "selected": true + } + ], + "value": "", + "optional": true, + "editable": true, + "advanced": false, + "hidden": false, + "modified": true + }, + "documentation": { + "metadata": { + "label": "Documentation", + "description": "Variable documentation in Markdown format" + }, + "types": [ + { + "fieldType": "TEXT", + "selected": true + } + ], + "value": "", + "optional": true, + "editable": true, + "advanced": false, + "hidden": false, + "modified": false + } + }, + "flags": 0 + } + }, + "response": { + "textEdits": { + "config_with_import.bal": [ + { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 54 + } + }, + "newText": "configurable http:CredentialsConfig token = ?;" + } + ] + } + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/source/config_update_test_project/config_with_import.bal b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/source/config_update_test_project/config_with_import.bal new file mode 100644 index 0000000000..847e67f9ba --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/configurable_variables_v2_update/source/config_update_test_project/config_with_import.bal @@ -0,0 +1,3 @@ +import ballerina/http; + +configurable http:PoolConfiguration poolConfig = ?; From 468ee79ad0d132adbbb597da00c4d1533cd66b85 Mon Sep 17 00:00:00 2001 From: LakshanWeerasinghe Date: Thu, 29 Jan 2026 14:59:10 +0530 Subject: [PATCH 3/3] Address copilot suggestions --- .../extension/ConfigEditorV2Service.java | 13 ++++++++----- .../config/config10.json | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ConfigEditorV2Service.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ConfigEditorV2Service.java index 5ea24000ef..94d7d173dc 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ConfigEditorV2Service.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/ConfigEditorV2Service.java @@ -349,7 +349,12 @@ private Map> constructSourceTextEdits(Project rootProject, * @param textEdits The list of text edits to which import statements will be added. */ private static void addImportEdits(FlowNode node, Document document, List textEdits) { - Map propImports = node.properties().get(Property.TYPE_KEY).imports(); + Property typeProperty = node.properties().get(Property.TYPE_KEY); + if (typeProperty == null) { + return; + } + + Map propImports = typeProperty.imports(); if (propImports == null || propImports.isEmpty()) { return; } @@ -362,8 +367,6 @@ private static void addImportEdits(FlowNode node, Document document, List { @@ -376,8 +379,8 @@ private static void addImportEdits(FlowNode node, Document document, List