From 2732087027575e019024c07516123f4e45d01ee3 Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Mon, 9 Oct 2023 20:00:35 +0530 Subject: [PATCH 1/3] Fix completion for singleton resource param --- .../builder/ResourcePathCompletionUtil.java | 47 ++++++++++--- ...ClientResourceAccessActionNodeContext.java | 32 +++++++-- ...lient_resource_access_action_config38.json | 40 +++++++++++ ...lient_resource_access_action_config39.json | 40 +++++++++++ ...lient_resource_access_action_config40.json | 40 +++++++++++ ...lient_resource_access_action_config41.json | 70 +++++++++++++++++++ ...lient_resource_access_action_config42.json | 70 +++++++++++++++++++ ...lient_resource_access_action_config43.json | 70 +++++++++++++++++++ ...client_resource_access_action_source31.bal | 10 +++ ...client_resource_access_action_source32.bal | 10 +++ ...client_resource_access_action_source33.bal | 10 +++ ...client_resource_access_action_source34.bal | 18 +++++ ...client_resource_access_action_source35.bal | 18 +++++ ...client_resource_access_action_source36.bal | 18 +++++ 14 files changed, 480 insertions(+), 13 deletions(-) create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config41.json create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config42.json create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config43.json create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source31.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source32.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source33.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source34.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source35.bal create mode 100644 language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source36.bal diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/builder/ResourcePathCompletionUtil.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/builder/ResourcePathCompletionUtil.java index f3be655069c2..36b316e385fe 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/builder/ResourcePathCompletionUtil.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/builder/ResourcePathCompletionUtil.java @@ -20,6 +20,7 @@ import io.ballerina.compiler.api.symbols.ResourceMethodSymbol; import io.ballerina.compiler.api.symbols.TypeDescKind; import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.api.symbols.resourcepath.ResourcePath; import io.ballerina.compiler.api.symbols.resourcepath.util.NamedPathSegment; import io.ballerina.compiler.api.symbols.resourcepath.util.PathSegment; @@ -106,7 +107,7 @@ public static List> getResourceAccessInfo(ResourceMethodSym signatureWithComputedResourceSegments.append("/").append(resourceAccessPart.computedPathSignature); insertTextWithComputedResourceSegments.append("/").append(resourceAccessPart.computedPathInsertText); - if (resourceAccessPart.isStringPathPram) { + if (resourceAccessPart.isStringPathParam) { isStringPathParamsAvailable = true; signatureWithNamedSegments.append("/").append(resourceAccessPart.namedPathSignature); insertTextWithNamedSegments.append("/").append(resourceAccessPart.namedPathInsertText); @@ -211,20 +212,48 @@ private static ResourceAccessPathPart getResourceAccessPartForSegment(PathSegmen String paramType = FunctionCompletionItemBuilder .getFunctionParameterSyntax(pathParameterSymbol, context).orElse(""); String computedSignature = "[" + paramType + "]"; - String computedInsertText = "[${" + placeHolderIndex + ":" + defaultValue.orElse("") + "}]"; + String computedInsertText = + typeSymbol.typeKind().equals(TypeDescKind.SINGLETON) ? "[" + defaultValue.orElse("") + "]" : + "[${" + placeHolderIndex + ":" + defaultValue.orElse("") + "}]"; ResourceAccessPathPart resourceAccessPathPart = new ResourceAccessPathPart(computedInsertText, computedSignature); if (context.currentSemanticModel().isPresent() && - context.currentSemanticModel().get().types().STRING.subtypeOf(typeSymbol)) { - resourceAccessPathPart.namedPathSignature = "<" + - (pathParameterSymbol.getName().isPresent() ? pathParameterSymbol.getName().get() : "path") + ">"; - resourceAccessPathPart.namedPathInsertText = "${" + placeHolderIndex + ":" + "path" + "}"; - resourceAccessPathPart.computedPathInsertText = "[${" + placeHolderIndex + ":" + "\"path\"" + "}]"; - resourceAccessPathPart.isStringPathPram = true; + checkSubtype(typeSymbol, context.currentSemanticModel().get().types().STRING)) { + if (typeSymbol.typeKind().equals(TypeDescKind.SINGLETON)) { + resourceAccessPathPart.namedPathSignature = + typeSymbol.signature().substring(1, typeSymbol.signature().length() - 1); + resourceAccessPathPart.namedPathInsertText = resourceAccessPathPart.namedPathSignature; + } else { + resourceAccessPathPart.namedPathSignature = "<" + + (pathParameterSymbol.getName().isPresent() ? pathParameterSymbol.getName().get() : "path") + ">"; + resourceAccessPathPart.namedPathInsertText = "${" + placeHolderIndex + ":" + "path" + "}"; + resourceAccessPathPart.computedPathInsertText = "[${" + placeHolderIndex + ":" + "\"path\"" + "}]"; + } + resourceAccessPathPart.isStringPathParam = true; } return resourceAccessPathPart; } + private static boolean checkSubtype(TypeSymbol paramType, TypeSymbol stringType) { + if (stringType.subtypeOf(paramType)) { + return true; + } + switch (paramType.typeKind()) { + case UNION: + for (TypeSymbol childSymbol : ((UnionTypeSymbol) paramType).memberTypeDescriptors()) { + if (checkSubtype(childSymbol, stringType)) { + return true; + } + } + return false; + case SINGLETON: + case TYPE_REFERENCE: + return paramType.subtypeOf(stringType); + default: + return false; + } + } + private static class ResourceAccessPathPart { private String computedPathInsertText; @@ -233,7 +262,7 @@ private static class ResourceAccessPathPart { private String namedPathSignature; private String namedPathInsertText; - boolean isStringPathPram = false; + boolean isStringPathParam = false; ResourceAccessPathPart(String computedPathInsertText, String computedPathSignature) { this.computedPathInsertText = computedPathInsertText; diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java index ae3b3efa59d0..ed922a1cef20 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java @@ -25,6 +25,7 @@ import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.SymbolKind; import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.api.symbols.resourcepath.PathRestParam; import io.ballerina.compiler.api.symbols.resourcepath.PathSegmentList; import io.ballerina.compiler.api.symbols.resourcepath.ResourcePath; @@ -32,6 +33,7 @@ import io.ballerina.compiler.api.symbols.resourcepath.util.PathSegment; import io.ballerina.compiler.syntax.tree.ClientResourceAccessActionNode; import io.ballerina.compiler.syntax.tree.ComputedResourceAccessSegmentNode; +import io.ballerina.compiler.syntax.tree.ExpressionNode; import io.ballerina.compiler.syntax.tree.IdentifierToken; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.ParenthesizedArgList; @@ -343,9 +345,10 @@ private Pair, Boolean> completableSegmentList(ResourceMethodSy return Pair.of(Collections.emptyList(), false); } if (node.kind() == SyntaxKind.COMPUTED_RESOURCE_ACCESS_SEGMENT) { + ExpressionNode exprNode = ((ComputedResourceAccessSegmentNode) node).expression(); Optional exprType = - semanticModel.get().typeOf(((ComputedResourceAccessSegmentNode) node).expression()); - if (exprType.isEmpty() || !exprType.get().subtypeOf(typeSymbol)) { + semanticModel.get().typeOf(exprNode); + if (exprType.isEmpty() || !checkSubtype(typeSymbol, exprType.get(), exprNode.toString())) { return Pair.of(Collections.emptyList(), false); } if (segment.pathSegmentKind() == PathSegment.Kind.PATH_REST_PARAMETER @@ -353,8 +356,8 @@ private Pair, Boolean> completableSegmentList(ResourceMethodSy completableSegmentStartIndex -= 1; } continue; - } else if (node.kind() == SyntaxKind.IDENTIFIER_TOKEN - && semanticModel.get().types().STRING.subtypeOf(typeSymbol)) { + } else if (node.kind() == SyntaxKind.IDENTIFIER_TOKEN && + checkSubtype(typeSymbol, semanticModel.get().types().STRING, "\"" + node + "\"")) { if (segment.pathSegmentKind() == PathSegment.Kind.PATH_REST_PARAMETER && !ResourcePathCompletionUtil.isInMethodCallContext(accNode, context)) { completableSegmentStartIndex -= 1; @@ -371,6 +374,27 @@ private Pair, Boolean> completableSegmentList(ResourceMethodSy return Pair.of(completableSegments, true); } + private boolean checkSubtype(TypeSymbol paramType, TypeSymbol exprType, String exprValue) { + if (exprType.subtypeOf(paramType)) { + return true; + } + switch (paramType.typeKind()) { + case UNION: + for (TypeSymbol childSymbol : ((UnionTypeSymbol) paramType).memberTypeDescriptors()) { + if (checkSubtype(childSymbol, exprType, exprValue)) { + return true; + } + } + return false; + case SINGLETON: + return paramType.subtypeOf(exprType) && exprValue.equals(paramType.signature()); + case TYPE_REFERENCE: + return paramType.subtypeOf(exprType); + default: + return false; + } + } + private boolean onSuggestClients(ClientResourceAccessActionNode node, BallerinaCompletionContext context) { int cursor = context.getCursorPositionInTree(); return cursor <= node.rightArrowToken().textRange().startOffset(); diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json new file mode 100644 index 000000000000..9f3fdc5e3e80 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json @@ -0,0 +1,40 @@ +{ + "position": { + "line": 8, + "character": 9 + }, + "source": "action_node_context/source/client_resource_access_action_source31.bal", + "description": "", + "items": [ + { + "label": "/[\"name\" a].accessor", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `\"name\"` a" + } + }, + "sortText": "CC", + "filterText": "|accessor", + "insertText": "/[\"name\"].accessor;", + "insertTextFormat": "Snippet" + }, + { + "label": "/name.accessor", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `\"name\"` a" + } + }, + "sortText": "CC", + "filterText": "|accessor", + "insertText": "/name.accessor;", + "insertTextFormat": "Snippet" + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json new file mode 100644 index 000000000000..50e5222d24d0 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json @@ -0,0 +1,40 @@ +{ + "position": { + "line": 8, + "character": 9 + }, + "source": "action_node_context/source/client_resource_access_action_source32.bal", + "description": "", + "items": [ + { + "label": "/[\"A1\"|\"A2\" a].accessor", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `\"A1\"|\"A2\"` a" + } + }, + "sortText": "CC", + "filterText": "|accessor", + "insertText": "/[${1:\"path\"}].accessor;", + "insertTextFormat": "Snippet" + }, + { + "label": "/.accessor", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `\"A1\"|\"A2\"` a" + } + }, + "sortText": "CC", + "filterText": "|accessor", + "insertText": "/${1:path}.accessor;", + "insertTextFormat": "Snippet" + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json new file mode 100644 index 000000000000..ed81cb3ad1fb --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json @@ -0,0 +1,40 @@ +{ + "position": { + "line": 8, + "character": 9 + }, + "source": "action_node_context/source/client_resource_access_action_source33.bal", + "description": "", + "items": [ + { + "label": "/[int|string:Char a].accessor", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `int|string:Char` a" + } + }, + "sortText": "CC", + "filterText": "|accessor", + "insertText": "/[${1:\"path\"}].accessor;", + "insertTextFormat": "Snippet" + }, + { + "label": "/.accessor", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `int|string:Char` a" + } + }, + "sortText": "CC", + "filterText": "|accessor", + "insertText": "/${1:path}.accessor;", + "insertTextFormat": "Snippet" + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config41.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config41.json new file mode 100644 index 000000000000..0e3649c42c6b --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config41.json @@ -0,0 +1,70 @@ +{ + "position": { + "line": 16, + "character": 18 + }, + "source": "action_node_context/source/client_resource_access_action_source34.bal", + "description": "", + "items": [ + { + "label": ".accessor", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `\"A31\"` a" + } + }, + "sortText": "C", + "filterText": "accessor", + "insertText": ".accessor", + "insertTextFormat": "Snippet", + "additionalTextEdits": [ + { + "range": { + "start": { + "line": 16, + "character": 17 + }, + "end": { + "line": 16, + "character": 18 + } + }, + "newText": "" + } + ] + }, + { + "label": ".put", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `\"A31\"|\"A32\"` a" + } + }, + "sortText": "C", + "filterText": "put", + "insertText": ".put", + "insertTextFormat": "Snippet", + "additionalTextEdits": [ + { + "range": { + "start": { + "line": 16, + "character": 17 + }, + "end": { + "line": 16, + "character": 18 + } + }, + "newText": "" + } + ] + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config42.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config42.json new file mode 100644 index 000000000000..78dc383d7ab7 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config42.json @@ -0,0 +1,70 @@ +{ + "position": { + "line": 16, + "character": 14 + }, + "source": "action_node_context/source/client_resource_access_action_source35.bal", + "description": "", + "items": [ + { + "label": ".accessor", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `\"A31\"` a" + } + }, + "sortText": "C", + "filterText": "accessor", + "insertText": ".accessor", + "insertTextFormat": "Snippet", + "additionalTextEdits": [ + { + "range": { + "start": { + "line": 16, + "character": 13 + }, + "end": { + "line": 16, + "character": 14 + } + }, + "newText": "" + } + ] + }, + { + "label": ".put", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `\"A31\"|\"A32\"` a" + } + }, + "sortText": "C", + "filterText": "put", + "insertText": ".put", + "insertTextFormat": "Snippet", + "additionalTextEdits": [ + { + "range": { + "start": { + "line": 16, + "character": 13 + }, + "end": { + "line": 16, + "character": 14 + } + }, + "newText": "" + } + ] + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config43.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config43.json new file mode 100644 index 000000000000..7dcfb5dbffc4 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config43.json @@ -0,0 +1,70 @@ +{ + "position": { + "line": 16, + "character": 12 + }, + "source": "action_node_context/source/client_resource_access_action_source36.bal", + "description": "", + "items": [ + { + "label": ".accessor", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `string:Char` a" + } + }, + "sortText": "C", + "filterText": "accessor", + "insertText": ".accessor", + "insertTextFormat": "Snippet", + "additionalTextEdits": [ + { + "range": { + "start": { + "line": 16, + "character": 11 + }, + "end": { + "line": 16, + "character": 12 + } + }, + "newText": "" + } + ] + }, + { + "label": ".put", + "kind": "Function", + "detail": "()", + "documentation": { + "right": { + "kind": "markdown", + "value": "**Package:** _._ \n \n \n**Params** \n- `string:Char|int` a" + } + }, + "sortText": "C", + "filterText": "put", + "insertText": ".put", + "insertTextFormat": "Snippet", + "additionalTextEdits": [ + { + "range": { + "start": { + "line": 16, + "character": 11 + }, + "end": { + "line": 16, + "character": 12 + } + }, + "newText": "" + } + ] + } + ] +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source31.bal b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source31.bal new file mode 100644 index 000000000000..cb5af73e564f --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source31.bal @@ -0,0 +1,10 @@ +client class MyClient { + resource function accessor ["name" a]() { + + } +} + +public function main() { + var cl = new MyClient(); + cl -> +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source32.bal b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source32.bal new file mode 100644 index 000000000000..314760e39a58 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source32.bal @@ -0,0 +1,10 @@ +client class MyClient { + resource function accessor ["A1"|"A2" a]() { + + } +} + +public function main() { + var cl = new MyClient(); + cl -> +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source33.bal b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source33.bal new file mode 100644 index 000000000000..f7fea0ed3e44 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source33.bal @@ -0,0 +1,10 @@ +client class MyClient { + resource function accessor [int|string:Char a]() { + + } +} + +public function main() { + var cl = new MyClient(); + cl -> +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source34.bal b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source34.bal new file mode 100644 index 000000000000..0470efd4cab3 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source34.bal @@ -0,0 +1,18 @@ +client class MyClient { + resource function accessor ["A31" a]() { + + } + + resource function put ["A31"|"A32" a]() { + + } + + resource function post ["A32" a]() { + + } +} + +public function main() { + var cl = new MyClient(); + cl ->/["A31"]. +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source35.bal b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source35.bal new file mode 100644 index 000000000000..ad54eeeced3b --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source35.bal @@ -0,0 +1,18 @@ +client class MyClient { + resource function accessor ["A31" a]() { + + } + + resource function put ["A31"|"A32" a]() { + + } + + resource function post ["A32" a]() { + + } +} + +public function main() { + var cl = new MyClient(); + cl ->/A31. +} diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source36.bal b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source36.bal new file mode 100644 index 000000000000..9200a144f2bf --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/source/client_resource_access_action_source36.bal @@ -0,0 +1,18 @@ +client class MyClient { + resource function accessor [string:Char a]() { + + } + + resource function put [string:Char|int a]() { + + } + + resource function post [int a]() { + + } +} + +public function main() { + var cl = new MyClient(); + cl ->/A. +} From 44d4e652dd464400bedbf52f069de3cde2ddb368 Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Thu, 19 Oct 2023 11:45:21 +0530 Subject: [PATCH 2/3] Add description to the test cases --- .../context/ClientResourceAccessActionNodeContext.java | 3 +-- .../config/client_resource_access_action_config38.json | 2 +- .../config/client_resource_access_action_config39.json | 2 +- .../config/client_resource_access_action_config40.json | 2 +- .../config/client_resource_access_action_config41.json | 2 +- .../config/client_resource_access_action_config42.json | 2 +- .../config/client_resource_access_action_config43.json | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java index ed922a1cef20..1647eb4c51dc 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java @@ -346,8 +346,7 @@ private Pair, Boolean> completableSegmentList(ResourceMethodSy } if (node.kind() == SyntaxKind.COMPUTED_RESOURCE_ACCESS_SEGMENT) { ExpressionNode exprNode = ((ComputedResourceAccessSegmentNode) node).expression(); - Optional exprType = - semanticModel.get().typeOf(exprNode); + Optional exprType = semanticModel.get().typeOf(exprNode); if (exprType.isEmpty() || !checkSubtype(typeSymbol, exprType.get(), exprNode.toString())) { return Pair.of(Collections.emptyList(), false); } diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json index 9f3fdc5e3e80..2b70e6b1194c 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json @@ -4,7 +4,7 @@ "character": 9 }, "source": "action_node_context/source/client_resource_access_action_source31.bal", - "description": "", + "description": "Test completions in singleton resource param", "items": [ { "label": "/[\"name\" a].accessor", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json index 50e5222d24d0..8aeb4c53682a 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json @@ -4,7 +4,7 @@ "character": 9 }, "source": "action_node_context/source/client_resource_access_action_source32.bal", - "description": "", + "description": "Test completions in union of singleton resource params", "items": [ { "label": "/[\"A1\"|\"A2\" a].accessor", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json index ed81cb3ad1fb..1ee1ab76a2dc 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json @@ -4,7 +4,7 @@ "character": 9 }, "source": "action_node_context/source/client_resource_access_action_source33.bal", - "description": "", + "description": "Test completions in resource param with a subtype of expression", "items": [ { "label": "/[int|string:Char a].accessor", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config41.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config41.json index 0e3649c42c6b..e2e5d347c9c0 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config41.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config41.json @@ -4,7 +4,7 @@ "character": 18 }, "source": "action_node_context/source/client_resource_access_action_source34.bal", - "description": "", + "description": "Test completions in multiple singleton resource access functions, and accessing with a field value", "items": [ { "label": ".accessor", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config42.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config42.json index 78dc383d7ab7..0c78be2743e4 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config42.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config42.json @@ -4,7 +4,7 @@ "character": 14 }, "source": "action_node_context/source/client_resource_access_action_source35.bal", - "description": "", + "description": "Test completions in multiple singleton resource access functions, and accessing with a label", "items": [ { "label": ".accessor", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config43.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config43.json index 7dcfb5dbffc4..343b18212b04 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config43.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config43.json @@ -4,7 +4,7 @@ "character": 12 }, "source": "action_node_context/source/client_resource_access_action_source36.bal", - "description": "", + "description": "Test completions in multiple resource access functions with subtypes of the expression", "items": [ { "label": ".accessor", From 4a2b7a0389464c40924e914176bbde6d525d79b7 Mon Sep 17 00:00:00 2001 From: Nipuna Fernando Date: Thu, 9 Nov 2023 13:38:27 +0530 Subject: [PATCH 3/3] Address review suggestions --- .../builder/ResourcePathCompletionUtil.java | 44 ++++++++++++++++--- ...ClientResourceAccessActionNodeContext.java | 28 ++---------- ...lient_resource_access_action_config38.json | 4 +- ...lient_resource_access_action_config39.json | 6 +-- ...lient_resource_access_action_config40.json | 6 +-- 5 files changed, 51 insertions(+), 37 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/builder/ResourcePathCompletionUtil.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/builder/ResourcePathCompletionUtil.java index 36b316e385fe..55b8bf722607 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/builder/ResourcePathCompletionUtil.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/builder/ResourcePathCompletionUtil.java @@ -192,6 +192,36 @@ public static String getFilterTextForClientResourceAccessAction(ResourceMethodSy + "|" + resourceMethodSymbol.getName().orElse(""); } + /** + * Check if the given expression node is a valid type for the given parameter node. + * + * @param paramType Type of the parameter + * @param exprType Type of the expression + * @param exprValue Value of the expression + * @return Returns true if the expression is a valid type for the parameter + */ + public static boolean checkSubtype(TypeSymbol paramType, TypeSymbol exprType, String exprValue) { + if (exprType.subtypeOf(paramType)) { + return true; + } + switch (paramType.typeKind()) { + case UNION: + for (TypeSymbol childSymbol : ((UnionTypeSymbol) paramType).memberTypeDescriptors()) { + if (checkSubtype(childSymbol, exprType, exprValue)) { + return true; + } + } + return false; + case SINGLETON: + return paramType.subtypeOf(exprType) && exprValue.equals(paramType.signature()); + case TYPE_REFERENCE: + return paramType.subtypeOf(exprType); + default: + return false; + } + } + + private static ResourceAccessPathPart getResourceAccessPartForSegment(PathSegment segment, int placeHolderIndex, BallerinaCompletionContext context) { @@ -217,15 +247,19 @@ private static ResourceAccessPathPart getResourceAccessPartForSegment(PathSegmen "[${" + placeHolderIndex + ":" + defaultValue.orElse("") + "}]"; ResourceAccessPathPart resourceAccessPathPart = new ResourceAccessPathPart(computedInsertText, computedSignature); + + // A resource method parameter can be a singleton or a union with a `string`. As the node "text" is always + // resolved to `string`, we need to check if typeSymbol is either a supertype or a subtype of `string`. if (context.currentSemanticModel().isPresent() && - checkSubtype(typeSymbol, context.currentSemanticModel().get().types().STRING)) { + isStringSubtype(typeSymbol, context.currentSemanticModel().get().types().STRING)) { if (typeSymbol.typeKind().equals(TypeDescKind.SINGLETON)) { resourceAccessPathPart.namedPathSignature = typeSymbol.signature().substring(1, typeSymbol.signature().length() - 1); resourceAccessPathPart.namedPathInsertText = resourceAccessPathPart.namedPathSignature; } else { - resourceAccessPathPart.namedPathSignature = "<" + - (pathParameterSymbol.getName().isPresent() ? pathParameterSymbol.getName().get() : "path") + ">"; + resourceAccessPathPart.namedPathSignature = "<" + + (pathParameterSymbol.getName().isPresent() ? pathParameterSymbol.getName().get() : "path") + + ">"; resourceAccessPathPart.namedPathInsertText = "${" + placeHolderIndex + ":" + "path" + "}"; resourceAccessPathPart.computedPathInsertText = "[${" + placeHolderIndex + ":" + "\"path\"" + "}]"; } @@ -234,14 +268,14 @@ private static ResourceAccessPathPart getResourceAccessPartForSegment(PathSegmen return resourceAccessPathPart; } - private static boolean checkSubtype(TypeSymbol paramType, TypeSymbol stringType) { + private static boolean isStringSubtype(TypeSymbol paramType, TypeSymbol stringType) { if (stringType.subtypeOf(paramType)) { return true; } switch (paramType.typeKind()) { case UNION: for (TypeSymbol childSymbol : ((UnionTypeSymbol) paramType).memberTypeDescriptors()) { - if (checkSubtype(childSymbol, stringType)) { + if (isStringSubtype(childSymbol, stringType)) { return true; } } diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java index 1647eb4c51dc..2c8224524e2d 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/completions/providers/context/ClientResourceAccessActionNodeContext.java @@ -25,7 +25,6 @@ import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.SymbolKind; import io.ballerina.compiler.api.symbols.TypeSymbol; -import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.api.symbols.resourcepath.PathRestParam; import io.ballerina.compiler.api.symbols.resourcepath.PathSegmentList; import io.ballerina.compiler.api.symbols.resourcepath.ResourcePath; @@ -347,7 +346,8 @@ private Pair, Boolean> completableSegmentList(ResourceMethodSy if (node.kind() == SyntaxKind.COMPUTED_RESOURCE_ACCESS_SEGMENT) { ExpressionNode exprNode = ((ComputedResourceAccessSegmentNode) node).expression(); Optional exprType = semanticModel.get().typeOf(exprNode); - if (exprType.isEmpty() || !checkSubtype(typeSymbol, exprType.get(), exprNode.toString())) { + if (exprType.isEmpty() || !ResourcePathCompletionUtil.checkSubtype(typeSymbol, exprType.get(), + exprNode.toString())) { return Pair.of(Collections.emptyList(), false); } if (segment.pathSegmentKind() == PathSegment.Kind.PATH_REST_PARAMETER @@ -356,7 +356,8 @@ private Pair, Boolean> completableSegmentList(ResourceMethodSy } continue; } else if (node.kind() == SyntaxKind.IDENTIFIER_TOKEN && - checkSubtype(typeSymbol, semanticModel.get().types().STRING, "\"" + node + "\"")) { + ResourcePathCompletionUtil.checkSubtype(typeSymbol, semanticModel.get().types().STRING, + "\"" + ((IdentifierToken) node).text() + "\"")) { if (segment.pathSegmentKind() == PathSegment.Kind.PATH_REST_PARAMETER && !ResourcePathCompletionUtil.isInMethodCallContext(accNode, context)) { completableSegmentStartIndex -= 1; @@ -373,27 +374,6 @@ private Pair, Boolean> completableSegmentList(ResourceMethodSy return Pair.of(completableSegments, true); } - private boolean checkSubtype(TypeSymbol paramType, TypeSymbol exprType, String exprValue) { - if (exprType.subtypeOf(paramType)) { - return true; - } - switch (paramType.typeKind()) { - case UNION: - for (TypeSymbol childSymbol : ((UnionTypeSymbol) paramType).memberTypeDescriptors()) { - if (checkSubtype(childSymbol, exprType, exprValue)) { - return true; - } - } - return false; - case SINGLETON: - return paramType.subtypeOf(exprType) && exprValue.equals(paramType.signature()); - case TYPE_REFERENCE: - return paramType.subtypeOf(exprType); - default: - return false; - } - } - private boolean onSuggestClients(ClientResourceAccessActionNode node, BallerinaCompletionContext context) { int cursor = context.getCursorPositionInTree(); return cursor <= node.rightArrowToken().textRange().startOffset(); diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json index 2b70e6b1194c..804b8dcaf1b5 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config38.json @@ -16,7 +16,7 @@ "value": "**Package:** _._ \n \n \n**Params** \n- `\"name\"` a" } }, - "sortText": "CC", + "sortText": "CD", "filterText": "|accessor", "insertText": "/[\"name\"].accessor;", "insertTextFormat": "Snippet" @@ -31,7 +31,7 @@ "value": "**Package:** _._ \n \n \n**Params** \n- `\"name\"` a" } }, - "sortText": "CC", + "sortText": "CE", "filterText": "|accessor", "insertText": "/name.accessor;", "insertTextFormat": "Snippet" diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json index 8aeb4c53682a..7a2ccc8530e7 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config39.json @@ -16,13 +16,13 @@ "value": "**Package:** _._ \n \n \n**Params** \n- `\"A1\"|\"A2\"` a" } }, - "sortText": "CC", + "sortText": "CD", "filterText": "|accessor", "insertText": "/[${1:\"path\"}].accessor;", "insertTextFormat": "Snippet" }, { - "label": "/.accessor", + "label": "/.accessor", "kind": "Function", "detail": "()", "documentation": { @@ -31,7 +31,7 @@ "value": "**Package:** _._ \n \n \n**Params** \n- `\"A1\"|\"A2\"` a" } }, - "sortText": "CC", + "sortText": "CE", "filterText": "|accessor", "insertText": "/${1:path}.accessor;", "insertTextFormat": "Snippet" diff --git a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json index 1ee1ab76a2dc..9596deff949f 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/action_node_context/config/client_resource_access_action_config40.json @@ -16,13 +16,13 @@ "value": "**Package:** _._ \n \n \n**Params** \n- `int|string:Char` a" } }, - "sortText": "CC", + "sortText": "CD", "filterText": "|accessor", "insertText": "/[${1:\"path\"}].accessor;", "insertTextFormat": "Snippet" }, { - "label": "/.accessor", + "label": "/.accessor", "kind": "Function", "detail": "()", "documentation": { @@ -31,7 +31,7 @@ "value": "**Package:** _._ \n \n \n**Params** \n- `int|string:Char` a" } }, - "sortText": "CC", + "sortText": "CE", "filterText": "|accessor", "insertText": "/${1:path}.accessor;", "insertTextFormat": "Snippet"