Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: LS Add A Code Action To Fix Return Type Signature #14737

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public enum DiagnosticCode {
GLOBAL_VARIABLE_CYCLIC_DEFINITION("global.variable.cyclic.reference"),

INCOMPATIBLE_TYPES("incompatible.types"),
INCOMPATIBLE_TYPES_EXP_RETURN("incompatible.types.exp.return"),
INCOMPATIBLE_TYPES_EXP_TUPLE("incompatible.types.exp.tuple"),
UNKNOWN_TYPE("unknown.type"),
BINARY_OP_INCOMPATIBLE_TYPES("binary.op.incompatible.types"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ public List<BLangPackage> compilePackages(boolean isBuild) {
outStream.println("Compiling source");
}
List<BLangPackage> compiledPackages = compilePackages(pkgList.stream(), isBuild);
if (this.dlog.errorCount > 0) {
// If it is a build and dlog is not empty, compilation should fail
if (isBuild && this.dlog.errorCount > 0) {
throw new BLangCompilerException("compilation contains errors");
}
return compiledPackages;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1890,7 +1890,8 @@ public void visit(BLangWorkerSend workerSendNode) {

@Override
public void visit(BLangReturn returnNode) {
this.typeChecker.checkExpr(returnNode.expr, this.env, this.env.enclInvokable.returnTypeNode.type);
this.typeChecker.checkExpr(returnNode.expr, this.env, this.env.enclInvokable.returnTypeNode.type,
DiagnosticCode.INCOMPATIBLE_TYPES_EXP_RETURN);
}

BType analyzeDef(BLangNode node, SymbolEnv env) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2313,7 +2313,7 @@ private void checkInvocationParamAndReturnType(BLangInvocation iExpr) {
actualType = getAccessExprFinalType(iExpr, actualType);
}

resultType = types.checkType(iExpr, actualType, this.expType);
resultType = types.checkType(iExpr, actualType, this.expType, this.diagCode);
}

private BType checkInvocationParam(BLangInvocation iExpr) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ error.invalid.function.pointer.assignment.for.handler=\
error.incompatible.types=\
incompatible types: expected ''{0}'', found ''{1}''

error.incompatible.types.exp.return=\
incompatible return types: expected ''{0}'', found ''{1}''

error.incompatible.types.exp.tuple=\
incompatible types: expected tuple, found ''{0}''

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.ballerinalang.langserver.command.executors.FixReturnTypeExecutor;
import org.ballerinalang.langserver.common.UtilSymbolKeys;
import org.ballerinalang.langserver.common.constants.CommandConstants;
import org.ballerinalang.langserver.common.constants.NodeContextKeys;
Expand All @@ -33,6 +34,8 @@
import org.ballerinalang.langserver.compiler.common.modal.BallerinaPackage;
import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentManager;
import org.ballerinalang.langserver.diagnostic.DiagnosticsHelper;
import org.ballerinalang.model.tree.Node;
import org.ballerinalang.model.tree.TopLevelNode;
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionParams;
Expand All @@ -51,18 +54,21 @@
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit;
import org.wso2.ballerinalang.compiler.tree.BLangNode;
import org.wso2.ballerinalang.compiler.tree.BLangPackage;
import org.wso2.ballerinalang.compiler.tree.BLangSimpleVariable;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangInvocation;
import org.wso2.ballerinalang.util.Flags;

import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.StringJoiner;
Expand Down Expand Up @@ -211,6 +217,15 @@ public static List<Either<Command, CodeAction>> getCommandsByDiagnostic(Diagnost
String commandTitle = CommandConstants.PULL_MOD_TITLE;
commands.add(Either.forLeft(new Command(commandTitle, CommandConstants.CMD_PULL_MODULE, args)));
}
} else if (isIncompatibleTypes(diagnosticMessage)) {
Matcher matcher = CommandConstants.INCOMPATIBLE_TYPE_PATTERN.matcher(diagnosticMessage);
if (matcher.find() && matcher.groupCount() > 1) {
String foundType = matcher.group(2);
CommandArgument typeArg = new CommandArgument(CommandConstants.ARG_KEY_NODE_TYPE, foundType);
List<Object> args = Arrays.asList(lineArg, colArg, typeArg, uriArg);
String commandTitle = CommandConstants.CHANGE_RETURN_TYPE_TITLE + foundType;
commands.add(Either.forLeft(new Command(commandTitle, FixReturnTypeExecutor.COMMAND, args)));
}
}
return commands;
}
Expand Down Expand Up @@ -319,6 +334,60 @@ public static BLangInvocation getFunctionNode(int line, int column, String uri,
}
}

public static Node getBLangNodeByPosition(int line, int column, String uri,
WorkspaceDocumentManager documentManager, LSCompiler lsCompiler,
LSContext context) {
Position position = new Position();
position.setLine(line);
position.setCharacter(column + 1);
context.put(DocumentServiceKeys.FILE_URI_KEY, uri);
TextDocumentIdentifier identifier = new TextDocumentIdentifier(uri);
context.put(DocumentServiceKeys.POSITION_KEY, new TextDocumentPositionParams(identifier, position));
List<BLangPackage> bLangPackages = lsCompiler.getBLangPackages(context, documentManager, false,
LSCustomErrorStrategy.class, true);

// Get the current package.
BLangPackage currentPackage = CommonUtil.getCurrentPackageByFileName(bLangPackages, uri);

if (currentPackage == null) {
return null;
}
context.put(DocumentServiceKeys.CURRENT_BLANG_PACKAGE_CONTEXT_KEY, currentPackage);

// If package is testable package process as tests
// else process normally
String relativeFilePath = context.get(DocumentServiceKeys.RELATIVE_FILE_PATH_KEY);
BLangCompilationUnit compilationUnit;
if (relativeFilePath.startsWith("tests" + File.separator)) {
compilationUnit = currentPackage.getTestablePkg().getCompilationUnits().stream().
filter(compUnit -> (relativeFilePath).equals(compUnit.getName()))
.findFirst().orElse(null);
} else {
compilationUnit = currentPackage.getCompilationUnits().stream().
filter(compUnit -> relativeFilePath.equals(compUnit.getName())).findFirst().orElse(null);
}
if (compilationUnit == null) {
return null;
}
Iterator<TopLevelNode> nodeIterator = compilationUnit.getTopLevelNodes().iterator();
Node result = null;
TopLevelNode next = (nodeIterator.hasNext()) ? nodeIterator.next() : null;
while (next != null) {
int sLine = next.getPosition().getStartLine();
int eLine = next.getPosition().getEndLine();
int sCol = next.getPosition().getStartColumn();
int eCol = next.getPosition().getEndColumn();
if ((line > sLine || (line == sLine && column >= sCol)) &&
(line < eLine || (line == eLine && column <= eCol))) {
result = next;
break;
}
//TODO: visit functions inside objects as well
next = (nodeIterator.hasNext()) ? nodeIterator.next() : null;
}
return result;
}

public static Pair<BLangNode, Object> getBLangNode(int line, int column, String uri,
WorkspaceDocumentManager documentManager, LSCompiler lsCompiler,
LSContext context) {
Expand Down Expand Up @@ -356,6 +425,10 @@ private static boolean isUnresolvedPackage(String diagnosticMessage) {
return diagnosticMessage.toLowerCase(Locale.ROOT).contains(CommandConstants.UNRESOLVED_MODULE);
}

private static boolean isIncompatibleTypes(String diagnosticMessage) {
return diagnosticMessage.toLowerCase(Locale.ROOT).contains(CommandConstants.INCOMPATIBLE_TYPES);
}

private static Either<Command, CodeAction> getDocGenerationCommand(String nodeType, String docUri, int line) {
CommandArgument nodeTypeArg = new CommandArgument(CommandConstants.ARG_KEY_NODE_TYPE, nodeType);
CommandArgument docUriArg = new CommandArgument(CommandConstants.ARG_KEY_DOC_URI, docUri);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved.
*
* Licensed 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.command.executors;

import com.google.gson.JsonObject;
import org.ballerinalang.annotation.JavaSPIService;
import org.ballerinalang.langserver.command.CommandUtil;
import org.ballerinalang.langserver.command.ExecuteCommandKeys;
import org.ballerinalang.langserver.command.LSCommandExecutor;
import org.ballerinalang.langserver.command.LSCommandExecutorException;
import org.ballerinalang.langserver.common.UtilSymbolKeys;
import org.ballerinalang.langserver.common.constants.CommandConstants;
import org.ballerinalang.langserver.common.utils.CommonUtil;
import org.ballerinalang.langserver.compiler.DocumentServiceKeys;
import org.ballerinalang.langserver.compiler.LSCompiler;
import org.ballerinalang.langserver.compiler.LSContext;
import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentManager;
import org.ballerinalang.model.elements.PackageID;
import org.ballerinalang.model.tree.Node;
import org.ballerinalang.model.types.TypeKind;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import org.wso2.ballerinalang.compiler.tree.BLangFunction;
import org.wso2.ballerinalang.compiler.tree.types.BLangValueType;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.ballerinalang.langserver.command.CommandUtil.applyWorkspaceEdit;

/**
* Command executor for changing return type of a function.
*
* @since 0.983.0
*/
@JavaSPIService("org.ballerinalang.langserver.command.LSCommandExecutor")
public class FixReturnTypeExecutor implements LSCommandExecutor {

public static final String COMMAND = "CHANGE_RETURN_TYPE";

private static final Pattern FQ_TYPE_PATTERN = Pattern.compile("(.*)/([^:]*):(?:.*:)?(.*)");

/**
* {@inheritDoc}
*/
@Override
public Object execute(LSContext context) throws LSCommandExecutorException {
String documentUri = null;
String type = null;
int sLine = -1;
int sCol = -1;
VersionedTextDocumentIdentifier textDocumentIdentifier = new VersionedTextDocumentIdentifier();

for (Object arg : context.get(ExecuteCommandKeys.COMMAND_ARGUMENTS_KEY)) {
String argKey = ((JsonObject) arg).get(ARG_KEY).getAsString();
String argVal = ((JsonObject) arg).get(ARG_VALUE).getAsString();
switch (argKey) {
case CommandConstants.ARG_KEY_DOC_URI:
documentUri = argVal;
textDocumentIdentifier.setUri(documentUri);
context.put(DocumentServiceKeys.FILE_URI_KEY, documentUri);
break;
case CommandConstants.ARG_KEY_NODE_LINE:
sLine = Integer.parseInt(argVal);
break;
case CommandConstants.ARG_KEY_NODE_COLUMN:
sCol = Integer.parseInt(argVal);
break;
case CommandConstants.ARG_KEY_NODE_TYPE:
type = argVal;
break;
default:
}
}

if (sLine == -1 || sCol == -1 || documentUri == null || type == null) {
throw new LSCommandExecutorException("Invalid parameters received for the change return type command!");
}

WorkspaceDocumentManager documentManager = context.get(ExecuteCommandKeys.DOCUMENT_MANAGER_KEY);
LSCompiler lsCompiler = context.get(ExecuteCommandKeys.LS_COMPILER_KEY);

List<TextEdit> edits = getReturnTypeTextEdits(sLine, sCol, type, documentUri, documentManager, lsCompiler,
context);
if (edits == null) {
throw new LSCommandExecutorException("Couldn't find the function node!");
}

LanguageClient client = context.get(ExecuteCommandKeys.LANGUAGE_SERVER_KEY).getClient();
TextDocumentEdit textDocumentEdit = new TextDocumentEdit(textDocumentIdentifier, edits);
return applyWorkspaceEdit(Collections.singletonList(Either.forLeft(textDocumentEdit)), client);
}

private static List<TextEdit> getReturnTypeTextEdits(int line, int column, String type, String uri,
WorkspaceDocumentManager documentManager,
LSCompiler lsCompiler,
LSContext context) {
List<TextEdit> edits = new ArrayList<>();
Node bLangNode = CommandUtil.getBLangNodeByPosition(line, column, uri, documentManager, lsCompiler, context);
if (bLangNode instanceof BLangFunction) {
// Process full-qualified BType name eg. ballerina/http:Client and if required; add an auto-import
Matcher matcher = FQ_TYPE_PATTERN.matcher(type);
String editText = type;
if (matcher.find() && matcher.groupCount() > 2) {
String orgName = matcher.group(1);
String alias = matcher.group(2);
String typeName = matcher.group(3);
String pkgId = orgName + "/" + alias;
PackageID currentPkgId = context.get(DocumentServiceKeys.CURRENT_BLANG_PACKAGE_CONTEXT_KEY).packageID;
if (pkgId.equals(currentPkgId.toString()) || ("ballerina".equals(orgName) && "builtin".equals(alias))) {
editText = typeName;
} else {
edits.addAll(CommonUtil.getAutoImportTextEdits(context, orgName, alias));
editText = alias + UtilSymbolKeys.PKG_DELIMITER_KEYWORD + typeName;
}
}

// Process function node
Position start = new Position(0, 0);
Position end = new Position(0, 0);
BLangFunction func = (BLangFunction) bLangNode;
if (func.returnTypeNode instanceof BLangValueType
&& TypeKind.NIL.equals(((BLangValueType) func.returnTypeNode).getTypeKind())
&& func.returnTypeNode.getWS() == null) {
// eg. function test() {...}
start.setLine(func.returnTypeNode.pos.sLine - 1);
start.setCharacter(func.returnTypeNode.pos.eCol - 1);
end.setLine(func.returnTypeNode.pos.eLine - 1);
end.setCharacter(func.returnTypeNode.pos.eCol - 1);
editText = " returns (" + editText + ")";
} else {
// eg. function test() returns () {...}
start.setLine(func.returnTypeNode.pos.sLine - 1);
start.setCharacter(func.returnTypeNode.pos.sCol - 1);
end.setLine(func.returnTypeNode.pos.eLine - 1);
end.setCharacter(func.returnTypeNode.pos.eCol - 1);
}
edits.add(new TextEdit(new Range(start, end), editText));
return edits;
}
return null;
}

/**
* {@inheritDoc}
*/
@Override
public String getCommand() {
return COMMAND;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public class CommandConstants {
public static final String UNRESOLVED_MODULE = "cannot resolve module";
public static final Pattern UNRESOLVED_MODULE_PATTERN = Pattern.compile("cannot resolve module '(.*)'");
public static final Pattern UNDEFINED_FUNCTION_PATTERN = Pattern.compile("undefined function '(.*)'");
public static final String INCOMPATIBLE_TYPES = "incompatible return types";
public static final Pattern INCOMPATIBLE_TYPE_PATTERN = Pattern.compile(
"incompatible return types: expected '(.*)', found '(.*)'");

// Command Arguments
public static final String ARG_KEY_DOC_URI = "doc.uri";
Expand Down Expand Up @@ -67,6 +70,8 @@ public class CommandConstants {

public static final String PULL_MOD_TITLE = "Pull from Ballerina Central";

public static final String CHANGE_RETURN_TYPE_TITLE = "Change Return Type to ";

// Commands List
public static final String CMD_IMPORT_MODULE = "IMPORT_MODULE";

Expand Down
Loading