Skip to content

Commit cf998a7

Browse files
maksim-grebeniuk-sonarsourcesonartech
authored andcommitted
SONARPY-3083 Store line number and rules commented as NOSONAR(SXXXX, SYYYY) (#371)
GitOrigin-RevId: e92ba54f24ff5ef87dad37757137b7dc2e7b8678
1 parent 21e1d20 commit cf998a7

13 files changed

Lines changed: 403 additions & 51 deletions

File tree

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.sonar.plugins.python.editions.RepositoryInfoProvider.RepositoryInfo;
3939
import org.sonar.plugins.python.indexer.PythonIndexer;
4040
import org.sonar.plugins.python.indexer.SonarQubePythonIndexer;
41+
import org.sonar.plugins.python.nosonar.NoSonarLineInfoCollector;
4142
import org.sonar.python.caching.CacheContextImpl;
4243
import org.sonar.python.parser.PythonParser;
4344

@@ -51,17 +52,23 @@ public final class IPynbSensor implements Sensor {
5152
private final PythonIndexer indexer;
5253
private static final String FAIL_FAST_PROPERTY_NAME = "sonar.internal.analysis.failFast";
5354
private final SensorTelemetryStorage sensorTelemetryStorage;
55+
private final NoSonarLineInfoCollector noSonarLineInfoCollector;
5456

55-
public IPynbSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter) {
56-
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, new RepositoryInfoProvider[]{new OpenSourceRepositoryInfoProvider()});
57+
public IPynbSensor(FileLinesContextFactory fileLinesContextFactory,
58+
CheckFactory checkFactory,
59+
NoSonarFilter noSonarFilter,
60+
NoSonarLineInfoCollector noSonarLineInfoCollector) {
61+
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, new RepositoryInfoProvider[]{new OpenSourceRepositoryInfoProvider()}, noSonarLineInfoCollector);
5762
}
5863

5964
public IPynbSensor(
6065
FileLinesContextFactory fileLinesContextFactory,
6166
CheckFactory checkFactory,
6267
NoSonarFilter noSonarFilter,
6368
@Nullable PythonIndexer indexer,
64-
RepositoryInfoProvider[] editionMetadataProviders) {
69+
RepositoryInfoProvider[] editionMetadataProviders,
70+
NoSonarLineInfoCollector noSonarLineInfoCollector) {
71+
this.noSonarLineInfoCollector = noSonarLineInfoCollector;
6572

6673
this.checks = createPythonChecks(checkFactory, editionMetadataProviders);
6774

@@ -96,7 +103,7 @@ public void execute(SensorContext context) {
96103
}
97104
if (isInSonarLintRuntime(context)) {
98105
PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, noSonarFilter, PythonParser::createIPythonParser,
99-
indexer, new DummyArchitectureCallback());
106+
indexer, new DummyArchitectureCallback(), noSonarLineInfoCollector);
100107
scanner.execute(pythonFiles, context);
101108
} else {
102109
processNotebooksFiles(pythonFiles, context);
@@ -110,7 +117,7 @@ private void processNotebooksFiles(List<PythonInputFile> pythonFiles, SensorCont
110117
CacheContext cacheContext = CacheContextImpl.dummyCache();
111118
PythonIndexer pythonIndexer = new SonarQubePythonIndexer(pythonFiles, cacheContext, context);
112119
PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, noSonarFilter, PythonParser::createIPythonParser,
113-
pythonIndexer, new DummyArchitectureCallback());
120+
pythonIndexer, new DummyArchitectureCallback(), noSonarLineInfoCollector);
114121
scanner.execute(pythonFiles, context);
115122
sensorTelemetryStorage.updateMetric(TelemetryMetricKey.NOTEBOOK_RECOGNITION_ERROR_KEY, scanner.getRecognitionErrorCount());
116123
updateDatabricksTelemetry(scanner);

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,29 @@
2626
import org.sonar.api.measures.FileLinesContextFactory;
2727
import org.sonar.api.measures.Metric;
2828
import org.sonar.plugins.python.api.PythonVisitorContext;
29+
import org.sonar.plugins.python.nosonar.NoSonarLineInfoCollector;
2930
import org.sonar.python.metrics.FileLinesVisitor;
3031
import org.sonar.python.metrics.FileMetrics;
3132

3233
public class MeasuresRepository {
3334
private final SensorContext context;
3435
private final NoSonarFilter noSonarFilter;
3536
private final FileLinesContextFactory fileLinesContextFactory;
37+
private final NoSonarLineInfoCollector noSonarLineInfoCollector;
3638
private final boolean isInSonarLint;
3739
private final Lock lock;
3840

39-
public MeasuresRepository(SensorContext context, NoSonarFilter noSonarFilter, FileLinesContextFactory fileLinesContextFactory, boolean isInSonarLint, Lock lock) {
41+
public MeasuresRepository(SensorContext context,
42+
NoSonarFilter noSonarFilter,
43+
FileLinesContextFactory fileLinesContextFactory,
44+
boolean isInSonarLint,
45+
NoSonarLineInfoCollector noSonarLineInfoCollector,
46+
Lock lock) {
4047
this.context = context;
4148
this.noSonarFilter = noSonarFilter;
4249
this.fileLinesContextFactory = fileLinesContextFactory;
4350
this.isInSonarLint = isInSonarLint;
51+
this.noSonarLineInfoCollector = noSonarLineInfoCollector;
4452
this.lock = lock;
4553
}
4654

@@ -57,7 +65,7 @@ private void saveInternal(PythonInputFile inputFile, PythonVisitorContext visito
5765
FileMetrics fileMetrics = new FileMetrics(visitorContext, isNotebook(inputFile));
5866
FileLinesVisitor fileLinesVisitor = fileMetrics.fileLinesVisitor();
5967

60-
processNoSonarInFile(inputFile, fileLinesVisitor);
68+
processNoSonarInFile(inputFile);
6169

6270
if (!isInSonarLint) {
6371
Set<Integer> linesOfCode = fileLinesVisitor.getLinesOfCode();
@@ -82,10 +90,11 @@ private void saveInternal(PythonInputFile inputFile, PythonVisitorContext visito
8290
}
8391
}
8492

85-
private void processNoSonarInFile(PythonInputFile inputFile, FileLinesVisitor fileLinesVisitor) {
93+
private void processNoSonarInFile(PythonInputFile inputFile) {
8694
try {
8795
lock.lock();
88-
noSonarFilter.noSonarInFile(inputFile.wrappedFile(), fileLinesVisitor.getLinesWithNoSonar());
96+
var linesWithEmptyNosonar = noSonarLineInfoCollector.getLinesWithEmptyNoSonar(inputFile.wrappedFile().key());
97+
noSonarFilter.noSonarInFile(inputFile.wrappedFile(), linesWithEmptyNosonar);
8998
} finally {
9099
lock.unlock();
91100
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.sonar.plugins.python.indexer.SonarLintPythonIndexer;
4040
import org.sonar.plugins.python.mypy.MypyRulesDefinition;
4141
import org.sonar.plugins.python.mypy.MypySensor;
42+
import org.sonar.plugins.python.nosonar.NoSonarLineInfoCollector;
4243
import org.sonar.plugins.python.pylint.PylintRulesDefinition;
4344
import org.sonar.plugins.python.pylint.PylintSensor;
4445
import org.sonar.plugins.python.ruff.RuffRulesDefinition;
@@ -67,6 +68,7 @@ private PythonExtensions() {
6768

6869
public static void addCommonExtensions(Plugin.Context context) {
6970
context.addExtensions(
71+
NoSonarLineInfoCollector.class,
7072
buildPythonSuffix(),
7173
buildIpynbPythonSuffix(),
7274
buildPythonVersion(),

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.sonar.plugins.python.api.tree.FileInput;
5252
import org.sonar.plugins.python.cpd.PythonCpdAnalyzer;
5353
import org.sonar.plugins.python.indexer.PythonIndexer;
54+
import org.sonar.plugins.python.nosonar.NoSonarLineInfoCollector;
5455
import org.sonar.python.IPythonLocation;
5556
import org.sonar.python.SubscriptionVisitor;
5657
import org.sonar.python.parser.PythonParser;
@@ -77,15 +78,17 @@ public class PythonScanner extends Scanner {
7778
private final PythonHighlighter pythonHighlighter;
7879
private final IssuesRepository issuesRepository;
7980
private final MeasuresRepository measuresRepository;
81+
private final NoSonarLineInfoCollector noSonarLineInfoCollector;
8082
private final Lock lock;
8183

8284
public PythonScanner(
8385
SensorContext context, PythonChecks checks, FileLinesContextFactory fileLinesContextFactory, NoSonarFilter noSonarFilter,
84-
Supplier<PythonParser> parserSupplier, PythonIndexer indexer, PythonFileConsumer architectureCallback) {
86+
Supplier<PythonParser> parserSupplier, PythonIndexer indexer, PythonFileConsumer architectureCallback, NoSonarLineInfoCollector noSonarLineInfoCollector) {
8587
super(context);
8688
this.checks = checks;
8789
this.parserSupplier = parserSupplier;
8890
this.indexer = indexer;
91+
this.noSonarLineInfoCollector = noSonarLineInfoCollector;
8992
this.indexer.buildOnce(context);
9093
this.architectureCallback = architectureCallback;
9194
this.checksExecutedWithoutParsingByFiles = new ConcurrentHashMap<>();
@@ -97,7 +100,7 @@ public PythonScanner(
97100
this.newSymbolsCollector = new NewSymbolsCollector(lock);
98101
this.pythonHighlighter = new PythonHighlighter(lock);
99102
this.issuesRepository = new IssuesRepository(context, checks, indexer, isInSonarLint(context), lock);
100-
this.measuresRepository = new MeasuresRepository(context, noSonarFilter, fileLinesContextFactory, isInSonarLint(context), lock);
103+
this.measuresRepository = new MeasuresRepository(context, noSonarFilter, fileLinesContextFactory, isInSonarLint(context), noSonarLineInfoCollector, lock);
101104
}
102105

103106
@Override
@@ -122,7 +125,16 @@ protected void scanFile(PythonInputFile inputFile) throws IOException {
122125

123126
runLockedByRepository(ARCHITECTURE_CALLBACK_LOCK_KEY, () -> architectureCallback.scanFile(visitorContext));
124127

125-
issuesRepository.save(inputFile, visitorContext.getIssues());
128+
129+
noSonarLineInfoCollector.collect(pythonFile.key(), visitorContext.rootTree());
130+
131+
if (fileType == InputFile.Type.MAIN && visitorContext.rootTree() != null) {
132+
pushTokens(inputFile, visitorContext);
133+
measuresRepository.save(inputFile, visitorContext);
134+
}
135+
136+
var issues = visitorContext.getIssues();
137+
issuesRepository.save(inputFile, issues);
126138

127139
if (visitorContext.rootTree() != null && !isInSonarLint(context)) {
128140
newSymbolsCollector.collect(context.newSymbolTable().onFile(inputFile.wrappedFile()), visitorContext.rootTree());
@@ -145,10 +157,7 @@ private PythonVisitorContext createVisitorContext(PythonInputFile inputFile, Pyt
145157
indexer.projectLevelSymbolTable(),
146158
indexer.cacheContext(),
147159
context.runtime().getProduct());
148-
if (fileType == InputFile.Type.MAIN) {
149-
pushTokens(inputFile, visitorContext);
150-
measuresRepository.save(inputFile, visitorContext);
151-
}
160+
152161
} catch (RecognitionException e) {
153162
visitorContext = new PythonVisitorContext(pythonFile, e, context.runtime().getProduct());
154163

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.sonar.plugins.python.indexer.PythonIndexer;
4848
import org.sonar.plugins.python.indexer.PythonIndexerWrapper;
4949
import org.sonar.plugins.python.indexer.SonarQubePythonIndexer;
50+
import org.sonar.plugins.python.nosonar.NoSonarLineInfoCollector;
5051
import org.sonar.plugins.python.warnings.AnalysisWarningsWrapper;
5152
import org.sonar.python.caching.CacheContextImpl;
5253
import org.sonar.python.parser.PythonParser;
@@ -77,6 +78,7 @@ public final class PythonSensor implements Sensor {
7778
""";
7879

7980
private final SensorTelemetryStorage sensorTelemetryStorage;
81+
private final NoSonarLineInfoCollector noSonarLineInfoCollector;
8082

8183
public PythonSensor(
8284
FileLinesContextFactory fileLinesContextFactory,
@@ -87,7 +89,8 @@ public PythonSensor(
8789
SonarLintCacheWrapper sonarLintCacheWrapper,
8890
AnalysisWarningsWrapper analysisWarnings,
8991
RepositoryInfoProviderWrapper editionMetadataProviderWrapper,
90-
ArchitectureCallbackWrapper architectureUDGBuilderWrapper) {
92+
ArchitectureCallbackWrapper architectureUDGBuilderWrapper, NoSonarLineInfoCollector noSonarLineInfoCollector) {
93+
this.noSonarLineInfoCollector = noSonarLineInfoCollector;
9194

9295
this.checks = createPythonChecks(checkFactory, editionMetadataProviderWrapper.infoProviders())
9396
.addCustomChecks(customRuleRepositoriesWrapper.customRuleRepositories());
@@ -134,7 +137,7 @@ public void execute(SensorContext context) {
134137
pythonIndexer.setSonarLintCache(sonarLintCache);
135138
TypeShed.setProjectLevelSymbolTable(pythonIndexer.projectLevelSymbolTable());
136139
PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, noSonarFilter, PythonParser::create,
137-
pythonIndexer, architectureCallback);
140+
pythonIndexer, architectureCallback, noSonarLineInfoCollector);
138141
scanner.execute(pythonFiles, context);
139142

140143
updateDatabricksTelemetry(scanner);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2025 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 Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.plugins.python.nosonar;
18+
19+
import java.util.Set;
20+
21+
public record NoSonarLineInfo(Integer line, Set<String> suppressedRuleKeys) {
22+
23+
public boolean isSuppressedRuleKeysEmpty() {
24+
return suppressedRuleKeys.isEmpty();
25+
}
26+
}

0 commit comments

Comments
 (0)