diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..d9d3955 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# See: https://help.github.com/articles/about-codeowners/ + +# These owners will be the default owners for everything in the repo. +* @azinneera diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..1c1bfe3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,31 @@ +name: "🐞 Report a Bug" +description: Create an issue if something does not work as expected. +labels: ["Type/Bug"] +body: + - type: textarea + id: background + attributes: + label: Description + description: Please share a clear and concise description of the problem. + placeholder: Description + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: List the steps you followed when you encountered the issue. Provide sample source code to reproduce the issue where applicable. + validations: + required: true + - type: input + id: version + attributes: + label: Version + description: Enter product/component version. + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment Details (with versions) + description: Mention the environment details (OS, Client, etc.) that the product is running on. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..aa6f24c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: +- name: '📚 Documentation Issue' + about: Request a new article, missing topic, or report an issue if a topic is incorrect in the current documentation. + url: https://github.com/ballerina-platform/ballerina-dev-website/issues/new/choose +- name: General Question + url: https://stackoverflow.com/questions/tagged/ballerina + about: "If you have a question then please ask on Stack Overflow using the #ballerina tag." +- name: Chat on Ballerina Discord Channel + url: https://discord.gg/ballerinalang + about: "Chat about anything else with the community." diff --git a/.github/ISSUE_TEMPLATE/improvement.yml b/.github/ISSUE_TEMPLATE/improvement.yml new file mode 100644 index 0000000..053bfa8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/improvement.yml @@ -0,0 +1,25 @@ +name: "🚀 Improvement Request" +description: Suggest an improvement to the product. +labels: ["Type/Improvement"] +body: + - type: textarea + id: limitation + attributes: + label: Current Limitation + description: Describe the the current limitation. + validations: + required: true + - type: textarea + id: suggestion + attributes: + label: Suggested Improvement + description: Describe the the improvement you suggest. + validations: + required: true + - type: input + id: version + attributes: + label: Version + description: Enter component version. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/new-feature.yml b/.github/ISSUE_TEMPLATE/new-feature.yml new file mode 100644 index 0000000..39dd56f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new-feature.yml @@ -0,0 +1,32 @@ +name: "💡 New Feature Request" +description: Suggest new functionality and features for the product. +labels: ["Type/NewFeature"] +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What is the problem this feature will solve? + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: Describe the solution you'd like to have. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives + description: Describe any alternatives have you considered + validations: + required: false + - type: input + id: version + attributes: + label: Version + description: Enter product/component version. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/task.yml b/.github/ISSUE_TEMPLATE/task.yml new file mode 100644 index 0000000..ff238a0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/task.yml @@ -0,0 +1,18 @@ +name: "✍️ Create a Task" +description: Create a new task. +labels: ["Type/Task"] +body: + - type: textarea + id: description + attributes: + label: Description + description: A clear description of what needs to be done. + validations: + required: true + - type: input + id: version + attributes: + label: Version + description: Enter product/component version. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/type_bug.md b/.github/ISSUE_TEMPLATE/type_bug.md deleted file mode 100644 index 4124685..0000000 --- a/.github/ISSUE_TEMPLATE/type_bug.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: "Bug Report" -about: "Report a bug if something is not working as expected" -labels: 'Type/Bug' - ---- - -**Description:** - - - -**Steps to reproduce:** - -**Affected Versions:** - -**OS, DB, other environment details and versions:** - -**Related Issues (optional):** - - - -**Suggested Labels (optional):** - - - -**Suggested Assignees (optional):** - - \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/type_improvement.md b/.github/ISSUE_TEMPLATE/type_improvement.md deleted file mode 100644 index 46df96a..0000000 --- a/.github/ISSUE_TEMPLATE/type_improvement.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: "Improvement Request" -about: "Create an improvement request for an existing feature" -labels: 'Type/Improvement' - ---- - -**Description:** - - - -**Describe your problem(s)** - -**Describe your solution(s)** - -**Related Issues (optional):** - - - -**Suggested Labels (optional):** - - - -**Suggested Assignees (optional):** - - \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/type_new_feature.md b/.github/ISSUE_TEMPLATE/type_new_feature.md deleted file mode 100644 index 45c55d2..0000000 --- a/.github/ISSUE_TEMPLATE/type_new_feature.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: "New Feature Request" -about: "Create a new feature request" -labels: 'Type/NewFeature' - ---- - -**Description:** - - - -**Describe your problem(s)** - -**Describe your solution(s)** - -**Related Issues (optional):** - - - -**Suggested Labels (optional):** - - - -**Suggested Assignees (optional):** - - \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/type_task.md b/.github/ISSUE_TEMPLATE/type_task.md deleted file mode 100644 index bcec65d..0000000 --- a/.github/ISSUE_TEMPLATE/type_task.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: "Task" -about: "Create a task which you want to keep track" -labels: 'Type/Task' - ---- - -**Description:** - - - -**Describe your task(s)** - -**Related Issues (optional):** - - - -**Suggested Labels (optional):** - - - -**Suggested Assignees (optional):** - - \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 5f6a820..19d07b4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ enterprisePluginVersion=3.18.1 # Dependency versions picoCLIVersion=4.7.5 gsonVersion=2.10.1 -ballerinaLangVersion=2201.10.1 +ballerinaLangVersion=2201.10.2 puppycrawlCheckstyleVersion=10.12.1 apacheCommonsLang3Version=3.0 commonsIoVersion=2.15.1 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 211049e..0990326 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 @@ -30,7 +30,9 @@ * @since 0.1.0 * */ enum CoreRule { - AVOID_CHECKPANIC(RuleFactory.createRule(1, "Avoid checkpanic", RuleKind.CODE_SMELL)); + AVOID_CHECKPANIC(RuleFactory.createRule(1, "Avoid checkpanic", RuleKind.CODE_SMELL)), + UNUSED_FUNCTION_PARAMETER(RuleFactory.createRule(2, + "Unused function parameter", RuleKind.CODE_SMELL)); private final Rule rule; diff --git a/scan-command/src/main/java/io/ballerina/scan/internal/ProjectAnalyzer.java b/scan-command/src/main/java/io/ballerina/scan/internal/ProjectAnalyzer.java index 2b60df4..84b7867 100644 --- a/scan-command/src/main/java/io/ballerina/scan/internal/ProjectAnalyzer.java +++ b/scan-command/src/main/java/io/ballerina/scan/internal/ProjectAnalyzer.java @@ -22,6 +22,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import io.ballerina.compiler.api.SemanticModel; import io.ballerina.projects.CompilerPluginCache; import io.ballerina.projects.Document; import io.ballerina.projects.DocumentConfig; @@ -111,9 +112,10 @@ List analyze(List inbuiltRules) { } private Consumer analyzeDocument(Module module, ScannerContextImpl scannerContext) { + SemanticModel semanticModel = module.getCompilation().getSemanticModel(); return documentId -> { Document document = module.document(documentId); - StaticCodeAnalyzer analyzer = new StaticCodeAnalyzer(document, scannerContext); + StaticCodeAnalyzer analyzer = new StaticCodeAnalyzer(document, scannerContext, semanticModel); analyzer.analyze(); }; } 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 d7cf2c0..782667d 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 @@ -18,14 +18,26 @@ package io.ballerina.scan.internal; +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.syntax.tree.CheckExpressionNode; +import io.ballerina.compiler.syntax.tree.ExplicitAnonymousFunctionExpressionNode; +import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.FunctionSignatureNode; +import io.ballerina.compiler.syntax.tree.ImplicitAnonymousFunctionExpressionNode; +import io.ballerina.compiler.syntax.tree.ImplicitAnonymousFunctionParameters; +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.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.projects.Document; import io.ballerina.scan.ScannerContext; +import java.util.Optional; + /** * {@code StaticCodeAnalyzer} contains the logic to perform core static code analysis on Ballerina documents. * @@ -35,11 +47,13 @@ class StaticCodeAnalyzer extends NodeVisitor { private final Document document; private final SyntaxTree syntaxTree; private final ScannerContext scannerContext; + private final SemanticModel semanticModel; - StaticCodeAnalyzer(Document document, ScannerContextImpl scannerContext) { + StaticCodeAnalyzer(Document document, ScannerContextImpl scannerContext, SemanticModel semanticModel) { this.document = document; this.syntaxTree = document.syntaxTree(); this.scannerContext = scannerContext; + this.semanticModel = semanticModel; } void analyze() { @@ -54,8 +68,63 @@ void analyze() { @Override public void visit(CheckExpressionNode checkExpressionNode) { if (checkExpressionNode.checkKeyword().kind().equals(SyntaxKind.CHECKPANIC_KEYWORD)) { - scannerContext.getReporter().reportIssue(document, checkExpressionNode.location(), - CoreRule.AVOID_CHECKPANIC.rule()); + reportIssue(checkExpressionNode, CoreRule.AVOID_CHECKPANIC); + } + } + + @Override + public void visit(FunctionDefinitionNode functionDefinitionNode) { + checkUnusedFunctionParameters(functionDefinitionNode.functionSignature()); + this.visitSyntaxNode(functionDefinitionNode); + } + + @Override + public void visit(ExplicitAnonymousFunctionExpressionNode explicitAnonymousFunctionExpressionNode) { + checkUnusedFunctionParameters(explicitAnonymousFunctionExpressionNode.functionSignature()); + this.visitSyntaxNode(explicitAnonymousFunctionExpressionNode); + } + + @Override + public void visit(ImplicitAnonymousFunctionExpressionNode implicitAnonymousFunctionExpressionNode) { + Node params = implicitAnonymousFunctionExpressionNode.params(); + if (params instanceof ImplicitAnonymousFunctionParameters parameters) { + parameters.parameters().forEach(parameter -> { + reportIssueIfNodeIsUnused(parameter, CoreRule.UNUSED_FUNCTION_PARAMETER); + }); + return; } + if (params instanceof SimpleNameReferenceNode) { + reportIssueIfNodeIsUnused(params, CoreRule.UNUSED_FUNCTION_PARAMETER); + } + + this.visitSyntaxNode(implicitAnonymousFunctionExpressionNode.expression()); + } + + private void checkUnusedFunctionParameters(FunctionSignatureNode functionSignatureNode) { + functionSignatureNode.parameters().forEach(parameter -> { + if (parameter instanceof IncludedRecordParameterNode includedRecordParameterNode) { + includedRecordParameterNode.paramName().ifPresent(name -> { + reportIssueIfNodeIsUnused(name, CoreRule.UNUSED_FUNCTION_PARAMETER); + }); + } else { + reportIssueIfNodeIsUnused(parameter, CoreRule.UNUSED_FUNCTION_PARAMETER); + } + this.visitSyntaxNode(parameter); + }); + } + + private void reportIssueIfNodeIsUnused(Node node, CoreRule coreRule) { + if (isUnusedNode(node)) { + reportIssue(node, coreRule); + } + } + + private void reportIssue(Node node, CoreRule coreRule) { + scannerContext.getReporter().reportIssue(document, node.location(), coreRule.rule()); + } + + private boolean isUnusedNode(Node node) { + Optional symbol = semanticModel.symbol(node); + return symbol.filter(value -> semanticModel.references(value).size() == 1).isPresent(); } } 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 212b359..1dd3c15 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 @@ -29,9 +29,12 @@ * @since 0.1.0 */ public class CoreRuleTest { + public static final String AVOID_CHECKPANIC = "Avoid checkpanic"; + public static final String UNUSED_FUNCTION_PARAMETER = "Unused function parameter"; + @Test(description = "test all rules") void testAllRules() { - Assert.assertEquals(CoreRule.rules().size(), 1); + Assert.assertEquals(CoreRule.rules().size(), 2); } @Test(description = "test checkpanic rule") @@ -39,7 +42,16 @@ void testCheckpanicRule() { Rule rule = CoreRule.AVOID_CHECKPANIC.rule(); Assert.assertEquals(rule.id(), "ballerina:1"); Assert.assertEquals(rule.numericId(), 1); - Assert.assertEquals(rule.description(), "Avoid checkpanic"); + Assert.assertEquals(rule.description(), AVOID_CHECKPANIC); + Assert.assertEquals(rule.kind(), RuleKind.CODE_SMELL); + } + + @Test(description = "test unused function parameters test") + void testUnusedFunctionParameterRule() { + Rule rule = CoreRule.UNUSED_FUNCTION_PARAMETER.rule(); + Assert.assertEquals(rule.id(), "ballerina:2"); + Assert.assertEquals(rule.numericId(), 2); + Assert.assertEquals(rule.description(), UNUSED_FUNCTION_PARAMETER); Assert.assertEquals(rule.kind(), RuleKind.CODE_SMELL); } } diff --git a/scan-command/src/test/java/io/ballerina/scan/internal/Rule001Test.java b/scan-command/src/test/java/io/ballerina/scan/internal/Rule001Test.java new file mode 100644 index 0000000..315316a --- /dev/null +++ b/scan-command/src/test/java/io/ballerina/scan/internal/Rule001Test.java @@ -0,0 +1,51 @@ +/* + * 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; + +/** + * Checkpanic usage analyzer tests. + * + * @since 0.1.0 + */ +public class Rule001Test extends StaticCodeAnalyzerTest { + public static final String AVOID_CHECKPANIC = "Avoid checkpanic"; + + @Test(description = "test checkpanic analyzer") + void testCheckpanicUsage() { + String documentName = "rule001_rule_checkpanic.bal"; + Document document = loadDocument(documentName); + ScannerContextImpl scannerContext = new ScannerContextImpl(List.of(CoreRule.AVOID_CHECKPANIC.rule())); + StaticCodeAnalyzer staticCodeAnalyzer = new StaticCodeAnalyzer(document, + scannerContext, document.module().getCompilation().getSemanticModel()); + staticCodeAnalyzer.analyze(); + List issues = scannerContext.getReporter().getIssues(); + Assert.assertEquals(issues.size(), 1); + + assertIssue(issues.get(0), documentName, 20, 17, 20, 39, "ballerina:1", 1, + AVOID_CHECKPANIC, 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 new file mode 100644 index 0000000..caa365e --- /dev/null +++ b/scan-command/src/test/java/io/ballerina/scan/internal/Rule002Test.java @@ -0,0 +1,129 @@ +/* + * 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; + +/** + * Unused function parameters analyzer tests. + * + * @since 0.1.0 + */ +public class Rule002Test extends StaticCodeAnalyzerTest { + public static final String UNUSED_FUNCTION_PARAMETER = "Unused function parameter"; + + @Test(description = "test unused function parameters analyzer") + void testUnusedFunctionParameter() { + String documentName = "rule002_unused_func_parameters.bal"; + Document document = loadDocument(documentName); + ScannerContextImpl scannerContext = new ScannerContextImpl(List.of(CoreRule.UNUSED_FUNCTION_PARAMETER.rule())); + StaticCodeAnalyzer staticCodeAnalyzer = new StaticCodeAnalyzer(document, + scannerContext, document.module().getCompilation().getSemanticModel()); + staticCodeAnalyzer.analyze(); + List issues = scannerContext.getReporter().getIssues(); + Assert.assertEquals(issues.size(), 17); + + assertIssue(issues.get(0), documentName, 28, 29, 28, 34, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(1), documentName, 36, 29, 36, 38, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(2), documentName, 40, 29, 40, 38, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(3), documentName, 42, 22, 42, 27, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(4), documentName, 42, 29, 42, 37, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(5), documentName, 44, 22, 44, 27, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(6), documentName, 44, 29, 44, 37, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(7), documentName, 53, 33, 53, 38, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(8), documentName, 57, 33, 57, 42, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(9), documentName, 61, 26, 61, 31, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(10), documentName, 61, 33, 61, 41, "ballerina:2", 2, + 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, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(13), documentName, 76, 32, 76, 37, "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); + assertIssue(issues.get(15), documentName, 77, 30, 77, 36, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(16), documentName, 83, 44, 83, 63, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + } + + @Test(description = "test unused anonymous function parameters analyzer") + void testUnusedAnonymousFunctionParameter() { + String documentName = "rule002_unused_anonymous_func_parameters.bal"; + Document document = loadDocument(documentName); + ScannerContextImpl scannerContext = new ScannerContextImpl(List.of(CoreRule.UNUSED_FUNCTION_PARAMETER.rule())); + StaticCodeAnalyzer staticCodeAnalyzer = new StaticCodeAnalyzer(document, + scannerContext, document.module().getCompilation() + .getSemanticModel()); + staticCodeAnalyzer.analyze(); + List issues = scannerContext.getReporter().getIssues(); + Assert.assertEquals(issues.size(), 16); + + assertIssue(issues.get(0), documentName, 16, 34, 16, 39, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(1), documentName, 16, 41, 16, 46, "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, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(4), documentName, 27, 50, 27, 57, "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, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(8), documentName, 33, 57, 33, 64, "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); + assertIssue(issues.get(10), documentName, 46, 12, 46, 13, "ballerina:2", 2, + 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, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(13), documentName, 56, 19, 56, 26, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(14), documentName, 60, 19, 60, 24, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + assertIssue(issues.get(15), documentName, 66, 46, 66, 47, "ballerina:2", 2, + UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL); + } +} diff --git a/scan-command/src/test/java/io/ballerina/scan/internal/StaticCodeAnalyzerTest.java b/scan-command/src/test/java/io/ballerina/scan/internal/StaticCodeAnalyzerTest.java index 242b11e..32faf32 100644 --- a/scan-command/src/test/java/io/ballerina/scan/internal/StaticCodeAnalyzerTest.java +++ b/scan-command/src/test/java/io/ballerina/scan/internal/StaticCodeAnalyzerTest.java @@ -29,46 +29,36 @@ import io.ballerina.scan.Source; import io.ballerina.tools.text.LineRange; import org.testng.Assert; -import org.testng.annotations.Test; import java.nio.file.Path; -import java.util.List; /** - * Core analyzer tests. + * Static code analyzer test. * * @since 0.1.0 */ public class StaticCodeAnalyzerTest extends BaseTest { private final Path coreRuleBalFiles = testResources.resolve("test-resources").resolve("core-rules"); - private Document loadDocument(String documentName) { + Document loadDocument(String documentName) { Project project = SingleFileProject.load(coreRuleBalFiles.resolve(documentName)); Module defaultModule = project.currentPackage().getDefaultModule(); return defaultModule.document(defaultModule.documentIds().iterator().next()); } - @Test(description = "test checkpanic analyzer") - void testCheckpanicAnalyzer() { - String documentName = "rule_checkpanic.bal"; - Document document = loadDocument(documentName); - ScannerContextImpl scannerContext = new ScannerContextImpl(List.of(CoreRule.AVOID_CHECKPANIC.rule())); - StaticCodeAnalyzer staticCodeAnalyzer = new StaticCodeAnalyzer(document, scannerContext); - staticCodeAnalyzer.analyze(); - List issues = scannerContext.getReporter().getIssues(); - Assert.assertEquals(issues.size(), 1); - Issue issue = issues.get(0); + void assertIssue(Issue issue, String documentName, int startLine, int startOffset, int endLine, int endOffset, + String ruleId, int numericId, String description, RuleKind ruleKind) { Assert.assertEquals(issue.source(), Source.BUILT_IN); LineRange location = issue.location().lineRange(); Assert.assertEquals(location.fileName(), documentName); - Assert.assertEquals(location.startLine().line(), 20); - Assert.assertEquals(location.startLine().offset(), 17); - Assert.assertEquals(location.endLine().line(), 20); - Assert.assertEquals(location.endLine().offset(), 39); + Assert.assertEquals(location.startLine().line(), startLine); + Assert.assertEquals(location.startLine().offset(), startOffset); + Assert.assertEquals(location.endLine().line(), endLine); + Assert.assertEquals(location.endLine().offset(), endOffset); Rule rule = issue.rule(); - Assert.assertEquals(rule.id(), "ballerina:1"); - Assert.assertEquals(rule.numericId(), 1); - Assert.assertEquals(rule.description(), "Avoid checkpanic"); - Assert.assertEquals(rule.kind(), RuleKind.CODE_SMELL); + Assert.assertEquals(rule.id(), ruleId); + Assert.assertEquals(rule.numericId(), numericId); + Assert.assertEquals(rule.description(), description); + Assert.assertEquals(rule.kind(), ruleKind); } } 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 991c7a8..5ad2e66 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,7 +1,8 @@ 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/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 51bcff0..431cad6 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,7 +1,8 @@ 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/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/list-rules-output.txt b/scan-command/src/test/resources/command-outputs/windows/list-rules-output.txt index e31d510..e384b4e 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,7 +1,8 @@ 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/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 9b4032f..cab063a 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,7 +1,8 @@ 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/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/test-resources/core-rules/rule_checkpanic.bal b/scan-command/src/test/resources/test-resources/core-rules/rule001_rule_checkpanic.bal similarity index 100% rename from scan-command/src/test/resources/test-resources/core-rules/rule_checkpanic.bal rename to scan-command/src/test/resources/test-resources/core-rules/rule001_rule_checkpanic.bal 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 new file mode 100644 index 0000000..9043b24 --- /dev/null +++ b/scan-command/src/test/resources/test-resources/core-rules/rule002_unused_anonymous_func_parameters.bal @@ -0,0 +1,67 @@ +// 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 testExprFunctions(int a, int b, int c) { // warning * 2 + [1,2].forEach(element => ()); // warning + [1,2].forEach(element => doNothing(element + c)); +} + +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 + +public 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 + +type F function (int, int) returns int; + +type R record { + F f = (a, b) => a; // BUG: https://github.com/ballerina-platform/ballerina-lang/issues/43474 + F anotherF = function(int a, int b) returns int { // warning + return b; + }; +}; + +public function testInlineFunctionDecl() { + F[] _ = [ + (a, b) => a, // warning + function(int a, int b) returns int { // warning + return b; + } + ]; +} + +public function main(int a, int b, int c) { // warning + _ = doNothing(a); + _ = doNothing(c); + [1,2].forEach((element) => ()); // warning + [1,2].forEach((element) => doNothing(element)); +} + +function doNothing(any a) { // warning + return; +} + +function (int a , int b) returns int v = (x, y) => x + y; + +function (int a , int b) returns int v2 = (x, y) => x; // 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 new file mode 100644 index 0000000..3894afc --- /dev/null +++ b/scan-command/src/test/resources/test-resources/core-rules/rule002_unused_func_parameters.bal @@ -0,0 +1,91 @@ +// 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. + +import ballerina/http; + +type TestObjType object { + function t(int a, int a2) returns int; +}; + +function test(int a, int b) returns int { + return a + b; +} + +function test2(int a, int b) returns int => a + b; + +function test3(int a, int b, int c) returns int { // warning + return a + b; +} + +function test4(int a, int b, int c = 3) returns int { + return a + b + c; +} + +function test5(int a, int b, int c = 3) returns int { //warning + return a + b; +} + +function test6(int a, int b, int c = 3) returns int => a + b; // warning + +function test7(int a, int b, int... c) returns string => a.toString(); // warning * 2 + +function test8(int a, int b, int... c) returns string { // warning * 2 + return a.toString(); +} + +class A { + function test(int a, int b) returns int { + return a + b; + } + + function test3(int a, int b, int c) returns int { // warning + return a + b; + } + + function test6(int a, int b, int c = 3) returns int { // warning + return a + b; + } + + function test7(int a, int b, int... c) returns string => a.toString(); // warning * 2 +} + +service /a on new http:Listener(8080) { + resource function get test(int a, int b) returns int { + return a + b; + } + + resource function get test2(int a, int b) returns int => a + b; +} + +function doNothing(int a) { // warning + return; +} + +public 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); +} + +type IncludedRecord record {int a;}; + +function testIncludedParams(*IncludedRecord includedRecordParam) {// warning + return; +} + +function testIncludedParams2(*IncludedRecord includedRecordParam) { + _ = includedRecordParam; + return; +} diff --git a/scan-command/src/test/resources/testng.xml b/scan-command/src/test/resources/testng.xml index 2ec7177..cbdaee0 100644 --- a/scan-command/src/test/resources/testng.xml +++ b/scan-command/src/test/resources/testng.xml @@ -29,7 +29,8 @@ under the License. - + + diff --git a/scan-command/tool-scan/Ballerina.toml b/scan-command/tool-scan/Ballerina.toml index 190051a..72c0113 100644 --- a/scan-command/tool-scan/Ballerina.toml +++ b/scan-command/tool-scan/Ballerina.toml @@ -2,7 +2,7 @@ org = "ballerina" name = "tool_scan" version = "0.1.0" -distribution = "2201.10.1" +distribution = "2201.10.2" authors = ["Ballerina"] keywords = ["scan", "static code analysis"] license = ["Apache-2.0"] diff --git a/scan-command/tool-scan/Dependencies.toml b/scan-command/tool-scan/Dependencies.toml index b9ab987..0137f0f 100644 --- a/scan-command/tool-scan/Dependencies.toml +++ b/scan-command/tool-scan/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.1" +distribution-version = "2201.10.2" [[package]] org = "ballerina"