Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add static rule for unused Function parameters #24

Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
eb08ccc
Create CODEOWNERS file
anupama-pathirage Sep 23, 2024
c5a4bcf
Merge pull request #22 from ballerina-platform/anupama-pathirage-patch-1
SasinduDilshara Sep 24, 2024
ddba9f1
Update CODEOWNERS
anupama-pathirage Oct 1, 2024
4fe2e4e
Merge pull request #23 from ballerina-platform/anupama-pathirage-patch-2
ThisaruGuruge Oct 1, 2024
3f0b233
Add static rule implementation for unused func params
SasinduDilshara Oct 2, 2024
fc3c5b6
Update scan cmd tests with unused function args test
SasinduDilshara Oct 8, 2024
a92d47c
refactor unused function parameter test file
SasinduDilshara Oct 8, 2024
ba32fc2
Add warning comments test files in unused function parameter rule
SasinduDilshara Oct 8, 2024
bba45c4
Update assertion text files for windows tests
SasinduDilshara Oct 8, 2024
88ed2cf
Add analyzer for anonymous function parameters
SasinduDilshara Oct 9, 2024
797b59f
Remove unused imports in static code rule tests
SasinduDilshara Oct 9, 2024
927847c
Refactor the unused function parameter analyzer
SasinduDilshara Oct 9, 2024
ea70e3a
Merge branch 'static-code-analysis-tool' of https://github.com/baller…
SasinduDilshara Oct 9, 2024
7b6c4d7
Refactor the tests in unused function parameter static rule
SasinduDilshara Oct 10, 2024
cb595f5
Update tests in function parameter rules
SasinduDilshara Oct 10, 2024
e4bcc28
Rename unused function parameter analyzer rule description
SasinduDilshara Oct 11, 2024
cc3f86b
Update the checkpanic expression test class name
SasinduDilshara Oct 11, 2024
2ef4c57
Rename the static code analysis test files
SasinduDilshara Oct 11, 2024
301a9ec
Add issue template
anupama-pathirage Oct 21, 2024
8b67aa0
Merge pull request #26 from ballerina-platform/addtemplate
keizer619 Oct 23, 2024
9fab3e8
Change the test file names in rule-002
SasinduDilshara Oct 30, 2024
5d1abf5
Merge branch 'main' of https://github.com/ballerina-platform/static-c…
SasinduDilshara Oct 30, 2024
833c0b3
Update ballerina version into 2201.10.2
SasinduDilshara Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ List<Issue> analyze(List<Rule> inbuiltRules) {
private Consumer<DocumentId> analyzeDocument(Module module, ScannerContextImpl scannerContext) {
return documentId -> {
Document document = module.document(documentId);
StaticCodeAnalyzer analyzer = new StaticCodeAnalyzer(document, scannerContext);
StaticCodeAnalyzer analyzer = new StaticCodeAnalyzer(document, scannerContext,
module.getCompilation().getSemanticModel());
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
analyzer.analyze();
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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() {
Expand All @@ -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> symbol = semanticModel.symbol(node);
return symbol.filter(value -> semanticModel.references(value).size() == 1).isPresent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import io.ballerina.scan.Rule;
import io.ballerina.scan.RuleKind;
import io.ballerina.scan.utils.RuleDescription;
import org.testng.Assert;
import org.testng.annotations.Test;

Expand All @@ -31,15 +32,24 @@
public class CoreRuleTest {
@Test(description = "test all rules")
void testAllRules() {
Assert.assertEquals(CoreRule.rules().size(), 1);
Assert.assertEquals(CoreRule.rules().size(), 2);
}

@Test(description = "test checkpanic rule")
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(), RuleDescription.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(), RuleDescription.UNUSED_FUNCTION_PARAMETER);
Assert.assertEquals(rule.kind(), RuleKind.CODE_SMELL);
}
}
51 changes: 51 additions & 0 deletions scan-command/src/test/java/io/ballerina/scan/internal/Rule001.java
Original file line number Diff line number Diff line change
@@ -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 io.ballerina.scan.utils.RuleDescription;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.List;

/**
* Checkpanic expression analyzer tests.
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
*
* @since 0.1.0
*/
public class Rule001 extends StaticCodeAnalyzerTest {
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved

@Test(description = "test checkpanic analyzer")
void testCheckpanicAnalyzer() {
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
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<Issue> issues = scannerContext.getReporter().getIssues();
Assert.assertEquals(issues.size(), 1);

assertIssue(issues.get(0), documentName, 20, 17, 20, 39, "ballerina:1", 1,
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
RuleDescription.AVOID_CHECKPANIC, RuleKind.CODE_SMELL);
}
}
111 changes: 111 additions & 0 deletions scan-command/src/test/java/io/ballerina/scan/internal/Rule002.java
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package io.ballerina.scan.internal;

import io.ballerina.projects.Document;
import io.ballerina.scan.Issue;
import io.ballerina.scan.RuleKind;
import io.ballerina.scan.utils.RuleDescription;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.List;

/**
* Unused function parameters analyzer tests.
*
* @since 0.1.0
*/
public class Rule002 extends StaticCodeAnalyzerTest {

@Test(description = "test unused function parameters analyzer")
void testUnusedFunctionParameterAnalyzer() {
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
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<Issue> issues = scannerContext.getReporter().getIssues();
Assert.assertEquals(issues.size(), 17);

assertIssue(issues.get(0), documentName, 28, 29, 28, 34, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(1), documentName, 36, 29, 36, 38, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(2), documentName, 40, 29, 40, 38, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(3), documentName, 42, 22, 42, 27, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(4), documentName, 42, 29, 42, 37, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(5), documentName, 44, 22, 44, 27, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(6), documentName, 44, 29, 44, 37, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(7), documentName, 53, 33, 53, 38, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(8), documentName, 57, 33, 57, 42, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(9), documentName, 61, 26, 61, 31, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(10), documentName, 61, 33, 61, 41, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(11), documentName, 72, 19, 72, 24, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(12), documentName, 76, 18, 76, 23, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(13), documentName, 76, 32, 76, 37, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(14), documentName, 77, 22, 77, 28, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(15), documentName, 77, 30, 77, 36, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(16), documentName, 83, 44, 83, 63, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
}

@Test(description = "test unused anonymous function parameters analyzer")
void testUnusedAnonymousFunctionParameterAnalyzer() {
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<Issue> issues = scannerContext.getReporter().getIssues();
Assert.assertEquals(issues.size(), 16);

assertIssue(issues.get(0), documentName, 16, 34, 16, 39, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(1), documentName, 16, 41, 16, 46, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(2), documentName, 17, 17, 17, 24, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(3), documentName, 25, 26, 25, 31, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(4), documentName, 27, 50, 27, 57, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(5), documentName, 29, 48, 29, 49, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(6), documentName, 31, 61, 31, 66, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(7), documentName, 33, 26, 33, 31, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(8), documentName, 33, 57, 33, 64, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(9), documentName, 39, 26, 39, 31, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(10), documentName, 46, 12, 46, 13, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(11), documentName, 47, 17, 47, 22, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(12), documentName, 53, 28, 53, 33, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(13), documentName, 56, 19, 56, 26, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(14), documentName, 60, 19, 60, 24, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
assertIssue(issues.get(15), documentName, 66, 46, 66, 47, "ballerina:2", 2,
RuleDescription.UNUSED_FUNCTION_PARAMETER, RuleKind.CODE_SMELL);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Issue> 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);
}
}
Loading
Loading