From 4e1ab616e4c4e1b71c8be0efccc6f05447adf12a Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Mon, 18 Mar 2024 10:07:52 +0530 Subject: [PATCH 1/6] Provide CA to change isolated field to private --- .../ChangeIsolatedFieldPrivateCodeAction.java | 82 +++++++++++++++++++ .../common/constants/CommandConstants.java | 2 + ...ngeIsolatedFieldPrivateCodeActionTest.java | 54 ++++++++++++ .../config/change_field_private1.json | 30 +++++++ .../config/change_field_private2.json | 30 +++++++ .../config/change_field_private3.json | 30 +++++++ .../source/change_field_private1.bal | 8 ++ .../source/change_field_private2.bal | 3 + .../source/change_field_private3.bal | 6 ++ 9 files changed, 245 insertions(+) create mode 100644 language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java create mode 100644 language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private1.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private2.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private3.bal diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java new file mode 100644 index 000000000000..018833dfcf89 --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.ballerinalang.langserver.codeaction.providers; + +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.tools.diagnostics.Diagnostic; +import io.ballerina.tools.text.LinePosition; +import org.ballerinalang.annotation.JavaSPIService; +import org.ballerinalang.langserver.codeaction.CodeActionNodeValidator; +import org.ballerinalang.langserver.codeaction.CodeActionUtil; +import org.ballerinalang.langserver.common.constants.CommandConstants; +import org.ballerinalang.langserver.common.utils.PositionUtil; +import org.ballerinalang.langserver.commons.CodeActionContext; +import org.ballerinalang.langserver.commons.codeaction.spi.DiagBasedPositionDetails; +import org.ballerinalang.langserver.commons.codeaction.spi.DiagnosticBasedCodeActionProvider; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CodeActionKind; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextEdit; + +import java.util.Collections; +import java.util.List; + +/** + * Code Action for changing isolated field to private. + * + * @since 2201.9.0 + */ +@JavaSPIService("org.ballerinalang.langserver.commons.codeaction.spi.LSCodeActionProvider") +public class ChangeIsolatedFieldPrivateCodeAction implements DiagnosticBasedCodeActionProvider { + + private static final String NAME = "Change isolated field to private"; + private static final String DIAGNOSTIC_CODE = "BCE3956"; + + @Override + public boolean validate(Diagnostic diagnostic, DiagBasedPositionDetails positionDetails, + CodeActionContext context) { + return DIAGNOSTIC_CODE.equals(diagnostic.diagnosticInfo().code()) + && CodeActionNodeValidator.validate(context.nodeAtRange()); + } + + @Override + public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionDetails positionDetails, + CodeActionContext context) { + Node cursorNode = positionDetails.matchedNode(); + LinePosition startLinePosition = cursorNode.lineRange().startLine(); + Position startPosition = PositionUtil.toPosition(startLinePosition); + Position endPosition = PositionUtil.toPosition(cursorNode.lineRange().endLine()); + + String editText = "private " + cursorNode.toSourceCode().substring(startLinePosition.offset()); + TextEdit makePrivateTextEdit = new TextEdit(new Range(startPosition, endPosition), editText); + + return Collections.singletonList(CodeActionUtil.createCodeAction( + CommandConstants.CHANGE_ISOLATED_FIELD_PRIVATE, + List.of(makePrivateTextEdit), + context.fileUri(), + CodeActionKind.QuickFix + )); + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java index eb0f5b0d4613..c414fd02dacd 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java @@ -120,6 +120,8 @@ public class CommandConstants { public static final String CHANGE_PARAM_TYPE_TITLE = "Change parameter '%s' type to '%s'"; + public static final String CHANGE_ISOLATED_FIELD_PRIVATE = "Change the field to private"; + public static final String CREATE_VAR_TYPE_GUARD_TITLE = "Create variable and type guard"; public static final String TYPE_GUARD_TITLE = "Type guard variable '%s'"; diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java new file mode 100644 index 000000000000..6e5c6a541111 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.ballerinalang.langserver.codeaction; + +import org.ballerinalang.langserver.commons.workspace.WorkspaceDocumentException; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.IOException; + +/** + * Test class to test the change isolated field to private code action + * + * @since 2201.9.0 + */ +public class ChangeIsolatedFieldPrivateCodeActionTest extends AbstractCodeActionTest { + + @Test(dataProvider = "codeaction-data-provider") + @Override + public void test(String config) throws IOException, WorkspaceDocumentException { + super.test(config); + } + + @DataProvider(name = "codeaction-data-provider") + @Override + public Object[][] dataProvider() { + return new Object[][]{ + {"change_field_private1.json"}, + {"change_field_private2.json"}, + {"change_field_private3.json"} + }; + } + + @Override + public String getResourceDir() { + return "change-field-private"; + } +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json new file mode 100644 index 000000000000..88a3218f3729 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 1, + "character": 11 + }, + "source": "change_field_private1.bal", + "description": "Change the isolated field to private for a structured type", + "expected": [ + { + "title": "Change the field to private", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 5, + "character": 6 + } + }, + "newText": "private map isolatedField = {\n a: \"1\",\n b: \"2\",\n c: \"3\"\n };\n" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json new file mode 100644 index 000000000000..73b5f3ee2773 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 1, + "character": 9 + }, + "source": "change_field_private2.bal", + "description": "Change the isolated field to private for a simple type", + "expected": [ + { + "title": "Change the field to private", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 15 + } + }, + "newText": "private int i = 32;\n" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json new file mode 100644 index 000000000000..55878b57241c --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 1, + "character": 10 + }, + "source": "change_field_private3.bal", + "description": "Change the isolated field to private for a multiline type", + "expected": [ + { + "title": "Change the field to private", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 4, + "character": 19 + } + }, + "newText": "private record {|\n int i;\n string val;\n |} isolatedRec;\n" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private1.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private1.bal new file mode 100644 index 000000000000..cdba5ab4bc8b --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private1.bal @@ -0,0 +1,8 @@ +isolated service class IsolatedClass { + map isolatedField = { + a: "1", + b: "2", + c: "3" + }; + int i = 32; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private2.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private2.bal new file mode 100644 index 000000000000..ada3bde7365c --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private2.bal @@ -0,0 +1,3 @@ +isolated service class IsolatedClass { + int i = 32; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private3.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private3.bal new file mode 100644 index 000000000000..3133ceac0358 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private3.bal @@ -0,0 +1,6 @@ +isolated service class IsolatedClass { + record {| + int i; + string val; + |} isolatedRec; +} From 958ccde7abedecd13669c7b0b1c6d30b171b604a Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Mon, 18 Mar 2024 20:47:52 +0530 Subject: [PATCH 2/6] Provide CA to make a variable immutable --- .../ChangeIsolatedFieldPrivateCodeAction.java | 10 +- .../MakeVariableImmutableCodeAction.java | 176 ++++++++++++++++++ .../common/constants/CommandConstants.java | 2 + .../MakeVariableImmutableCodeActionTest.java | 66 +++++++ .../config/make_variable_immutable1.json | 30 +++ .../config/make_variable_immutable10.json | 43 +++++ .../config/make_variable_immutable11.json | 30 +++ .../config/make_variable_immutable12.json | 43 +++++ .../config/make_variable_immutable13.json | 56 ++++++ .../config/make_variable_immutable14.json | 43 +++++ .../config/make_variable_immutable15.json | 30 +++ .../config/make_variable_immutable2.json | 43 +++++ .../config/make_variable_immutable3.json | 30 +++ .../config/make_variable_immutable4.json | 56 ++++++ .../config/make_variable_immutable5.json | 30 +++ .../config/make_variable_immutable6.json | 30 +++ .../config/make_variable_immutable7.json | 56 ++++++ .../config/make_variable_immutable8.json | 30 +++ .../config/make_variable_immutable9.json | 30 +++ .../source/make_variable_immutable1.bal | 3 + .../source/make_variable_immutable10.bal | 13 ++ .../source/make_variable_immutable11.bal | 13 ++ .../source/make_variable_immutable12.bal | 7 + .../source/make_variable_immutable13.bal | 3 + .../source/make_variable_immutable14.bal | 3 + .../source/make_variable_immutable15.bal | 4 + .../source/make_variable_immutable2.bal | 10 + .../source/make_variable_immutable3.bal | 6 + .../source/make_variable_immutable4.bal | 7 + .../source/make_variable_immutable5.bal | 7 + .../source/make_variable_immutable6.bal | 3 + .../source/make_variable_immutable7.bal | 7 + .../source/make_variable_immutable8.bal | 3 + .../source/make_variable_immutable9.bal | 7 + 34 files changed, 925 insertions(+), 5 deletions(-) create mode 100644 language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java create mode 100644 language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/MakeVariableImmutableCodeActionTest.java create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable1.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable10.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable11.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable12.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable13.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable14.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable15.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable2.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable3.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable4.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable5.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable6.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable7.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable8.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable9.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable1.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable10.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable11.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable12.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable13.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable14.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable15.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable2.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable3.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable4.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable5.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable6.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable7.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable8.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable9.bal diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java index 018833dfcf89..599bf2ec6c46 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java @@ -19,6 +19,7 @@ package org.ballerinalang.langserver.codeaction.providers; import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.text.LinePosition; import org.ballerinalang.annotation.JavaSPIService; @@ -60,12 +61,11 @@ public boolean validate(Diagnostic diagnostic, DiagBasedPositionDetails position public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionDetails positionDetails, CodeActionContext context) { Node cursorNode = positionDetails.matchedNode(); - LinePosition startLinePosition = cursorNode.lineRange().startLine(); - Position startPosition = PositionUtil.toPosition(startLinePosition); - Position endPosition = PositionUtil.toPosition(cursorNode.lineRange().endLine()); + LinePosition linePosition = cursorNode.lineRange().startLine(); + Position position = PositionUtil.toPosition(linePosition); - String editText = "private " + cursorNode.toSourceCode().substring(startLinePosition.offset()); - TextEdit makePrivateTextEdit = new TextEdit(new Range(startPosition, endPosition), editText); + String editText = SyntaxKind.PRIVATE_KEYWORD.stringValue() + " "; + TextEdit makePrivateTextEdit = new TextEdit(new Range(position, position), editText); return Collections.singletonList(CodeActionUtil.createCodeAction( CommandConstants.CHANGE_ISOLATED_FIELD_PRIVATE, diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java new file mode 100644 index 000000000000..451d65ad57ba --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.ballerinalang.langserver.codeaction.providers; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ClassFieldSymbol; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.SymbolKind; +import io.ballerina.compiler.api.symbols.TypeDescKind; +import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NonTerminalNode; +import io.ballerina.compiler.syntax.tree.ObjectFieldNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.tools.diagnostics.Diagnostic; +import io.ballerina.tools.text.LinePosition; +import org.ballerinalang.annotation.JavaSPIService; +import org.ballerinalang.langserver.codeaction.CodeActionNodeValidator; +import org.ballerinalang.langserver.codeaction.CodeActionUtil; +import org.ballerinalang.langserver.common.constants.CommandConstants; +import org.ballerinalang.langserver.common.utils.PositionUtil; +import org.ballerinalang.langserver.commons.CodeActionContext; +import org.ballerinalang.langserver.commons.codeaction.spi.DiagBasedPositionDetails; +import org.ballerinalang.langserver.commons.codeaction.spi.DiagnosticBasedCodeActionProvider; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CodeActionKind; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextEdit; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Code Action for making a variable immutable. This will ensure that the given variable is both final and readonly. + * + * @since 2201.9.0 + */ +@JavaSPIService("org.ballerinalang.langserver.commons.codeaction.spi.LSCodeActionProvider") +public class MakeVariableImmutableCodeAction implements DiagnosticBasedCodeActionProvider { + + private static final String NAME = "Make variable immutable"; + private static final String DIAGNOSTIC_CODE = "BCE3956"; + + @Override + public boolean validate(Diagnostic diagnostic, DiagBasedPositionDetails positionDetails, + CodeActionContext context) { + return DIAGNOSTIC_CODE.equals(diagnostic.diagnosticInfo().code()) + && CodeActionNodeValidator.validate(context.nodeAtRange()); + } + + @Override + public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionDetails positionDetails, + CodeActionContext context) { + NonTerminalNode cursorNode = positionDetails.matchedNode(); + if (cursorNode.kind() != SyntaxKind.OBJECT_FIELD) { + return Collections.emptyList(); + } + + ObjectFieldNode objectFieldNode = (ObjectFieldNode) cursorNode; + Node typeNode = objectFieldNode.typeName(); + List textEdits = new ArrayList<>(); + + // Check if the type is final + boolean isFinal = objectFieldNode.qualifierList().stream() + .anyMatch(token -> token.kind().equals(SyntaxKind.FINAL_KEYWORD)); + if (!isFinal) { + textEdits.add(getFinalTextEdit(typeNode)); + } + + // Check if the type is readonly + TypeSymbol typeSymbol, readonlyType; + try { + SemanticModel semanticModel = context.currentSemanticModel().orElseThrow(); + readonlyType = semanticModel.types().READONLY; + Symbol symbol = semanticModel.symbol(cursorNode).orElseThrow(); + typeSymbol = getTypeSymbol(symbol).orElseThrow(); + } catch (Exception e) { + return Collections.emptyList(); + } + boolean isReadonly = typeSymbol.subtypeOf(readonlyType); + if (!isReadonly) { + textEdits.addAll(getReadonlyTextEdits(typeNode, typeSymbol.typeKind() == TypeDescKind.UNION)); + } + + // Check if the text edits are empty + if (textEdits.isEmpty()) { + return Collections.emptyList(); + } + + // Generate and return the code action + return Collections.singletonList(CodeActionUtil.createCodeAction( + String.format(CommandConstants.CHANGE_VARIABLE_TO_IMMUTABLE, getTitleText(isFinal, isReadonly)), + textEdits, + context.fileUri(), + CodeActionKind.QuickFix)); + } + + private static Optional getTypeSymbol(Symbol symbol) { + TypeSymbol typeSymbol; + if (Objects.requireNonNull(symbol.kind()) == SymbolKind.CLASS_FIELD) { + typeSymbol = ((ClassFieldSymbol) symbol).typeDescriptor(); + } else { + assert false : "Unconsidered symbol type found: " + symbol.kind(); + return Optional.empty(); + } + return typeSymbol.typeKind() == TypeDescKind.COMPILATION_ERROR ? Optional.empty() : Optional.of(typeSymbol); + } + + private static TextEdit getFinalTextEdit(Node typeNode) { + LinePosition linePosition = typeNode.lineRange().startLine(); + Position position = PositionUtil.toPosition(linePosition); + String editText = SyntaxKind.FINAL_KEYWORD.stringValue() + " "; + return new TextEdit(new Range(position, position), editText); + } + + private static List getReadonlyTextEdits(Node typeNode, boolean isUnion) { + LinePosition startLinePosition = typeNode.lineRange().startLine(); + LinePosition endLinePosition = typeNode.lineRange().endLine(); + List textEdits = new ArrayList<>(); + + if (isUnion) { + Position startPosition = PositionUtil.toPosition(startLinePosition); + TextEdit startTextEdit = new TextEdit(new Range(startPosition, startPosition), "("); + textEdits.add(startTextEdit); + } + + Position endPosition = PositionUtil.toPosition(endLinePosition); + String editText = (isUnion ? ")" : "") + " & " + SyntaxKind.READONLY_KEYWORD.stringValue(); + TextEdit endTextEdit = new TextEdit(new Range(endPosition, endPosition), editText); + textEdits.add(endTextEdit); + + return textEdits; + } + + private static String getTitleText(boolean isFinal, boolean isReadonly) { + StringBuilder result = new StringBuilder(); + + if (!isFinal) { + result.append("'").append(SyntaxKind.FINAL_KEYWORD.stringValue()).append("'"); + } + + if (!isReadonly) { + if (result.length() > 0) { + result.append(" and "); + } + result.append("'").append(SyntaxKind.READONLY_KEYWORD.stringValue()).append("'"); + } + + return result.toString(); + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java index c414fd02dacd..41ad4dddc525 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java @@ -122,6 +122,8 @@ public class CommandConstants { public static final String CHANGE_ISOLATED_FIELD_PRIVATE = "Change the field to private"; + public static final String CHANGE_VARIABLE_TO_IMMUTABLE = "Add %s to the variable"; + public static final String CREATE_VAR_TYPE_GUARD_TITLE = "Create variable and type guard"; public static final String TYPE_GUARD_TITLE = "Type guard variable '%s'"; diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/MakeVariableImmutableCodeActionTest.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/MakeVariableImmutableCodeActionTest.java new file mode 100644 index 000000000000..64560f1b1502 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/MakeVariableImmutableCodeActionTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.ballerinalang.langserver.codeaction; + +import org.ballerinalang.langserver.commons.workspace.WorkspaceDocumentException; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.IOException; + +/** + * Test class to test the converting a variable to immutable code action. + * + * @since 2201.9.0 + */ +public class MakeVariableImmutableCodeActionTest extends AbstractCodeActionTest { + + @Test(dataProvider = "codeaction-data-provider") + @Override + public void test(String config) throws IOException, WorkspaceDocumentException { + super.test(config); + } + + @DataProvider(name = "codeaction-data-provider") + @Override + public Object[][] dataProvider() { + return new Object[][]{ + {"make_variable_immutable1.json"}, + {"make_variable_immutable2.json"}, + {"make_variable_immutable3.json"}, + {"make_variable_immutable4.json"}, + {"make_variable_immutable5.json"}, + {"make_variable_immutable6.json"}, + {"make_variable_immutable7.json"}, + {"make_variable_immutable8.json"}, + {"make_variable_immutable9.json"}, + {"make_variable_immutable10.json"}, + {"make_variable_immutable11.json"}, + {"make_variable_immutable12.json"}, + {"make_variable_immutable13.json"}, + {"make_variable_immutable14.json"}, + {"make_variable_immutable15.json"} + }; + } + + @Override + public String getResourceDir() { + return "make-variable-immutable"; + } +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable1.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable1.json new file mode 100644 index 000000000000..3d0e19f9d228 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable1.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 1, + "character": 7 + }, + "source": "make_variable_immutable1.bal", + "description": "Make immutable of a simple type variable", + "expected": [ + { + "title": "Add 'final' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable10.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable10.json new file mode 100644 index 000000000000..5b2f3e17885e --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable10.json @@ -0,0 +1,43 @@ +{ + "position": { + "line": 7, + "character": 13 + }, + "source": "make_variable_immutable10.bal", + "description": "Make immutable of a object variable", + "expected": [ + { + "title": "Add 'final' and 'readonly' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 7, + "character": 4 + }, + "end": { + "line": 7, + "character": 4 + } + }, + "newText": "final " + }, + { + "range": { + "start": { + "line": 7, + "character": 14 + }, + "end": { + "line": 7, + "character": 14 + } + }, + "newText": " & readonly" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable11.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable11.json new file mode 100644 index 000000000000..adc74f997bf5 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable11.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 7, + "character": 10 + }, + "source": "make_variable_immutable11.bal", + "description": "Make immutable of a readonly object", + "expected": [ + { + "title": "Add 'final' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 7, + "character": 4 + }, + "end": { + "line": 7, + "character": 4 + } + }, + "newText": "final " + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable12.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable12.json new file mode 100644 index 000000000000..9bdca8934b7f --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable12.json @@ -0,0 +1,43 @@ +{ + "position": { + "line": 1, + "character": 10 + }, + "source": "make_variable_immutable12.bal", + "description": "Make immutable of a variable", + "expected": [ + { + "title": "Add 'final' and 'readonly' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + }, + { + "range": { + "start": { + "line": 1, + "character": 11 + }, + "end": { + "line": 1, + "character": 11 + } + }, + "newText": " & readonly" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable13.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable13.json new file mode 100644 index 000000000000..030a22d07c64 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable13.json @@ -0,0 +1,56 @@ +{ + "position": { + "line": 1, + "character": 8 + }, + "source": "make_variable_immutable13.bal", + "description": "Make immutable of a nullable mutable variable", + "expected": [ + { + "title": "Add 'final' and 'readonly' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + }, + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "(" + }, + { + "range": { + "start": { + "line": 1, + "character": 16 + }, + "end": { + "line": 1, + "character": 16 + } + }, + "newText": ") & readonly" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable14.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable14.json new file mode 100644 index 000000000000..d35519222bf9 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable14.json @@ -0,0 +1,43 @@ +{ + "position": { + "line": 1, + "character": 11 + }, + "source": "make_variable_immutable14.bal", + "description": "Make immutable of a variable", + "expected": [ + { + "title": "Add 'final' and 'readonly' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 11 + }, + "end": { + "line": 1, + "character": 11 + } + }, + "newText": "final " + }, + { + "range": { + "start": { + "line": 1, + "character": 16 + }, + "end": { + "line": 1, + "character": 16 + } + }, + "newText": " & readonly" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable15.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable15.json new file mode 100644 index 000000000000..fff0412c3711 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable15.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 2, + "character": 9 + }, + "source": "make_variable_immutable15.bal", + "description": "Make immutable of a documented public variable", + "expected": [ + { + "title": "Add 'final' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 2, + "character": 4 + }, + "end": { + "line": 2, + "character": 4 + } + }, + "newText": "final " + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable2.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable2.json new file mode 100644 index 000000000000..f8eb57bd3f39 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable2.json @@ -0,0 +1,43 @@ +{ + "position": { + "line": 2, + "character": 12 + }, + "source": "make_variable_immutable2.bal", + "description": "Make immutable of a record variable", + "expected": [ + { + "title": "Add 'final' and 'readonly' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + }, + { + "range": { + "start": { + "line": 4, + "character": 6 + }, + "end": { + "line": 4, + "character": 6 + } + }, + "newText": " & readonly" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable3.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable3.json new file mode 100644 index 000000000000..85bb2632dc93 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable3.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 4, + "character": 12 + }, + "source": "make_variable_immutable3.bal", + "description": "Make immutable of a readonly record variable", + "expected": [ + { + "title": "Add 'final' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable4.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable4.json new file mode 100644 index 000000000000..8c4c72db0dce --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable4.json @@ -0,0 +1,56 @@ +{ + "position": { + "line": 1, + "character": 7 + }, + "source": "make_variable_immutable4.bal", + "description": "Make immutable of a union variable", + "expected": [ + { + "title": "Add 'final' and 'readonly' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + }, + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "(" + }, + { + "range": { + "start": { + "line": 1, + "character": 19 + }, + "end": { + "line": 1, + "character": 19 + } + }, + "newText": ") & readonly" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable5.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable5.json new file mode 100644 index 000000000000..8845f0254f59 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable5.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 1, + "character": 7 + }, + "source": "make_variable_immutable5.bal", + "description": "Make immutable of a union variable", + "expected": [ + { + "title": "Add 'final' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable6.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable6.json new file mode 100644 index 000000000000..e7dc76295015 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable6.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 1, + "character": 10 + }, + "source": "make_variable_immutable6.bal", + "description": "Make immutable of a union variable", + "expected": [ + { + "title": "Add 'final' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable7.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable7.json new file mode 100644 index 000000000000..5c55317e89f7 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable7.json @@ -0,0 +1,56 @@ +{ + "position": { + "line": 1, + "character": 14 + }, + "source": "make_variable_immutable7.bal", + "description": "Make immutable of a union variable", + "expected": [ + { + "title": "Add 'final' and 'readonly' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + }, + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "(" + }, + { + "range": { + "start": { + "line": 1, + "character": 21 + }, + "end": { + "line": 1, + "character": 21 + } + }, + "newText": ") & readonly" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable8.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable8.json new file mode 100644 index 000000000000..e49e471a3947 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable8.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 1, + "character": 5 + }, + "source": "make_variable_immutable8.bal", + "description": "Make immutable of a singleton variable", + "expected": [ + { + "title": "Add 'final' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable9.json b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable9.json new file mode 100644 index 000000000000..022971cc0850 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/config/make_variable_immutable9.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 1, + "character": 10 + }, + "source": "make_variable_immutable9.bal", + "description": "Make immutable of a function variable", + "expected": [ + { + "title": "Add 'final' to the variable", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "final " + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable1.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable1.bal new file mode 100644 index 000000000000..2d069bd035ce --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable1.bal @@ -0,0 +1,3 @@ +isolated class IsolatedClass { + int i = 2; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable10.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable10.bal new file mode 100644 index 000000000000..258fc9443729 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable10.bal @@ -0,0 +1,13 @@ +type ObjectName object { + int i; + + function name(); +}; + +isolated class IsolatedClass { + ObjectName myObj; + + function init(ObjectName & readonly myObj) { + self.myObj = myObj; + } +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable11.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable11.bal new file mode 100644 index 000000000000..a75a0a159391 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable11.bal @@ -0,0 +1,13 @@ +type ObjectName object { + int i; + + function name(); +} & readonly; + +isolated class IsolatedClass { + ObjectName myObj; + + function init(ObjectName myObj) { + self.myObj = myObj; + } +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable12.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable12.bal new file mode 100644 index 000000000000..744de40de58e --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable12.bal @@ -0,0 +1,7 @@ +isolated class IsolatedClass { + anydata val; + + function init(string val) { + self.val = val; + } +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable13.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable13.bal new file mode 100644 index 000000000000..eef987ab6833 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable13.bal @@ -0,0 +1,3 @@ +isolated class IsolatedClass { + map? mp = {}; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable14.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable14.bal new file mode 100644 index 000000000000..8b0270ba715a --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable14.bal @@ -0,0 +1,3 @@ +isolated class IsolatedClass { + public int[] arr = [2]; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable15.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable15.bal new file mode 100644 index 000000000000..b488214c6d38 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable15.bal @@ -0,0 +1,4 @@ +isolated class IsolatedClass { + # Documented node + int i = 2; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable2.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable2.bal new file mode 100644 index 000000000000..1fbd35bbd1c4 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable2.bal @@ -0,0 +1,10 @@ +isolated class IsolatedClass { + record {| + string id; + int count; + |} rec; + + function init() { + self.rec = {count: 0, id: ""}; + } +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable3.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable3.bal new file mode 100644 index 000000000000..82a21e2431f6 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable3.bal @@ -0,0 +1,6 @@ +isolated class IsolatedClass { + record {| + string id; + int count; + |} & readonly rec = {count: 0, id: ""}; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable4.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable4.bal new file mode 100644 index 000000000000..876f982b5597 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable4.bal @@ -0,0 +1,7 @@ +isolated class IsolatedClass { + int|map val; + + function init(int|map & readonly val) { + self.val = val; + } +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable5.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable5.bal new file mode 100644 index 000000000000..67a194f99a58 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable5.bal @@ -0,0 +1,7 @@ +isolated class IsolatedClass { + int|string val; + + function init(int|string val) { + self.val = val; + } +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable6.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable6.bal new file mode 100644 index 000000000000..bb526b7e3cad --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable6.bal @@ -0,0 +1,3 @@ +isolated class IsolatedClass { + map & readonly|int|boolean val = false; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable7.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable7.bal new file mode 100644 index 000000000000..820d7a16a845 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable7.bal @@ -0,0 +1,7 @@ +isolated class IsolatedClass { + int[]|map val; + + function init(int[]|map val) { + self.val = val.cloneReadOnly(); + } +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable8.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable8.bal new file mode 100644 index 000000000000..4d125af1e5c7 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable8.bal @@ -0,0 +1,3 @@ +isolated class IsolatedClass { + "Value" val = "Value"; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable9.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable9.bal new file mode 100644 index 000000000000..251748fd6e6f --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/make-variable-immutable/source/make_variable_immutable9.bal @@ -0,0 +1,7 @@ +isolated class IsolatedClass { + function (int i) returns boolean fn; + + function init(function (int i) returns boolean fn) { + self.fn = fn; + } +} From 08c8b83aa0f9aed388202c76fcb00f5522012d2d Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Tue, 19 Mar 2024 10:01:32 +0530 Subject: [PATCH 3/6] Support metadata and qualifiers for the CA --- .../ChangeIsolatedFieldPrivateCodeAction.java | 37 +++++++++++++++---- ...ngeIsolatedFieldPrivateCodeActionTest.java | 5 ++- .../config/change_field_private1.json | 6 +-- .../config/change_field_private2.json | 4 +- .../config/change_field_private3.json | 6 +-- .../config/change_field_private4.json | 30 +++++++++++++++ .../config/change_field_private5.json | 30 +++++++++++++++ .../config/change_field_private6.json | 30 +++++++++++++++ .../source/change_field_private4.bal | 7 ++++ .../source/change_field_private5.bal | 3 ++ .../source/change_field_private6.bal | 4 ++ 11 files changed, 146 insertions(+), 16 deletions(-) create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private4.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private5.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private6.json create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private4.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private5.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private6.bal diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java index 599bf2ec6c46..579acdd1e7cc 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java @@ -19,9 +19,11 @@ package org.ballerinalang.langserver.codeaction.providers; import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ObjectFieldNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.compiler.syntax.tree.Token; import io.ballerina.tools.diagnostics.Diagnostic; -import io.ballerina.tools.text.LinePosition; import org.ballerinalang.annotation.JavaSPIService; import org.ballerinalang.langserver.codeaction.CodeActionNodeValidator; import org.ballerinalang.langserver.codeaction.CodeActionUtil; @@ -38,6 +40,7 @@ import java.util.Collections; import java.util.List; +import java.util.Optional; /** * Code Action for changing isolated field to private. @@ -61,20 +64,40 @@ public boolean validate(Diagnostic diagnostic, DiagBasedPositionDetails position public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionDetails positionDetails, CodeActionContext context) { Node cursorNode = positionDetails.matchedNode(); - LinePosition linePosition = cursorNode.lineRange().startLine(); - Position position = PositionUtil.toPosition(linePosition); - - String editText = SyntaxKind.PRIVATE_KEYWORD.stringValue() + " "; - TextEdit makePrivateTextEdit = new TextEdit(new Range(position, position), editText); + if (cursorNode.kind() != SyntaxKind.OBJECT_FIELD) { + return Collections.emptyList(); + } return Collections.singletonList(CodeActionUtil.createCodeAction( CommandConstants.CHANGE_ISOLATED_FIELD_PRIVATE, - List.of(makePrivateTextEdit), + List.of(getTextEdit((ObjectFieldNode) cursorNode)), context.fileUri(), CodeActionKind.QuickFix )); } + private static TextEdit getTextEdit(ObjectFieldNode node) { + String privateKeyword = SyntaxKind.PRIVATE_KEYWORD.stringValue(); + + // Get the line range of the existing visibility qualifier + Optional visibilityQualifier = node.visibilityQualifier(); + if (visibilityQualifier.isPresent()) { + return new TextEdit(PositionUtil.toRange(visibilityQualifier.get().lineRange()), privateKeyword); + } + privateKeyword += " "; + + // Get the start position of the qualifier list + NodeList qualifiers = node.qualifierList(); + if (qualifiers.size() > 0) { + Position position = PositionUtil.toPosition(qualifiers.get(0).lineRange().startLine()); + return new TextEdit(new Range(position, position), privateKeyword); + } + + // Get the start position of the type name + Position position = PositionUtil.toPosition(node.typeName().lineRange().startLine()); + return new TextEdit(new Range(position, position), privateKeyword); + } + @Override public String getName() { return NAME; diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java index 6e5c6a541111..270f3223cc16 100644 --- a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java @@ -43,7 +43,10 @@ public Object[][] dataProvider() { return new Object[][]{ {"change_field_private1.json"}, {"change_field_private2.json"}, - {"change_field_private3.json"} + {"change_field_private3.json"}, + {"change_field_private4.json"}, + {"change_field_private5.json"}, + {"change_field_private6.json"}, }; } diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json index 88a3218f3729..1a7dab89eff0 100644 --- a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json @@ -17,11 +17,11 @@ "character": 4 }, "end": { - "line": 5, - "character": 6 + "line": 1, + "character": 4 } }, - "newText": "private map isolatedField = {\n a: \"1\",\n b: \"2\",\n c: \"3\"\n };\n" + "newText": "private " } ], "resolvable": false diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json index 73b5f3ee2773..e06ea8f78f03 100644 --- a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json @@ -18,10 +18,10 @@ }, "end": { "line": 1, - "character": 15 + "character": 4 } }, - "newText": "private int i = 32;\n" + "newText": "private " } ], "resolvable": false diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json index 55878b57241c..bdb407d8f56f 100644 --- a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json @@ -17,11 +17,11 @@ "character": 4 }, "end": { - "line": 4, - "character": 19 + "line": 1, + "character": 4 } }, - "newText": "private record {|\n int i;\n string val;\n |} isolatedRec;\n" + "newText": "private " } ], "resolvable": false diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private4.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private4.json new file mode 100644 index 000000000000..e402f7d99f42 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private4.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 1, + "character": 25 + }, + "source": "change_field_private4.bal", + "description": "Change the isolated field to private for a public type", + "expected": [ + { + "title": "Change the field to private", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 10 + } + }, + "newText": "private" + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private5.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private5.json new file mode 100644 index 000000000000..0d86ea463319 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private5.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 1, + "character": 10 + }, + "source": "change_field_private5.bal", + "description": "Change the isolated field to private for a final type", + "expected": [ + { + "title": "Change the field to private", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 4 + }, + "end": { + "line": 1, + "character": 4 + } + }, + "newText": "private " + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private6.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private6.json new file mode 100644 index 000000000000..370ceade81ff --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private6.json @@ -0,0 +1,30 @@ +{ + "position": { + "line": 2, + "character": 8 + }, + "source": "change_field_private6.bal", + "description": "Change the isolated field to private for a documented type", + "expected": [ + { + "title": "Change the field to private", + "kind": "quickfix", + "edits": [ + { + "range": { + "start": { + "line": 2, + "character": 4 + }, + "end": { + "line": 2, + "character": 4 + } + }, + "newText": "private " + } + ], + "resolvable": false + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private4.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private4.bal new file mode 100644 index 000000000000..2a55b0a6a579 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private4.bal @@ -0,0 +1,7 @@ +isolated service class IsolatedClass { + public final map isolatedField = { + a: "1", + b: "2", + c: "3" + }; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private5.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private5.bal new file mode 100644 index 000000000000..d4d7df6ac1ab --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private5.bal @@ -0,0 +1,3 @@ +isolated service class IsolatedClass { + final string[] strArr = []; +} diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private6.bal b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private6.bal new file mode 100644 index 000000000000..d0e96c7283c5 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/source/change_field_private6.bal @@ -0,0 +1,4 @@ +isolated service class IsolatedClass { + # Documentation + final string[] strArr = []; +} From d53f3bfb8caddb02bc4a65daa4c1acbe7a0db1d5 Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Wed, 20 Mar 2024 09:27:19 +0530 Subject: [PATCH 4/6] Fix checkstyle issues in the CA --- .../codeaction/providers/MakeVariableImmutableCodeAction.java | 4 +++- .../codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java index 451d65ad57ba..912a37208893 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java @@ -72,6 +72,8 @@ public boolean validate(Diagnostic diagnostic, DiagBasedPositionDetails position public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionDetails positionDetails, CodeActionContext context) { NonTerminalNode cursorNode = positionDetails.matchedNode(); + + // The current implementation of the CA only supports object fields. if (cursorNode.kind() != SyntaxKind.OBJECT_FIELD) { return Collections.emptyList(); } @@ -94,7 +96,7 @@ public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionD readonlyType = semanticModel.types().READONLY; Symbol symbol = semanticModel.symbol(cursorNode).orElseThrow(); typeSymbol = getTypeSymbol(symbol).orElseThrow(); - } catch (Exception e) { + } catch (RuntimeException e) { return Collections.emptyList(); } boolean isReadonly = typeSymbol.subtypeOf(readonlyType); diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java index 270f3223cc16..6bffa12a22ca 100644 --- a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java @@ -25,7 +25,7 @@ import java.io.IOException; /** - * Test class to test the change isolated field to private code action + * Test class to test the change isolated field to private code action. * * @since 2201.9.0 */ From e5716e013b28c901e19834e5f7a8770086c43ab4 Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Wed, 20 Mar 2024 10:57:55 +0530 Subject: [PATCH 5/6] Change CA name for adding private qualifier --- ...tion.java => AddPrivateQualifierCodeAction.java} | 8 ++++---- .../providers/MakeVariableImmutableCodeAction.java | 13 +++++++------ .../common/constants/CommandConstants.java | 4 ++-- ....java => AddPrivateQualifierCodeActionTest.java} | 2 +- .../config/change_field_private1.json | 2 +- .../config/change_field_private2.json | 2 +- .../config/change_field_private3.json | 2 +- .../config/change_field_private4.json | 2 +- .../config/change_field_private5.json | 2 +- .../config/change_field_private6.json | 2 +- 10 files changed, 20 insertions(+), 19 deletions(-) rename language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/{ChangeIsolatedFieldPrivateCodeAction.java => AddPrivateQualifierCodeAction.java} (93%) rename language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/{ChangeIsolatedFieldPrivateCodeActionTest.java => AddPrivateQualifierCodeActionTest.java} (95%) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/AddPrivateQualifierCodeAction.java similarity index 93% rename from language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java rename to language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/AddPrivateQualifierCodeAction.java index 579acdd1e7cc..aee93aa83e6c 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/ChangeIsolatedFieldPrivateCodeAction.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/AddPrivateQualifierCodeAction.java @@ -43,14 +43,14 @@ import java.util.Optional; /** - * Code Action for changing isolated field to private. + * Code Action for adding the private visibility qualifier ƒor an object field. * * @since 2201.9.0 */ @JavaSPIService("org.ballerinalang.langserver.commons.codeaction.spi.LSCodeActionProvider") -public class ChangeIsolatedFieldPrivateCodeAction implements DiagnosticBasedCodeActionProvider { +public class AddPrivateQualifierCodeAction implements DiagnosticBasedCodeActionProvider { - private static final String NAME = "Change isolated field to private"; + private static final String NAME = "Add private visibility qualifier"; private static final String DIAGNOSTIC_CODE = "BCE3956"; @Override @@ -69,7 +69,7 @@ public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionD } return Collections.singletonList(CodeActionUtil.createCodeAction( - CommandConstants.CHANGE_ISOLATED_FIELD_PRIVATE, + CommandConstants.ADD_PRIVATE_QUALIFIER, List.of(getTextEdit((ObjectFieldNode) cursorNode)), context.fileUri(), CodeActionKind.QuickFix diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java index 912a37208893..b2666158d4f7 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java @@ -47,7 +47,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.Optional; /** @@ -73,7 +72,7 @@ public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionD CodeActionContext context) { NonTerminalNode cursorNode = positionDetails.matchedNode(); - // The current implementation of the CA only supports object fields. + // The current implementation of the CA only supports object fields if (cursorNode.kind() != SyntaxKind.OBJECT_FIELD) { return Collections.emptyList(); } @@ -111,7 +110,7 @@ public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionD // Generate and return the code action return Collections.singletonList(CodeActionUtil.createCodeAction( - String.format(CommandConstants.CHANGE_VARIABLE_TO_IMMUTABLE, getTitleText(isFinal, isReadonly)), + String.format(CommandConstants.MAKE_VARIABLE_IMMUTABLE, getTitleText(isFinal, isReadonly)), textEdits, context.fileUri(), CodeActionKind.QuickFix)); @@ -119,7 +118,7 @@ public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionD private static Optional getTypeSymbol(Symbol symbol) { TypeSymbol typeSymbol; - if (Objects.requireNonNull(symbol.kind()) == SymbolKind.CLASS_FIELD) { + if (symbol.kind() == SymbolKind.CLASS_FIELD) { typeSymbol = ((ClassFieldSymbol) symbol).typeDescriptor(); } else { assert false : "Unconsidered symbol type found: " + symbol.kind(); @@ -142,12 +141,14 @@ private static List getReadonlyTextEdits(Node typeNode, boolean isUnio if (isUnion) { Position startPosition = PositionUtil.toPosition(startLinePosition); - TextEdit startTextEdit = new TextEdit(new Range(startPosition, startPosition), "("); + TextEdit startTextEdit = new TextEdit(new Range(startPosition, startPosition), + SyntaxKind.OPEN_PAREN_TOKEN.stringValue()); textEdits.add(startTextEdit); } Position endPosition = PositionUtil.toPosition(endLinePosition); - String editText = (isUnion ? ")" : "") + " & " + SyntaxKind.READONLY_KEYWORD.stringValue(); + String editText = (isUnion ? SyntaxKind.CLOSE_PAREN_TOKEN.stringValue() : "") + " & " + + SyntaxKind.READONLY_KEYWORD.stringValue(); TextEdit endTextEdit = new TextEdit(new Range(endPosition, endPosition), editText); textEdits.add(endTextEdit); diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java index 41ad4dddc525..72216df953e8 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java @@ -120,9 +120,9 @@ public class CommandConstants { public static final String CHANGE_PARAM_TYPE_TITLE = "Change parameter '%s' type to '%s'"; - public static final String CHANGE_ISOLATED_FIELD_PRIVATE = "Change the field to private"; + public static final String ADD_PRIVATE_QUALIFIER = "Add private qualifier"; - public static final String CHANGE_VARIABLE_TO_IMMUTABLE = "Add %s to the variable"; + public static final String MAKE_VARIABLE_IMMUTABLE = "Add %s to the variable"; public static final String CREATE_VAR_TYPE_GUARD_TITLE = "Create variable and type guard"; diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/AddPrivateQualifierCodeActionTest.java similarity index 95% rename from language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java rename to language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/AddPrivateQualifierCodeActionTest.java index 6bffa12a22ca..d85d8593ac40 100644 --- a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/ChangeIsolatedFieldPrivateCodeActionTest.java +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codeaction/AddPrivateQualifierCodeActionTest.java @@ -29,7 +29,7 @@ * * @since 2201.9.0 */ -public class ChangeIsolatedFieldPrivateCodeActionTest extends AbstractCodeActionTest { +public class AddPrivateQualifierCodeActionTest extends AbstractCodeActionTest { @Test(dataProvider = "codeaction-data-provider") @Override diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json index 1a7dab89eff0..1a0adec8efbd 100644 --- a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private1.json @@ -7,7 +7,7 @@ "description": "Change the isolated field to private for a structured type", "expected": [ { - "title": "Change the field to private", + "title": "Add private qualifier", "kind": "quickfix", "edits": [ { diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json index e06ea8f78f03..e1e4c02e8230 100644 --- a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private2.json @@ -7,7 +7,7 @@ "description": "Change the isolated field to private for a simple type", "expected": [ { - "title": "Change the field to private", + "title": "Add private qualifier", "kind": "quickfix", "edits": [ { diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json index bdb407d8f56f..0c21fa714fe3 100644 --- a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private3.json @@ -7,7 +7,7 @@ "description": "Change the isolated field to private for a multiline type", "expected": [ { - "title": "Change the field to private", + "title": "Add private qualifier", "kind": "quickfix", "edits": [ { diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private4.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private4.json index e402f7d99f42..bbab3fd7a862 100644 --- a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private4.json +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private4.json @@ -7,7 +7,7 @@ "description": "Change the isolated field to private for a public type", "expected": [ { - "title": "Change the field to private", + "title": "Add private qualifier", "kind": "quickfix", "edits": [ { diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private5.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private5.json index 0d86ea463319..57399d619e04 100644 --- a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private5.json +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private5.json @@ -7,7 +7,7 @@ "description": "Change the isolated field to private for a final type", "expected": [ { - "title": "Change the field to private", + "title": "Add private qualifier", "kind": "quickfix", "edits": [ { diff --git a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private6.json b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private6.json index 370ceade81ff..d549f3554f94 100644 --- a/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private6.json +++ b/language-server/modules/langserver-core/src/test/resources/codeaction/change-field-private/config/change_field_private6.json @@ -7,7 +7,7 @@ "description": "Change the isolated field to private for a documented type", "expected": [ { - "title": "Change the field to private", + "title": "Add private qualifier", "kind": "quickfix", "edits": [ { From b57ea61f820bea6e674f4c4c484ccdf6fc3dd68e Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Thu, 21 Mar 2024 17:08:31 +0530 Subject: [PATCH 6/6] Document unreachable code lines --- .../AddPrivateQualifierCodeAction.java | 1 + .../MakeVariableImmutableCodeAction.java | 17 ++++++----------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/AddPrivateQualifierCodeAction.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/AddPrivateQualifierCodeAction.java index aee93aa83e6c..e56ac4064b68 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/AddPrivateQualifierCodeAction.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/AddPrivateQualifierCodeAction.java @@ -65,6 +65,7 @@ public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionD CodeActionContext context) { Node cursorNode = positionDetails.matchedNode(); if (cursorNode.kind() != SyntaxKind.OBJECT_FIELD) { + assert false : "This line is unreachable as the diagnostic is only generated for an object field."; return Collections.emptyList(); } diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java index b2666158d4f7..2234dbfcb316 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codeaction/providers/MakeVariableImmutableCodeAction.java @@ -74,6 +74,7 @@ public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionD // The current implementation of the CA only supports object fields if (cursorNode.kind() != SyntaxKind.OBJECT_FIELD) { + assert false : "This line is unreachable as the diagnostic is only generated for an object field."; return Collections.emptyList(); } @@ -96,6 +97,8 @@ public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionD Symbol symbol = semanticModel.symbol(cursorNode).orElseThrow(); typeSymbol = getTypeSymbol(symbol).orElseThrow(); } catch (RuntimeException e) { + assert false : "This line is unreachable because the semantic model cannot be empty, and the type " + + "symbol does not contain errors."; return Collections.emptyList(); } boolean isReadonly = typeSymbol.subtypeOf(readonlyType); @@ -103,11 +106,6 @@ public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionD textEdits.addAll(getReadonlyTextEdits(typeNode, typeSymbol.typeKind() == TypeDescKind.UNION)); } - // Check if the text edits are empty - if (textEdits.isEmpty()) { - return Collections.emptyList(); - } - // Generate and return the code action return Collections.singletonList(CodeActionUtil.createCodeAction( String.format(CommandConstants.MAKE_VARIABLE_IMMUTABLE, getTitleText(isFinal, isReadonly)), @@ -117,14 +115,11 @@ public List getCodeActions(Diagnostic diagnostic, DiagBasedPositionD } private static Optional getTypeSymbol(Symbol symbol) { - TypeSymbol typeSymbol; if (symbol.kind() == SymbolKind.CLASS_FIELD) { - typeSymbol = ((ClassFieldSymbol) symbol).typeDescriptor(); - } else { - assert false : "Unconsidered symbol type found: " + symbol.kind(); - return Optional.empty(); + return Optional.of(((ClassFieldSymbol) symbol).typeDescriptor()); } - return typeSymbol.typeKind() == TypeDescKind.COMPILATION_ERROR ? Optional.empty() : Optional.of(typeSymbol); + assert false : "Unconsidered symbol type found: " + symbol.kind(); + return Optional.empty(); } private static TextEdit getFinalTextEdit(Node typeNode) {