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 10 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 @@ -20,6 +20,7 @@

import io.ballerina.scan.Rule;
import io.ballerina.scan.RuleKind;
import io.ballerina.scan.utils.RuleDescription;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -30,7 +31,9 @@
* @since 0.1.0
* */
enum CoreRule {
AVOID_CHECKPANIC(RuleFactory.createRule(1, "Avoid checkpanic", RuleKind.CODE_SMELL));
AVOID_CHECKPANIC(RuleFactory.createRule(1, RuleDescription.AVOID_CHECKPANIC, RuleKind.CODE_SMELL)),
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
UNUSED_FUNCTION_PARAMETERS(RuleFactory.createRule(2,
RuleDescription.UNUSED_FUNCTION_PARAMETERS, RuleKind.CODE_SMELL));
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved

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,24 @@

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.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 +45,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 +66,57 @@ 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(FunctionSignatureNode functionSignatureNode) {
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
functionSignatureNode.parameters().forEach(parameter -> {
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
if (parameter instanceof IncludedRecordParameterNode includedRecordParameterNode) {
includedRecordParameterNode.paramName().ifPresent(name -> {
reportIssueIfNodeIsUnused(name, CoreRule.UNUSED_FUNCTION_PARAMETERS);
});
} else {
reportIssueIfNodeIsUnused(parameter, CoreRule.UNUSED_FUNCTION_PARAMETERS);
}
this.visitSyntaxNode(parameter);
});
functionSignatureNode.parameters().forEach(parameterNode -> parameterNode.accept(this));
functionSignatureNode.returnTypeDesc().ifPresent(returnTypeDesc -> returnTypeDesc.accept(this));
}

@Override
public void visit(ImplicitAnonymousFunctionExpressionNode implicitAnonymousFunctionExpressionNode) {
checkUnusedParametersInImplicitFunctionExpression(implicitAnonymousFunctionExpressionNode.params());
this.visitSyntaxNode(implicitAnonymousFunctionExpressionNode.expression());
}

private void checkUnusedParametersInImplicitFunctionExpression(Node params) {
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
if (params instanceof ImplicitAnonymousFunctionParameters parameters) {
parameters.parameters().forEach(parameter -> {
reportIssueIfNodeIsUnused(parameter, CoreRule.UNUSED_FUNCTION_PARAMETERS);
});
return;
}

if (params instanceof SimpleNameReferenceNode) {
reportIssueIfNodeIsUnused(params, CoreRule.UNUSED_FUNCTION_PARAMETERS);
}
}

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();
}
}
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.utils;
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved

/**
*
* This class provides the descriptions for Ballerina static coding rules.
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
*
* @since 0.1.0
*/
public class RuleDescription {

/**
* Description for the rule that advises avoiding the use of 'checkpanic'.
*/
public static final String AVOID_CHECKPANIC = "Avoid checkpanic";

/**
* Description for the rule that warns against unused function parameters.
*/
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
public static final String UNUSED_FUNCTION_PARAMETERS = "Unused function parameters";

private RuleDescription() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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;
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved

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;

SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
public class CheckpanicAnalyzerTest extends StaticCodeAnalyzerTest {

@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, 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,
RuleDescription.AVOID_CHECKPANIC, RuleKind.CODE_SMELL);
}
}
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_PARAMETERS.rule();
Assert.assertEquals(rule.id(), "ballerina:2");
Assert.assertEquals(rule.numericId(), 2);
Assert.assertEquals(rule.description(), RuleDescription.UNUSED_FUNCTION_PARAMETERS);
Assert.assertEquals(rule.kind(), RuleKind.CODE_SMELL);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@
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.
Expand All @@ -42,33 +40,25 @@
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