diff --git a/compiler-plugin-test/src/test/java/io/ballerina/lib/data/yaml/compiler/CompilerPluginTest.java b/compiler-plugin-test/src/test/java/io/ballerina/lib/data/yaml/compiler/CompilerPluginTest.java index 8a57b54..2da3b37 100644 --- a/compiler-plugin-test/src/test/java/io/ballerina/lib/data/yaml/compiler/CompilerPluginTest.java +++ b/compiler-plugin-test/src/test/java/io/ballerina/lib/data/yaml/compiler/CompilerPluginTest.java @@ -99,6 +99,19 @@ public void testDuplicateField2() { "invalid field: duplicate field found"); } + @Test + public void testDuplicateFieldInUnion() { + DiagnosticResult diagnosticResult = + CompilerPluginTestUtils.loadPackage("sample_package_9").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)).toList(); + Assert.assertEquals(errorDiagnosticsList.size(), 2); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + "invalid field: duplicate field found"); + Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), + "invalid field: duplicate field found"); + } + @Test public void testComplexUnionTypeAsExpectedType() { DiagnosticResult diagnosticResult = @@ -119,4 +132,18 @@ public void testComplexUnionTypeAsMemberOfIntersection() { Assert.assertEquals(errorDiagnosticsList.size(), 1); Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), UNSUPPORTED_TYPE); } + + @Test + public void testComplexUnionTypeWithUnsupportedTypeAndDuplicateFields() { + DiagnosticResult diagnosticResult = + CompilerPluginTestUtils.loadPackage("sample_package_10").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)).toList(); + Assert.assertEquals(errorDiagnosticsList.size(), 3); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + "invalid field: duplicate field found"); + Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), + "invalid field: duplicate field found"); + Assert.assertEquals(errorDiagnosticsList.get(2).diagnosticInfo().messageFormat(), UNSUPPORTED_TYPE); + } } diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_10/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_10/Ballerina.toml new file mode 100644 index 0000000..d6f8340 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_10/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "yamldata_test" +name = "sample_10" +version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_10/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_10/sample.bal new file mode 100644 index 0000000..67e0098 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_10/sample.bal @@ -0,0 +1,49 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/data.yaml; + +type ArrayExpectedType record {| + @yaml:Name { + value: "first_name" + } + string firstName; + + @yaml:Name { + value: "first_name" + } + string lastName; +|}[]; + +type TupleExpectedType [record {| + @yaml:Name { + value: "first_name" + } + string firstName; + + @yaml:Name { + value: "first_name" + } + string lastName; +|}]; + +type ImmutableType readonly & ArrayExpectedType; + +type UnionType ArrayExpectedType|TupleExpectedType|ImmutableType|xml|table; + +function call(string data) returns error? { + UnionType result = check yaml:parseString(data); +} diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/Ballerina.toml new file mode 100644 index 0000000..d393c5a --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "yamldata_test" +name = "sample_9" +version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/sample.bal new file mode 100644 index 0000000..cc58e60 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/sample.bal @@ -0,0 +1,54 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/data.yaml; + +type ArrayExpectedType record {| + @yaml:Name { + value: "first_name" + } + string firstName; + + @yaml:Name { + value: "first_name" + } + string lastName; +|}[]; + +type TupleExpectedType [record {| + @yaml:Name { + value: "first_name" + } + string firstName; + + @yaml:Name { + value: "first_name" + } + string lastName; +|}]; + +type ImmutableType readonly & ArrayExpectedType; + +type UnionType ArrayExpectedType|TupleExpectedType|ImmutableType; + +function call() returns UnionType { + UnionType callResult = call(); + + return [{ + firstName: "", + lastName: "" + }]; +} diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/data/yaml/compiler/YamlDataTypeValidator.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/yaml/compiler/YamlDataTypeValidator.java index 4ac1d9d..54b00f4 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/data/yaml/compiler/YamlDataTypeValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/yaml/compiler/YamlDataTypeValidator.java @@ -46,6 +46,7 @@ import io.ballerina.compiler.syntax.tree.ModuleVariableDeclarationNode; import io.ballerina.compiler.syntax.tree.NameReferenceNode; import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; @@ -103,7 +104,9 @@ public void perform(SyntaxNodeAnalysisContext ctx) { } } - for (ModuleMemberDeclarationNode member : rootNode.members()) { + NodeList members = rootNode.members(); + for (int i = 0; i < members.size(); i++) { + ModuleMemberDeclarationNode member = members.get(i); switch (member.kind()) { case FUNCTION_DEFINITION -> processFunctionDefinitionNode((FunctionDefinitionNode) member, ctx); case MODULE_VAR_DECL -> @@ -187,7 +190,7 @@ private boolean isParseFunctionOfStringSource(ExpressionNode expressionNode) { private void validateExpectedType(TypeSymbol typeSymbol, SyntaxNodeAnalysisContext ctx) { typeSymbol.getLocation().ifPresent(location -> currentLocation = location); switch (typeSymbol.typeKind()) { - case UNION -> validateUnionType((UnionTypeSymbol) typeSymbol, typeSymbol.getLocation(), ctx); + case UNION -> validateUnionType((UnionTypeSymbol) typeSymbol, ctx); case RECORD -> validateRecordType((RecordTypeSymbol) typeSymbol, ctx); case ARRAY -> validateExpectedType(((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(), ctx); case TUPLE -> validateTupleType((TupleTypeSymbol) typeSymbol, ctx); @@ -215,35 +218,11 @@ private void validateRecordType(RecordTypeSymbol recordTypeSymbol, SyntaxNodeAna } } - private void validateUnionType(UnionTypeSymbol unionTypeSymbol, Optional location, + private void validateUnionType(UnionTypeSymbol unionTypeSymbol, SyntaxNodeAnalysisContext ctx) { - boolean isHasUnsupportedType = false; List memberTypeSymbols = unionTypeSymbol.memberTypeDescriptors(); for (TypeSymbol memberTypeSymbol : memberTypeSymbols) { - if (isSupportedUnionMemberType(getRawType(memberTypeSymbol))) { - continue; - } - isHasUnsupportedType = true; - } - - if (isHasUnsupportedType) { - reportDiagnosticInfo(ctx, location, YamlDataDiagnosticCodes.UNSUPPORTED_TYPE); - } - } - - private boolean isSupportedUnionMemberType(TypeSymbol typeSymbol) { - TypeDescKind kind = typeSymbol.typeKind(); - if (kind == TypeDescKind.TYPE_REFERENCE) { - kind = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor().typeKind(); - } - - switch (kind) { - case TABLE, XML -> { - return false; - } - default -> { - return true; - } + validateExpectedType(getRawType(memberTypeSymbol), ctx); } } @@ -342,16 +321,11 @@ private String getNameFromAnnotation(String fieldName, } private String getAnnotModuleName(AnnotationSymbol annotation) { - Optional moduleSymbol = annotation.getModule(); - if (moduleSymbol.isEmpty()) { - return ""; - } - Optional moduleName = moduleSymbol.get().getName(); - return moduleName.orElse(""); + return annotation.getModule().flatMap(Symbol::getName).orElse(""); } public static boolean isYamlImport(ModuleSymbol moduleSymbol) { - return BALLERINA.equals(moduleSymbol.id().orgName()) + return BALLERINA.equals(moduleSymbol.id().orgName()) && DATA_YAML.equals(moduleSymbol.id().moduleName()); } }