From 554149b8bbb35edb8a5ac6a46700e67bfc6ba4f7 Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 4 Nov 2025 12:12:59 +0530 Subject: [PATCH 1/5] Fix compiler plugin validation error --- ballerina/build.gradle | 12 + build.gradle | 3 + .../ftp/plugin/FtpServiceValidationTest.java | 5 - .../plugin/FtpContentFunctionValidator.java | 73 ++++- .../ftp/plugin/FtpFileDeletedValidator.java | 13 +- .../stdlib/ftp/plugin/PluginConstants.java | 22 +- .../stdlib/ftp/plugin/PluginUtils.java | 9 + gradle.properties | 3 + native/build.gradle | 3 + .../ftp/server/FtpContentCallbackHandler.java | 39 +-- .../stdlib/ftp/util/FtpContentConverter.java | 282 +++++------------- native/src/main/java/module-info.java | 3 + 12 files changed, 200 insertions(+), 267 deletions(-) diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 4fb2ee3a5..11a172fc0 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -79,6 +79,15 @@ dependencies { externalJars(group: 'commons-io', name: 'commons-io', version: "${commonsIoVersion}") { transitive = false } + externalJars(group: 'io.ballerina.lib', name: 'data.jsondata-native', version: "${stdlibDataJsonDataVersion}") { + transitive = false + } + externalJars(group: 'io.ballerina.lib', name: 'data.xmldata-native', version: "${stdlibDataXmlDataVersion}") { + transitive = false + } + externalJars(group: 'io.ballerina.lib', name: 'data.csv-native', version: "${stdlibDataCsvVersion}") { + transitive = false + } } task updateTomlFiles { @@ -110,6 +119,9 @@ task updateTomlFiles { newConfig = newConfig.replace("@jcl.slf4j.version@", stdlibDependentJclSlf4jVersion) newConfig = newConfig.replace("@commons.io.version@", stdlibDependentCommonsIoVersion) newConfig = newConfig.replace("@commons.lang3.version@", stdlibDependentCommonsLang3Version) + newConfig = newConfig.replace("@data.jsondata.version@", stdlibDataJsonDataVersion) + newConfig = newConfig.replace("@data.xmldata.version@", stdlibDataXmlDataVersion) + newConfig = newConfig.replace("@data.csv.version@", stdlibDataCsvVersion) ballerinaTomlFile.text = newConfig diff --git a/build.gradle b/build.gradle index e3089d31c..5f90d3ea7 100644 --- a/build.gradle +++ b/build.gradle @@ -78,6 +78,9 @@ subprojects { ballerinaStdLibs "io.ballerina.stdlib:time-ballerina:${stdlibTimeVersion}" ballerinaStdLibs "io.ballerina.stdlib:observe-ballerina:${observeVersion}" ballerinaStdLibs "io.ballerina:observe-ballerina:${observeInternalVersion}" + + ballerinaStdLibs "io.ballerina.lib:data.jsondata-ballerina:${stdlibDataJsonDataVersion}" + ballerinaStdLibs "io.ballerina.lib:data.xmldata-ballerina:${stdlibDataXmlDataVersion}" } } diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/ftp/plugin/FtpServiceValidationTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/ftp/plugin/FtpServiceValidationTest.java index 09c7d11ec..075916d01 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/ftp/plugin/FtpServiceValidationTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/ftp/plugin/FtpServiceValidationTest.java @@ -28,19 +28,14 @@ import static io.ballerina.stdlib.ftp.plugin.CompilerPluginTestUtils.assertDiagnostic; import static io.ballerina.stdlib.ftp.plugin.CompilerPluginTestUtils.loadPackage; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_CALLER_PARAMETER; -import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_ON_FILE_DELETED_CALLER_PARAMETER; -import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_ON_FILE_DELETED_PARAMETER; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_REMOTE_FUNCTION; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_RETURN_TYPE_ERROR_OR_NIL; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_WATCHEVENT_PARAMETER; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.METHOD_MUST_BE_REMOTE; -import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.MULTIPLE_CONTENT_METHODS; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.MUST_HAVE_WATCHEVENT; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.NO_ON_FILE_CHANGE; -import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.ON_FILE_DELETED_MUST_BE_REMOTE; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.ONLY_PARAMS_ALLOWED; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.RESOURCE_FUNCTION_NOT_ALLOWED; -import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.TOO_MANY_PARAMETERS_ON_FILE_DELETED; /** * Tests for FTP package compiler plugin. diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java index 0c71f88b8..6d635a7bc 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java @@ -88,7 +88,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 parameters = contentFunctionDefinitionNode.functionSignature().parameters(); @@ -99,19 +100,24 @@ public void validate() { private void validateContentFunctionParameters(SeparatedNodeList 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 @@ -121,7 +127,7 @@ private void validateContentFunctionParameters(SeparatedNodeList boolean isCaller = validateCallerParameter(secondParameter); 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 @@ -133,7 +139,7 @@ private void validateContentFunctionParameters(SeparatedNodeList } } else { // Second was Caller, third is invalid - reportErrorDiagnostic(TOO_MANY_PARAMETERS, thirdParameter.location()); + reportErrorDiagnostic(TOO_MANY_PARAMETERS, thirdParameter.location(), contentMethodName); } } } @@ -141,15 +147,20 @@ private void validateContentFunctionParameters(SeparatedNodeList private boolean validateContentParameter(ParameterNode parameterNode) { RequiredParameterNode requiredParameterNode = (RequiredParameterNode) parameterNode; - Node parameterTypeNode = requiredParameterNode.typeName(); SemanticModel semanticModel = syntaxNodeAnalysisContext.semanticModel(); - Optional paramSymbol = semanticModel.symbol(parameterTypeNode); + Optional paramSymbolOpt = semanticModel.symbol(requiredParameterNode); + + if (paramSymbolOpt.isEmpty()) { + return false; + } - if (paramSymbol.isEmpty()) { + Symbol symbol = paramSymbolOpt.get(); + if (!(symbol instanceof ParameterSymbol)) { return false; } - TypeSymbol typeSymbol = ((ParameterSymbol) paramSymbol.get()).typeDescriptor(); + ParameterSymbol parameterSymbol = (ParameterSymbol) symbol; + TypeSymbol typeSymbol = parameterSymbol.typeDescriptor(); if (typeSymbol == null) { return false; } @@ -312,8 +323,50 @@ private void validateReturnTypeErrorOrNil(FunctionDefinitionNode functionDefinit } } + private String getExpectedContentType() { + return switch (contentMethodName) { + case ON_FILE_FUNC -> "byte[] or stream"; + 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"; + default -> "unknown"; + }; + } + + private String getActualParameterType(ParameterNode parameterNode) { + if (!(parameterNode instanceof RequiredParameterNode)) { + return "unknown"; + } + RequiredParameterNode requiredParameterNode = (RequiredParameterNode) parameterNode; + SemanticModel semanticModel = syntaxNodeAnalysisContext.semanticModel(); + Optional paramSymbolOpt = semanticModel.symbol(requiredParameterNode); + + if (paramSymbolOpt.isEmpty()) { + return "unknown"; + } + + Symbol symbol = paramSymbolOpt.get(); + if (!(symbol instanceof ParameterSymbol)) { + return "unknown"; + } + + ParameterSymbol parameterSymbol = (ParameterSymbol) symbol; + TypeSymbol typeSymbol = parameterSymbol.typeDescriptor(); + if (typeSymbol == null) { + return "unknown"; + } + + return typeSymbol.signature(); + } + 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)); + } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java index c9ee9b4e5..aaacaa346 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java @@ -114,15 +114,20 @@ private boolean validateStringArrayParameter(ParameterNode parameterNode) { } RequiredParameterNode requiredParameterNode = (RequiredParameterNode) parameterNode; - Node parameterTypeNode = requiredParameterNode.typeName(); SemanticModel semanticModel = syntaxNodeAnalysisContext.semanticModel(); - Optional paramSymbol = semanticModel.symbol(parameterTypeNode); + Optional paramSymbolOpt = semanticModel.symbol(requiredParameterNode); + + if (paramSymbolOpt.isEmpty()) { + return false; + } - if (paramSymbol.isEmpty()) { + Symbol symbol = paramSymbolOpt.get(); + if (!(symbol instanceof ParameterSymbol)) { return false; } - TypeSymbol typeSymbol = ((ParameterSymbol) paramSymbol.get()).typeDescriptor(); + ParameterSymbol parameterSymbol = (ParameterSymbol) symbol; + TypeSymbol typeSymbol = parameterSymbol.typeDescriptor(); if (typeSymbol == null) { return false; } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginConstants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginConstants.java index b087e461e..0fa1842e9 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginConstants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginConstants.java @@ -42,10 +42,6 @@ private PluginConstants() {} public static final String CALLER = "Caller"; public static final String FILE_INFO = "FileInfo"; - // annotation - public static final String FILE_CONFIG_ANNOTATION = "FileConfig"; - public static final String ANNOTATION_PATTERN_FIELD = "pattern"; - // return types error or nil public static final String ERROR = "error"; @@ -77,21 +73,19 @@ enum CompilationErrors { MULTIPLE_CONTENT_METHODS("Only one content handling strategy is allowed. Cannot mix onFileChange with " + "content methods (onFile, onFileText, onFileJson, onFileXml, onFileCsv) or onFileDeleted.", "FTP_112"), MULTIPLE_GENERIC_CONTENT_METHODS("Only one generic onFile method is allowed in a service.", "FTP_113"), - MIXED_GENERIC_AND_FORMAT_SPECIFIC("Cannot mix generic onFile method with format-specific methods " + - "(onFileText, onFileJson, onFileXml, onFileCsv).", "FTP_114"), - CONTENT_METHOD_MUST_BE_REMOTE("Content handler method must be remote.", "FTP_115"), - INVALID_CONTENT_PARAMETER_TYPE("Invalid first parameter type. Expected content type based on method name.", + CONTENT_METHOD_MUST_BE_REMOTE("Content handler method '%s' must be remote.", "FTP_115"), + INVALID_CONTENT_PARAMETER_TYPE("Invalid first parameter type for '%s'. Expected '%s', found '%s'.", "FTP_116"), - INVALID_FILEINFO_PARAMETER("Invalid fileInfo parameter. Only ftp:FileInfo is allowed.", "FTP_117"), - TOO_MANY_PARAMETERS("Too many parameters. Content methods accept at most 3 parameters: " + + INVALID_FILEINFO_PARAMETER("Invalid fileInfo parameter for '%s'. Only ftp:FileInfo is allowed.", "FTP_117"), + TOO_MANY_PARAMETERS("Too many parameters for '%s'. Content methods accept at most 3 parameters: " + "(content, fileInfo?, caller?).", "FTP_118"), NO_VALID_REMOTE_METHOD("No valid remote method found. Service must have either onFileChange or content " + "handler methods (onFile, onFileText, onFileJson, onFileXml, onFileCsv) or onFileDeleted.", "FTP_119"), - ANNOTATION_PATTERN_NOT_SUBSET("FileConfig annotation pattern must be a subset of listener's fileNamePattern.", - "FTP_120"), - OVERLAPPING_ANNOTATION_PATTERNS("Multiple methods have overlapping FileConfig annotation patterns.", + ANNOTATION_PATTERN_NOT_SUBSET("FunctionConfig annotation fileNamePattern must be a subset of listener's " + + "fileNamePattern.", "FTP_120"), + OVERLAPPING_ANNOTATION_PATTERNS("Multiple methods have overlapping FunctionConfig annotation patterns.", "FTP_121"), - INVALID_ANNOTATION_USAGE("FileConfig annotation can only be used on content handler methods " + + INVALID_ANNOTATION_USAGE("FunctionConfig annotation can only be used on content handler methods " + "(onFile, onFileText, onFileJson, onFileXml, onFileCsv).", "FTP_122"), // onFileDeleted validation errors diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java index 7874449b9..347dbf881 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java @@ -54,6 +54,15 @@ public static Diagnostic getDiagnostic(CompilationErrors error, DiagnosticSeveri return DiagnosticFactory.createDiagnostic(diagnosticInfo, location); } + public static Diagnostic getDiagnostic(CompilationErrors error, DiagnosticSeverity severity, Location location, + Object... args) { + String errorTemplate = error.getError(); + String errorMessage = String.format(errorTemplate, args); + String diagnosticCode = error.getErrorCode(); + DiagnosticInfo diagnosticInfo = new DiagnosticInfo(diagnosticCode, errorMessage, severity); + return DiagnosticFactory.createDiagnostic(diagnosticInfo, location); + } + public static boolean validateModuleId(ModuleSymbol moduleSymbol) { if (moduleSymbol != null) { String moduleName = moduleSymbol.id().moduleName(); diff --git a/gradle.properties b/gradle.properties index 5ff613802..d25aa0bcd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,3 +36,6 @@ stdlibIoVersion=1.8.0 stdlibTimeVersion=2.7.0 observeVersion=1.5.0 observeInternalVersion=1.5.0 +stdlibDataJsonDataVersion=1.1.2 +stdlibDataXmlDataVersion=1.5.2 +stdlibDataCsvVersion=0.8.1 diff --git a/native/build.gradle b/native/build.gradle index 18322e4dd..93265d64e 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -35,6 +35,9 @@ dependencies { implementation group: 'org.apache.commons', name: 'commons-vfs2', version: "${commonsVfsVersion}" implementation group: 'org.apache.commons', name: 'commons-lang3', version: "${commonsLang3Version}" implementation group: 'commons-io', name: 'commons-io', version: "${commonsIoVersion}" + implementation group: 'io.ballerina.lib', name: 'data.jsondata-native', version: "${stdlibDataJsonDataVersion}" + implementation group: 'io.ballerina.lib', name: 'data.xmldata-native', version: "${stdlibDataXmlDataVersion}" + implementation group: 'io.ballerina.lib', name: 'data.csv-native', version: "${stdlibDataCsvVersion}" } checkstyle { diff --git a/native/src/main/java/io/ballerina/stdlib/ftp/server/FtpContentCallbackHandler.java b/native/src/main/java/io/ballerina/stdlib/ftp/server/FtpContentCallbackHandler.java index a8d9090e5..eddbee0ea 100644 --- a/native/src/main/java/io/ballerina/stdlib/ftp/server/FtpContentCallbackHandler.java +++ b/native/src/main/java/io/ballerina/stdlib/ftp/server/FtpContentCallbackHandler.java @@ -55,7 +55,6 @@ import static io.ballerina.runtime.api.types.TypeTags.ARRAY_TAG; import static io.ballerina.runtime.api.types.TypeTags.OBJECT_TYPE_TAG; import static io.ballerina.runtime.api.types.TypeTags.RECORD_TYPE_TAG; -import static io.ballerina.runtime.api.types.TypeTags.STREAM_TAG; import static io.ballerina.stdlib.ftp.util.FtpConstants.ON_FILE_CSV_REMOTE_FUNCTION; import static io.ballerina.stdlib.ftp.util.FtpConstants.ON_FILE_JSON_REMOTE_FUNCTION; import static io.ballerina.stdlib.ftp.util.FtpConstants.ON_FILE_REMOTE_FUNCTION; @@ -157,7 +156,8 @@ private byte[] fetchFileContentFromRemote(FileInfo fileInfo) throws Exception { private Object convertFileContent(byte[] fileContent, MethodType methodType) throws Exception { String methodName = methodType.getName(); Parameter firstParameter = methodType.getParameters()[0]; - int firstParamTypeTag = TypeUtils.getReferredType(firstParameter.type).getTag(); + Type firstParamType = TypeUtils.getReferredType(firstParameter.type); + int firstParamTypeTag = firstParamType.getTag(); switch (methodName) { case ON_FILE_REMOTE_FUNCTION: @@ -165,11 +165,11 @@ private Object convertFileContent(byte[] fileContent, MethodType methodType) thr case ON_FILE_TEXT_REMOTE_FUNCTION: return FtpContentConverter.convertBytesToString(fileContent); case ON_FILE_JSON_REMOTE_FUNCTION: - return FtpContentConverter.convertBytesToJson(fileContent); + return FtpContentConverter.convertBytesToJson(fileContent, firstParamType); case ON_FILE_XML_REMOTE_FUNCTION: - return FtpContentConverter.convertBytesToXml(fileContent); + return FtpContentConverter.convertBytesToXml(fileContent, firstParamType); case ON_FILE_CSV_REMOTE_FUNCTION: - return convertOnFileCsvContent(fileContent, methodType); + return FtpContentConverter.convertBytesToCsv(fileContent, firstParamType); default: throw new IllegalArgumentException("Unknown content method: " + methodName); } @@ -188,35 +188,6 @@ private Object convertOnFileContent(byte[] fileContent, int firstParamTypeTag) { return createByteStreamFromContent(fileContent); } - /** - * Converts content for onFileCsv method. - */ - private Object convertOnFileCsvContent(byte[] fileContent, MethodType methodType) throws Exception { - Parameter firstParameter = methodType.getParameters()[0]; - Type firstParamType = TypeUtils.getReferredType(firstParameter.type); - int firstParamTypeTag = firstParamType.getTag(); - - if (firstParamTypeTag == ARRAY_TAG) { - // Check if it's string[][] or record{}[] - ArrayType arrayType = (ArrayType) firstParamType; - Type elementType = TypeUtils.getReferredType(arrayType.getElementType()); - - if (elementType.getTag() == ARRAY_TAG) { - // It's string[][] - 2D array - return FtpContentConverter.convertBytesToCsvStringArray(fileContent); - } else if (elementType.getTag() == RECORD_TYPE_TAG) { - // It's record{}[] - array of records - return FtpContentConverter.convertBytesToCsvRecordArray(fileContent, elementType); - } else { - // Default to string[][] - return FtpContentConverter.convertBytesToCsvStringArray(fileContent); - } - } else if (firstParamTypeTag == STREAM_TAG) { - // Return as stream for large CSV files - return createByteStreamFromContent(fileContent); - } - return FtpContentConverter.convertBytesToCsvStringArray(fileContent); - } /** * Creates a Ballerina byte stream from byte array content. diff --git a/native/src/main/java/io/ballerina/stdlib/ftp/util/FtpContentConverter.java b/native/src/main/java/io/ballerina/stdlib/ftp/util/FtpContentConverter.java index 4e8701841..ef1d52748 100644 --- a/native/src/main/java/io/ballerina/stdlib/ftp/util/FtpContentConverter.java +++ b/native/src/main/java/io/ballerina/stdlib/ftp/util/FtpContentConverter.java @@ -18,43 +18,30 @@ package io.ballerina.stdlib.ftp.util; -import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; -import io.ballerina.runtime.api.types.ArrayType; -import io.ballerina.runtime.api.types.Field; -import io.ballerina.runtime.api.types.PredefinedTypes; -import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.Type; -import io.ballerina.runtime.api.types.TypeTags; -import io.ballerina.runtime.api.utils.JsonUtils; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; -import io.ballerina.runtime.api.utils.XmlUtils; import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTypedesc; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import static io.ballerina.runtime.api.creators.ValueCreator.createRecordValue; +import static io.ballerina.stdlib.ftp.util.FtpUtil.ErrorType.Error; /** - * Utility class for converting file content to various Ballerina types. + * Utility class for converting file content to various Ballerina types using data binding modules. + * Uses io.ballerina.lib.data.jsondata, xmldata, and csvdata for proper data binding. */ public final class FtpContentConverter { private static final Logger log = LoggerFactory.getLogger(FtpContentConverter.class); - private static final String CSV_DELIMITER = ","; - private static final String CSV_QUOTE = "\""; + private static final BString ALLOW_DATA_PROJECTION = StringUtils.fromString("allowDataProjection"); private FtpContentConverter() { // private constructor @@ -72,232 +59,127 @@ public static BString convertBytesToString(byte[] content) { } /** - * Converts byte array to Ballerina JSON. + * Converts byte array to Ballerina JSON using data.jsondata module. * * @param content The byte array content - * @return Ballerina JSON object - * @throws Exception if parsing fails + * @param targetType The target Ballerina type for data binding + * @return Ballerina JSON object or BError */ - public static Object convertBytesToJson(byte[] content) throws Exception { - String jsonString = new String(content, StandardCharsets.UTF_8); - return JsonUtils.parse(jsonString); - } - - /** - * Converts byte array to Ballerina XML. - * - * @param content The byte array content - * @return Ballerina XML object - * @throws Exception if parsing fails - */ - public static Object convertBytesToXml(byte[] content) throws Exception { - String xmlString = new String(content, StandardCharsets.UTF_8); - return XmlUtils.parse(xmlString); - } - - /** - * Converts byte array to Ballerina string[][] (CSV as array of arrays). - * - * @param content The byte array content - * @return Ballerina string[][] - */ - public static BArray convertBytesToCsvStringArray(byte[] content) { - List> csvData = parseCsvContent(content); - - // Create 2D string array - ArrayType innerArrayType = TypeCreator.createArrayType(PredefinedTypes.TYPE_STRING); - ArrayType outerArrayType = TypeCreator.createArrayType(innerArrayType); + public static Object convertBytesToJson(byte[] content, Type targetType) { + try { + BArray byteArray = ValueCreator.createArrayValue(content); + BMap options = createJsonParseOptions(); + BTypedesc typedesc = ValueCreator.createTypedescValue(targetType); - BArray outerArray = ValueCreator.createArrayValue(outerArrayType); + Object result = io.ballerina.lib.data.jsondata.json.Native.parseBytes(byteArray, options, typedesc); - for (List row : csvData) { - BString[] rowArray = new BString[row.size()]; - for (int i = 0; i < row.size(); i++) { - rowArray[i] = StringUtils.fromString(row.get(i)); + if (result instanceof BError) { + log.error("Failed to parse JSON content: {}", ((BError) result).getMessage()); + return result; } - BArray innerArray = ValueCreator.createArrayValue(rowArray, innerArrayType); - outerArray.append(innerArray); - } - return outerArray; + return result; + } catch (Exception e) { + log.error("Error converting bytes to JSON", e); + return FtpUtil.createError("Failed to parse JSON content: " + e.getMessage(), Error.errorType()); + } } /** - * Converts byte array to Ballerina record{}[] (CSV as array of records). - * First row is treated as header row containing field names. + * Converts byte array to Ballerina XML using data.xmldata module. * * @param content The byte array content - * @param recordType The record type for each row (contains field definitions) - * @return Ballerina record{}[] + * @param targetType The target Ballerina type for data binding + * @return Ballerina XML object or BError */ - public static BArray convertBytesToCsvRecordArray(byte[] content, Type recordType) { - List> csvData = parseCsvContent(content); - - if (csvData.isEmpty()) { - // Return empty array if no data - ArrayType arrayType = TypeCreator.createArrayType(recordType); - return ValueCreator.createArrayValue(arrayType); - } - - // First row is headers - List headers = csvData.get(0); - - // Get record type information - RecordType recType = (RecordType) TypeUtils.getReferredType(recordType); - Map fields = recType.getFields(); - - // Create array to hold records - ArrayType arrayType = TypeCreator.createArrayType(recordType); - BMap[] records = new BMap[csvData.size() - 1]; + public static Object convertBytesToXml(byte[] content, Type targetType) { + try { + BArray byteArray = ValueCreator.createArrayValue(content); + BMap options = createXmlParseOptions(); - // Process each data row (skip header row) - for (int i = 1; i < csvData.size(); i++) { - List row = csvData.get(i); - Map recordValues = new HashMap<>(); + Type referredType = TypeUtils.getReferredType(targetType); + BTypedesc typedesc = ValueCreator.createTypedescValue(referredType); - // Map each column to record field - for (int j = 0; j < headers.size() && j < row.size(); j++) { - String fieldName = headers.get(j).trim(); - String fieldValue = row.get(j); + Object result = io.ballerina.lib.data.xmldata.xml.Native.parseBytes(byteArray, options, typedesc); - // Check if this field exists in the record type - Field field = fields.get(fieldName); - if (field != null) { - // Convert value to appropriate type - Object convertedValue = convertCsvValueToType(fieldValue, field.getFieldType()); - recordValues.put(fieldName, convertedValue); - } + if (result instanceof BError) { + log.error("Failed to parse XML content: {}", ((BError) result).getMessage()); + return result; } - // Create record with values - records[i - 1] = createRecordValue((BMap) recType, recordValues); + return result; + } catch (Exception e) { + log.error("Error converting bytes to XML", e); + return FtpUtil.createError("Failed to parse XML content: " + e.getMessage(), Error.errorType()); } - - return ValueCreator.createArrayValue(records, arrayType); } /** - * Converts a CSV string value to the appropriate Ballerina type based on the field type. + * Converts byte array to CSV using data.csvdata module. * - * @param value The string value from CSV - * @param targetType The target Ballerina type - * @return Converted value + * @param content The byte array content + * @param targetType The target Ballerina type for data binding + * @return Ballerina CSV data (string[][], record[][], or custom type) or BError */ - private static Object convertCsvValueToType(String value, Type targetType) { - Type referredType = TypeUtils.getReferredType(targetType); - + public static Object convertBytesToCsv(byte[] content, Type targetType) { try { - switch (referredType.getTag()) { - case TypeTags.STRING_TAG: - return StringUtils.fromString(value); - - case TypeTags.INT_TAG: - return Long.parseLong(value.trim()); - - case TypeTags.FLOAT_TAG: - return Double.parseDouble(value.trim()); + BArray byteArray = ValueCreator.createArrayValue(content); + BMap options = createCsvParseOptions(); - case TypeTags.BOOLEAN_TAG: - return Boolean.parseBoolean(value.trim()); + Type referredType = TypeUtils.getReferredType(targetType); + BTypedesc typedesc = ValueCreator.createTypedescValue(referredType); - case TypeTags.DECIMAL_TAG: - return ValueCreator.createDecimalValue(value.trim()); + Object result = io.ballerina.lib.data.csvdata.csv.Native.parseBytes(byteArray, options, typedesc); - default: - // For other types, return as string - return StringUtils.fromString(value); + if (result instanceof BError) { + log.error("Failed to parse CSV content: {}", ((BError) result).getMessage()); + return result; } - } catch (NumberFormatException | ArithmeticException e) { - log.warn("Failed to convert CSV value '{}' to type {}. Using default value.", - value, referredType.getName()); - // Return default value for the type - return getDefaultValueForType(referredType); + + return result; + } catch (Exception e) { + log.error("Error converting bytes to CSV", e); + return FtpUtil.createError("Failed to parse CSV content: " + e.getMessage(), Error.errorType()); } } /** - * Gets the default value for a given Ballerina type. + * Creates parse options for JSON data binding. + * Enables lax data projection for flexible type matching. * - * @param type The Ballerina type - * @return Default value for the type + * @return BMap containing parse options */ - private static Object getDefaultValueForType(Type type) { - switch (type.getTag()) { - case TypeTags.STRING_TAG: - return StringUtils.fromString(""); - case TypeTags.INT_TAG: - return 0L; - case TypeTags.FLOAT_TAG: - return 0.0; - case TypeTags.BOOLEAN_TAG: - return false; - case TypeTags.DECIMAL_TAG: - return ValueCreator.createDecimalValue("0"); - default: - return null; - } + private static BMap createJsonParseOptions() { + BMap options = ValueCreator.createMapValue(); + // Enable flexible data projection + options.put(ALLOW_DATA_PROJECTION, true); + return options; } /** - * Parses CSV content from byte array to list of lists. + * Creates parse options for XML data binding. + * Enables lax data projection for flexible type matching. * - * @param content The byte array content - * @return List of CSV rows, where each row is a list of values + * @return BMap containing parse options */ - private static List> parseCsvContent(byte[] content) { - List> csvData = new ArrayList<>(); - - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(new ByteArrayInputStream(content), StandardCharsets.UTF_8))) { - - String line; - while ((line = reader.readLine()) != null) { - List row = parseCsvLine(line); - csvData.add(row); - } - } catch (Exception e) { - log.error("Error parsing CSV content", e); - } - - return csvData; + private static BMap createXmlParseOptions() { + BMap options = ValueCreator.createMapValue(); + // Enable flexible data projection + options.put(ALLOW_DATA_PROJECTION, true); + return options; } /** - * Parses a single CSV line following RFC 4180 rules. + * Creates parse options for CSV data binding. + * Enables lax data projection for flexible type matching. * - * @param line The CSV line to parse - * @return List of field values + * @return BMap containing parse options */ - private static List parseCsvLine(String line) { - List fields = new ArrayList<>(); - StringBuilder currentField = new StringBuilder(); - boolean inQuotes = false; - - for (int i = 0; i < line.length(); i++) { - char currentChar = line.charAt(i); - - if (currentChar == '\"') { - if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '\"') { - // Escaped quote (two consecutive quotes) - currentField.append('\"'); - i++; // Skip next quote - } else { - // Toggle quote state - inQuotes = !inQuotes; - } - } else if (currentChar == ',' && !inQuotes) { - // Field delimiter outside quotes - fields.add(currentField.toString()); - currentField = new StringBuilder(); - } else { - currentField.append(currentChar); - } - } - - // Add last field - fields.add(currentField.toString()); - - return fields; + private static BMap createCsvParseOptions() { + BMap options = ValueCreator.createMapValue(); + // Enable flexible data projection + options.put(ALLOW_DATA_PROJECTION, true); + return options; } /** diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index 7ac071612..b83a28836 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -24,6 +24,9 @@ requires org.slf4j; requires java.logging; requires org.apache.commons.vfs2; + requires io.ballerina.lib.data; + requires io.ballerina.lib.data.xmldata; + requires io.ballerina.lib.data.csvdata; exports io.ballerina.stdlib.ftp.client; exports io.ballerina.stdlib.ftp.server; exports io.ballerina.stdlib.ftp.util; From c32c9b450c8ad8b874f9c3f506ed0a9536086a39 Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 4 Nov 2025 12:43:31 +0530 Subject: [PATCH 2/5] Fix code duplication --- .../plugin/FtpContentFunctionValidator.java | 106 +------------- .../ftp/plugin/FtpFileDeletedValidator.java | 78 +---------- .../stdlib/ftp/plugin/PluginUtils.java | 131 ++++++++++++++++++ 3 files changed, 137 insertions(+), 178 deletions(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java index 6d635a7bc..9f9fe44e2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java @@ -19,15 +19,11 @@ 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; @@ -35,7 +31,6 @@ import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.ballerina.tools.diagnostics.Location; -import java.util.List; import java.util.Objects; import java.util.Optional; @@ -46,24 +41,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. @@ -123,8 +112,8 @@ private void validateContentFunctionParameters(SeparatedNodeList // 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(), contentMethodName); @@ -134,7 +123,7 @@ private void validateContentFunctionParameters(SeparatedNodeList 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 { @@ -232,95 +221,8 @@ 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 paramSymbol = semanticModel.symbol(parameterTypeNode); - - if (paramSymbol.isPresent()) { - Optional moduleSymbol = paramSymbol.get().getModule(); - if (moduleSymbol.isPresent()) { - String paramName = paramSymbol.get().getName().orElse(""); - return validateModuleId(moduleSymbol.get()) && paramName.equals(FILE_INFO); - } - } - return false; - } - - 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 paramSymbol = semanticModel.symbol(parameterTypeNode); - - if (paramSymbol.isPresent()) { - Optional moduleSymbol = paramSymbol.get().getModule(); - if (moduleSymbol.isPresent()) { - String paramName = paramSymbol.get().getName().orElse(""); - return validateModuleId(moduleSymbol.get()) && paramName.equals(CALLER); - } - } - return false; - } - private void validateReturnTypeErrorOrNil(FunctionDefinitionNode functionDefinitionNode) { - MethodSymbol methodSymbol = getMethodSymbol(syntaxNodeAnalysisContext, functionDefinitionNode); - if (methodSymbol == null) { - return; - } - - Optional returnTypeDesc = methodSymbol.typeDescriptor().returnTypeDescriptor(); - if (returnTypeDesc.isEmpty()) { - return; - } - - TypeDescKind returnTypeKind = returnTypeDesc.get().typeKind(); - - if (returnTypeKind == TypeDescKind.NIL) { - return; - } - - if (returnTypeKind == TypeDescKind.UNION) { - List 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()); - } + PluginUtils.validateReturnTypeErrorOrNil(functionDefinitionNode, syntaxNodeAnalysisContext); } private String getExpectedContentType() { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java index aaacaa346..40eb9f814 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java @@ -19,15 +19,10 @@ 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; @@ -35,22 +30,16 @@ import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.ballerina.tools.diagnostics.Location; -import java.util.List; import java.util.Objects; import java.util.Optional; import static io.ballerina.compiler.api.symbols.TypeDescKind.ARRAY; -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.INVALID_ON_FILE_DELETED_CALLER_PARAMETER; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_ON_FILE_DELETED_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.ON_FILE_DELETED_MUST_BE_REMOTE; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.TOO_MANY_PARAMETERS_ON_FILE_DELETED; 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; /** * Validator for onFileDeleted function. @@ -102,7 +91,7 @@ private void validateOnFileDeletedParameters(SeparatedNodeList pa // Second parameter (if exists) must be Caller if (parameters.size() == 2) { ParameterNode secondParameter = parameters.get(1); - if (!validateCallerParameter(secondParameter)) { + if (!PluginUtils.validateCallerParameter(secondParameter, syntaxNodeAnalysisContext)) { reportErrorDiagnostic(INVALID_ON_FILE_DELETED_CALLER_PARAMETER, secondParameter.location()); } } @@ -136,71 +125,8 @@ private boolean validateStringArrayParameter(ParameterNode parameterNode) { return typeSymbol.typeKind() == ARRAY && typeSymbol.signature().equals("string[]"); } - 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 paramSymbol = semanticModel.symbol(parameterTypeNode); - - if (paramSymbol.isPresent()) { - Optional moduleSymbol = paramSymbol.get().getModule(); - if (moduleSymbol.isPresent()) { - String paramName = paramSymbol.get().getName().orElse(""); - return validateModuleId(moduleSymbol.get()) && paramName.equals(CALLER); - } - } - return false; - } - private void validateReturnTypeErrorOrNil(FunctionDefinitionNode functionDefinitionNode) { - MethodSymbol methodSymbol = getMethodSymbol(syntaxNodeAnalysisContext, functionDefinitionNode); - if (methodSymbol == null) { - return; - } - - Optional returnTypeDesc = methodSymbol.typeDescriptor().returnTypeDescriptor(); - if (returnTypeDesc.isEmpty()) { - return; - } - - TypeDescKind returnTypeKind = returnTypeDesc.get().typeKind(); - - if (returnTypeKind == TypeDescKind.NIL) { - return; - } - - if (returnTypeKind == TypeDescKind.UNION) { - List returnTypeMembers = - ((UnionTypeSymbol) returnTypeDesc.get()).memberTypeDescriptors(); - for (TypeSymbol returnType : returnTypeMembers) { - if (returnType.typeKind() != TypeDescKind.NIL) { - if (returnType.typeKind() == TypeDescKind.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()); - } + PluginUtils.validateReturnTypeErrorOrNil(functionDefinitionNode, syntaxNodeAnalysisContext); } public void reportErrorDiagnostic(PluginConstants.CompilationErrors error, Location location) { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java index 347dbf881..82ecd7f53 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java @@ -23,9 +23,15 @@ import io.ballerina.compiler.api.symbols.ModuleSymbol; import io.ballerina.compiler.api.symbols.Qualifier; 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.ModulePartNode; +import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NonTerminalNode; +import io.ballerina.compiler.syntax.tree.ParameterNode; +import io.ballerina.compiler.syntax.tree.RequiredParameterNode; import io.ballerina.compiler.syntax.tree.SyntaxTree; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; import io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors; @@ -38,8 +44,15 @@ import io.ballerina.tools.text.TextDocument; import io.ballerina.tools.text.TextRange; +import java.util.List; import java.util.Optional; +import static io.ballerina.compiler.api.symbols.TypeDescKind.TYPE_REFERENCE; +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.INVALID_RETURN_TYPE_ERROR_OR_NIL; +import static io.ballerina.stdlib.ftp.plugin.PluginConstants.FILE_INFO; + /** * Util class for the compiler plugin. */ @@ -98,4 +111,122 @@ public static NonTerminalNode findNode(SyntaxTree syntaxTree, LineRange lineRang int end = textDocument.textPositionFrom(lineRange.endLine()); return ((ModulePartNode) syntaxTree.rootNode()).findNode(TextRange.from(start, end - start), true); } + + /** + * Validates that a parameter is of type ftp:FileInfo. + * + * @param parameterNode The parameter node to validate + * @param context The syntax node analysis context + * @return true if the parameter is ftp:FileInfo, false otherwise + */ + public static boolean validateFileInfoParameter(ParameterNode parameterNode, + SyntaxNodeAnalysisContext context) { + 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 = context.semanticModel(); + Optional paramSymbol = semanticModel.symbol(parameterTypeNode); + + if (paramSymbol.isPresent()) { + Optional moduleSymbol = paramSymbol.get().getModule(); + if (moduleSymbol.isPresent()) { + String paramName = paramSymbol.get().getName().orElse(""); + return validateModuleId(moduleSymbol.get()) && paramName.equals(FILE_INFO); + } + } + return false; + } + + /** + * Validates that a parameter is of type ftp:Caller. + * + * @param parameterNode The parameter node to validate + * @param context The syntax node analysis context + * @return true if the parameter is ftp:Caller, false otherwise + */ + public static boolean validateCallerParameter(ParameterNode parameterNode, + SyntaxNodeAnalysisContext context) { + 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 = context.semanticModel(); + Optional paramSymbol = semanticModel.symbol(parameterTypeNode); + + if (paramSymbol.isPresent()) { + Optional moduleSymbol = paramSymbol.get().getModule(); + if (moduleSymbol.isPresent()) { + String paramName = paramSymbol.get().getName().orElse(""); + return validateModuleId(moduleSymbol.get()) && paramName.equals(CALLER); + } + } + return false; + } + + /** + * Validates that the return type of a function is error? or nil. + * Reports a diagnostic error if the return type is invalid. + * + * @param functionDefinitionNode The function definition node to validate + * @param context The syntax node analysis context + */ + public static void validateReturnTypeErrorOrNil(FunctionDefinitionNode functionDefinitionNode, + SyntaxNodeAnalysisContext context) { + MethodSymbol methodSymbol = getMethodSymbol(context, functionDefinitionNode); + if (methodSymbol == null) { + return; + } + + Optional returnTypeDesc = methodSymbol.typeDescriptor().returnTypeDescriptor(); + if (returnTypeDesc.isEmpty()) { + return; + } + + TypeDescKind returnTypeKind = returnTypeDesc.get().typeKind(); + + if (returnTypeKind == TypeDescKind.NIL) { + return; + } + + if (returnTypeKind == TypeDescKind.UNION) { + List 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())) { + context.reportDiagnostic(getDiagnostic(INVALID_RETURN_TYPE_ERROR_OR_NIL, + DiagnosticSeverity.ERROR, + functionDefinitionNode.functionSignature().location())); + return; + } + } else if (returnType.typeKind() != TypeDescKind.ERROR) { + context.reportDiagnostic(getDiagnostic(INVALID_RETURN_TYPE_ERROR_OR_NIL, + DiagnosticSeverity.ERROR, + functionDefinitionNode.functionSignature().location())); + return; + } + } + } + } else { + context.reportDiagnostic(getDiagnostic(INVALID_RETURN_TYPE_ERROR_OR_NIL, + DiagnosticSeverity.ERROR, + functionDefinitionNode.functionSignature().location())); + } + } } From c5c8e0d5976f19bd95fd1998991e42b0e133b8a3 Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 4 Nov 2025 13:17:16 +0530 Subject: [PATCH 3/5] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- ballerina/CompilerPlugin.toml | 2 +- ballerina/Dependencies.toml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index ea4b6e16c..0642d753b 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -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" @@ -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" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 8f3aa9843..c293e9136 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -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" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 7cd8625c1..455a1212d 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -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"} @@ -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"}, @@ -163,7 +163,7 @@ modules = [ [[package]] org = "ballerina" name = "observe" -version = "1.5.0" +version = "1.5.1" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From 9025fcd3bb48ded8c0a66b5b206548bb9971d8f5 Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 4 Nov 2025 13:25:59 +0530 Subject: [PATCH 4/5] Refactor method reuse --- .../plugin/FtpContentFunctionValidator.java | 48 ++----------------- .../ftp/plugin/FtpFileDeletedValidator.java | 34 ++----------- .../ftp/plugin/FtpFunctionValidator.java | 42 +--------------- .../stdlib/ftp/plugin/PluginUtils.java | 23 +++++++++ 4 files changed, 32 insertions(+), 115 deletions(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java index 9f9fe44e2..5b5a121e7 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpContentFunctionValidator.java @@ -18,14 +18,10 @@ package io.ballerina.stdlib.ftp.plugin; -import io.ballerina.compiler.api.SemanticModel; -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.syntax.tree.FunctionDefinitionNode; 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; @@ -135,25 +131,13 @@ private void validateContentFunctionParameters(SeparatedNodeList } private boolean validateContentParameter(ParameterNode parameterNode) { - RequiredParameterNode requiredParameterNode = (RequiredParameterNode) parameterNode; - SemanticModel semanticModel = syntaxNodeAnalysisContext.semanticModel(); - Optional paramSymbolOpt = semanticModel.symbol(requiredParameterNode); - - if (paramSymbolOpt.isEmpty()) { - return false; - } - - Symbol symbol = paramSymbolOpt.get(); - if (!(symbol instanceof ParameterSymbol)) { - return false; - } - - ParameterSymbol parameterSymbol = (ParameterSymbol) symbol; - TypeSymbol typeSymbol = parameterSymbol.typeDescriptor(); - if (typeSymbol == null) { + Optional typeSymbolOpt = PluginUtils.getParameterTypeSymbol(parameterNode, + syntaxNodeAnalysisContext); + if (typeSymbolOpt.isEmpty()) { return false; } + TypeSymbol typeSymbol = typeSymbolOpt.get(); TypeDescKind typeKind = typeSymbol.typeKind(); switch (contentMethodName) { @@ -237,29 +221,7 @@ private String getExpectedContentType() { } private String getActualParameterType(ParameterNode parameterNode) { - if (!(parameterNode instanceof RequiredParameterNode)) { - return "unknown"; - } - RequiredParameterNode requiredParameterNode = (RequiredParameterNode) parameterNode; - SemanticModel semanticModel = syntaxNodeAnalysisContext.semanticModel(); - Optional paramSymbolOpt = semanticModel.symbol(requiredParameterNode); - - if (paramSymbolOpt.isEmpty()) { - return "unknown"; - } - - Symbol symbol = paramSymbolOpt.get(); - if (!(symbol instanceof ParameterSymbol)) { - return "unknown"; - } - - ParameterSymbol parameterSymbol = (ParameterSymbol) symbol; - TypeSymbol typeSymbol = parameterSymbol.typeDescriptor(); - if (typeSymbol == null) { - return "unknown"; - } - - return typeSymbol.signature(); + return PluginUtils.getParameterTypeSignature(parameterNode, syntaxNodeAnalysisContext); } public void reportErrorDiagnostic(PluginConstants.CompilationErrors error, Location location) { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java index 40eb9f814..2b15bf2f5 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFileDeletedValidator.java @@ -18,20 +18,14 @@ package io.ballerina.stdlib.ftp.plugin; -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.api.symbols.ParameterSymbol; -import io.ballerina.compiler.api.symbols.Symbol; -import io.ballerina.compiler.api.symbols.TypeSymbol; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; 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.Objects; -import java.util.Optional; import static io.ballerina.compiler.api.symbols.TypeDescKind.ARRAY; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_ON_FILE_DELETED_CALLER_PARAMETER; @@ -98,31 +92,9 @@ private void validateOnFileDeletedParameters(SeparatedNodeList pa } private boolean validateStringArrayParameter(ParameterNode parameterNode) { - if (!(parameterNode instanceof RequiredParameterNode)) { - return false; - } - - RequiredParameterNode requiredParameterNode = (RequiredParameterNode) parameterNode; - SemanticModel semanticModel = syntaxNodeAnalysisContext.semanticModel(); - Optional paramSymbolOpt = semanticModel.symbol(requiredParameterNode); - - if (paramSymbolOpt.isEmpty()) { - return false; - } - - Symbol symbol = paramSymbolOpt.get(); - if (!(symbol instanceof ParameterSymbol)) { - return false; - } - - ParameterSymbol parameterSymbol = (ParameterSymbol) symbol; - TypeSymbol typeSymbol = parameterSymbol.typeDescriptor(); - if (typeSymbol == null) { - return false; - } - - // Check if it's string[] - return typeSymbol.typeKind() == ARRAY && typeSymbol.signature().equals("string[]"); + return PluginUtils.getParameterTypeSymbol(parameterNode, syntaxNodeAnalysisContext) + .map(typeSymbol -> typeSymbol.typeKind() == ARRAY && + typeSymbol.signature().equals("string[]")).orElse(false); } private void validateReturnTypeErrorOrNil(FunctionDefinitionNode functionDefinitionNode) { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFunctionValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFunctionValidator.java index e5619e363..1aaa564fa 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFunctionValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/FtpFunctionValidator.java @@ -20,13 +20,9 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.IntersectionTypeSymbol; -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; @@ -37,7 +33,6 @@ import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; import io.ballerina.tools.diagnostics.Location; -import java.util.List; import java.util.Objects; import java.util.Optional; @@ -46,7 +41,6 @@ import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CALLER; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_CALLER_PARAMETER; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_PARAMETERS; -import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_RETURN_TYPE_ERROR_OR_NIL; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.INVALID_WATCHEVENT_PARAMETER; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.METHOD_MUST_BE_REMOTE; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.MUST_HAVE_WATCHEVENT; @@ -54,7 +48,6 @@ import static io.ballerina.stdlib.ftp.plugin.PluginConstants.CompilationErrors.ONLY_PARAMS_ALLOWED; import static io.ballerina.stdlib.ftp.plugin.PluginConstants.WATCHEVENT; 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; import static io.ballerina.tools.diagnostics.DiagnosticSeverity.ERROR; @@ -83,7 +76,7 @@ public void validate() { } SeparatedNodeList parameters = onFileChange.functionSignature().parameters(); validateFunctionArguments(parameters, onFileChange); - validateReturnTypeErrorOrNil(onFileChange); + PluginUtils.validateReturnTypeErrorOrNil(onFileChange, context); } } @@ -260,39 +253,6 @@ private boolean validateCallerParam(ParameterNode parameterNode) { return false; } - private void validateReturnTypeErrorOrNil(FunctionDefinitionNode functionDefinitionNode) { - MethodSymbol methodSymbol = getMethodSymbol(context, functionDefinitionNode); - if (methodSymbol != null) { - Optional returnTypeDesc = methodSymbol.typeDescriptor().returnTypeDescriptor(); - if (returnTypeDesc.isPresent()) { - if (returnTypeDesc.get().typeKind() == TypeDescKind.NIL) { - return; - } - if (returnTypeDesc.get().typeKind() == TypeDescKind.UNION) { - List returnTypeMembers = - ((UnionTypeSymbol) returnTypeDesc.get()).memberTypeDescriptors(); - for (TypeSymbol returnType : returnTypeMembers) { - if (returnType.typeKind() != TypeDescKind.NIL) { - if (returnType.typeKind() == TypeDescKind.TYPE_REFERENCE) { - if (!returnType.signature().equals(PluginConstants.ERROR) && - !validateModuleId(returnType.getModule().get())) { - reportErrorDiagnostic(INVALID_RETURN_TYPE_ERROR_OR_NIL, - functionDefinitionNode.functionSignature().location()); - } - } else if (returnType.typeKind() != TypeDescKind.ERROR) { - reportErrorDiagnostic(INVALID_RETURN_TYPE_ERROR_OR_NIL, - functionDefinitionNode.functionSignature().location()); - } - } - } - } else { - reportErrorDiagnostic(INVALID_RETURN_TYPE_ERROR_OR_NIL, - functionDefinitionNode.functionSignature().location()); - } - } - } - } - public void reportErrorDiagnostic(PluginConstants.CompilationErrors error, Location location) { context.reportDiagnostic(getDiagnostic(error, ERROR, location)); } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java index 82ecd7f53..b33bebb2d 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java @@ -21,6 +21,7 @@ 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.Qualifier; import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.TypeDescKind; @@ -176,6 +177,28 @@ public static boolean validateCallerParameter(ParameterNode parameterNode, return false; } + public static Optional getParameterTypeSymbol(ParameterNode parameterNode, + SyntaxNodeAnalysisContext context) { + if (!(parameterNode instanceof RequiredParameterNode requiredParameterNode)) { + return Optional.empty(); + } + + SemanticModel semanticModel = context.semanticModel(); + Optional symbol = semanticModel.symbol(requiredParameterNode); + if (symbol.isEmpty() || !(symbol.get() instanceof ParameterSymbol parameterSymbol)) { + return Optional.empty(); + } + + return Optional.ofNullable(parameterSymbol.typeDescriptor()); + } + + public static String getParameterTypeSignature(ParameterNode parameterNode, + SyntaxNodeAnalysisContext context) { + return getParameterTypeSymbol(parameterNode, context) + .map(TypeSymbol::signature) + .orElse("unknown"); + } + /** * Validates that the return type of a function is error? or nil. * Reports a diagnostic error if the return type is invalid. From d3f28f110b3aae2a33d6126e66b99aba7499e1a8 Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 4 Nov 2025 13:53:02 +0530 Subject: [PATCH 5/5] Refactor duplicated methods --- .../stdlib/ftp/plugin/PluginUtils.java | 53 ++++++------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java index b33bebb2d..e5a6b8921 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/ftp/plugin/PluginUtils.java @@ -29,7 +29,6 @@ import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; -import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NonTerminalNode; import io.ballerina.compiler.syntax.tree.ParameterNode; import io.ballerina.compiler.syntax.tree.RequiredParameterNode; @@ -122,27 +121,7 @@ public static NonTerminalNode findNode(SyntaxTree syntaxTree, LineRange lineRang */ public static boolean validateFileInfoParameter(ParameterNode parameterNode, SyntaxNodeAnalysisContext context) { - 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 = context.semanticModel(); - Optional paramSymbol = semanticModel.symbol(parameterTypeNode); - - if (paramSymbol.isPresent()) { - Optional moduleSymbol = paramSymbol.get().getModule(); - if (moduleSymbol.isPresent()) { - String paramName = paramSymbol.get().getName().orElse(""); - return validateModuleId(moduleSymbol.get()) && paramName.equals(FILE_INFO); - } - } - return false; + return validateQualifiedFtpParameter(parameterNode, context, FILE_INFO); } /** @@ -154,27 +133,27 @@ public static boolean validateFileInfoParameter(ParameterNode parameterNode, */ public static boolean validateCallerParameter(ParameterNode parameterNode, SyntaxNodeAnalysisContext context) { - if (!(parameterNode instanceof RequiredParameterNode)) { + return validateQualifiedFtpParameter(parameterNode, context, CALLER); + } + + private static boolean validateQualifiedFtpParameter(ParameterNode parameterNode, + SyntaxNodeAnalysisContext context, + String expectedTypeName) { + if (!(parameterNode instanceof RequiredParameterNode requiredParameterNode)) { return false; } - - RequiredParameterNode requiredParameterNode = (RequiredParameterNode) parameterNode; if (requiredParameterNode.typeName().kind() != QUALIFIED_NAME_REFERENCE) { return false; } - - Node parameterTypeNode = requiredParameterNode.typeName(); - SemanticModel semanticModel = context.semanticModel(); - Optional paramSymbol = semanticModel.symbol(parameterTypeNode); - - if (paramSymbol.isPresent()) { - Optional moduleSymbol = paramSymbol.get().getModule(); - if (moduleSymbol.isPresent()) { - String paramName = paramSymbol.get().getName().orElse(""); - return validateModuleId(moduleSymbol.get()) && paramName.equals(CALLER); - } + Optional typeSymbol = getParameterTypeSymbol(parameterNode, context); + if (typeSymbol.isEmpty()) { + return false; } - return false; + Optional moduleSymbol = typeSymbol.get().getModule(); + if (moduleSymbol.isEmpty() || !validateModuleId(moduleSymbol.get())) { + return false; + } + return typeSymbol.get().getName().map(expectedTypeName::equals).orElse(false); } public static Optional getParameterTypeSymbol(ParameterNode parameterNode,