From 30629fbf3e871f83899ec32d3e4a05988b3d4727 Mon Sep 17 00:00:00 2001 From: Guillaume Dequenne Date: Mon, 15 Nov 2021 15:11:00 +0100 Subject: [PATCH] SONARPY-898 Avoid failing on older SonarLint (#1002) --- .../sonar/plugins/python/PythonPlugin.java | 6 ++-- .../plugins/python/PythonReportSensor.java | 2 +- .../sonar/plugins/python/PythonSensor.java | 22 ++++++------ .../python/coverage/PythonCoverageSensor.java | 2 +- .../warnings/AnalysisWarningsWrapper.java | 26 +++++++++++--- .../DefaultAnalysisWarningsWrapper.java | 36 ------------------- .../plugins/python/PythonPluginTest.java | 6 ++-- .../plugins/python/PythonSensorTest.java | 20 +++++------ .../coverage/PythonCoverageSensorTest.java | 2 +- .../warnings/AnalysisWarningsWrapperTest.java | 6 ++-- .../python/xunit/PythonXUnitSensorTest.java | 2 +- 11 files changed, 54 insertions(+), 76 deletions(-) delete mode 100644 sonar-python-plugin/src/main/java/org/sonar/plugins/python/warnings/DefaultAnalysisWarningsWrapper.java diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonPlugin.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonPlugin.java index b612b7abff..73a2fbf113 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonPlugin.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonPlugin.java @@ -35,7 +35,7 @@ import org.sonar.plugins.python.indexer.SonarLintPythonIndexer; import org.sonar.plugins.python.pylint.PylintRulesDefinition; import org.sonar.plugins.python.pylint.PylintSensor; -import org.sonar.plugins.python.warnings.DefaultAnalysisWarningsWrapper; +import org.sonar.plugins.python.warnings.AnalysisWarningsWrapper; import org.sonar.plugins.python.xunit.PythonXUnitSensor; public class PythonPlugin implements Plugin { @@ -72,11 +72,11 @@ public void define(Context context) { PythonProfile.class, PythonSensor.class, - PythonRuleRepository.class); + PythonRuleRepository.class, + AnalysisWarningsWrapper.class); SonarRuntime sonarRuntime = context.getRuntime(); if (sonarRuntime.getProduct() != SonarProduct.SONARLINT) { - context.addExtension(DefaultAnalysisWarningsWrapper.class); addCoberturaExtensions(context); addXUnitExtensions(context); addPylintExtensions(context); diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonReportSensor.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonReportSensor.java index 42d49fc512..cf884bca1c 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonReportSensor.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonReportSensor.java @@ -62,7 +62,7 @@ public void execute(SensorContext context) { processReports(context, reports); } catch (Exception e) { LOG.warn("Cannot read report '{}', the following exception occurred: {}", reportPath, e.getMessage()); - analysisWarnings.addWarning(String.format("An error occurred while trying to import %s report(s): '%s'", reportType, reportPath)); + analysisWarnings.addUnique(String.format("An error occurred while trying to import %s report(s): '%s'", reportType, reportPath)); } } diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonSensor.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonSensor.java index 11384bd19f..82049888a5 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonSensor.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonSensor.java @@ -38,7 +38,6 @@ import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.issue.NoSonarFilter; import org.sonar.api.measures.FileLinesContextFactory; -import org.sonar.api.notifications.AnalysisWarnings; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.plugins.python.api.ProjectPythonVersion; @@ -49,6 +48,7 @@ import org.sonar.plugins.python.api.tree.FileInput; import org.sonar.plugins.python.indexer.PythonIndexer; import org.sonar.plugins.python.indexer.SonarQubePythonIndexer; +import org.sonar.plugins.python.warnings.AnalysisWarningsWrapper; import org.sonar.python.checks.CheckList; import org.sonar.python.parser.PythonParser; import org.sonar.python.semantic.ProjectLevelSymbolTable; @@ -67,8 +67,7 @@ public final class PythonSensor implements Sensor { private final FileLinesContextFactory fileLinesContextFactory; private final NoSonarFilter noSonarFilter; private final PythonIndexer indexer; - @Nullable - private final AnalysisWarnings analysisWarnings; + private final AnalysisWarningsWrapper analysisWarnings; private static final Logger LOG = Loggers.get(PythonSensor.class); static final String UNSET_VERSION_WARNING = "Your code is analyzed as compatible with python 2 and 3 by default. This will prevent the detection of issues specific to python 2 or python 3." + @@ -77,22 +76,23 @@ public final class PythonSensor implements Sensor { /** * Constructor to be used by pico if neither PythonCustomRuleRepository nor PythonIndexer are to be found and injected. */ - public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter, AnalysisWarnings analysisWarnings) { + public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, + NoSonarFilter noSonarFilter, AnalysisWarningsWrapper analysisWarnings) { this(fileLinesContextFactory, checkFactory, noSonarFilter, null, null, analysisWarnings); } public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter, - PythonIndexer indexer) { - this(fileLinesContextFactory, checkFactory, noSonarFilter, null, indexer, null); + PythonCustomRuleRepository[] customRuleRepositories, AnalysisWarningsWrapper analysisWarnings) { + this(fileLinesContextFactory, checkFactory, noSonarFilter, customRuleRepositories, null, analysisWarnings); } public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter, - PythonCustomRuleRepository[] customRuleRepositories, AnalysisWarnings analysisWarnings) { - this(fileLinesContextFactory, checkFactory, noSonarFilter, customRuleRepositories, null, analysisWarnings); + PythonIndexer indexer, AnalysisWarningsWrapper analysisWarnings) { + this(fileLinesContextFactory, checkFactory, noSonarFilter, null, indexer, analysisWarnings); } public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter, - @Nullable PythonCustomRuleRepository[] customRuleRepositories, @Nullable PythonIndexer indexer, @Nullable AnalysisWarnings analysisWarnings) { + @Nullable PythonCustomRuleRepository[] customRuleRepositories, @Nullable PythonIndexer indexer, AnalysisWarningsWrapper analysisWarnings) { this.checks = new PythonChecks(checkFactory) .addChecks(CheckList.REPOSITORY_KEY, CheckList.getChecks()) .addCustomChecks(customRuleRepositories); @@ -118,9 +118,7 @@ public void execute(SensorContext context) { Optional pythonVersionParameter = context.config().get(PYTHON_VERSION_KEY); if (!pythonVersionParameter.isPresent() && context.runtime().getProduct() != SonarProduct.SONARLINT) { LOG.warn(UNSET_VERSION_WARNING); - if (analysisWarnings != null) { - analysisWarnings.addUnique(UNSET_VERSION_WARNING); - } + analysisWarnings.addUnique(UNSET_VERSION_WARNING); } pythonVersionParameter.ifPresent(value -> ProjectPythonVersion.setCurrentVersions(PythonVersionUtils.fromString(value))); PythonIndexer pythonIndexer = this.indexer != null ? this.indexer : new SonarQubePythonIndexer(mainFiles); diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/coverage/PythonCoverageSensor.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/coverage/PythonCoverageSensor.java index 6fa2e5b0bd..e9958f1906 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/coverage/PythonCoverageSensor.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/coverage/PythonCoverageSensor.java @@ -98,7 +98,7 @@ private void warnDeprecatedPropertyUsage(Configuration config) { if (config.hasKey(REPORT_PATH_KEY)) { String msg = "Property 'sonar.python.coverage.reportPath' has been removed. Please use 'sonar.python.coverage.reportPaths' instead."; LOG.warn(msg); - analysisWarnings.addWarning(msg); + analysisWarnings.addUnique(msg); } } diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/warnings/AnalysisWarningsWrapper.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/warnings/AnalysisWarningsWrapper.java index 206ca479c7..673570ff7b 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/warnings/AnalysisWarningsWrapper.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/warnings/AnalysisWarningsWrapper.java @@ -19,8 +19,10 @@ */ package org.sonar.plugins.python.warnings; -import org.sonar.api.batch.InstantiationStrategy; -import org.sonar.api.batch.ScannerSide; +import javax.annotation.Nullable; +import org.sonar.api.notifications.AnalysisWarnings; +import org.sonar.api.scanner.ScannerSide; +import org.sonarsource.api.sonarlint.SonarLintSide; /** * As {@link org.sonar.api.notifications.AnalysisWarnings} has been added in SQ 7.4, previous version of the API @@ -29,7 +31,21 @@ * warnings to the underlying {@link org.sonar.api.notifications.AnalysisWarnings} or do nothing when not available. */ @ScannerSide -@InstantiationStrategy("PER_BATCH") -public interface AnalysisWarningsWrapper { - void addWarning(String text); +@SonarLintSide(lifespan = SonarLintSide.MULTIPLE_ANALYSES) +public class AnalysisWarningsWrapper { + private final AnalysisWarnings analysisWarnings; + + public AnalysisWarningsWrapper(@Nullable AnalysisWarnings analysisWarnings) { + this.analysisWarnings = analysisWarnings; + } + + public AnalysisWarningsWrapper() { + this.analysisWarnings = null; + } + + public void addUnique(String text) { + if (analysisWarnings != null) { + this.analysisWarnings.addUnique(text); + } + } } diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/warnings/DefaultAnalysisWarningsWrapper.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/warnings/DefaultAnalysisWarningsWrapper.java deleted file mode 100644 index 6a128fcf1b..0000000000 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/warnings/DefaultAnalysisWarningsWrapper.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2021 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.plugins.python.warnings; - -import org.sonar.api.notifications.AnalysisWarnings; - -public class DefaultAnalysisWarningsWrapper implements AnalysisWarningsWrapper { - - private final AnalysisWarnings analysisWarnings; - - public DefaultAnalysisWarningsWrapper(AnalysisWarnings analysisWarnings) { - this.analysisWarnings = analysisWarnings; - } - - @Override - public void addWarning(String text) { - this.analysisWarnings.addUnique(text); - } -} diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonPluginTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonPluginTest.java index ccc640e5fe..500550e896 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonPluginTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonPluginTest.java @@ -29,7 +29,7 @@ import org.sonar.api.utils.Version; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; -import org.sonar.plugins.python.warnings.DefaultAnalysisWarningsWrapper; +import org.sonar.plugins.python.warnings.AnalysisWarningsWrapper; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -45,8 +45,8 @@ public void testGetExtensions() { Version v79 = Version.create(7, 9); SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(v79, SonarQubeSide.SERVER, SonarEdition.DEVELOPER); assertThat(extensions(runtime)).hasSize(21); - assertThat(extensions(runtime)).contains(DefaultAnalysisWarningsWrapper.class); - assertThat(extensions(SonarRuntimeImpl.forSonarLint(v79))).hasSize(6); + assertThat(extensions(runtime)).contains(AnalysisWarningsWrapper.class); + assertThat(extensions(SonarRuntimeImpl.forSonarLint(v79))).hasSize(7); } @Test diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java index c878e5a2bc..f2f67b7ab8 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java @@ -58,7 +58,6 @@ import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.FileLinesContext; import org.sonar.api.measures.FileLinesContextFactory; -import org.sonar.api.notifications.AnalysisWarnings; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.Version; import org.sonar.api.utils.log.LogTester; @@ -73,6 +72,7 @@ import org.sonar.plugins.python.indexer.PythonIndexer; import org.sonar.plugins.python.indexer.SonarLintPythonIndexer; import org.sonar.plugins.python.indexer.TestModuleFileSystem; +import org.sonar.plugins.python.warnings.AnalysisWarningsWrapper; import org.sonar.python.checks.CheckList; import static org.assertj.core.api.Assertions.assertThat; @@ -131,7 +131,7 @@ public void scanFile(PythonVisitorContext visitorContext) { private ActiveRules activeRules; - private final AnalysisWarnings analysisWarning = mock(AnalysisWarnings.class); + private final AnalysisWarningsWrapper analysisWarning = mock(AnalysisWarningsWrapper.class); @org.junit.Rule public LogTester logTester = new LogTester(); @@ -319,7 +319,7 @@ public void cross_files_issues_only_one_file_analyzed() { // "mod.py" created but not added to context InputFile modFile = createInputFile("mod.py"); PythonIndexer pythonIndexer = pythonIndexer(Arrays.asList(mainFile, modFile)); - sensor(CUSTOM_RULES, pythonIndexer, null).execute(context); + sensor(null, pythonIndexer, analysisWarning).execute(context); assertThat(context.allIssues()).hasSize(1); Issue issue = context.allIssues().iterator().next(); assertThat(issue.primaryLocation().inputComponent()).isEqualTo(mainFile); @@ -341,7 +341,7 @@ public void no_indexer_when_project_too_large_sonarlint() { InputFile mainFile = inputFile("main.py"); PythonIndexer pythonIndexer = pythonIndexer(Collections.singletonList(mainFile)); - sensor(CUSTOM_RULES, pythonIndexer, null).execute(context); + sensor(CUSTOM_RULES, pythonIndexer, analysisWarning).execute(context); assertThat(context.allIssues()).isEmpty(); assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Project symbol table deactivated due to project size (total number of lines is 4, maximum for indexing is 1)"); assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Update \"sonar.python.sonarlint.indexing.maxlines\" to set a different limit."); @@ -358,7 +358,7 @@ public void loop_in_class_hierarchy() { InputFile mainFile = inputFile("modA.py"); InputFile modFile = inputFile("modB.py"); PythonIndexer pythonIndexer = pythonIndexer(Arrays.asList(mainFile, modFile)); - sensor(null, pythonIndexer, null).execute(context); + sensor(null, pythonIndexer, analysisWarning).execute(context); assertThat(context.allIssues()).hasSize(1); } @@ -494,7 +494,7 @@ public void cancelled_analysis() { InputFile inputFile = inputFile(FILE_1); activeRules = (new ActiveRulesBuilder()).build(); context.setCancelled(true); - sensor(null, null, null).execute(context); + sensor(null, null, analysisWarning).execute(context); assertThat(context.measure(inputFile.key(), CoreMetrics.NCLOC)).isNull(); assertThat(context.allAnalysisErrors()).isEmpty(); } @@ -562,7 +562,7 @@ private PythonSensor sensor() { return sensor(CUSTOM_RULES, null, analysisWarning); } - private PythonSensor sensor(@Nullable PythonCustomRuleRepository[] customRuleRepositories, @Nullable PythonIndexer indexer, @Nullable AnalysisWarnings analysisWarnings) { + private PythonSensor sensor(@Nullable PythonCustomRuleRepository[] customRuleRepositories, @Nullable PythonIndexer indexer, AnalysisWarningsWrapper analysisWarnings) { FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class); FileLinesContext fileLinesContext = mock(FileLinesContext.class); when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext); @@ -570,12 +570,12 @@ private PythonSensor sensor(@Nullable PythonCustomRuleRepository[] customRuleRep if (indexer == null && customRuleRepositories == null) { return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), analysisWarnings); } - if (indexer != null && customRuleRepositories == null) { - return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), indexer); - } if (indexer == null) { return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, analysisWarnings); } + if (customRuleRepositories == null) { + return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), indexer, analysisWarnings); + } return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, indexer, analysisWarnings); } diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/coverage/PythonCoverageSensorTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/coverage/PythonCoverageSensorTest.java index 4fdb0b738a..3ba89e6ccc 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/coverage/PythonCoverageSensorTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/coverage/PythonCoverageSensorTest.java @@ -198,7 +198,7 @@ public void test_unique_report() { List actual = IntStream.range(1, 18).mapToObj(line -> context.lineHits(FILE4_KEY, line)).collect(Collectors.toList()); Integer coverageAtLine6 = actual.get(5); assertThat(coverageAtLine6).isEqualTo(1); - verify(analysisWarnings, times(1)).addWarning(eq("Property 'sonar.python.coverage.reportPath' has been removed. Please use 'sonar.python.coverage.reportPaths' instead.")); + verify(analysisWarnings, times(1)).addUnique(eq("Property 'sonar.python.coverage.reportPath' has been removed. Please use 'sonar.python.coverage.reportPaths' instead.")); } @Test diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/warnings/AnalysisWarningsWrapperTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/warnings/AnalysisWarningsWrapperTest.java index db2a32cbe9..00d821c7fe 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/warnings/AnalysisWarningsWrapperTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/warnings/AnalysisWarningsWrapperTest.java @@ -32,9 +32,9 @@ public class AnalysisWarningsWrapperTest { @Test public void test() { AnalysisWarnings analysisWarnings = spy(AnalysisWarnings.class); - DefaultAnalysisWarningsWrapper defaultAnalysisWarningsWrapper = new DefaultAnalysisWarningsWrapper(analysisWarnings); - defaultAnalysisWarningsWrapper.addWarning("abcd"); - defaultAnalysisWarningsWrapper.addWarning("def"); + AnalysisWarningsWrapper defaultAnalysisWarningsWrapper = new AnalysisWarningsWrapper(analysisWarnings); + defaultAnalysisWarningsWrapper.addUnique("abcd"); + defaultAnalysisWarningsWrapper.addUnique("def"); verify(analysisWarnings, times(2)).addUnique(anyString()); } diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/PythonXUnitSensorTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/PythonXUnitSensorTest.java index 78c165fbfc..d6d51614a8 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/PythonXUnitSensorTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/PythonXUnitSensorTest.java @@ -119,7 +119,7 @@ public void shouldLogWarningWhenGivenInvalidTime() { assertThat(logTester.logs(LoggerLevel.WARN)).contains("Cannot read report 'xunit-reports/invalid-time-xunit-report.xml', " + "the following exception occurred: java.text.ParseException: Unparseable number: \"brrrr\""); verify(analysisWarnings, times(1)) - .addWarning(eq("An error occurred while trying to import XUnit report(s): 'xunit-reports/invalid-time-xunit-report.xml'")); + .addUnique(eq("An error occurred while trying to import XUnit report(s): 'xunit-reports/invalid-time-xunit-report.xml'")); } @Test