Skip to content
Merged
6 changes: 3 additions & 3 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "ftp"
version = "2.15.0"
version = "2.15.1"
authors = ["Ballerina"]
keywords = ["FTP", "SFTP", "remote file", "file transfer", "client", "service"]
repository = "https://github.com/ballerina-platform/module-ballerina-ftp"
Expand Down Expand Up @@ -45,8 +45,8 @@ path = "./lib/commons-lang3-3.18.0.jar"
[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "ftp-native"
version = "2.15.0"
path = "../native/build/libs/ftp-native-2.15.0.jar"
version = "2.15.1"
path = "../native/build/libs/ftp-native-2.15.1-SNAPSHOT.jar"

[[platform.java21.dependency]]
groupId = "io.ballerina.lib"
Expand Down
2 changes: 1 addition & 1 deletion ballerina/CompilerPlugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ id = "ftp-compiler-plugin"
class = "io.ballerina.stdlib.ftp.plugin.FtpCompilerPlugin"

[[dependency]]
path = "../compiler-plugin/build/libs/ftp-compiler-plugin-2.15.0.jar"
path = "../compiler-plugin/build/libs/ftp-compiler-plugin-2.15.1-SNAPSHOT.jar"
6 changes: 3 additions & 3 deletions ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ distribution-version = "2201.12.0"
[[package]]
org = "ballerina"
name = "data.jsondata"
version = "1.1.2"
version = "1.1.3"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.object"}
Expand All @@ -34,7 +34,7 @@ modules = [
[[package]]
org = "ballerina"
name = "ftp"
version = "2.15.0"
version = "2.15.1"
dependencies = [
{org = "ballerina", name = "data.jsondata"},
{org = "ballerina", name = "data.xmldata"},
Expand Down Expand Up @@ -163,7 +163,7 @@ modules = [
[[package]]
org = "ballerina"
name = "observe"
version = "1.5.0"
version = "1.5.1"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,15 @@

package io.ballerina.stdlib.ftp.plugin;

import io.ballerina.compiler.api.SemanticModel;
import io.ballerina.compiler.api.symbols.MethodSymbol;
import io.ballerina.compiler.api.symbols.ModuleSymbol;
import io.ballerina.compiler.api.symbols.ParameterSymbol;
import io.ballerina.compiler.api.symbols.Symbol;
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.syntax.tree.FunctionDefinitionNode;
import io.ballerina.compiler.syntax.tree.Node;
import io.ballerina.compiler.syntax.tree.ParameterNode;
import io.ballerina.compiler.syntax.tree.RequiredParameterNode;
import io.ballerina.compiler.syntax.tree.SeparatedNodeList;
import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext;
import io.ballerina.tools.diagnostics.DiagnosticSeverity;
import io.ballerina.tools.diagnostics.Location;

import java.util.List;
import java.util.Objects;
import java.util.Optional;

Expand All @@ -46,24 +37,18 @@
import static io.ballerina.compiler.api.symbols.TypeDescKind.STRING;
import static io.ballerina.compiler.api.symbols.TypeDescKind.TYPE_REFERENCE;
import static io.ballerina.compiler.api.symbols.TypeDescKind.XML;
import static io.ballerina.compiler.syntax.tree.SyntaxKind.QUALIFIED_NAME_REFERENCE;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CALLER;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.CONTENT_METHOD_MUST_BE_REMOTE;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_CALLER_PARAMETER;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_CONTENT_PARAMETER_TYPE;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_FILEINFO_PARAMETER;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_RETURN_TYPE_ERROR_OR_NIL;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.TOO_MANY_PARAMETERS;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.FILE_INFO;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.ON_FILE_CSV_FUNC;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.ON_FILE_FUNC;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.ON_FILE_JSON_FUNC;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.ON_FILE_TEXT_FUNC;
import static io.ballerina.stdlib.ftp.plugin.PluginConstants.ON_FILE_XML_FUNC;
import static io.ballerina.stdlib.ftp.plugin.PluginUtils.getDiagnostic;
import static io.ballerina.stdlib.ftp.plugin.PluginUtils.getMethodSymbol;
import static io.ballerina.stdlib.ftp.plugin.PluginUtils.isRemoteFunction;
import static io.ballerina.stdlib.ftp.plugin.PluginUtils.validateModuleId;

/**
* FTP content function validator.
Expand All @@ -88,7 +73,8 @@ public void validate() {
}

if (!isRemoteFunction(syntaxNodeAnalysisContext, contentFunctionDefinitionNode)) {
reportErrorDiagnostic(CONTENT_METHOD_MUST_BE_REMOTE, contentFunctionDefinitionNode.location());
reportErrorDiagnostic(CONTENT_METHOD_MUST_BE_REMOTE, contentFunctionDefinitionNode.location(),
contentMethodName);
}

SeparatedNodeList<ParameterNode> parameters = contentFunctionDefinitionNode.functionSignature().parameters();
Expand All @@ -99,61 +85,59 @@ public void validate() {
private void validateContentFunctionParameters(SeparatedNodeList<ParameterNode> parameters,
FunctionDefinitionNode functionDefinitionNode) {
if (parameters.isEmpty()) {
reportErrorDiagnostic(INVALID_CONTENT_PARAMETER_TYPE, functionDefinitionNode.location());
String expectedType = getExpectedContentType();
reportErrorDiagnostic(INVALID_CONTENT_PARAMETER_TYPE, functionDefinitionNode.location(),
contentMethodName, expectedType, "none");
return;
}

if (parameters.size() > 3) {
reportErrorDiagnostic(TOO_MANY_PARAMETERS, functionDefinitionNode.location());
reportErrorDiagnostic(TOO_MANY_PARAMETERS, functionDefinitionNode.location(), contentMethodName);
return;
}

// First parameter must be content parameter
ParameterNode firstParameter = parameters.get(0);
if (!validateContentParameter(firstParameter)) {
reportErrorDiagnostic(INVALID_CONTENT_PARAMETER_TYPE, firstParameter.location());
String expectedType = getExpectedContentType();
String actualType = getActualParameterType(firstParameter);
reportErrorDiagnostic(INVALID_CONTENT_PARAMETER_TYPE, firstParameter.location(),
contentMethodName, expectedType, actualType);
}

// Second parameter (if exists) can be FileInfo or Caller
if (parameters.size() >= 2) {
ParameterNode secondParameter = parameters.get(1);
boolean isFileInfo = validateFileInfoParameter(secondParameter);
boolean isCaller = validateCallerParameter(secondParameter);
boolean isFileInfo = PluginUtils.validateFileInfoParameter(secondParameter, syntaxNodeAnalysisContext);
boolean isCaller = PluginUtils.validateCallerParameter(secondParameter, syntaxNodeAnalysisContext);

if (!isFileInfo && !isCaller) {
reportErrorDiagnostic(INVALID_FILEINFO_PARAMETER, secondParameter.location());
reportErrorDiagnostic(INVALID_FILEINFO_PARAMETER, secondParameter.location(), contentMethodName);
}

// Third parameter (if exists) must be Caller if second was FileInfo, or validation error
if (parameters.size() == 3) {
ParameterNode thirdParameter = parameters.get(2);
if (isFileInfo) {
if (!validateCallerParameter(thirdParameter)) {
if (!PluginUtils.validateCallerParameter(thirdParameter, syntaxNodeAnalysisContext)) {
reportErrorDiagnostic(INVALID_CALLER_PARAMETER, thirdParameter.location());
}
} else {
// Second was Caller, third is invalid
reportErrorDiagnostic(TOO_MANY_PARAMETERS, thirdParameter.location());
reportErrorDiagnostic(TOO_MANY_PARAMETERS, thirdParameter.location(), contentMethodName);
}
}
}
}

private boolean validateContentParameter(ParameterNode parameterNode) {
RequiredParameterNode requiredParameterNode = (RequiredParameterNode) parameterNode;
Node parameterTypeNode = requiredParameterNode.typeName();
SemanticModel semanticModel = syntaxNodeAnalysisContext.semanticModel();
Optional<Symbol> paramSymbol = semanticModel.symbol(parameterTypeNode);

if (paramSymbol.isEmpty()) {
return false;
}

TypeSymbol typeSymbol = ((ParameterSymbol) paramSymbol.get()).typeDescriptor();
if (typeSymbol == null) {
Optional<TypeSymbol> typeSymbolOpt = PluginUtils.getParameterTypeSymbol(parameterNode,
syntaxNodeAnalysisContext);
if (typeSymbolOpt.isEmpty()) {
return false;
}

TypeSymbol typeSymbol = typeSymbolOpt.get();
TypeDescKind typeKind = typeSymbol.typeKind();

switch (contentMethodName) {
Expand Down Expand Up @@ -221,99 +205,32 @@ private boolean isRecordTypeReference(TypeSymbol typeSymbol) {
return false;
}

private boolean validateFileInfoParameter(ParameterNode parameterNode) {
if (!(parameterNode instanceof RequiredParameterNode)) {
return false;
}

RequiredParameterNode requiredParameterNode = (RequiredParameterNode) parameterNode;
if (requiredParameterNode.typeName().kind() != QUALIFIED_NAME_REFERENCE) {
return false;
}

Node parameterTypeNode = requiredParameterNode.typeName();
SemanticModel semanticModel = syntaxNodeAnalysisContext.semanticModel();
Optional<Symbol> paramSymbol = semanticModel.symbol(parameterTypeNode);

if (paramSymbol.isPresent()) {
Optional<ModuleSymbol> moduleSymbol = paramSymbol.get().getModule();
if (moduleSymbol.isPresent()) {
String paramName = paramSymbol.get().getName().orElse("");
return validateModuleId(moduleSymbol.get()) && paramName.equals(FILE_INFO);
}
}
return false;
private void validateReturnTypeErrorOrNil(FunctionDefinitionNode functionDefinitionNode) {
PluginUtils.validateReturnTypeErrorOrNil(functionDefinitionNode, syntaxNodeAnalysisContext);
}

private boolean validateCallerParameter(ParameterNode parameterNode) {
if (!(parameterNode instanceof RequiredParameterNode)) {
return false;
}

RequiredParameterNode requiredParameterNode = (RequiredParameterNode) parameterNode;
if (requiredParameterNode.typeName().kind() != QUALIFIED_NAME_REFERENCE) {
return false;
}

Node parameterTypeNode = requiredParameterNode.typeName();
SemanticModel semanticModel = syntaxNodeAnalysisContext.semanticModel();
Optional<Symbol> paramSymbol = semanticModel.symbol(parameterTypeNode);

if (paramSymbol.isPresent()) {
Optional<ModuleSymbol> moduleSymbol = paramSymbol.get().getModule();
if (moduleSymbol.isPresent()) {
String paramName = paramSymbol.get().getName().orElse("");
return validateModuleId(moduleSymbol.get()) && paramName.equals(CALLER);
}
}
return false;
private String getExpectedContentType() {
return switch (contentMethodName) {
case ON_FILE_FUNC -> "byte[] or stream<byte[], error?>";
case ON_FILE_TEXT_FUNC -> "string";
case ON_FILE_JSON_FUNC -> "json or record type";
case ON_FILE_XML_FUNC -> "xml or record type";
case ON_FILE_CSV_FUNC -> "string[][], record{}[], or stream<byte[], error?>";
default -> "unknown";
};
}

private void validateReturnTypeErrorOrNil(FunctionDefinitionNode functionDefinitionNode) {
MethodSymbol methodSymbol = getMethodSymbol(syntaxNodeAnalysisContext, functionDefinitionNode);
if (methodSymbol == null) {
return;
}

Optional<TypeSymbol> returnTypeDesc = methodSymbol.typeDescriptor().returnTypeDescriptor();
if (returnTypeDesc.isEmpty()) {
return;
}

TypeDescKind returnTypeKind = returnTypeDesc.get().typeKind();

if (returnTypeKind == TypeDescKind.NIL) {
return;
}

if (returnTypeKind == TypeDescKind.UNION) {
List<TypeSymbol> returnTypeMembers =
((UnionTypeSymbol) returnTypeDesc.get()).memberTypeDescriptors();
for (TypeSymbol returnType : returnTypeMembers) {
if (returnType.typeKind() != TypeDescKind.NIL) {
if (returnType.typeKind() == TYPE_REFERENCE) {
if (!returnType.signature().equals(PluginConstants.ERROR) &&
returnType.getModule().isPresent() &&
!validateModuleId(returnType.getModule().get())) {
reportErrorDiagnostic(INVALID_RETURN_TYPE_ERROR_OR_NIL,
functionDefinitionNode.functionSignature().location());
return;
}
} else if (returnType.typeKind() != TypeDescKind.ERROR) {
reportErrorDiagnostic(INVALID_RETURN_TYPE_ERROR_OR_NIL,
functionDefinitionNode.functionSignature().location());
return;
}
}
}
} else {
reportErrorDiagnostic(INVALID_RETURN_TYPE_ERROR_OR_NIL,
functionDefinitionNode.functionSignature().location());
}
private String getActualParameterType(ParameterNode parameterNode) {
return PluginUtils.getParameterTypeSignature(parameterNode, syntaxNodeAnalysisContext);
}

public void reportErrorDiagnostic(PluginConstants.CompilationErrors error, Location location) {
syntaxNodeAnalysisContext.reportDiagnostic(getDiagnostic(error,
DiagnosticSeverity.ERROR, location));
}

public void reportErrorDiagnostic(PluginConstants.CompilationErrors error, Location location, Object... args) {
syntaxNodeAnalysisContext.reportDiagnostic(getDiagnostic(error,
DiagnosticSeverity.ERROR, location, args));
}
}
Loading