Skip to content

Commit

Permalink
SONARGO-96 Get rid of slang-checks (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
mstachniuk authored Jan 7, 2025
1 parent 6f092da commit c57d862
Show file tree
Hide file tree
Showing 176 changed files with 6,487 additions and 184 deletions.
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ sonar-plugin-api = { group = "org.sonarsource.api.plugin", name = "sonar-plugin-
sonar-analyzer-commons = { group = "org.sonarsource.analyzer-commons", name = "sonar-analyzer-commons", version.ref = "analyzer-commons" }
sonar-analyzer-test-commons = { group = "org.sonarsource.analyzer-commons", name = "sonar-analyzer-test-commons", version.ref = "analyzer-commons" }
slang-api = { group = "org.sonarsource.slang", name = "slang-api", version.ref = "slang-dependencies" }
slang-checks = { group = "org.sonarsource.slang", name = "slang-checks", version.ref = "slang-dependencies" }
slang-antlr = { group = "org.sonarsource.slang", name = "slang-antlr", version.ref = "slang-dependencies" }
slang-testing = { group = "org.sonarsource.slang", name = "slang-testing", version.ref = "slang-dependencies" }
checkstyle-import = { group = "org.sonarsource.slang", name = "checkstyle-import", version.ref = "slang-dependencies" }
minimal-json = { group = "com.eclipsesource.minimal-json", name = "minimal-json", version.ref = "minimal-json" }
sonar-plugin-api-test-fixtures = { group = "org.sonarsource.api.plugin", name = "sonar-plugin-api-test-fixtures", version.ref = "plugin-api" }
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@ rootProject.name = "sonar-go"

include(":sonar-go-to-slang")
include(":sonar-go-plugin")
include(":sonar-go-checks")
include(":sonar-go-commons")
include(":its:plugin")
include(":its:ruling")
41 changes: 41 additions & 0 deletions sonar-go-checks/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* SonarSource Go
* Copyright (C) 2018-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
plugins {
id("org.sonarsource.cloud-native.java-conventions")
id("org.sonarsource.cloud-native.code-style-conventions")
}

dependencies {
compileOnly(libs.sonar.plugin.api)

implementation(project(":sonar-go-to-slang", configuration = "goBinaries"))
implementation(libs.sonar.analyzer.commons)
implementation(libs.slang.api)

testImplementation(libs.assertj.core)
testImplementation(libs.mockito.core)
testImplementation(libs.slang.antlr)
testImplementation(libs.slang.testing)
testImplementation(libs.sonar.analyzer.test.commons)
testImplementation(libs.classgraph)
testImplementation(libs.junit.jupiter.api)
testImplementation(libs.sonar.plugin.api.impl)
testImplementation(libs.sonar.plugin.api.test.fixtures)
testImplementation(testFixtures(project(":sonar-go-commons")))

testRuntimeOnly(libs.junit.jupiter.engine)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* SonarSource Go
* Copyright (C) 2018-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.go.checks;

import java.util.ArrayList;
import java.util.List;
import org.sonarsource.slang.api.IfTree;
import org.sonarsource.slang.api.MatchCaseTree;
import org.sonarsource.slang.api.MatchTree;
import org.sonarsource.slang.api.Tree;
import org.sonarsource.slang.checks.api.CheckContext;
import org.sonarsource.slang.checks.api.InitContext;
import org.sonarsource.slang.checks.api.SlangCheck;

import static org.sonarsource.slang.utils.SyntacticEquivalence.areEquivalent;

public abstract class AbstractBranchDuplicationCheck implements SlangCheck {

protected abstract void checkDuplicatedBranches(CheckContext ctx, Tree tree, List<Tree> branches);

protected abstract void onAllIdenticalBranches(CheckContext ctx, Tree tree);

@Override
public void initialize(InitContext init) {
init.register(IfTree.class, (ctx, tree) -> {
Tree parent = ctx.parent();
if (!(parent instanceof IfTree ifTree) || tree == ifTree.thenBranch()) {
checkConditionalStructure(ctx, tree, new ConditionalStructure(tree));
}
});
init.register(MatchTree.class, (ctx, tree) -> checkConditionalStructure(ctx, tree, new ConditionalStructure(tree)));
}

protected void checkConditionalStructure(CheckContext ctx, Tree tree, ConditionalStructure conditional) {
if (conditional.allBranchesArePresent && conditional.allBranchesAreIdentical()) {
onAllIdenticalBranches(ctx, tree);
} else {
checkDuplicatedBranches(ctx, tree, conditional.branches);
}
}

public static class ConditionalStructure {

private boolean allBranchesArePresent = false;

private final List<Tree> branches = new ArrayList<>();

private ConditionalStructure(IfTree ifTree) {
branches.add(ifTree.thenBranch());
Tree elseBranch = ifTree.elseBranch();
while (elseBranch != null) {
if (elseBranch instanceof IfTree elseIf) {
branches.add(elseIf.thenBranch());
elseBranch = elseIf.elseBranch();
} else {
branches.add(elseBranch);
allBranchesArePresent = true;
elseBranch = null;
}
}
}

private ConditionalStructure(MatchTree tree) {
for (MatchCaseTree caseTree : tree.cases()) {
branches.add(caseTree.body());
if (caseTree.expression() == null) {
allBranchesArePresent = true;
}
}
}

private boolean allBranchesAreIdentical() {
return branches.size() > 1 &&
branches.stream()
.skip(1)
.allMatch(branch -> areEquivalent(branches.get(0), branch));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,22 @@
*/
package org.sonar.go.checks;

import java.util.List;
import org.sonar.check.Rule;
import org.sonarsource.slang.api.NativeTree;
import org.sonarsource.slang.api.Tree;
import org.sonarsource.slang.checks.CodeAfterJumpCheck;
import org.sonarsource.slang.checks.api.CheckContext;

import static org.sonar.go.checks.NativeKinds.LABEL;
import static org.sonar.go.checks.NativeKinds.SEMICOLON;
@Rule(key = "S3923")
public class AllBranchesIdenticalCheck extends AbstractBranchDuplicationCheck {

@Rule(key = "S1763")
public class CodeAfterJumpGoCheck extends CodeAfterJumpCheck {
@Override
protected boolean isValidAfterJump(Tree tree) {
return tree instanceof NativeTree &&
((NativeTree) tree).nativeKind().toString().contains(LABEL);
protected void checkDuplicatedBranches(CheckContext ctx, Tree tree, List<Tree> branches) {
// handled by S1871
}

@Override
protected boolean shouldIgnore(Tree tree) {
return tree instanceof NativeTree &&
((NativeTree) tree).nativeKind().toString().equals(SEMICOLON);
protected void onAllIdenticalBranches(CheckContext ctx, Tree tree) {
ctx.reportIssue(tree, "Remove this conditional structure or edit its code blocks so that they're not all the same.");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* SonarSource Go
* Copyright (C) 2018-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.go.checks;

import java.util.regex.Pattern;
import org.sonar.check.Rule;
import org.sonar.check.RuleProperty;
import org.sonarsource.slang.api.FunctionDeclarationTree;
import org.sonarsource.slang.api.IdentifierTree;
import org.sonarsource.slang.checks.api.InitContext;
import org.sonarsource.slang.checks.api.SlangCheck;

@Rule(key = "S100")
public class BadFunctionNameCheck implements SlangCheck {

@RuleProperty(
key = "format",
description = "Regular expression used to check the function names against.",
defaultValue = GoChecksConstants.GO_NAMING_DEFAULT)
public String format = GoChecksConstants.GO_NAMING_DEFAULT;

private String message(String name) {
return "Rename function \"" + name + "\" to match the regular expression " + format;
}

@Override
public void initialize(InitContext init) {
Pattern pattern = Pattern.compile(format);
init.register(FunctionDeclarationTree.class, (ctx, fnDeclarationTree) -> {
IdentifierTree name = fnDeclarationTree.name();
if (!fnDeclarationTree.isConstructor() && name != null && !pattern.matcher(name.name()).matches()) {
ctx.reportIssue(fnDeclarationTree.name(), message(name.name()));
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* SonarSource Go
* Copyright (C) 2018-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.go.checks;

import java.util.EnumMap;
import java.util.Map;
import org.sonar.check.Rule;
import org.sonarsource.slang.api.BinaryExpressionTree;
import org.sonarsource.slang.api.BinaryExpressionTree.Operator;
import org.sonarsource.slang.api.Tree;
import org.sonarsource.slang.api.UnaryExpressionTree;
import org.sonarsource.slang.checks.api.InitContext;
import org.sonarsource.slang.checks.api.SlangCheck;

import static org.sonar.go.checks.utils.ExpressionUtils.skipParentheses;

@Rule(key = "S1940")
public class BooleanInversionCheck implements SlangCheck {

private static final Map<Operator, String> OPERATORS = createOperatorsMap();

private static Map<Operator, String> createOperatorsMap() {
Map<Operator, String> operatorsMap = new EnumMap<>(Operator.class);
operatorsMap.put(Operator.EQUAL_TO, "!=");
operatorsMap.put(Operator.NOT_EQUAL_TO, "==");
operatorsMap.put(Operator.LESS_THAN, ">=");
operatorsMap.put(Operator.GREATER_THAN, "<=");
operatorsMap.put(Operator.LESS_THAN_OR_EQUAL_TO, ">");
operatorsMap.put(Operator.GREATER_THAN_OR_EQUAL_TO, "<");
return operatorsMap;
}

@Override
public void initialize(InitContext init) {
init.register(UnaryExpressionTree.class, (ctx, tree) -> {
Tree innerExpression = skipParentheses(tree.operand());
if (tree.operator() == UnaryExpressionTree.Operator.NEGATE && innerExpression instanceof BinaryExpressionTree binaryExpression) {
String oppositeOperator = OPERATORS.get(binaryExpression.operator());
if (oppositeOperator != null) {
String message = String.format("Use the opposite operator (\"%s\") instead.", oppositeOperator);
ctx.reportIssue(tree, message);
}
}
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* SonarSource Go
* Copyright (C) 2018-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.go.checks;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.go.checks.utils.ExpressionUtils;
import org.sonarsource.slang.api.BinaryExpressionTree;
import org.sonarsource.slang.api.BlockTree;
import org.sonarsource.slang.api.IfTree;
import org.sonarsource.slang.api.Tree;
import org.sonarsource.slang.api.UnaryExpressionTree;
import org.sonarsource.slang.checks.api.InitContext;
import org.sonarsource.slang.checks.api.SlangCheck;

@Rule(key = "S1125")
public class BooleanLiteralCheck implements SlangCheck {
private static final List<BinaryExpressionTree.Operator> CONDITIONAL_BINARY_OPERATORS = Arrays.asList(
BinaryExpressionTree.Operator.CONDITIONAL_AND,
BinaryExpressionTree.Operator.CONDITIONAL_OR);

private static final String MESSAGE = "Remove the unnecessary Boolean literal.";

@Override
public void initialize(InitContext init) {
init.register(IfTree.class, (ctx, ifTree) -> {
if (isIfWithMaxTwoBranches(ctx.parent(), ifTree) && !hasBlockBranch(ifTree)) {
getBooleanLiteral(ifTree.thenBranch(), ifTree.elseBranch())
.ifPresent(booleanLiteral -> ctx.reportIssue(booleanLiteral, MESSAGE));
}
});

init.register(BinaryExpressionTree.class, (ctx, binaryExprTree) -> {
if (CONDITIONAL_BINARY_OPERATORS.contains(binaryExprTree.operator())) {
getBooleanLiteral(binaryExprTree.leftOperand(), binaryExprTree.rightOperand())
.ifPresent(booleanLiteral -> ctx.reportIssue(booleanLiteral, MESSAGE));
}
});

init.register(UnaryExpressionTree.class, (ctx, unaryExprTree) -> {
if (UnaryExpressionTree.Operator.NEGATE.equals(unaryExprTree.operator())) {
getBooleanLiteral(unaryExprTree.operand())
.ifPresent(booleanLiteral -> ctx.reportIssue(booleanLiteral, MESSAGE));
}
});
}

private static boolean isIfWithMaxTwoBranches(@Nullable Tree parent, IfTree ifTree) {
boolean isElseIf = parent instanceof IfTree parentIfTree && parentIfTree.elseBranch() == ifTree;
boolean isIfElseIf = ifTree.elseBranch() instanceof IfTree;
return !isElseIf && !isIfElseIf;
}

private static boolean hasBlockBranch(IfTree ifTree) {
return ifTree.thenBranch() instanceof BlockTree || ifTree.elseBranch() instanceof BlockTree;
}

private static Optional<Tree> getBooleanLiteral(Tree... trees) {
return Arrays.stream(trees)
.map(ExpressionUtils::skipParentheses)
.filter(ExpressionUtils::isBooleanLiteral)
.findFirst();
}
}
Loading

0 comments on commit c57d862

Please sign in to comment.