Skip to content

Commit 46bc0ac

Browse files
Introduce end of analysis API (#1321)
1 parent a97b90a commit 46bc0ac

File tree

5 files changed

+101
-2
lines changed

5 files changed

+101
-2
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.plugins.python.api.internal;
21+
22+
import org.sonar.api.Beta;
23+
import org.sonar.plugins.python.api.caching.CacheContext;
24+
25+
/**
26+
* Common interface for providing callbacks that are triggered at the end of an analysis, after all files have been scanned.
27+
* <b>Warning: keeping state between files can lead to memory leaks. Implement with care.</b>
28+
*/
29+
@Beta
30+
public interface EndOfAnalysis {
31+
32+
/**
33+
* A method called when all files have been processed.
34+
* @param cacheContext CacheContext that can be used to store or retrieve information in the server cache.
35+
*/
36+
void endOfAnalysis(CacheContext cacheContext);
37+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
@ParametersAreNonnullByDefault
21+
package org.sonar.plugins.python.api.internal;
22+
23+
import javax.annotation.ParametersAreNonnullByDefault;
24+

sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonScanner.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.sonar.plugins.python.api.PythonInputFileContext;
5151
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
5252
import org.sonar.plugins.python.api.PythonVisitorContext;
53+
import org.sonar.plugins.python.api.internal.EndOfAnalysis;
5354
import org.sonar.plugins.python.api.tree.FileInput;
5455
import org.sonar.plugins.python.cpd.PythonCpdAnalyzer;
5556
import org.sonar.plugins.python.indexer.PythonIndexer;
@@ -154,6 +155,14 @@ public boolean scanFileWithoutParsing(InputFile inputFile) {
154155
return true;
155156
}
156157

158+
@Override
159+
public void endOfAnalysis() {
160+
checks.all().stream()
161+
.filter(EndOfAnalysis.class::isInstance)
162+
.map(EndOfAnalysis.class::cast)
163+
.forEach(c -> c.endOfAnalysis(indexer.cacheContext()));
164+
}
165+
157166
boolean isCheckApplicable(PythonCheck pythonCheck, InputFile.Type fileType) {
158167
PythonCheck.CheckScope checkScope = pythonCheck.scope();
159168
if (checkScope == PythonCheck.CheckScope.ALL) {

sonar-python-plugin/src/main/java/org/sonar/plugins/python/Scanner.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public void execute(List<InputFile> files, SensorContext context) {
6969
progressReport.nextFile();
7070
}
7171
}
72-
72+
endOfAnalysis();
7373
progressReport.stop();
7474
this.reportStatistics(numScannedWithoutParsing, files.size());
7575
}
@@ -82,6 +82,10 @@ protected boolean scanFileWithoutParsing(InputFile file) throws IOException {
8282
return false;
8383
}
8484

85+
protected void endOfAnalysis() {
86+
// no op
87+
}
88+
8589
protected abstract void processException(Exception e, InputFile file);
8690

8791
protected void reportStatistics(int numSkippedFiles, int numTotalFiles) {

sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@
6363
import org.sonar.api.rule.RuleKey;
6464
import org.sonar.api.utils.Version;
6565
import org.sonar.api.utils.log.LogTester;
66+
import org.sonar.api.utils.log.Logger;
6667
import org.sonar.api.utils.log.LoggerLevel;
68+
import org.sonar.api.utils.log.Loggers;
6769
import org.sonar.check.Rule;
6870
import org.sonar.check.RuleProperty;
6971
import org.sonar.plugins.python.api.ProjectPythonVersion;
@@ -72,6 +74,8 @@
7274
import org.sonar.plugins.python.api.PythonInputFileContext;
7375
import org.sonar.plugins.python.api.PythonVersionUtils;
7476
import org.sonar.plugins.python.api.PythonVisitorContext;
77+
import org.sonar.plugins.python.api.caching.CacheContext;
78+
import org.sonar.plugins.python.api.internal.EndOfAnalysis;
7579
import org.sonar.plugins.python.caching.TestReadCache;
7680
import org.sonar.plugins.python.caching.TestWriteCache;
7781
import org.sonar.plugins.python.indexer.PythonIndexer;
@@ -138,7 +142,10 @@ public List<Class> checkClasses() {
138142
name = "name",
139143
description = "desc",
140144
tags = {"bug"})
141-
public static class MyCustomRule implements PythonCheck {
145+
public static class MyCustomRule implements PythonCheck, EndOfAnalysis {
146+
147+
private static final Logger LOG = Loggers.get(MyCustomRule.class);
148+
142149
@RuleProperty(
143150
key = "customParam",
144151
description = "Custom parameter",
@@ -154,6 +161,11 @@ public void scanFile(PythonVisitorContext visitorContext) {
154161
public boolean scanWithoutParsing(PythonInputFileContext inputFile) {
155162
return false;
156163
}
164+
165+
@Override
166+
public void endOfAnalysis(CacheContext cacheContext) {
167+
LOG.trace("End of analysis called!");
168+
}
157169
}
158170

159171
private final File baseDir = new File("src/test/resources/org/sonar/plugins/python/sensor").getAbsoluteFile();
@@ -406,6 +418,19 @@ public void cross_files_issues_only_one_file_analyzed() {
406418
assertThat(flow.locations().get(1).inputComponent()).isEqualTo(modFile);
407419
}
408420

421+
@Test
422+
public void end_of_analysis_called() {
423+
inputFile(FILE_2);
424+
activeRules = new ActiveRulesBuilder()
425+
.addRule(new NewActiveRule.Builder()
426+
.setRuleKey(RuleKey.of(CUSTOM_REPOSITORY_KEY, CUSTOM_RULE_KEY))
427+
.build())
428+
.build();
429+
sensor().execute(context);
430+
431+
assertThat(logTester.logs(LoggerLevel.TRACE)).containsExactly("End of analysis called!");
432+
}
433+
409434
@Test
410435
public void no_indexer_when_project_too_large_sonarlint() {
411436
activeRules = new ActiveRulesBuilder()

0 commit comments

Comments
 (0)