From 8276d5b3e56140e40de134b96a7b89afeb7821bd Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Mon, 7 Apr 2025 23:40:09 +0530 Subject: [PATCH 1/3] Fix IndexOutOfBoundException in STModify API --- .../document/BallerinaTreeModifyUtil.java | 65 ++++++++++++------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java index 13cc250fd0f9..fa400f5e699a 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java @@ -138,7 +138,6 @@ public static TextEdit createTextEdit(TextDocument oldTextDocument, theEndOffset - theStartOffset), mainStartMapping); } - public static JsonElement modifyTree(ASTModification[] astModifications, Path compilationPath, WorkspaceManager workspaceManager) throws Exception { @@ -243,32 +242,54 @@ private static boolean importExist(UnusedSymbolsVisitor unusedSymbolsVisitor, AS || unusedSymbolsVisitor.getUnusedImports().containsKey(importValue)); } - private static TextEdit constructEdit( - UnusedSymbolsVisitor unusedSymbolsVisitor, TextDocument oldTextDocument, - ASTModification astModification) { - String mapping = BallerinaTreeModifyUtil.resolveMapping(astModification.getType(), + private static TextEdit constructEdit(UnusedSymbolsVisitor unusedSymbolsVisitor, TextDocument oldTextDocument, + ASTModification astModification) { + + String textEdit = BallerinaTreeModifyUtil.resolveMapping(astModification.getType(), astModification.getConfig() == null ? new JsonObject() : astModification.getConfig()); - if (mapping != null) { - boolean doEdit = false; - if (DELETE.equals(astModification.getType())) { - if (unusedSymbolsVisitor.toBeDeletedRanges().contains(astModification)) { - doEdit = true; - } - } else { + if (textEdit == null) { + return null; + } + + boolean doEdit = false; + if (DELETE.equals(astModification.getType())) { + if (unusedSymbolsVisitor.toBeDeletedRanges().contains(astModification)) { doEdit = true; } - if (doEdit) { - LinePosition startLinePos = LinePosition.from(astModification.getStartLine(), - astModification.getStartColumn()); - LinePosition endLinePos = LinePosition.from(astModification.getEndLine(), - astModification.getEndColumn()); - int startOffset = oldTextDocument.textPositionFrom(startLinePos); - int endOffset = oldTextDocument.textPositionFrom(endLinePos); - return TextEdit.from( - TextRange.from(startOffset, - endOffset - startOffset), mapping); + } else { + doEdit = true; + } + if (doEdit) { + TextRange range = getRange(astModification, oldTextDocument, textEdit); + if (range == null) { + return null; } + return TextEdit.from(range, textEdit); } return null; } + + public static TextRange getRange(ASTModification modification, TextDocument oldTextDocument, String textEdit) { + LinePosition startLinePos = LinePosition.from(modification.getStartLine(), modification.getStartColumn()); + LinePosition endLinePos = LinePosition.from(modification.getEndLine(), modification.getEndColumn()); + + int startOffset; + try { + startOffset = oldTextDocument.textPositionFrom(startLinePos); + int endOffset = oldTextDocument.textPositionFrom(endLinePos); + return TextRange.from(startOffset, endOffset - startOffset); + } catch (IndexOutOfBoundsException e) { + // If the start offset is out of bounds, we need to check if the insertion line is the last line and if so, + // we can still insert at the end of the document. + if (startLinePos.line() == oldTextDocument.textLines().size()) { + startOffset = oldTextDocument.toCharArray().length; + return TextRange.from(startOffset, 0); + } else { + return null; + } + } catch (IllegalArgumentException e) { + // TODO: Handle the case where the start offset is out of bounds + return null; + } + } } From a13fef695506ab98c8930b7e6eb77ddb0d51171a Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 8 Apr 2025 08:29:47 +0530 Subject: [PATCH 2/3] Improve logic to handle indexoutofbounds in end offset --- .../document/BallerinaTreeModifyUtil.java | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java index fa400f5e699a..5dc2167d725c 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java @@ -204,7 +204,7 @@ public static JsonElement modifyTree(ASTModification[] astModifications, Path co newSyntaxTree = Formatter.format(newSyntaxTree); SemanticModel newSemanticModel = updateWorkspaceDocument(compilationPath, newSyntaxTree.toSourceCode(), - workspaceManager); + workspaceManager); Optional formattedSrcFile = workspaceManager.document(compilationPath); if (formattedSrcFile.isEmpty()) { @@ -270,26 +270,36 @@ private static TextEdit constructEdit(UnusedSymbolsVisitor unusedSymbolsVisitor, } public static TextRange getRange(ASTModification modification, TextDocument oldTextDocument, String textEdit) { + // Get line positions from modification LinePosition startLinePos = LinePosition.from(modification.getStartLine(), modification.getStartColumn()); LinePosition endLinePos = LinePosition.from(modification.getEndLine(), modification.getEndColumn()); - int startOffset; + // Calculate offsets + int startOffset = calculateOffset(oldTextDocument, startLinePos); + int endOffset = calculateOffset(oldTextDocument, endLinePos); + + if (startOffset < 0 || endOffset < 0) { + return null; + } + + return TextRange.from(startOffset, endOffset - startOffset); + } + + /** + * Helper method to calculate text offset from a line position + */ + private static int calculateOffset(TextDocument document, LinePosition linePos) { try { - startOffset = oldTextDocument.textPositionFrom(startLinePos); - int endOffset = oldTextDocument.textPositionFrom(endLinePos); - return TextRange.from(startOffset, endOffset - startOffset); + return document.textPositionFrom(linePos); } catch (IndexOutOfBoundsException e) { - // If the start offset is out of bounds, we need to check if the insertion line is the last line and if so, - // we can still insert at the end of the document. - if (startLinePos.line() == oldTextDocument.textLines().size()) { - startOffset = oldTextDocument.toCharArray().length; - return TextRange.from(startOffset, 0); - } else { - return null; + // If the line position is at the end of the document, return the document length + if (linePos.line() == document.textLines().size()) { + return document.toCharArray().length; } - } catch (IllegalArgumentException e) { - // TODO: Handle the case where the start offset is out of bounds - return null; + return -1; + } catch (Exception e) { + // TODO: Handle other exceptions as needed + return -1; } } } From cf3d208cd1268d86662a5fc5a7f669d7e0be0cc5 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 8 Apr 2025 08:46:53 +0530 Subject: [PATCH 3/3] Fix checkstyle --- .../document/BallerinaTreeModifyUtil.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java index 5dc2167d725c..fc75f1bca071 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/extensions/ballerina/document/BallerinaTreeModifyUtil.java @@ -55,12 +55,11 @@ public final class BallerinaTreeModifyUtil { private static final String DELETE = "delete"; - private static final String IMPORT = "import"; private BallerinaTreeModifyUtil() { } - private static final Map typeMapping = new HashMap() {{ + private static final Map typeMapping = new HashMap<>() {{ put("DELETE", ""); put("INSERT", "$STATEMENT"); }}; @@ -232,8 +231,7 @@ private static SemanticModel updateWorkspaceDocument(Path compilationPath, Strin // Update project instance PackageCompilation packageCompilation = updatedDoc.module().packageInstance().getCompilation(); - SemanticModel semanticModel = packageCompilation.getSemanticModel(updatedDoc.module().moduleId()); - return semanticModel; + return packageCompilation.getSemanticModel(updatedDoc.module().moduleId()); } private static boolean importExist(UnusedSymbolsVisitor unusedSymbolsVisitor, ASTModification astModification) { @@ -245,9 +243,9 @@ private static boolean importExist(UnusedSymbolsVisitor unusedSymbolsVisitor, AS private static TextEdit constructEdit(UnusedSymbolsVisitor unusedSymbolsVisitor, TextDocument oldTextDocument, ASTModification astModification) { - String textEdit = BallerinaTreeModifyUtil.resolveMapping(astModification.getType(), + String editText = BallerinaTreeModifyUtil.resolveMapping(astModification.getType(), astModification.getConfig() == null ? new JsonObject() : astModification.getConfig()); - if (textEdit == null) { + if (editText == null) { return null; } @@ -260,16 +258,16 @@ private static TextEdit constructEdit(UnusedSymbolsVisitor unusedSymbolsVisitor, doEdit = true; } if (doEdit) { - TextRange range = getRange(astModification, oldTextDocument, textEdit); + TextRange range = getRange(astModification, oldTextDocument); if (range == null) { return null; } - return TextEdit.from(range, textEdit); + return TextEdit.from(range, editText); } return null; } - public static TextRange getRange(ASTModification modification, TextDocument oldTextDocument, String textEdit) { + public static TextRange getRange(ASTModification modification, TextDocument oldTextDocument) { // Get line positions from modification LinePosition startLinePos = LinePosition.from(modification.getStartLine(), modification.getStartColumn()); LinePosition endLinePos = LinePosition.from(modification.getEndLine(), modification.getEndColumn()); @@ -286,7 +284,7 @@ public static TextRange getRange(ASTModification modification, TextDocument oldT } /** - * Helper method to calculate text offset from a line position + * Helper method to calculate text offset from a line position. */ private static int calculateOffset(TextDocument document, LinePosition linePos) { try {