Skip to content

Commit 65ebfe4

Browse files
thomas-serre-sonarsourcesonartech
authored andcommitted
SONARPY-2788 Solve no Pyspark issue raised on SonarQubeCloud (#185)
GitOrigin-RevId: 6d6d43006cc8e70f72ce4f50a37336d3b03fff28
1 parent 1805c54 commit 65ebfe4

12 files changed

Lines changed: 364 additions & 56 deletions

File tree

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,18 @@
2424
import org.sonar.api.SonarRuntime;
2525
import org.sonar.api.config.PropertyDefinition;
2626
import org.sonar.api.resources.Qualifiers;
27+
import org.sonar.plugins.python.api.PythonCustomRuleRepositoryWrapper;
2728
import org.sonar.plugins.python.api.SonarLintCache;
29+
import org.sonar.plugins.python.api.SonarLintCacheWrapper;
2830
import org.sonar.plugins.python.architecture.ArchitectureCallbackWrapper;
2931
import org.sonar.plugins.python.bandit.BanditRulesDefinition;
3032
import org.sonar.plugins.python.bandit.BanditSensor;
3133
import org.sonar.plugins.python.coverage.PythonCoverageSensor;
3234
import org.sonar.plugins.python.editions.OpenSourceRepositoryInfoProvider;
35+
import org.sonar.plugins.python.editions.RepositoryInfoProviderWrapper;
3336
import org.sonar.plugins.python.flake8.Flake8RulesDefinition;
3437
import org.sonar.plugins.python.flake8.Flake8Sensor;
38+
import org.sonar.plugins.python.indexer.PythonIndexerWrapper;
3539
import org.sonar.plugins.python.indexer.SonarLintPythonIndexer;
3640
import org.sonar.plugins.python.mypy.MypyRulesDefinition;
3741
import org.sonar.plugins.python.mypy.MypySensor;
@@ -74,7 +78,11 @@ public static void addCommonExtensions(Plugin.Context context) {
7478
PythonRuleRepository.class,
7579
AnalysisWarningsWrapper.class,
7680
ArchitectureCallbackWrapper.class,
77-
81+
PythonCustomRuleRepositoryWrapper.class,
82+
PythonIndexerWrapper.class,
83+
RepositoryInfoProviderWrapper.class,
84+
SonarLintCacheWrapper.class,
85+
7886
IPynb.class,
7987
IPynbProfile.class,
8088
IPynbSensor.class,

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

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import java.util.Collections;
2222
import java.util.List;
2323
import java.util.Optional;
24+
2425
import javax.annotation.Nullable;
26+
2527
import org.slf4j.Logger;
2628
import org.slf4j.LoggerFactory;
2729
import org.sonar.api.SonarProduct;
@@ -35,16 +37,18 @@
3537
import org.sonar.api.issue.NoSonarFilter;
3638
import org.sonar.api.measures.FileLinesContextFactory;
3739
import org.sonar.plugins.python.api.ProjectPythonVersion;
38-
import org.sonar.plugins.python.api.PythonCustomRuleRepository;
40+
import org.sonar.plugins.python.api.PythonCustomRuleRepositoryWrapper;
3941
import org.sonar.plugins.python.api.PythonFileConsumer;
4042
import org.sonar.plugins.python.api.PythonVersionUtils;
4143
import org.sonar.plugins.python.api.SonarLintCache;
44+
import org.sonar.plugins.python.api.SonarLintCacheWrapper;
4245
import org.sonar.plugins.python.api.caching.CacheContext;
4346
import org.sonar.plugins.python.architecture.ArchitectureCallbackWrapper;
44-
import org.sonar.plugins.python.editions.OpenSourceRepositoryInfoProvider;
4547
import org.sonar.plugins.python.editions.RepositoryInfoProvider;
4648
import org.sonar.plugins.python.editions.RepositoryInfoProvider.RepositoryInfo;
49+
import org.sonar.plugins.python.editions.RepositoryInfoProviderWrapper;
4750
import org.sonar.plugins.python.indexer.PythonIndexer;
51+
import org.sonar.plugins.python.indexer.PythonIndexerWrapper;
4852
import org.sonar.plugins.python.indexer.SonarQubePythonIndexer;
4953
import org.sonar.plugins.python.warnings.AnalysisWarningsWrapper;
5054
import org.sonar.python.caching.CacheContextImpl;
@@ -71,53 +75,28 @@ public final class PythonSensor implements Sensor {
7175
private final AnalysisWarningsWrapper analysisWarnings;
7276
private static final Logger LOG = LoggerFactory.getLogger(PythonSensor.class);
7377
static final String UNSET_VERSION_WARNING = "Your code is analyzed as compatible with all Python 3 versions by default." +
74-
" You can get a more precise analysis by setting the exact Python version in your configuration via the parameter \"sonar.python" +
78+
" You can get a more precise analysis by setting the exact Python version in your configuration via the parameter \"sonar.python"+
7579
".version\"";
7680

7781
private final SensorTelemetryStorage sensorTelemetryStorage;
7882

79-
/**
80-
* Constructor to be used by pico if neither PythonCustomRuleRepository nor PythonIndexer are to be found and injected.
81-
*/
82-
public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory,
83-
NoSonarFilter noSonarFilter, AnalysisWarningsWrapper analysisWarnings, ArchitectureCallbackWrapper architectureCallbackWrapper ) {
84-
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, null, null, analysisWarnings,
85-
new RepositoryInfoProvider[]{new OpenSourceRepositoryInfoProvider()}, architectureCallbackWrapper);
86-
}
87-
88-
public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter,
89-
@Nullable PythonCustomRuleRepository[] customRuleRepositories, AnalysisWarningsWrapper analysisWarnings, ArchitectureCallbackWrapper architectureCallbackWrapper) {
90-
this(fileLinesContextFactory, checkFactory, noSonarFilter, customRuleRepositories, null, null, analysisWarnings,
91-
new RepositoryInfoProvider[]{new OpenSourceRepositoryInfoProvider()}, architectureCallbackWrapper);
92-
}
93-
94-
public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter,
95-
PythonIndexer indexer, SonarLintCache sonarLintCache, AnalysisWarningsWrapper analysisWarnings, ArchitectureCallbackWrapper architectureCallbackWrapper) {
96-
// ^^ This constructor implicitly assumes that a PythonIndexer and a SonarLintCache are always available at the same time.
97-
// In practice, this is currently the case, since both are provided by PythonPlugin under the same conditions.
98-
// See also PythonPlugin::SonarLintPluginAPIManager::addSonarlintPythonIndexer.
99-
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, indexer, sonarLintCache, analysisWarnings,
100-
new RepositoryInfoProvider[]{new OpenSourceRepositoryInfoProvider()}, architectureCallbackWrapper);
101-
}
102-
10383
public PythonSensor(
10484
FileLinesContextFactory fileLinesContextFactory,
10585
CheckFactory checkFactory,
10686
NoSonarFilter noSonarFilter,
107-
@Nullable PythonCustomRuleRepository[] customRuleRepositories,
108-
@Nullable PythonIndexer indexer,
109-
@Nullable SonarLintCache sonarLintCache,
87+
PythonCustomRuleRepositoryWrapper customRuleRepositoriesWrapper,
88+
PythonIndexerWrapper indexerWrapper,
89+
SonarLintCacheWrapper sonarLintCacheWrapper,
11090
AnalysisWarningsWrapper analysisWarnings,
111-
RepositoryInfoProvider[] editionMetadataProviders,
91+
RepositoryInfoProviderWrapper editionMetadataProviderWrapper,
11292
ArchitectureCallbackWrapper architectureUDGBuilderWrapper) {
11393

114-
this.checks = createPythonChecks(checkFactory, editionMetadataProviders)
115-
.addCustomChecks(customRuleRepositories);
116-
94+
this.checks = createPythonChecks(checkFactory, editionMetadataProviderWrapper.infoProviders())
95+
.addCustomChecks(customRuleRepositoriesWrapper.customRuleRepositories());
11796
this.fileLinesContextFactory = fileLinesContextFactory;
11897
this.noSonarFilter = noSonarFilter;
119-
this.indexer = indexer;
120-
this.sonarLintCache = sonarLintCache;
98+
this.indexer = indexerWrapper.indexer();
99+
this.sonarLintCache = sonarLintCacheWrapper.sonarLintCache();
121100
this.analysisWarnings = analysisWarnings;
122101
this.sensorTelemetryStorage = new SensorTelemetryStorage();
123102
this.architectureCallback = architectureUDGBuilderWrapper.architectureUdgBuilder();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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.editions;
18+
19+
import org.sonar.api.scanner.ScannerSide;
20+
import org.sonarsource.api.sonarlint.SonarLintSide;
21+
22+
@ScannerSide
23+
@SonarLintSide
24+
public class RepositoryInfoProviderWrapper {
25+
26+
private final RepositoryInfoProvider[] editionMetadataProviders;
27+
28+
public RepositoryInfoProviderWrapper() {
29+
// Constructor to be used by pico if no editionMetadataProviders are to be found and injected.
30+
this(new RepositoryInfoProvider[] {new OpenSourceRepositoryInfoProvider()});
31+
}
32+
33+
public RepositoryInfoProviderWrapper(RepositoryInfoProvider[] editionMetadataProviders) {
34+
this.editionMetadataProviders = editionMetadataProviders;
35+
}
36+
37+
public RepositoryInfoProvider[] infoProviders() {
38+
return editionMetadataProviders;
39+
}
40+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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.indexer;
18+
19+
import javax.annotation.CheckForNull;
20+
import javax.annotation.Nullable;
21+
22+
import org.sonar.api.scanner.ScannerSide;
23+
import org.sonarsource.api.sonarlint.SonarLintSide;
24+
25+
@ScannerSide
26+
@SonarLintSide
27+
public class PythonIndexerWrapper {
28+
29+
private final PythonIndexer indexer;
30+
31+
public PythonIndexerWrapper() {
32+
// Constructor to be used by pico if no indexer is to be found and injected.
33+
this(null);
34+
}
35+
36+
public PythonIndexerWrapper(@Nullable PythonIndexer indexer) {
37+
this.indexer = indexer;
38+
}
39+
40+
@CheckForNull
41+
public PythonIndexer indexer() {
42+
return indexer;
43+
}
44+
}

python-commons/src/test/java/org/sonar/plugins/python/PythonExtensionsTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ class PythonExtensionsTest {
4343
void testGetExtensions() {
4444
Version v79 = Version.create(7, 9);
4545
SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(v79, SonarQubeSide.SERVER, SonarEdition.DEVELOPER);
46-
assertThat(extensions(runtime)).hasSize(36);
46+
assertThat(extensions(runtime)).hasSize(40);
4747
assertThat(extensions(runtime)).contains(AnalysisWarningsWrapper.class);
4848
assertThat(extensions(SonarRuntimeImpl.forSonarLint(v79)))
49-
.hasSize(16)
49+
.hasSize(20)
5050
.contains(SonarLintCache.class);
5151
}
5252

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

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -79,21 +79,22 @@
7979
import org.sonar.plugins.python.api.ProjectPythonVersion;
8080
import org.sonar.plugins.python.api.PythonCheck;
8181
import org.sonar.plugins.python.api.PythonCustomRuleRepository;
82+
import org.sonar.plugins.python.api.PythonCustomRuleRepositoryWrapper;
8283
import org.sonar.plugins.python.api.PythonFileConsumer;
8384
import org.sonar.plugins.python.api.PythonInputFileContext;
8485
import org.sonar.plugins.python.api.PythonVersionUtils;
8586
import org.sonar.plugins.python.api.PythonVisitorContext;
86-
import org.sonar.plugins.python.api.SonarLintCache;
87+
import org.sonar.plugins.python.api.SonarLintCacheWrapper;
8788
import org.sonar.plugins.python.api.caching.CacheContext;
8889
import org.sonar.plugins.python.api.internal.EndOfAnalysis;
8990
import org.sonar.plugins.python.api.tree.Token;
9091
import org.sonar.plugins.python.architecture.ArchitectureCallbackWrapper;
9192
import org.sonar.plugins.python.caching.Caching;
9293
import org.sonar.plugins.python.caching.TestReadCache;
9394
import org.sonar.plugins.python.caching.TestWriteCache;
94-
import org.sonar.plugins.python.editions.OpenSourceRepositoryInfoProvider;
95-
import org.sonar.plugins.python.editions.RepositoryInfoProvider;
95+
import org.sonar.plugins.python.editions.RepositoryInfoProviderWrapper;
9696
import org.sonar.plugins.python.indexer.PythonIndexer;
97+
import org.sonar.plugins.python.indexer.PythonIndexerWrapper;
9798
import org.sonar.plugins.python.indexer.SonarLintPythonIndexer;
9899
import org.sonar.plugins.python.indexer.TestModuleFileSystem;
99100
import org.sonar.plugins.python.warnings.AnalysisWarningsWrapper;
@@ -1523,25 +1524,16 @@ private PythonSensor sensor(@Nullable PythonCustomRuleRepository[] customRuleRep
15231524
FileLinesContext fileLinesContext = mock(FileLinesContext.class);
15241525
when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext);
15251526
CheckFactory checkFactory = new CheckFactory(activeRules);
1526-
if (indexer == null && customRuleRepositories == null) {
1527-
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), analysisWarnings, architectureUDGBuilderWrapper);
1528-
}
1529-
if (indexer == null) {
1530-
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, analysisWarnings, architectureUDGBuilderWrapper);
1531-
}
1532-
if (customRuleRepositories == null) {
1533-
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), indexer, new SonarLintCache(), analysisWarnings, architectureUDGBuilderWrapper);
1534-
}
15351527
return new PythonSensor(
15361528
fileLinesContextFactory,
15371529
checkFactory,
15381530
mock(NoSonarFilter.class),
1539-
customRuleRepositories,
1540-
indexer,
1541-
new SonarLintCache(),
1531+
new PythonCustomRuleRepositoryWrapper(customRuleRepositories),
1532+
new PythonIndexerWrapper(indexer),
1533+
new SonarLintCacheWrapper(),
15421534
analysisWarnings,
1543-
new RepositoryInfoProvider[]{new OpenSourceRepositoryInfoProvider()},
1544-
new ArchitectureCallbackWrapper()
1535+
new RepositoryInfoProviderWrapper(),
1536+
architectureUDGBuilderWrapper
15451537
);
15461538
}
15471539

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.editions;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
23+
class RepositoryInfoProviderWrapperTest {
24+
25+
@Test
26+
void testEmptyConstructor() {
27+
RepositoryInfoProviderWrapper repositoryInfoProviderWrapper = new RepositoryInfoProviderWrapper();
28+
assertThat(repositoryInfoProviderWrapper.infoProviders()).hasSize(1);
29+
assertThat(repositoryInfoProviderWrapper.infoProviders()[0]).isInstanceOf(OpenSourceRepositoryInfoProvider.class);
30+
}
31+
32+
@Test
33+
void testConstructorWithParameter() {
34+
RepositoryInfoProvider[] editionMetadataProviders = new RepositoryInfoProvider[] {new OpenSourceRepositoryInfoProvider(), new OpenSourceRepositoryInfoProvider()};
35+
RepositoryInfoProviderWrapper repositoryInfoProviderWrapper = new RepositoryInfoProviderWrapper(editionMetadataProviders);
36+
assertThat(repositoryInfoProviderWrapper.infoProviders()).hasSize(2);
37+
assertThat(repositoryInfoProviderWrapper.infoProviders()[0]).isInstanceOf(OpenSourceRepositoryInfoProvider.class);
38+
assertThat(repositoryInfoProviderWrapper.infoProviders()[1]).isInstanceOf(OpenSourceRepositoryInfoProvider.class);
39+
}
40+
41+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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.indexer;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
23+
import java.util.ArrayList;
24+
25+
class PythonIndexerWrapperTest {
26+
27+
@Test
28+
void testEmptyConstructor() {
29+
PythonIndexerWrapper wrapper = new PythonIndexerWrapper();
30+
assertThat(wrapper.indexer()).isNull();
31+
}
32+
33+
@Test
34+
void testConstructorWithParameter() {
35+
TestModuleFileSystem moduleFileSystem = new TestModuleFileSystem(new ArrayList<>());
36+
PythonIndexerWrapper wrapper = new PythonIndexerWrapper(new SonarLintPythonIndexer(moduleFileSystem));
37+
assertThat(wrapper.indexer()).isNotNull().isInstanceOf(PythonIndexer.class);
38+
}
39+
40+
}

0 commit comments

Comments
 (0)