diff --git a/scan-command/src/main/java/io/ballerina/scan/internal/CoreRule.java b/scan-command/src/main/java/io/ballerina/scan/internal/CoreRule.java index 0990326c..742f91c6 100644 --- a/scan-command/src/main/java/io/ballerina/scan/internal/CoreRule.java +++ b/scan-command/src/main/java/io/ballerina/scan/internal/CoreRule.java @@ -31,8 +31,15 @@ * */ enum CoreRule { AVOID_CHECKPANIC(RuleFactory.createRule(1, "Avoid checkpanic", RuleKind.CODE_SMELL)), - UNUSED_FUNCTION_PARAMETER(RuleFactory.createRule(2, - "Unused function parameter", RuleKind.CODE_SMELL)); + UNUSED_FUNCTION_PARAMETER(RuleFactory.createRule(2, "Unused function parameter", RuleKind.CODE_SMELL)), + PUBLIC_NON_ISOLATED_FUNCTION_CONSTRUCT(RuleFactory.createRule(3, + "Non isolated public function", RuleKind.CODE_SMELL)), + PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT(RuleFactory.createRule(4, + "Non isolated public method", RuleKind.CODE_SMELL)), + PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT(RuleFactory.createRule(5, + "Non isolated public class", RuleKind.CODE_SMELL)), + PUBLIC_NON_ISOLATED_OBJECT_CONSTRUCT(RuleFactory.createRule(6, + "Non isolated public object", RuleKind.CODE_SMELL)); private final Rule rule; diff --git a/scan-command/src/main/java/io/ballerina/scan/internal/StaticCodeAnalyzer.java b/scan-command/src/main/java/io/ballerina/scan/internal/StaticCodeAnalyzer.java index 782667d5..b39c7201 100644 --- a/scan-command/src/main/java/io/ballerina/scan/internal/StaticCodeAnalyzer.java +++ b/scan-command/src/main/java/io/ballerina/scan/internal/StaticCodeAnalyzer.java @@ -19,8 +19,14 @@ package io.ballerina.scan.internal; import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ObjectTypeSymbol; +import io.ballerina.compiler.api.symbols.Qualifier; import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.SymbolKind; +import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; +import io.ballerina.compiler.api.symbols.TypeSymbol; import io.ballerina.compiler.syntax.tree.CheckExpressionNode; +import io.ballerina.compiler.syntax.tree.ClassDefinitionNode; import io.ballerina.compiler.syntax.tree.ExplicitAnonymousFunctionExpressionNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.FunctionSignatureNode; @@ -29,15 +35,24 @@ import io.ballerina.compiler.syntax.tree.IncludedRecordParameterNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.NodeVisitor; import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.compiler.syntax.tree.Token; +import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; import io.ballerina.projects.Document; import io.ballerina.scan.ScannerContext; +import java.util.List; import java.util.Optional; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.ISOLATED_KEYWORD; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.PUBLIC_KEYWORD; +import static io.ballerina.scan.utils.Constants.INIT_FUNCTION; +import static io.ballerina.scan.utils.Constants.MAIN_FUNCTION; + /** * {@code StaticCodeAnalyzer} contains the logic to perform core static code analysis on Ballerina documents. * @@ -75,6 +90,10 @@ public void visit(CheckExpressionNode checkExpressionNode) { @Override public void visit(FunctionDefinitionNode functionDefinitionNode) { checkUnusedFunctionParameters(functionDefinitionNode.functionSignature()); + String functionName = functionDefinitionNode.functionName().text(); + if (!functionName.equals(MAIN_FUNCTION) && !functionName.equals(INIT_FUNCTION)) { + checkNonIsolatedPublicFunction(functionDefinitionNode); + } this.visitSyntaxNode(functionDefinitionNode); } @@ -100,6 +119,97 @@ public void visit(ImplicitAnonymousFunctionExpressionNode implicitAnonymousFunct this.visitSyntaxNode(implicitAnonymousFunctionExpressionNode.expression()); } + @Override + public void visit(ClassDefinitionNode classDefinitionNode) { + checkNonIsolatedPublicClassDefinition(classDefinitionNode); + checkNonIsolatedPublicClassMethod(classDefinitionNode); + this.visitSyntaxNode(classDefinitionNode); + } + + @Override + public void visit(TypeDefinitionNode typeDefinitionNode) { + checkNonIsolatedConstructsInTypeDefinition(typeDefinitionNode); + this.visitSyntaxNode(typeDefinitionNode); + } + + private void checkNonIsolatedConstructsInTypeDefinition(TypeDefinitionNode typeDefinitionNode) { + semanticModel.symbol(typeDefinitionNode).ifPresent(symbol -> { + if (symbol instanceof TypeDefinitionSymbol typeDefinitionSymbol) { + TypeSymbol typeSymbol = typeDefinitionSymbol.typeDescriptor(); + if (typeSymbol instanceof ObjectTypeSymbol objectTypeSymbol) { + List qualifiers = objectTypeSymbol.qualifiers(); + List typeDefQualifiers = typeDefinitionSymbol.qualifiers(); + if (hasQualifier(typeDefQualifiers, PUBLIC_KEYWORD) && + !hasQualifier(qualifiers, ISOLATED_KEYWORD)) { + reportIssue(typeDefinitionNode, CoreRule.PUBLIC_NON_ISOLATED_OBJECT_CONSTRUCT); + } + } + } + }); + } + + private void checkNonIsolatedPublicClassDefinition(ClassDefinitionNode classDefinitionNode) { + semanticModel.symbol(classDefinitionNode).ifPresent(symbol -> { + if (symbol instanceof ObjectTypeSymbol objectTypeSymbol) { + List qualifiers = objectTypeSymbol.qualifiers(); + if (isPublicIsolatedConstruct(qualifiers)) { + reportIssue(classDefinitionNode, CoreRule.PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT); + } + } + }); + } + + private void checkNonIsolatedPublicClassMethod(ClassDefinitionNode classDefinitionNode) { + semanticModel.symbol(classDefinitionNode).ifPresent(classSymbol -> { + if (classSymbol instanceof ObjectTypeSymbol objectTypeSymbol) { + boolean isPublicObjectTypeSymbol = hasQualifier(objectTypeSymbol.qualifiers(), PUBLIC_KEYWORD); + classDefinitionNode.members().forEach(member -> { + semanticModel.symbol(member).ifPresent(memberSymbol -> { + if (isPublicObjectTypeSymbol && memberSymbol.kind() == SymbolKind.METHOD) { + checkNonIsolatedPublicMethod((FunctionDefinitionNode) member); + } + }); + }); + } + }); + } + + private void checkNonIsolatedPublicMethod(FunctionDefinitionNode member) { + if (isPublicIsolatedConstruct(member.qualifierList())) { + reportIssue(member, CoreRule.PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT); + } + } + + private void checkNonIsolatedPublicFunction(FunctionDefinitionNode functionDefinitionNode) { + semanticModel.symbol(functionDefinitionNode).ifPresent(symbol -> { + if (symbol.kind() != SymbolKind.METHOD) { + NodeList qualifiers = functionDefinitionNode.qualifierList(); + if (isPublicIsolatedConstruct(qualifiers)) { + reportIssue(functionDefinitionNode, CoreRule.PUBLIC_NON_ISOLATED_FUNCTION_CONSTRUCT); + } + } + }); + } + + private boolean hasQualifier(List qualifierList, SyntaxKind qualifierValue) { + String qualifierValueStr = qualifierValue.stringValue(); + for (Qualifier qualifier : qualifierList) { + if (qualifier.getValue().equals(qualifierValueStr)) { + return true; + } + } + return false; + } + + private boolean hasQualifier(NodeList qualifierList, SyntaxKind qualifier) { + for (Token token : qualifierList) { + if (qualifier == token.kind()) { + return true; + } + } + return false; + } + private void checkUnusedFunctionParameters(FunctionSignatureNode functionSignatureNode) { functionSignatureNode.parameters().forEach(parameter -> { if (parameter instanceof IncludedRecordParameterNode includedRecordParameterNode) { @@ -127,4 +237,12 @@ private boolean isUnusedNode(Node node) { Optional symbol = semanticModel.symbol(node); return symbol.filter(value -> semanticModel.references(value).size() == 1).isPresent(); } + + private boolean isPublicIsolatedConstruct(NodeList qualifiers) { + return hasQualifier(qualifiers, PUBLIC_KEYWORD) && !hasQualifier(qualifiers, ISOLATED_KEYWORD); + } + + private boolean isPublicIsolatedConstruct(List qualifiers) { + return hasQualifier(qualifiers, PUBLIC_KEYWORD) && !hasQualifier(qualifiers, ISOLATED_KEYWORD); + } } diff --git a/scan-command/src/main/java/io/ballerina/scan/utils/Constants.java b/scan-command/src/main/java/io/ballerina/scan/utils/Constants.java index 78f8fe76..e7a07c2c 100644 --- a/scan-command/src/main/java/io/ballerina/scan/utils/Constants.java +++ b/scan-command/src/main/java/io/ballerina/scan/utils/Constants.java @@ -61,6 +61,8 @@ public class Constants { static final String RULE_KIND_COLUMN = "Rule Kind"; static final String RULE_DESCRIPTION_COLUMN = "Rule Description"; static final String[] RULE_PRIORITY_LIST = {"ballerina", "ballerinax", "wso2"}; + public static final String MAIN_FUNCTION = "main"; + public static final String INIT_FUNCTION = "init"; private Constants() { } diff --git a/scan-command/src/test/java/io/ballerina/scan/internal/CoreRuleTest.java b/scan-command/src/test/java/io/ballerina/scan/internal/CoreRuleTest.java index 1dd3c15f..ec24936a 100644 --- a/scan-command/src/test/java/io/ballerina/scan/internal/CoreRuleTest.java +++ b/scan-command/src/test/java/io/ballerina/scan/internal/CoreRuleTest.java @@ -31,10 +31,18 @@ public class CoreRuleTest { public static final String AVOID_CHECKPANIC = "Avoid checkpanic"; public static final String UNUSED_FUNCTION_PARAMETER = "Unused function parameter"; + public static final String PUBLIC_NON_ISOLATED_FUNCTION_CONSTRUCT = + "Non isolated public function"; + public static final String PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT = + "Non isolated public method"; + public static final String PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT = + "Non isolated public class"; + public static final String PUBLIC_NON_ISOLATED_OBJECT_CONSTRUCT = + "Non isolated public object"; @Test(description = "test all rules") void testAllRules() { - Assert.assertEquals(CoreRule.rules().size(), 2); + Assert.assertEquals(CoreRule.rules().size(), 6); } @Test(description = "test checkpanic rule") @@ -54,4 +62,40 @@ void testUnusedFunctionParameterRule() { Assert.assertEquals(rule.description(), UNUSED_FUNCTION_PARAMETER); Assert.assertEquals(rule.kind(), RuleKind.CODE_SMELL); } + + @Test(description = "test non isolated public functions") + void testNonIsolatedPublicFunctionConstructsRule() { + Rule rule = CoreRule.PUBLIC_NON_ISOLATED_FUNCTION_CONSTRUCT.rule(); + Assert.assertEquals(rule.id(), "ballerina:3"); + Assert.assertEquals(rule.numericId(), 3); + Assert.assertEquals(rule.description(), PUBLIC_NON_ISOLATED_FUNCTION_CONSTRUCT); + Assert.assertEquals(rule.kind(), RuleKind.CODE_SMELL); + } + + @Test(description = "test non isolated public methods") + void testNonIsolatedPublicMethodConstructsRule() { + Rule rule = CoreRule.PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT.rule(); + Assert.assertEquals(rule.id(), "ballerina:4"); + Assert.assertEquals(rule.numericId(), 4); + Assert.assertEquals(rule.description(), PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT); + Assert.assertEquals(rule.kind(), RuleKind.CODE_SMELL); + } + + @Test(description = "test non isolated public classes") + void testNonIsolatedPublicClassConstructsRule() { + Rule rule = CoreRule.PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT.rule(); + Assert.assertEquals(rule.id(), "ballerina:5"); + Assert.assertEquals(rule.numericId(), 5); + Assert.assertEquals(rule.description(), PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT); + Assert.assertEquals(rule.kind(), RuleKind.CODE_SMELL); + } + + @Test(description = "test non isolated public objects") + void testNonIsolatedPublicObjectConstructsRule() { + Rule rule = CoreRule.PUBLIC_NON_ISOLATED_OBJECT_CONSTRUCT.rule(); + Assert.assertEquals(rule.id(), "ballerina:6"); + Assert.assertEquals(rule.numericId(), 6); + Assert.assertEquals(rule.description(), PUBLIC_NON_ISOLATED_OBJECT_CONSTRUCT); + Assert.assertEquals(rule.kind(), RuleKind.CODE_SMELL); + } } diff --git a/scan-command/src/test/java/io/ballerina/scan/internal/Rule002Test.java b/scan-command/src/test/java/io/ballerina/scan/internal/Rule002Test.java index caa365e7..47faa51d 100644 --- a/scan-command/src/test/java/io/ballerina/scan/internal/Rule002Test.java +++ b/scan-command/src/test/java/io/ballerina/scan/internal/Rule002Test.java @@ -69,9 +69,9 @@ void testUnusedFunctionParameter() { UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); assertIssue(issues.get(11), documentName, 72, 19, 72, 24, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); - assertIssue(issues.get(12), documentName, 76, 18, 76, 23, "ballerina:2", 2, + assertIssue(issues.get(12), documentName, 76, 11, 76, 16, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); - assertIssue(issues.get(13), documentName, 76, 32, 76, 37, "ballerina:2", 2, + assertIssue(issues.get(13), documentName, 76, 25, 76, 30, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); assertIssue(issues.get(14), documentName, 77, 22, 77, 28, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); @@ -93,23 +93,23 @@ void testUnusedAnonymousFunctionParameter() { List issues = scannerContext.getReporter().getIssues(); Assert.assertEquals(issues.size(), 16); - assertIssue(issues.get(0), documentName, 16, 34, 16, 39, "ballerina:2", 2, + assertIssue(issues.get(0), documentName, 16, 27, 16, 32, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); - assertIssue(issues.get(1), documentName, 16, 41, 16, 46, "ballerina:2", 2, + assertIssue(issues.get(1), documentName, 16, 34, 16, 39, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); assertIssue(issues.get(2), documentName, 17, 17, 17, 24, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); - assertIssue(issues.get(3), documentName, 25, 26, 25, 31, "ballerina:2", 2, + assertIssue(issues.get(3), documentName, 25, 19, 25, 24, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); - assertIssue(issues.get(4), documentName, 27, 50, 27, 57, "ballerina:2", 2, + assertIssue(issues.get(4), documentName, 27, 43, 27, 50, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); assertIssue(issues.get(5), documentName, 29, 48, 29, 49, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); assertIssue(issues.get(6), documentName, 31, 61, 31, 66, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); - assertIssue(issues.get(7), documentName, 33, 26, 33, 31, "ballerina:2", 2, + assertIssue(issues.get(7), documentName, 33, 19, 33, 24, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); - assertIssue(issues.get(8), documentName, 33, 57, 33, 64, "ballerina:2", 2, + assertIssue(issues.get(8), documentName, 33, 50, 33, 57, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); assertIssue(issues.get(9), documentName, 39, 26, 39, 31, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); @@ -117,7 +117,7 @@ void testUnusedAnonymousFunctionParameter() { UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); assertIssue(issues.get(11), documentName, 47, 17, 47, 22, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); - assertIssue(issues.get(12), documentName, 53, 28, 53, 33, "ballerina:2", 2, + assertIssue(issues.get(12), documentName, 53, 21, 53, 26, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); assertIssue(issues.get(13), documentName, 56, 19, 56, 26, "ballerina:2", 2, UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); diff --git a/scan-command/src/test/java/io/ballerina/scan/internal/Rule003And004Test.java b/scan-command/src/test/java/io/ballerina/scan/internal/Rule003And004Test.java new file mode 100644 index 00000000..fd0de087 --- /dev/null +++ b/scan-command/src/test/java/io/ballerina/scan/internal/Rule003And004Test.java @@ -0,0 +1,81 @@ +/* + * 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. + */ + +package io.ballerina.scan.internal; + +import io.ballerina.projects.Document; +import io.ballerina.scan.Issue; +import io.ballerina.scan.RuleKind; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.List; + +/** + * Non isolated public constructs usage tests. + * + * @since 0.1.0 + */ +public class Rule003And004Test extends StaticCodeAnalyzerTest { + public static final String PUBLIC_NON_ISOLATED_FUNCTION_CONSTRUCT = + "Non isolated public function"; + public static final String PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT = + "Non isolated public method"; + public static final String PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT = + "Non isolated public class"; + + @Test(description = "test non isolated public functions analyzer") + void testNonIsolatedPublicFunctionOrMethodConstructsUsage() { + String documentName = "rule003_and_004_rules_non_isolated_public_functions_or_methods.bal"; + Document document = loadDocument(documentName); + ScannerContextImpl scannerContext = new ScannerContextImpl(List.of( + CoreRule.PUBLIC_NON_ISOLATED_FUNCTION_CONSTRUCT.rule(), + CoreRule.PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT.rule(), + CoreRule.PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT.rule())); + StaticCodeAnalyzer staticCodeAnalyzer = new StaticCodeAnalyzer(document, scannerContext, + document.module().getCompilation().getSemanticModel()); + staticCodeAnalyzer.analyze(); + + List issues = scannerContext.getReporter().getIssues(); + Assert.assertEquals(issues.size(), 12); + assertIssue(issues.get(0), documentName, 16, 0, 18, 1, "ballerina:3", 3, + PUBLIC_NON_ISOLATED_FUNCTION_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(1), documentName, 63, 4, 65, 5, "ballerina:4", 4, + PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(2), documentName, 67, 4, 69, 5, "ballerina:4", 4, + PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(3), documentName, 75, 4, 77, 5, "ballerina:4", 4, + PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(4), documentName, 89, 4, 91, 5, "ballerina:4", 4, + PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(5), documentName, 97, 4, 99, 5, "ballerina:4", 4, + PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(6), documentName, 102, 0, 122, 1, "ballerina:5", 5, + PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(7), documentName, 111, 4, 113, 5, "ballerina:4", 4, + PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(8), documentName, 133, 4, 135, 5, "ballerina:4", 4, + PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(9), documentName, 146, 0, 166, 1, "ballerina:5", 5, + PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(10), documentName, 155, 4, 157, 5, "ballerina:4", 4, + PUBLIC_NON_ISOLATED_METHOD_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(11), documentName, 180, 0, 184, 1, "ballerina:5", 5, + PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT, RuleKind.CODE_SMELL); + } +} diff --git a/scan-command/src/test/java/io/ballerina/scan/internal/Rule005And006Test.java b/scan-command/src/test/java/io/ballerina/scan/internal/Rule005And006Test.java new file mode 100644 index 00000000..545833e0 --- /dev/null +++ b/scan-command/src/test/java/io/ballerina/scan/internal/Rule005And006Test.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025, 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. + */ + +package io.ballerina.scan.internal; + +import io.ballerina.projects.Document; +import io.ballerina.scan.Issue; +import io.ballerina.scan.RuleKind; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.List; + +/** + * Non isolated public constructs usage tests. + * + * @since 0.1.0 + */ +public class Rule005And006Test extends StaticCodeAnalyzerTest { + public static final String PUBLIC_NON_ISOLATED_OBJECT_CONSTRUCT = + "Non isolated public object"; + public static final String PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT = + "Non isolated public class"; + + @Test(description = "test non isolated public class or object analyzer") + void testNonIsolatedPublicClassOrObjectConstructsUsage() { + String documentName = "rule005_and_006_rule_non_isolated_public_classes_or_objects.bal"; + Document document = loadDocument(documentName); + ScannerContextImpl scannerContext = new ScannerContextImpl(List.of( + CoreRule.PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT.rule(), + CoreRule.PUBLIC_NON_ISOLATED_OBJECT_CONSTRUCT.rule())); + StaticCodeAnalyzer staticCodeAnalyzer = new StaticCodeAnalyzer(document, scannerContext, + document.module().getCompilation().getSemanticModel()); + staticCodeAnalyzer.analyze(); + + List issues = scannerContext.getReporter().getIssues(); + Assert.assertEquals(issues.size(), 4); + assertIssue(issues.get(0), documentName, 20, 0, 22, 1, "ballerina:5", 5, + PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(1), documentName, 37, 0, 40, 2, "ballerina:6", 6, + PUBLIC_NON_ISOLATED_OBJECT_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(2), documentName, 57, 0, 60, 2, "ballerina:6", 6, + PUBLIC_NON_ISOLATED_OBJECT_CONSTRUCT, RuleKind.CODE_SMELL); + assertIssue(issues.get(3), documentName, 74, 0, 76, 1, "ballerina:5", 5, + PUBLIC_NON_ISOLATED_CLASS_CONSTRUCT, RuleKind.CODE_SMELL); + } +} diff --git a/scan-command/src/test/resources/command-outputs/unix/list-rules-output.txt b/scan-command/src/test/resources/command-outputs/unix/list-rules-output.txt index 5ad2e669..6dea19c6 100644 --- a/scan-command/src/test/resources/command-outputs/unix/list-rules-output.txt +++ b/scan-command/src/test/resources/command-outputs/unix/list-rules-output.txt @@ -1,8 +1,12 @@ Loading scan tool configurations from src/test/resources/test-resources/bal-project-with-config-file/Scan.toml - RuleID | Rule Kind | Rule Description - --------------------------------------------------------------------------------------------- + RuleID | Rule Kind | Rule Description + ------------------------------------------------------------------------------------------------ ballerina:1 | CODE_SMELL | Avoid checkpanic ballerina:2 | CODE_SMELL | Unused function parameter + ballerina:3 | CODE_SMELL | Non isolated public function + ballerina:4 | CODE_SMELL | Non isolated public method + ballerina:5 | CODE_SMELL | Non isolated public class + ballerina:6 | CODE_SMELL | Non isolated public object ballerina/example_module_static_code_analyzer:1 | CODE_SMELL | rule 1 ballerina/example_module_static_code_analyzer:2 | BUG | rule 2 ballerina/example_module_static_code_analyzer:3 | VULNERABILITY | rule 3 diff --git a/scan-command/src/test/resources/command-outputs/unix/print-rules-to-console.txt b/scan-command/src/test/resources/command-outputs/unix/print-rules-to-console.txt index 431cad6f..6dea19c6 100644 --- a/scan-command/src/test/resources/command-outputs/unix/print-rules-to-console.txt +++ b/scan-command/src/test/resources/command-outputs/unix/print-rules-to-console.txt @@ -1,8 +1,12 @@ Loading scan tool configurations from src/test/resources/test-resources/bal-project-with-config-file/Scan.toml - RuleID | Rule Kind | Rule Description - --------------------------------------------------------------------------------------------- + RuleID | Rule Kind | Rule Description + ------------------------------------------------------------------------------------------------ ballerina:1 | CODE_SMELL | Avoid checkpanic ballerina:2 | CODE_SMELL | Unused function parameter + ballerina:3 | CODE_SMELL | Non isolated public function + ballerina:4 | CODE_SMELL | Non isolated public method + ballerina:5 | CODE_SMELL | Non isolated public class + ballerina:6 | CODE_SMELL | Non isolated public object ballerina/example_module_static_code_analyzer:1 | CODE_SMELL | rule 1 ballerina/example_module_static_code_analyzer:2 | BUG | rule 2 ballerina/example_module_static_code_analyzer:3 | VULNERABILITY | rule 3 @@ -11,4 +15,4 @@ Loading scan tool configurations from src/test/resources/test-resources/bal-proj ballerinax/example_module_static_code_analyzer:3 | VULNERABILITY | rule 3 exampleOrg/example_module_static_code_analyzer:1 | CODE_SMELL | rule 1 exampleOrg/example_module_static_code_analyzer:2 | BUG | rule 2 - exampleOrg/example_module_static_code_analyzer:3 | VULNERABILITY | rule 3 \ No newline at end of file + exampleOrg/example_module_static_code_analyzer:3 | VULNERABILITY | rule 3 diff --git a/scan-command/src/test/resources/command-outputs/windows/list-rules-output.txt b/scan-command/src/test/resources/command-outputs/windows/list-rules-output.txt index e384b4ea..7fd737b3 100644 --- a/scan-command/src/test/resources/command-outputs/windows/list-rules-output.txt +++ b/scan-command/src/test/resources/command-outputs/windows/list-rules-output.txt @@ -1,8 +1,12 @@ Loading scan tool configurations from src\test\resources\test-resources\bal-project-with-config-file\Scan.toml - RuleID | Rule Kind | Rule Description - --------------------------------------------------------------------------------------------- + RuleID | Rule Kind | Rule Description + ------------------------------------------------------------------------------------------------ ballerina:1 | CODE_SMELL | Avoid checkpanic ballerina:2 | CODE_SMELL | Unused function parameter + ballerina:3 | CODE_SMELL | Non isolated public function + ballerina:4 | CODE_SMELL | Non isolated public method + ballerina:5 | CODE_SMELL | Non isolated public class + ballerina:6 | CODE_SMELL | Non isolated public object ballerina/example_module_static_code_analyzer:1 | CODE_SMELL | rule 1 ballerina/example_module_static_code_analyzer:2 | BUG | rule 2 ballerina/example_module_static_code_analyzer:3 | VULNERABILITY | rule 3 diff --git a/scan-command/src/test/resources/command-outputs/windows/print-rules-to-console.txt b/scan-command/src/test/resources/command-outputs/windows/print-rules-to-console.txt index cab063a4..7fd737b3 100644 --- a/scan-command/src/test/resources/command-outputs/windows/print-rules-to-console.txt +++ b/scan-command/src/test/resources/command-outputs/windows/print-rules-to-console.txt @@ -1,8 +1,12 @@ Loading scan tool configurations from src\test\resources\test-resources\bal-project-with-config-file\Scan.toml - RuleID | Rule Kind | Rule Description - --------------------------------------------------------------------------------------------- + RuleID | Rule Kind | Rule Description + ------------------------------------------------------------------------------------------------ ballerina:1 | CODE_SMELL | Avoid checkpanic ballerina:2 | CODE_SMELL | Unused function parameter + ballerina:3 | CODE_SMELL | Non isolated public function + ballerina:4 | CODE_SMELL | Non isolated public method + ballerina:5 | CODE_SMELL | Non isolated public class + ballerina:6 | CODE_SMELL | Non isolated public object ballerina/example_module_static_code_analyzer:1 | CODE_SMELL | rule 1 ballerina/example_module_static_code_analyzer:2 | BUG | rule 2 ballerina/example_module_static_code_analyzer:3 | VULNERABILITY | rule 3 @@ -11,4 +15,4 @@ Loading scan tool configurations from src\test\resources\test-resources\bal-proj ballerinax/example_module_static_code_analyzer:3 | VULNERABILITY | rule 3 exampleOrg/example_module_static_code_analyzer:1 | CODE_SMELL | rule 1 exampleOrg/example_module_static_code_analyzer:2 | BUG | rule 2 - exampleOrg/example_module_static_code_analyzer:3 | VULNERABILITY | rule 3 \ No newline at end of file + exampleOrg/example_module_static_code_analyzer:3 | VULNERABILITY | rule 3 diff --git a/scan-command/src/test/resources/test-resources/core-rules/rule002_unused_anonymous_func_parameters.bal b/scan-command/src/test/resources/test-resources/core-rules/rule002_unused_anonymous_func_parameters.bal index 9043b248..c5502906 100644 --- a/scan-command/src/test/resources/test-resources/core-rules/rule002_unused_anonymous_func_parameters.bal +++ b/scan-command/src/test/resources/test-resources/core-rules/rule002_unused_anonymous_func_parameters.bal @@ -14,7 +14,7 @@ // specific language governing permissions and limitations // under the License. -public function testExprFunctions(int a, int b, int c) { // warning * 2 +function testExprFunctions(int a, int b, int c) { // warning * 2 [1,2].forEach(element => ()); // warning [1,2].forEach(element => doNothing(element + c)); } @@ -23,15 +23,15 @@ function (int, int) returns int anonFunc1 = (x, y) => x + y; function (int, int) returns int anonFunc2 = function (int x, int y) returns int => x + y; -public function anonFunc3(int a) => [1,2].forEach(element => doNothing(element)); // warning +function anonFunc3(int a) => [1,2].forEach(element => doNothing(element)); // warning -public function anonFunc4(int a) => [1,2].forEach(element => doNothing(a)); // warning +function anonFunc4(int a) => [1,2].forEach(element => doNothing(a)); // warning function (int, int) returns int anonFunc5 = (x, y) => x; // warning function (int, int) returns int anonFunc6 = function (int x, int y) returns int => x; // warning -public function anonFunc7(int a, int b) => [1,2].forEach(element => doNothing(b)); // warning * 2 +function anonFunc7(int a, int b) => [1,2].forEach(element => doNothing(b)); // warning * 2 type F function (int, int) returns int; @@ -42,7 +42,7 @@ type R record { }; }; -public function testInlineFunctionDecl() { +function testInlineFunctionDecl() { F[] _ = [ (a, b) => a, // warning function(int a, int b) returns int { // warning @@ -51,7 +51,7 @@ public function testInlineFunctionDecl() { ]; } -public function main(int a, int b, int c) { // warning +function main(int a, int b, int c) { // warning _ = doNothing(a); _ = doNothing(c); [1,2].forEach((element) => ()); // warning diff --git a/scan-command/src/test/resources/test-resources/core-rules/rule002_unused_func_parameters.bal b/scan-command/src/test/resources/test-resources/core-rules/rule002_unused_func_parameters.bal index 3894afc5..e89ff062 100644 --- a/scan-command/src/test/resources/test-resources/core-rules/rule002_unused_func_parameters.bal +++ b/scan-command/src/test/resources/test-resources/core-rules/rule002_unused_func_parameters.bal @@ -74,7 +74,7 @@ function doNothing(int a) { // warning return; } -public function t(int a, int b, int c) { // warning * 2 +function t(int a, int b, int c) { // warning * 2 var fn = function(int a2, int b2) returns int => b; // warning * 2 int _ = fn(1,2); } diff --git a/scan-command/src/test/resources/test-resources/core-rules/rule003_and_004_rules_non_isolated_public_functions_or_methods.bal b/scan-command/src/test/resources/test-resources/core-rules/rule003_and_004_rules_non_isolated_public_functions_or_methods.bal new file mode 100644 index 00000000..408ba27b --- /dev/null +++ b/scan-command/src/test/resources/test-resources/core-rules/rule003_and_004_rules_non_isolated_public_functions_or_methods.bal @@ -0,0 +1,189 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 Inc. 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. + +public function a() { // warning + +} + +public isolated function a2() { + +} + +public function main() { + +} + +function b() { + +} + +function init() { + +} + +class A { + function init() { + + } + + public isolated function c2() { + + } + + public function c() { + + } + + function d() { + + } + + public isolated function main() { + + } +} + +public isolated class A2 { + public isolated function c2() { + + } + + public function init() { // warning + + } + + public function c() { // warning + + } + + function d() { + + } + + public function main() { // warning + + } +} + +public isolated class A3 { + public isolated function c2() { + + } + + public isolated function init() { + + } + + public function c() { // warning + + } + + function d() { + + } + + public function main() { // warning + + } +} + +public class A4 { // warning + function init() { + + } + + public isolated function c2() { + + } + + public function c() { // warning + + } + + function d() { + + } + + public isolated function main() { + + } +} + +public isolated service class SC4 { + function init() { + + } + + public isolated function c2() { + + } + + public function c() { // warning + + } + + function d() { + + } + + public isolated function main() { + + } +} + +public service class SC { // warning + function init() { + + } + + public isolated function c2() { + + } + + public function c() { // warning + + } + + function d() { + + } + + public isolated function main() { + + } +} + +public isolated service class SC3 { + isolated function c2() { + + } +} + +isolated service class SC6 { + isolated function c2() { + + } +} + +public service class SC5 { // warning + isolated function c2() { + + } +} + +isolated function a3() { + +} diff --git a/scan-command/src/test/resources/test-resources/core-rules/rule005_and_006_rule_non_isolated_public_classes_or_objects.bal b/scan-command/src/test/resources/test-resources/core-rules/rule005_and_006_rule_non_isolated_public_classes_or_objects.bal new file mode 100644 index 00000000..aec5978c --- /dev/null +++ b/scan-command/src/test/resources/test-resources/core-rules/rule005_and_006_rule_non_isolated_public_classes_or_objects.bal @@ -0,0 +1,77 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 Inc. 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. + +class A { + +} + +public class B { // warning + +} + +isolated class C { + +} + +public isolated class D { + +} + +type E object { + public function hash() returns int; + function hash2() returns int; +}; + +public type F object { // warning + public function hash() returns int; + function hash2() returns int; +}; + +type G isolated object { + public isolated function hash() returns int; + function hash2() returns int; +}; + +public type Hashable isolated object { + public function hash() returns int; + function hash2() returns int; +}; + +type H object { + public isolated function hash() returns int; + function hash2() returns int; +}; + +public type I object { // warning + public isolated function hash() returns int; + function hash2() returns int; +}; + +isolated service class J { + +} + +service class SA2 { + +} + +public isolated service class SA3 { + +} + +public service class SA4 { // warning + +} diff --git a/scan-command/src/test/resources/testng.xml b/scan-command/src/test/resources/testng.xml index cbdaee06..15465740 100644 --- a/scan-command/src/test/resources/testng.xml +++ b/scan-command/src/test/resources/testng.xml @@ -31,6 +31,8 @@ under the License. + +