Skip to content

Commit db9bf9e

Browse files
authored
SONARCH-708, SONARJAVA-5497: Extend SonarJava CheckRegistrar API for registering custom file scanner hooks (#5083)
1 parent 33b4cc1 commit db9bf9e

6 files changed

Lines changed: 176 additions & 4 deletions

File tree

java-checks-testkit/src/main/java/org/sonar/java/checks/verifier/TestCheckRegistrarContext.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
import java.util.Set;
2525
import org.apache.commons.lang3.StringUtils;
2626
import org.sonar.api.rule.RuleKey;
27+
import org.sonar.api.rule.RuleScope;
2728
import org.sonar.api.rules.RuleAnnotationUtils;
2829
import org.sonar.plugins.java.api.CheckRegistrar;
2930
import org.sonar.plugins.java.api.JavaCheck;
31+
import org.sonar.plugins.java.api.JavaFileScanner;
3032

3133
public class TestCheckRegistrarContext extends CheckRegistrar.RegistrarContext {
3234

@@ -52,16 +54,34 @@ public void registerTestChecks(String repositoryKey, Collection<?> javaCheckClas
5254

5355
@Override
5456
public void registerMainSharedCheck(JavaCheck check, Collection<RuleKey> ruleKeys) {
57+
registerMainHook(check);
58+
mainRuleKeys.addAll(ruleKeys);
59+
}
60+
61+
private void registerMainHook(JavaCheck check) {
5562
mainCheckClasses.add(check.getClass());
5663
mainCheckInstances.add(check);
57-
mainRuleKeys.addAll(ruleKeys);
5864
}
5965

6066
@Override
6167
public void registerTestSharedCheck(JavaCheck check, Collection<RuleKey> ruleKeys) {
68+
registerTestHook(check);
69+
testRuleKeys.addAll(ruleKeys);
70+
}
71+
72+
private void registerTestHook(JavaCheck check) {
6273
testCheckClasses.add(check.getClass());
6374
testCheckInstances.add(check);
64-
testRuleKeys.addAll(ruleKeys);
75+
}
76+
77+
@Override
78+
public void registerCustomFileScanner(RuleScope ruleScope, JavaFileScanner scanner) {
79+
if (ruleScope == RuleScope.MAIN || ruleScope == RuleScope.ALL) {
80+
registerMainHook(scanner);
81+
}
82+
if (ruleScope == RuleScope.TEST || ruleScope == RuleScope.ALL) {
83+
registerTestHook(scanner);
84+
}
6585
}
6686

6787
@Override

java-checks-testkit/src/test/java/org/sonar/java/checks/verifier/TestCheckRegistrarContextTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
import java.util.List;
2020
import org.junit.jupiter.api.Test;
2121
import org.sonar.api.rule.RuleKey;
22+
import org.sonar.api.rule.RuleScope;
2223
import org.sonar.check.Rule;
2324
import org.sonar.plugins.java.api.JavaCheck;
25+
import org.sonar.plugins.java.api.JavaFileScanner;
26+
import org.sonar.plugins.java.api.JavaFileScannerContext;
2427

2528
import static org.assertj.core.api.Assertions.assertThat;
2629
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -125,4 +128,34 @@ void should_fail_if_not_a_JavaCheck() {
125128
.hasMessage("Fail to instantiate class java.lang.Object");
126129
}
127130

131+
@Test
132+
void register_custom_file_scanners_with_no_active_rules() {
133+
class DummyScanner implements JavaFileScanner {
134+
@Override
135+
public void scanFile(JavaFileScannerContext context) {
136+
// Dummy implementation. We just need the class instance
137+
}
138+
}
139+
140+
class MainScanner extends DummyScanner {
141+
}
142+
143+
class TestScanner extends DummyScanner {
144+
}
145+
146+
class AllScanner extends DummyScanner {
147+
}
148+
149+
var ctx = new TestCheckRegistrarContext();
150+
ctx.registerCustomFileScanner(RuleScope.MAIN, new MainScanner());
151+
ctx.registerCustomFileScanner(RuleScope.TEST, new TestScanner());
152+
ctx.registerCustomFileScanner(RuleScope.ALL, new AllScanner());
153+
154+
assertThat(ctx.mainCheckInstances)
155+
.extracting(c -> c.getClass().getSimpleName())
156+
.containsExactly("MainScanner", "AllScanner");
157+
assertThat(ctx.testCheckInstances)
158+
.extracting(c -> c.getClass().getSimpleName())
159+
.containsExactly("TestScanner", "AllScanner");
160+
}
128161
}

java-frontend/src/main/java/org/sonar/java/SonarComponents.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.sonar.api.measures.FileLinesContext;
5757
import org.sonar.api.measures.FileLinesContextFactory;
5858
import org.sonar.api.rule.RuleKey;
59+
import org.sonar.api.rule.RuleScope;
5960
import org.sonar.api.utils.Version;
6061
import org.sonar.java.annotations.VisibleForTesting;
6162
import org.sonar.java.caching.ContentHashCache;
@@ -69,6 +70,7 @@
6970
import org.sonar.java.reporting.JavaIssue;
7071
import org.sonar.plugins.java.api.CheckRegistrar;
7172
import org.sonar.plugins.java.api.JavaCheck;
73+
import org.sonar.plugins.java.api.JavaFileScanner;
7274
import org.sonar.plugins.java.api.JspCodeVisitor;
7375
import org.sonar.plugins.java.api.caching.SonarLintCache;
7476
import org.sonarsource.api.sonarlint.SonarLintSide;
@@ -305,6 +307,16 @@ public void registerTestSharedCheck(JavaCheck check, Collection<RuleKey> ruleKey
305307
}
306308
}
307309

310+
@Override
311+
public void registerCustomFileScanner(RuleScope ruleScope, JavaFileScanner scanner) {
312+
if (ruleScope == RuleScope.MAIN || ruleScope == RuleScope.ALL) {
313+
mainChecks.add(scanner);
314+
}
315+
if (ruleScope == RuleScope.TEST || ruleScope == RuleScope.ALL) {
316+
testChecks.add(scanner);
317+
}
318+
}
319+
308320
@Override
309321
public void registerAutoScanCompatibleRules(Collection<RuleKey> ruleKeys) {
310322
additionalAutoScanCompatibleRuleKeys.addAll(ruleKeys);

java-frontend/src/main/java/org/sonar/plugins/java/api/CheckRegistrar.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.sonar.api.batch.rule.CheckFactory;
2626
import org.sonar.api.batch.rule.Checks;
2727
import org.sonar.api.rule.RuleKey;
28+
import org.sonar.api.rule.RuleScope;
2829
import org.sonar.api.server.rule.RulesDefinition;
2930
import org.sonar.java.Preconditions;
3031
import org.sonar.java.annotations.Beta;
@@ -171,6 +172,16 @@ public void registerMainSharedCheck(JavaCheck check, Collection<RuleKey> ruleKey
171172
// to be overridden
172173
}
173174

175+
/**
176+
* Registers a custom file scanner not related to any rule or repository.
177+
* CheckRegistrars call this function to register a custom file scanner for execution during the analysis
178+
* on all source files that match the given rule scope (MAIN, TEST or ALL).
179+
* Custom file scanners reporting an issue will have no effect, since no rule is associated.
180+
*/
181+
public void registerCustomFileScanner(RuleScope ruleScope, JavaFileScanner scanner) {
182+
// to be overridden
183+
}
184+
174185
/**
175186
* Registers one test code check related to not one but a list of rules. The check will be active if at least one
176187
* of the given rule key is active. In this context injection of @RuleProperty and auto instantiation of rules

java-frontend/src/test/java/org/sonar/java/JavaFrontendTest.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.List;
2727
import java.util.regex.Matcher;
2828
import java.util.regex.Pattern;
29+
import javax.annotation.Nullable;
2930
import org.eclipse.core.runtime.OperationCanceledException;
3031
import org.jetbrains.annotations.NotNull;
3132
import org.junit.Rule;
@@ -51,6 +52,7 @@
5152
import org.sonar.api.issue.NoSonarFilter;
5253
import org.sonar.api.measures.FileLinesContext;
5354
import org.sonar.api.measures.FileLinesContextFactory;
55+
import org.sonar.api.rule.RuleScope;
5456
import org.sonar.api.scan.issue.filter.FilterableIssue;
5557
import org.sonar.api.scan.issue.filter.IssueFilterChain;
5658
import org.sonar.api.testfixtures.log.LogTesterJUnit5;
@@ -60,6 +62,8 @@
6062
import org.sonar.java.exceptions.ApiMismatchException;
6163
import org.sonar.java.filters.SonarJavaIssueFilter;
6264
import org.sonar.java.model.JavaVersionImpl;
65+
import org.sonar.plugins.java.api.CheckRegistrar;
66+
import org.sonar.plugins.java.api.JavaCheck;
6367
import org.sonar.plugins.java.api.JavaFileScanner;
6468
import org.sonar.plugins.java.api.JavaFileScannerContext;
6569
import org.sonar.plugins.java.api.JavaResourceLocator;
@@ -191,6 +195,38 @@ void scanning_empty_project_should_be_logged_in_batch() {
191195
);
192196
}
193197

198+
@Test
199+
void scan_method_is_called_for_hook() throws IOException {
200+
class Hook implements CheckRegistrar, JavaFileScanner {
201+
int callCount;
202+
203+
@Override
204+
public void register(RegistrarContext registrarContext) {
205+
registrarContext.registerCustomFileScanner(RuleScope.ALL, this);
206+
}
207+
208+
@Override
209+
public void scanFile(JavaFileScannerContext context) {
210+
callCount++;
211+
}
212+
}
213+
214+
var settings = new MapSettings();
215+
if (sensorContext == null) {
216+
File baseDir = temp.getRoot().getAbsoluteFile();
217+
sensorContext = SensorContextTester.create(baseDir);
218+
sensorContext.setSettings(settings);
219+
}
220+
221+
var hook = new Hook();
222+
var inputFiles = List.of(
223+
addFile("class A {}", sensorContext),
224+
addFile("class B {}", sensorContext)
225+
);
226+
scan(settings, SONARLINT_RUNTIME, inputFiles, new CheckRegistrar[]{hook});
227+
assertThat(hook.callCount).isEqualTo(2);
228+
}
229+
194230
@Test
195231
void scanning_empty_project_should_be_logged_in_autoscan() {
196232
MapSettings settings = new MapSettings();
@@ -808,6 +844,10 @@ private List<InputFile> scan(MapSettings settings, SonarRuntime sonarRuntime, St
808844
}
809845

810846
private List<InputFile> scan(MapSettings settings, SonarRuntime sonarRuntime, List<InputFile> inputFiles) {
847+
return scan(settings, sonarRuntime, inputFiles, null);
848+
}
849+
850+
private List<InputFile> scan(MapSettings settings, SonarRuntime sonarRuntime, List<InputFile> inputFiles, @Nullable CheckRegistrar[] checkRegistrars) {
811851
if (sensorContext == null) {
812852
File baseDir = temp.getRoot().getAbsoluteFile();
813853
sensorContext = SensorContextTester.create(baseDir);
@@ -823,15 +863,15 @@ private List<InputFile> scan(MapSettings settings, SonarRuntime sonarRuntime, Li
823863
javaClasspath = mock(ClasspathForMain.class);
824864
javaTestClasspath = mock(ClasspathForTest.class);
825865
sonarComponents = new SonarComponents(fileLinesContextFactory, sensorContext.fileSystem(), javaClasspath, javaTestClasspath,
826-
mock(CheckFactory.class), mock(ActiveRules.class));
866+
mock(CheckFactory.class), mock(ActiveRules.class), checkRegistrars);
827867
sonarComponents.setSensorContext(sensorContext);
828868
sonarComponents.mainChecks().add(mainCodeIssueScannerAndFilter);
829869
sonarComponents.testChecks().add(testCodeIssueScannerAndFilter);
830870
JavaVersion javaVersion = settings.asConfig().get(JavaVersion.SOURCE_VERSION)
831871
.map(JavaVersionImpl::fromString)
832872
.orElse(new JavaVersionImpl());
833873
JavaFrontend frontend = new JavaFrontend(javaVersion, sonarComponents, new Measurer(sensorContext, mock(NoSonarFilter.class)), mock(JavaResourceLocator.class),
834-
null, mainCodeIssueScannerAndFilter);
874+
null, sonarComponents.mainChecks().toArray(new JavaCheck[0]));
835875
frontend.scan(inputFiles, Collections.emptyList(), Collections.emptyList());
836876

837877
return inputFiles;

java-frontend/src/test/java/org/sonar/java/SonarComponentsTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import org.sonar.api.measures.FileLinesContext;
6969
import org.sonar.api.measures.FileLinesContextFactory;
7070
import org.sonar.api.rule.RuleKey;
71+
import org.sonar.api.rule.RuleScope;
7172
import org.sonar.api.testfixtures.log.LogTesterJUnit5;
7273
import org.sonar.api.utils.Version;
7374
import org.sonar.check.Rule;
@@ -81,6 +82,8 @@
8182
import org.sonar.java.testing.ThreadLocalLogTester;
8283
import org.sonar.plugins.java.api.CheckRegistrar;
8384
import org.sonar.plugins.java.api.JavaCheck;
85+
import org.sonar.plugins.java.api.JavaFileScanner;
86+
import org.sonar.plugins.java.api.JavaFileScannerContext;
8487
import org.sonar.plugins.java.api.JspCodeVisitor;
8588
import org.sonar.plugins.java.api.caching.SonarLintCache;
8689
import org.sonarsource.sonarlint.core.plugin.commons.sonarapi.SonarLintRuntimeImpl;
@@ -407,6 +410,44 @@ class RuleF implements JavaCheck {
407410
.containsExactly("RuleE");
408411
}
409412

413+
@Test
414+
void register_custom_file_scanners_with_no_active_rules() {
415+
var noActiveRules = (new ActiveRulesBuilder()).build();
416+
CheckFactory specificCheckFactory = new CheckFactory(noActiveRules);
417+
SensorContextTester specificContext = SensorContextTester.create(new File(".")).setActiveRules(noActiveRules);
418+
419+
class DummyScanner implements JavaFileScanner {
420+
@Override
421+
public void scanFile(JavaFileScannerContext context) {
422+
// Dummy implementation. We just need the class instance
423+
}
424+
}
425+
426+
class MainScanner extends DummyScanner {
427+
}
428+
429+
class TestScanner extends DummyScanner {
430+
}
431+
432+
class AllScanner extends DummyScanner {
433+
}
434+
435+
SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, null, null,
436+
null, specificCheckFactory, noActiveRules, new CheckRegistrar[]{
437+
ctx -> ctx.registerCustomFileScanner(RuleScope.MAIN, new MainScanner()),
438+
ctx -> ctx.registerCustomFileScanner(RuleScope.TEST, new TestScanner()),
439+
ctx -> ctx.registerCustomFileScanner(RuleScope.ALL, new AllScanner())
440+
});
441+
442+
sonarComponents.setSensorContext(specificContext);
443+
assertThat(sonarComponents.mainChecks())
444+
.extracting(c -> c.getClass().getSimpleName())
445+
.containsExactly("MainScanner", "AllScanner");
446+
assertThat(sonarComponents.testChecks())
447+
.extracting(c -> c.getClass().getSimpleName())
448+
.containsExactly("TestScanner", "AllScanner");
449+
}
450+
410451
@Test
411452
void register_custom_rule_by_instances_instead_of_classes() {
412453
ActiveRules activeRules = activeRules("java:S101", "java:S102");
@@ -471,6 +512,21 @@ void no_issue_when_check_not_found() {
471512
verify(context, never()).newIssue();
472513
}
473514

515+
@Test
516+
void no_issue_when_reporting_from_custom_file_scanner() {
517+
JavaFileScanner customScanner = scannerContext -> {
518+
// empty
519+
};
520+
CheckRegistrar registrar = registrarContext ->
521+
registrarContext.registerCustomFileScanner(RuleScope.ALL, customScanner);
522+
SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, null, null,
523+
null, checkFactory, context.activeRules(), new CheckRegistrar[]{registrar});
524+
sonarComponents.setSensorContext(context);
525+
526+
sonarComponents.addIssue(TestUtils.emptyInputFile("file.java"), customScanner, 0, "message", null);
527+
verify(context, never()).newIssue();
528+
}
529+
474530
@Test
475531
void add_issue_or_parse_error() {
476532
JavaCheck expectedCheck = new CustomCheck();

0 commit comments

Comments
 (0)