Skip to content

Commit

Permalink
SONARJAVA-4609 [Custom Rules] CheckRegistrar classes can register che…
Browse files Browse the repository at this point in the history
…ck instances, default quality profile and AutoScan (#4470)
  • Loading branch information
alban-auzeill authored Sep 22, 2023
1 parent 1947bdb commit 3d80c6c
Show file tree
Hide file tree
Showing 29 changed files with 912 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,45 @@
package org.sonar.samples.java;

import org.junit.jupiter.api.Test;
import org.sonar.plugins.java.api.CheckRegistrar;
import org.sonar.api.rule.RuleKey;
import org.sonar.java.checks.verifier.TestCheckRegistrarContext;

import static org.assertj.core.api.Assertions.assertThat;

class MyJavaFileCheckRegistrarTest {

@Test
void checkNumberRules() {
CheckRegistrar.RegistrarContext context = new CheckRegistrar.RegistrarContext();
void checkRegisteredRulesKeysAndClasses() {
TestCheckRegistrarContext context = new TestCheckRegistrarContext();

MyJavaFileCheckRegistrar registrar = new MyJavaFileCheckRegistrar();
registrar.register(context);

assertThat(context.checkClasses()).hasSize(8);
assertThat(context.testCheckClasses()).hasSize(1);
assertThat(context.mainRuleKeys).extracting(RuleKey::toString).containsExactly(
"mycompany-java:SpringControllerRequestMappingEntity",
"mycompany-java:AvoidAnnotation",
"mycompany-java:AvoidBrandInMethodNames",
"mycompany-java:AvoidMethodDeclaration",
"mycompany-java:AvoidSuperClass",
"mycompany-java:AvoidTreeList",
"mycompany-java:AvoidMethodWithSameTypeInArgument",
"mycompany-java:SecurityAnnotationMandatory");

assertThat(context.mainCheckClasses).extracting(Class::getSimpleName).containsExactly(
"SpringControllerRequestMappingEntityRule",
"AvoidAnnotationRule",
"AvoidBrandInMethodNamesRule",
"AvoidMethodDeclarationRule",
"AvoidSuperClassRule",
"AvoidTreeListRule",
"MyCustomSubscriptionRule",
"SecurityAnnotationMandatoryRule");

assertThat(context.testRuleKeys).extracting(RuleKey::toString).containsExactly(
"mycompany-java:NoIfStatementInTests");

assertThat(context.testCheckClasses).extracting(Class::getSimpleName).containsExactly(
"NoIfStatementInTestsRule");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@ void testName() {

new MyJavaRulesPlugin().define(context);

assertThat(context.getExtensions()).hasSize(2);
assertThat(context.getExtensions())
.extracting(ext -> ((Class) ext).getSimpleName())
.containsExactlyInAnyOrder(
"MyJavaRulesDefinition",
"MyJavaFileCheckRegistrar");
}

public static class MockedSonarRuntime implements SonarRuntime {

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* SonarQube Java
* Copyright (C) 2012-2023 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 GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* 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 GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.checks.verifier;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleAnnotationUtils;
import org.sonar.plugins.java.api.CheckRegistrar;
import org.sonar.plugins.java.api.JavaCheck;

public class TestCheckRegistrarContext extends CheckRegistrar.RegistrarContext {

public final List<Class<? extends JavaCheck>> mainCheckClasses = new ArrayList<>();
public final List<JavaCheck> mainCheckInstances = new ArrayList<>();
public final List<RuleKey> mainRuleKeys = new ArrayList<>();

public final List<Class<? extends JavaCheck>> testCheckClasses = new ArrayList<>();
public final List<JavaCheck> testCheckInstances = new ArrayList<>();
public final List<RuleKey> testRuleKeys = new ArrayList<>();

public final Set<RuleKey> autoScanCompatibleRules = new HashSet<>();

@Override
public void registerMainChecks(String repositoryKey, Collection<?> javaCheckClassesAndInstances) {
validateAndRegisterChecks(repositoryKey, javaCheckClassesAndInstances, mainCheckClasses, mainCheckInstances, mainRuleKeys);
}

@Override
public void registerTestChecks(String repositoryKey, Collection<?> javaCheckClassesAndInstances) {
validateAndRegisterChecks(repositoryKey, javaCheckClassesAndInstances, testCheckClasses, testCheckInstances, testRuleKeys);
}

@Override
public void registerMainSharedCheck(JavaCheck check, Collection<RuleKey> ruleKeys) {
mainCheckClasses.add(check.getClass());
mainCheckInstances.add(check);
mainRuleKeys.addAll(ruleKeys);
}

@Override
public void registerTestSharedCheck(JavaCheck check, Collection<RuleKey> ruleKeys) {
testCheckClasses.add(check.getClass());
testCheckInstances.add(check);
testRuleKeys.addAll(ruleKeys);
}

@Override
public void registerAutoScanCompatibleRules(Collection<RuleKey> ruleKeys) {
autoScanCompatibleRules.addAll(ruleKeys);
}

private static void validateAndRegisterChecks(String repositoryKey,
Collection<?> javaCheckClassesAndInstances,
List<Class<? extends JavaCheck>> destCheckClasses,
List<JavaCheck> destCheckInstances,
List<RuleKey> destRuleKeys) {
if (StringUtils.isBlank(repositoryKey)) {
throw new IllegalArgumentException("Please specify a non blank repository key");
}
for (Object javaCheckClassOrInstance : javaCheckClassesAndInstances) {
Class<? extends JavaCheck> checkClass;
JavaCheck check;
try {
if (javaCheckClassOrInstance instanceof Class) {
checkClass = (Class<? extends JavaCheck>) javaCheckClassOrInstance;
check = checkClass.getDeclaredConstructor().newInstance();
} else {
check = (JavaCheck) javaCheckClassOrInstance;
checkClass = check.getClass();
}
} catch (ClassCastException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
throw new IllegalStateException(String.format("Fail to instantiate %s", javaCheckClassOrInstance), e);
}
RuleKey ruleKey = RuleKey.of(repositoryKey, RuleAnnotationUtils.getRuleKey(checkClass));
destCheckClasses.add(checkClass);
destCheckInstances.add(check);
destRuleKeys.add(ruleKey);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SonarQube Java
* Copyright (C) 2012-2023 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 GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* 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 GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.checks.verifier;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.sonar.api.rule.RuleKey;
import org.sonar.plugins.java.api.ProfileRegistrar;

public class TestProfileRegistrarContext implements ProfileRegistrar.RegistrarContext {

public final Set<RuleKey> defaultQualityProfileRules = new HashSet<>();

@Override
public void registerDefaultQualityProfileRules(Collection<RuleKey> ruleKeys) {
defaultQualityProfileRules.addAll(ruleKeys);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ private SonarComponents sonarComponents() {
ClasspathForMain classpathForMain = new ClasspathForMain(config, fileSystem);
ClasspathForTest classpathForTest = new ClasspathForTest(config, fileSystem);

SonarComponents sonarComponents = new SonarComponents(null, fileSystem, classpathForMain, classpathForTest, null) {
SonarComponents sonarComponents = new SonarComponents(null, fileSystem, classpathForMain, classpathForTest, null, null) {
@Override
public boolean reportAnalysisError(RecognitionException re, InputFile inputFile) {
throw new AssertionError(String.format("Should not fail analysis (%s)", re.getMessage()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* SonarQube Java
* Copyright (C) 2012-2023 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 GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* 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 GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.checks.verifier;

import java.util.List;
import org.junit.jupiter.api.Test;
import org.sonar.api.rule.RuleKey;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaCheck;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class TestCheckRegistrarContextTest {

@Rule(key = "X1")
static class Rule1 implements JavaCheck {
}
@Rule(key = "X2")
static class Rule2 implements JavaCheck {
}
@Rule(key = "X3")
static class Rule3 implements JavaCheck {
}
@Rule(key = "X4")
static class Rule4 implements JavaCheck {
}
@Rule(key = "X5")
static class Rule5 implements JavaCheck {
}
@Rule(key = "X6")
static class Rule6 implements JavaCheck {
}
static class Scanner implements JavaCheck {
}

@Test
void store_registration() {
TestCheckRegistrarContext context = new TestCheckRegistrarContext();

var rule4 = new Rule4();

context.registerClassesForRepository(
"customRepo",
List.of(Rule1.class, Rule2.class),
List.of(Rule1.class, Rule3.class));

context.registerMainChecks("customRepo", List.of(
rule4,
Rule5.class));
context.registerTestChecks("customRepo", List.of(
rule4,
Rule6.class));
context.registerMainSharedCheck(new Scanner(), List.of(
RuleKey.of("securityRepo", "R1"),
RuleKey.of("securityRepo", "R2")));
context.registerTestSharedCheck(new Scanner(), List.of(
RuleKey.of("securityRepo", "R3"),
RuleKey.of("securityRepo", "R4")));
context.registerAutoScanCompatibleRules(List.of(
RuleKey.of("customRepo", "X1")));

assertThat(context.mainRuleKeys).extracting(RuleKey::toString).containsExactly(
"customRepo:X1",
"customRepo:X2",
"customRepo:X4",
"customRepo:X5",
"securityRepo:R1",
"securityRepo:R2");

assertThat(context.mainCheckClasses).extracting(Class::getSimpleName).containsExactly(
"Rule1",
"Rule2",
"Rule4",
"Rule5",
"Scanner");

assertThat(context.testRuleKeys).extracting(RuleKey::toString).containsExactly(
"customRepo:X1",
"customRepo:X3",
"customRepo:X4",
"customRepo:X6",
"securityRepo:R3",
"securityRepo:R4");

assertThat(context.testCheckClasses).extracting(Class::getSimpleName).containsExactly(
"Rule1",
"Rule3",
"Rule4",
"Rule6",
"Scanner");

assertThat(context.autoScanCompatibleRules).containsExactly(RuleKey.of("customRepo", "X1"));
}

@Test
void should_fail_if_repository_key_is_blank() {
TestCheckRegistrarContext context = new TestCheckRegistrarContext();
List<?> checkClasses = List.of(Rule1.class);
assertThatThrownBy(() -> context.registerMainChecks(" ", checkClasses))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Please specify a non blank repository key");
}

@Test
void should_fail_if_not_a_JavaCheck() {
TestCheckRegistrarContext context = new TestCheckRegistrarContext();
List<?> checkClasses = List.of(Object.class);
assertThatThrownBy(() -> context.registerMainChecks("repo", checkClasses))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Fail to instantiate class java.lang.Object");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* SonarQube Java
* Copyright (C) 2012-2023 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 GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* 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 GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.checks.verifier;

import java.util.List;
import org.junit.jupiter.api.Test;
import org.sonar.api.rule.RuleKey;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

class TestProfileRegistrarContextTest {

@Test
void store_registration() {
TestProfileRegistrarContext context = new TestProfileRegistrarContext();
context.registerDefaultQualityProfileRules(List.of(RuleKey.of("java", "S1234")));
assertThat(context.defaultQualityProfileRules).containsExactly(RuleKey.of("java", "S1234"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ private static SonarComponents sonarComponents(File moduleBaseDir, List<InputFil
.setProperty(SonarComponents.FAIL_ON_EXCEPTION_KEY, true)
.setProperty(SonarComponents.SONAR_BATCH_MODE_KEY, true));
DefaultFileSystem fileSystem = context.fileSystem();
SonarComponents sonarComponents = new SonarComponents(null, fileSystem, null, null, null) {
SonarComponents sonarComponents = new SonarComponents(null, fileSystem, null, null, null, null) {
@Override
public boolean reportAnalysisError(RecognitionException re, InputFile inputFile) {
return false;
Expand Down
Loading

0 comments on commit 3d80c6c

Please sign in to comment.