Skip to content

Commit 2ccfdf9

Browse files
maksim-grebeniuk-sonarsourcesonartech
authored andcommitted
SONARPY-2755 Implement instantiation of all rules for each file (#210)
GitOrigin-RevId: ba3f7280b1131ec45b54dd14fed7e8a39408ffae
1 parent f9c375e commit 2ccfdf9

2 files changed

Lines changed: 48 additions & 22 deletions

File tree

python-commons/src/main/java/org/sonar/plugins/python/PythonChecks.java

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,46 +16,73 @@
1616
*/
1717
package org.sonar.plugins.python;
1818

19-
import java.util.ArrayList;
19+
import java.util.Collection;
2020
import java.util.List;
21-
import java.util.Objects;
21+
import java.util.Map;
22+
import java.util.concurrent.ConcurrentHashMap;
23+
import java.util.stream.Stream;
2224
import javax.annotation.Nullable;
2325
import org.sonar.api.batch.rule.CheckFactory;
2426
import org.sonar.api.batch.rule.Checks;
2527
import org.sonar.api.rule.RuleKey;
2628
import org.sonar.plugins.python.api.PythonCheck;
2729
import org.sonar.plugins.python.api.PythonCustomRuleRepository;
30+
import org.sonar.plugins.python.api.internal.EndOfAnalysis;
2831

2932
public class PythonChecks {
3033
private final CheckFactory checkFactory;
31-
private List<Checks<PythonCheck>> checksByRepository = new ArrayList<>();
34+
private final Map<String, RepositoryChecksInfo> repositoriesChecks;
35+
private final Map<Class<? extends PythonCheck>, RuleKey> ruleKeys;
3236

3337
PythonChecks(CheckFactory checkFactory) {
3438
this.checkFactory = checkFactory;
39+
this.repositoriesChecks = new ConcurrentHashMap<>();
40+
this.ruleKeys = new ConcurrentHashMap<>();
3541
}
36-
public PythonChecks addChecks(String repositoryKey, Iterable<Class<?>> checkClass) {
37-
checksByRepository.add(checkFactory.<PythonCheck>create(repositoryKey).addAnnotatedChecks(checkClass));
3842

43+
public PythonChecks addChecks(String repositoryKey, Iterable<Class<?>> checkClasses) {
44+
var repositoryChecksInfo = new RepositoryChecksInfo(repositoryKey, checkClasses);
45+
var checks = createChecks(repositoryChecksInfo);
46+
checks.all().forEach(check -> {
47+
var checkClass = check.getClass();
48+
var ruleKey = checks.ruleKey(check);
49+
ruleKeys.put(checkClass, ruleKey);
50+
});
51+
repositoriesChecks.put(repositoryChecksInfo.repositoryKey, repositoryChecksInfo);
3952
return this;
4053
}
4154

4255
public PythonChecks addCustomChecks(@Nullable PythonCustomRuleRepository[] customRuleRepositories) {
43-
if (customRuleRepositories != null) {
44-
for (PythonCustomRuleRepository ruleRepository : customRuleRepositories) {
45-
addChecks(ruleRepository.repositoryKey(), ruleRepository.checkClasses());
46-
}
47-
}
48-
56+
Stream.ofNullable(customRuleRepositories)
57+
.flatMap(Stream::of)
58+
.forEach(ruleRepository -> addChecks(ruleRepository.repositoryKey(), ruleRepository.checkClasses()));
4959
return this;
5060
}
5161

52-
public List<PythonCheck> all() {
53-
return checksByRepository.stream().flatMap(c -> c.all().stream()).toList();
62+
public synchronized List<PythonCheck> all() {
63+
return repositoriesChecks.values().stream()
64+
.map(this::createChecks)
65+
.map(Checks::all)
66+
.flatMap(Collection::stream)
67+
.toList();
68+
}
69+
70+
public List<EndOfAnalysis> endOfAnalyses() {
71+
return all().stream()
72+
.filter(EndOfAnalysis.class::isInstance)
73+
.map(EndOfAnalysis.class::cast)
74+
.toList();
5475
}
5576

5677
@Nullable
5778
public RuleKey ruleKey(PythonCheck check) {
58-
return checksByRepository.stream().map(c -> c.ruleKey(check)).filter(Objects::nonNull).findFirst().orElse(null);
79+
return ruleKeys.getOrDefault(check.getClass(), null);
5980
}
6081

82+
private Checks<PythonCheck> createChecks(RepositoryChecksInfo repositoryChecksInfo) {
83+
return checkFactory.<PythonCheck>create(repositoryChecksInfo.repositoryKey).addAnnotatedChecks(repositoryChecksInfo.checkClasses);
84+
}
85+
86+
private record RepositoryChecksInfo(String repositoryKey, Iterable<Class<?>> checkClasses) {}
87+
6188
}

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Map;
3131
import java.util.Optional;
3232
import java.util.Set;
33+
import java.util.concurrent.ConcurrentHashMap;
3334
import java.util.function.Supplier;
3435
import java.util.regex.Pattern;
3536
import javax.annotation.CheckForNull;
@@ -79,7 +80,7 @@ public class PythonScanner extends Scanner {
7980
private final NoSonarFilter noSonarFilter;
8081
private final PythonCpdAnalyzer cpdAnalyzer;
8182
private final PythonIndexer indexer;
82-
private final Map<PythonInputFile, Set<PythonCheck>> checksExecutedWithoutParsingByFiles = new HashMap<>();
83+
private final Map<PythonInputFile, Set<Class<? extends PythonCheck>>> checksExecutedWithoutParsingByFiles;
8384
private int recognitionErrorCount = 0;
8485
private static final Pattern DATABRICKS_MAGIC_COMMAND_PATTERN = Pattern.compile("^\\h*#\\h*(MAGIC|COMMAND).*");
8586
private boolean foundDatabricks = false;
@@ -97,6 +98,7 @@ public PythonScanner(
9798
this.indexer = indexer;
9899
this.indexer.buildOnce(context);
99100
this.architectureCallback = architectureCallback;
101+
this.checksExecutedWithoutParsingByFiles = new ConcurrentHashMap<>();
100102
}
101103

102104
@Override
@@ -141,7 +143,7 @@ protected void scanFile(PythonInputFile inputFile) throws IOException {
141143
List<PythonSubscriptionCheck> checksBasedOnTree = new ArrayList<>();
142144
for (PythonCheck check : checks.all()) {
143145
if (!isCheckApplicable(check, fileType)
144-
|| checksExecutedWithoutParsingByFiles.getOrDefault(inputFile, Collections.emptySet()).contains(check)) {
146+
|| checksExecutedWithoutParsingByFiles.getOrDefault(inputFile, Collections.emptySet()).contains(check.getClass())) {
145147
continue;
146148
}
147149
if (check instanceof PythonSubscriptionCheck pythonSubscriptionCheck) {
@@ -202,8 +204,8 @@ public boolean scanFileWithoutParsing(PythonInputFile inputFile) {
202204
}
203205

204206
if (check.scanWithoutParsing(inputFileContext)) {
205-
Set<PythonCheck> executedChecks = checksExecutedWithoutParsingByFiles.getOrDefault(inputFile, new HashSet<>());
206-
executedChecks.add(check);
207+
var executedChecks = checksExecutedWithoutParsingByFiles.getOrDefault(inputFile, new HashSet<>());
208+
executedChecks.add(check.getClass());
207209
checksExecutedWithoutParsingByFiles.putIfAbsent(inputFile, executedChecks);
208210
} else {
209211
result = false;
@@ -225,10 +227,7 @@ private boolean checkRequiresParsingOfImpactedFile(PythonInputFile inputFile, Py
225227
@Override
226228
public void endOfAnalysis() {
227229
indexer.postAnalysis(context);
228-
checks.all().stream()
229-
.filter(EndOfAnalysis.class::isInstance)
230-
.map(EndOfAnalysis.class::cast)
231-
.forEach(c -> c.endOfAnalysis(indexer.cacheContext()));
230+
checks.endOfAnalyses().forEach(c -> c.endOfAnalysis(indexer.cacheContext()));
232231
}
233232

234233
boolean isCheckApplicable(PythonCheck pythonCheck, InputFile.Type fileType) {

0 commit comments

Comments
 (0)