From 19ab00021d468ce7860e5359205e6913e251d7f4 Mon Sep 17 00:00:00 2001 From: Thomas Serre <118730793+thomas-serre-sonarsource@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:50:00 +0100 Subject: [PATCH 1/2] SONARPY-2371 Move the custom rule examples to sonar-python repository (#2183) Co-authored-by: Maksim Grebeniuk --- docs/LICENSE.txt | 18 ++++ docs/pom.xml | 20 ++++ docs/python-custom-rule-examples/pom.xml | 91 +++++++++++++++++++ .../python/CustomPythonRuleRepository.java | 69 ++++++++++++++ .../python/CustomPythonRulesPlugin.java | 16 ++++ .../checks/CustomPythonSubscriptionCheck.java | 26 ++++++ .../checks/CustomPythonVisitorCheck.java | 27 ++++++ .../samples/python/checks/package-info.java | 9 ++ .../sonar/samples/python/package-info.java | 9 ++ .../src/main/resources/license-header.txt | 2 + .../python/rules/python/subscription.html | 9 ++ .../l10n/python/rules/python/visitor.html | 9 ++ .../CustomPythonRuleRepositoryTest.java | 46 ++++++++++ .../python/CustomPythonRulesPluginTest.java | 26 ++++++ .../CustomPythonSubscriptionCheckTest.java | 15 +++ .../checks/CustomPythonVisitorCheckTest.java | 15 +++ .../checks/customPythonSubscriptionCheck.py | 4 + .../checks/customPythonVisitorCheck.py | 4 + pom.xml | 1 + 19 files changed, 416 insertions(+) create mode 100644 docs/LICENSE.txt create mode 100644 docs/pom.xml create mode 100644 docs/python-custom-rule-examples/pom.xml create mode 100644 docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java create mode 100644 docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRulesPlugin.java create mode 100644 docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java create mode 100644 docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java create mode 100644 docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/package-info.java create mode 100644 docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/package-info.java create mode 100644 docs/python-custom-rule-examples/src/main/resources/license-header.txt create mode 100644 docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/subscription.html create mode 100644 docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/visitor.html create mode 100644 docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java create mode 100644 docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java create mode 100644 docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java create mode 100644 docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java create mode 100644 docs/python-custom-rule-examples/src/test/resources/checks/customPythonSubscriptionCheck.py create mode 100644 docs/python-custom-rule-examples/src/test/resources/checks/customPythonVisitorCheck.py diff --git a/docs/LICENSE.txt b/docs/LICENSE.txt new file mode 100644 index 0000000000..e3493679e7 --- /dev/null +++ b/docs/LICENSE.txt @@ -0,0 +1,18 @@ +MIT No Attribution + +Copyright 2023, SonarSource SA + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/pom.xml b/docs/pom.xml new file mode 100644 index 0000000000..52dac58e1e --- /dev/null +++ b/docs/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + + org.sonarsource.python + python + 4.25-SNAPSHOT + + + docs + pom + + SonarQube Python :: Documentation + + + python-custom-rule-examples + + + diff --git a/docs/python-custom-rule-examples/pom.xml b/docs/python-custom-rule-examples/pom.xml new file mode 100644 index 0000000000..1f5156404a --- /dev/null +++ b/docs/python-custom-rule-examples/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + + org.sonarsource.python + docs + 4.25-SNAPSHOT + + + python-custom-rule-examples + sonar-plugin + + SonarQube Python :: Documentation :: Custom Rules Example + Python custom rule examples for SonarQube + + + 3.15.0.9787 + + + + org.sonarsource.sonarqube + sonar-plugin-api + 7.9 + provided + + + org.sonarsource.python + sonar-python-plugin + sonar-plugin + ${sonar.python.version} + provided + + + org.sonarsource.python + python-checks-testkit + ${sonar.python.version} + test + + + junit + junit + 4.13.1 + test + + + org.mockito + mockito-core + test + + + + + + + org.sonarsource.sonar-packaging-maven-plugin + sonar-packaging-maven-plugin + 1.21.0.505 + true + + org.sonar.samples.python.CustomPythonRulesPlugin + python:${sonar.python.version} + true + true + + + + + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + com.mycila + license-maven-plugin + + + +
${project.basedir}/src/main/resources/license-header.txt
+
+
+
+
+
+
+ +
diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java new file mode 100644 index 0000000000..ee8d080fcf --- /dev/null +++ b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.api.server.rule.RulesDefinitionAnnotationLoader; +import org.sonar.plugins.python.api.PythonCustomRuleRepository; +import org.sonar.samples.python.checks.CustomPythonSubscriptionCheck; +import org.sonar.samples.python.checks.CustomPythonVisitorCheck; + +public class CustomPythonRuleRepository implements RulesDefinition, PythonCustomRuleRepository { + + @Override + public void define(Context context) { + NewRepository repository = context.createRepository(repositoryKey(), "py").setName("My custom repo"); + new RulesDefinitionAnnotationLoader().load(repository, checkClasses().toArray(new Class[] {})); + Map remediationCosts = new HashMap<>(); + remediationCosts.put(CustomPythonVisitorCheck.RULE_KEY, "5min"); + remediationCosts.put(CustomPythonSubscriptionCheck.RULE_KEY, "10min"); + repository.rules().forEach(rule -> rule.setDebtRemediationFunction( + rule.debtRemediationFunctions().constantPerIssue(remediationCosts.get(rule.key())))); + + // Optionally override html description from annotation with content from html files + repository.rules().forEach(rule -> rule.setHtmlDescription(loadResource("/org/sonar/l10n/python/rules/python/" + rule.key() + ".html"))); + repository.done(); + } + + @Override + public String repositoryKey() { + return "python-custom-rules"; + } + + @Override + public List checkClasses() { + return Arrays.asList(CustomPythonVisitorCheck.class, CustomPythonSubscriptionCheck.class); + } + + String loadResource(String path) { + URL resource = getClass().getResource(path); + if (resource == null) { + throw new IllegalStateException("Resource not found: " + path); + } + return readResource(resource); + } + + static String readResource(URL resource) { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + try (InputStream in = resource.openStream()) { + byte[] buffer = new byte[1024]; + for (int len = in.read(buffer); len != -1; len = in.read(buffer)) { + result.write(buffer, 0, len); + } + return new String(result.toByteArray(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalStateException("Failed to read resource: " + resource, e); + } + } +} diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRulesPlugin.java b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRulesPlugin.java new file mode 100644 index 0000000000..c64f507085 --- /dev/null +++ b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRulesPlugin.java @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python; + +import org.sonar.api.Plugin; + +public class CustomPythonRulesPlugin implements Plugin { + + @Override + public void define(Context context) { + context.addExtension(CustomPythonRuleRepository.class); + } + +} diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java new file mode 100644 index 0000000000..e5d19099f2 --- /dev/null +++ b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python.checks; + +import org.sonar.check.Priority; +import org.sonar.check.Rule; +import org.sonar.plugins.python.api.PythonSubscriptionCheck; +import org.sonar.plugins.python.api.tree.ForStatement; +import org.sonar.plugins.python.api.tree.Tree; + +@Rule( + key = CustomPythonSubscriptionCheck.RULE_KEY, + priority = Priority.MINOR, + name = "Python subscription visitor check", + description = "desc") +public class CustomPythonSubscriptionCheck extends PythonSubscriptionCheck { + + public static final String RULE_KEY = "subscription"; + + @Override + public void initialize(Context context) { + context.registerSyntaxNodeConsumer(Tree.Kind.FOR_STMT, ctx -> ctx.addIssue(((ForStatement) ctx.syntaxNode()).forKeyword(), "For statement.")); + } +} diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java new file mode 100644 index 0000000000..cdfd46cd43 --- /dev/null +++ b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python.checks; + +import org.sonar.check.Priority; +import org.sonar.check.Rule; +import org.sonar.plugins.python.api.PythonVisitorCheck; +import org.sonar.plugins.python.api.tree.FunctionDef; + +@Rule( + key = CustomPythonVisitorCheck.RULE_KEY, + priority = Priority.MINOR, + name = "Python visitor check", + description = "desc") +public class CustomPythonVisitorCheck extends PythonVisitorCheck { + + public static final String RULE_KEY = "visitor"; + + @Override + public void visitFunctionDef(FunctionDef pyFunctionDefTree) { + addIssue(pyFunctionDefTree.name(), "Function def."); + super.visitFunctionDef(pyFunctionDefTree); + } + +} diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/package-info.java b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/package-info.java new file mode 100644 index 0000000000..979ed078cf --- /dev/null +++ b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/package-info.java @@ -0,0 +1,9 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +@ParametersAreNonnullByDefault +package org.sonar.samples.python.checks; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/package-info.java b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/package-info.java new file mode 100644 index 0000000000..3cf9ca9ddf --- /dev/null +++ b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/package-info.java @@ -0,0 +1,9 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +@ParametersAreNonnullByDefault +package org.sonar.samples.python; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/docs/python-custom-rule-examples/src/main/resources/license-header.txt b/docs/python-custom-rule-examples/src/main/resources/license-header.txt new file mode 100644 index 0000000000..27a2c5c0c2 --- /dev/null +++ b/docs/python-custom-rule-examples/src/main/resources/license-header.txt @@ -0,0 +1,2 @@ +Copyright (C) ${license.years} ${license.owner} - ${license.mailto} +This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. \ No newline at end of file diff --git a/docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/subscription.html b/docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/subscription.html new file mode 100644 index 0000000000..bd3027f657 --- /dev/null +++ b/docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/subscription.html @@ -0,0 +1,9 @@ +

raises an issue on a For statement.

+

Noncompliant Code Example

+
+TO DO 
+
+

Compliant Solution

+
+TO DO 
+
diff --git a/docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/visitor.html b/docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/visitor.html new file mode 100644 index 0000000000..67572dce7f --- /dev/null +++ b/docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/visitor.html @@ -0,0 +1,9 @@ +

raises an issue on a For statement. with a visitor on which you can control the visit.

+

Noncompliant Code Example

+
+TO DO 
+
+

Compliant Solution

+
+TO DO 
+
diff --git a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java new file mode 100644 index 0000000000..20346b91a5 --- /dev/null +++ b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python; + +import java.io.IOException; +import java.net.URL; +import org.junit.Test; +import org.sonar.api.server.rule.RulesDefinition; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class CustomPythonRuleRepositoryTest { + + @Test + public void test_rule_repository() { + CustomPythonRuleRepository customPythonRuleRepository = new CustomPythonRuleRepository(); + RulesDefinition.Context context = new RulesDefinition.Context(); + customPythonRuleRepository.define(context); + assertThat(customPythonRuleRepository.repositoryKey()).isEqualTo("python-custom-rules"); + assertThat(context.repositories()).hasSize(1).extracting("key").containsExactly(customPythonRuleRepository.repositoryKey()); + assertThat(context.repositories().get(0).rules()).hasSize(2); + assertThat(customPythonRuleRepository.checkClasses()).hasSize(2); + } + + @Test + public void test_unfound_resource() { + CustomPythonRuleRepository repository = new CustomPythonRuleRepository(); + assertThatThrownBy(() -> repository.loadResource("/unknown")) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Resource not found: /unknown"); + } + + @Test + public void test_read_exception_resource() throws IOException { + URL urlMock = Mockito.mock(URL.class); + Mockito.when(urlMock.openStream()).thenThrow(IOException.class); + Mockito.when(urlMock.toString()).thenReturn("MyURL"); + assertThatThrownBy(() -> CustomPythonRuleRepository.readResource(urlMock)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Failed to read resource: MyURL"); + } +} diff --git a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java new file mode 100644 index 0000000000..565b627791 --- /dev/null +++ b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python; + +import org.junit.Test; +import org.sonar.api.Plugin; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.SonarRuntime; +import org.sonar.api.internal.PluginContextImpl; +import org.sonar.api.internal.SonarRuntimeImpl; +import org.sonar.api.utils.Version; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CustomPythonRulesPluginTest { + @Test + public void test() { + SonarRuntime sonarRuntime = SonarRuntimeImpl.forSonarQube(Version.create(7, 9), SonarQubeSide.SCANNER, SonarEdition.DEVELOPER); + Plugin.Context context = new PluginContextImpl.Builder().setSonarRuntime(sonarRuntime).build(); + new CustomPythonRulesPlugin().define(context); + assertThat(context.getExtensions()).hasSize(1); + } +} diff --git a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java new file mode 100644 index 0000000000..5073b54229 --- /dev/null +++ b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python.checks; + +import org.junit.Test; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class CustomPythonSubscriptionCheckTest { + @Test + public void test() { + PythonCheckVerifier.verify("src/test/resources/checks/customPythonSubscriptionCheck.py", new CustomPythonSubscriptionCheck()); + } +} diff --git a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java new file mode 100644 index 0000000000..c7f1ce24ba --- /dev/null +++ b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python.checks; + +import org.junit.Test; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class CustomPythonVisitorCheckTest { + @Test + public void test() { + PythonCheckVerifier.verify("src/test/resources/checks/customPythonVisitorCheck.py", new CustomPythonVisitorCheck()); + } +} diff --git a/docs/python-custom-rule-examples/src/test/resources/checks/customPythonSubscriptionCheck.py b/docs/python-custom-rule-examples/src/test/resources/checks/customPythonSubscriptionCheck.py new file mode 100644 index 0000000000..0dbd62c4f8 --- /dev/null +++ b/docs/python-custom-rule-examples/src/test/resources/checks/customPythonSubscriptionCheck.py @@ -0,0 +1,4 @@ +class A: + for i in foo: # Noncompliant {{For statement.}} +# ^^^ + pass diff --git a/docs/python-custom-rule-examples/src/test/resources/checks/customPythonVisitorCheck.py b/docs/python-custom-rule-examples/src/test/resources/checks/customPythonVisitorCheck.py new file mode 100644 index 0000000000..aeda8ea08d --- /dev/null +++ b/docs/python-custom-rule-examples/src/test/resources/checks/customPythonVisitorCheck.py @@ -0,0 +1,4 @@ +class A: + def fun(): # Noncompliant {{Function def.}} +# ^^^ + pass diff --git a/pom.xml b/pom.xml index f10f6687d4..ccb0ae6222 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,7 @@ sonar-python-plugin its python-checks-testkit + docs From a7deee8d6cc37a6c33479b5c69d0fd42420f19b6 Mon Sep 17 00:00:00 2001 From: Maksim Grebeniuk <122789225+maksim-grebeniuk-sonarsource@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:30:16 +0100 Subject: [PATCH 2/2] SONARPY-2379 Update the custom rules in the sonar-python repository to match LAYC format (#2188) --- docs/pom.xml | 2 +- .../python/CustomPythonRuleRepository.java | 69 ----------------- .../CustomPythonRuleRepositoryTest.java | 46 ----------- .../pom.xml | 40 ++++++---- .../python/CustomPythonRuleRepository.java | 42 ++++++++++ .../python/CustomPythonRulesPlugin.java | 0 .../org/sonar/samples/python/RulesList.java | 43 +++++++++++ .../checks/CustomPythonSubscriptionCheck.java | 7 +- .../checks/CustomPythonVisitorCheck.java | 7 +- .../samples/python/checks/package-info.java | 0 .../sonar/samples/python/package-info.java | 0 .../src/main/resources/license-header.txt | 0 .../python/rules/python/subscription.html | 0 .../python/rules/python/subscription.json | 21 +++++ .../l10n/python/rules/python/visitor.html | 0 .../l10n/python/rules/python/visitor.json | 21 +++++ .../CustomPythonRuleRepositoryTest.java | 31 ++++++++ .../python/CustomPythonRulesPluginTest.java | 8 +- .../CustomPythonSubscriptionCheckTest.java | 6 +- .../checks/CustomPythonVisitorCheckTest.java | 6 +- .../checks/customPythonSubscriptionCheck.py | 0 .../checks/customPythonVisitorCheck.py | 0 .../profile-python-custom-rules-example.xml | 15 ++++ .../it/plugin/CustomRulesExampleTest.java | 77 +++++++++++++++++++ .../sonar/python/it/plugin/TestsUtils.java | 2 + its/pom.xml | 21 +++++ pom.xml | 2 +- 27 files changed, 310 insertions(+), 156 deletions(-) delete mode 100644 docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java delete mode 100644 docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java rename docs/{python-custom-rule-examples => python-custom-rules-example}/pom.xml (69%) create mode 100644 docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/main/java/org/sonar/samples/python/CustomPythonRulesPlugin.java (100%) create mode 100644 docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/RulesList.java rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java (80%) rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java (79%) rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/main/java/org/sonar/samples/python/checks/package-info.java (100%) rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/main/java/org/sonar/samples/python/package-info.java (100%) rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/main/resources/license-header.txt (100%) rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/main/resources/org/sonar/l10n/python/rules/python/subscription.html (100%) create mode 100644 docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/subscription.json rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/main/resources/org/sonar/l10n/python/rules/python/visitor.html (100%) create mode 100644 docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/visitor.json create mode 100644 docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java (84%) rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java (81%) rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java (82%) rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/test/resources/checks/customPythonSubscriptionCheck.py (100%) rename docs/{python-custom-rule-examples => python-custom-rules-example}/src/test/resources/checks/customPythonVisitorCheck.py (100%) create mode 100644 its/plugin/it-python-plugin-test/profiles/profile-python-custom-rules-example.xml create mode 100644 its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/CustomRulesExampleTest.java diff --git a/docs/pom.xml b/docs/pom.xml index 52dac58e1e..1b454000c4 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -14,7 +14,7 @@ SonarQube Python :: Documentation - python-custom-rule-examples + python-custom-rules-example diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java deleted file mode 100644 index ee8d080fcf..0000000000 --- a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com - * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. - */ -package org.sonar.samples.python; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.sonar.api.server.rule.RulesDefinition; -import org.sonar.api.server.rule.RulesDefinitionAnnotationLoader; -import org.sonar.plugins.python.api.PythonCustomRuleRepository; -import org.sonar.samples.python.checks.CustomPythonSubscriptionCheck; -import org.sonar.samples.python.checks.CustomPythonVisitorCheck; - -public class CustomPythonRuleRepository implements RulesDefinition, PythonCustomRuleRepository { - - @Override - public void define(Context context) { - NewRepository repository = context.createRepository(repositoryKey(), "py").setName("My custom repo"); - new RulesDefinitionAnnotationLoader().load(repository, checkClasses().toArray(new Class[] {})); - Map remediationCosts = new HashMap<>(); - remediationCosts.put(CustomPythonVisitorCheck.RULE_KEY, "5min"); - remediationCosts.put(CustomPythonSubscriptionCheck.RULE_KEY, "10min"); - repository.rules().forEach(rule -> rule.setDebtRemediationFunction( - rule.debtRemediationFunctions().constantPerIssue(remediationCosts.get(rule.key())))); - - // Optionally override html description from annotation with content from html files - repository.rules().forEach(rule -> rule.setHtmlDescription(loadResource("/org/sonar/l10n/python/rules/python/" + rule.key() + ".html"))); - repository.done(); - } - - @Override - public String repositoryKey() { - return "python-custom-rules"; - } - - @Override - public List checkClasses() { - return Arrays.asList(CustomPythonVisitorCheck.class, CustomPythonSubscriptionCheck.class); - } - - String loadResource(String path) { - URL resource = getClass().getResource(path); - if (resource == null) { - throw new IllegalStateException("Resource not found: " + path); - } - return readResource(resource); - } - - static String readResource(URL resource) { - ByteArrayOutputStream result = new ByteArrayOutputStream(); - try (InputStream in = resource.openStream()) { - byte[] buffer = new byte[1024]; - for (int len = in.read(buffer); len != -1; len = in.read(buffer)) { - result.write(buffer, 0, len); - } - return new String(result.toByteArray(), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new IllegalStateException("Failed to read resource: " + resource, e); - } - } -} diff --git a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java deleted file mode 100644 index 20346b91a5..0000000000 --- a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com - * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. - */ -package org.sonar.samples.python; - -import java.io.IOException; -import java.net.URL; -import org.junit.Test; -import org.sonar.api.server.rule.RulesDefinition; -import org.mockito.Mockito; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class CustomPythonRuleRepositoryTest { - - @Test - public void test_rule_repository() { - CustomPythonRuleRepository customPythonRuleRepository = new CustomPythonRuleRepository(); - RulesDefinition.Context context = new RulesDefinition.Context(); - customPythonRuleRepository.define(context); - assertThat(customPythonRuleRepository.repositoryKey()).isEqualTo("python-custom-rules"); - assertThat(context.repositories()).hasSize(1).extracting("key").containsExactly(customPythonRuleRepository.repositoryKey()); - assertThat(context.repositories().get(0).rules()).hasSize(2); - assertThat(customPythonRuleRepository.checkClasses()).hasSize(2); - } - - @Test - public void test_unfound_resource() { - CustomPythonRuleRepository repository = new CustomPythonRuleRepository(); - assertThatThrownBy(() -> repository.loadResource("/unknown")) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Resource not found: /unknown"); - } - - @Test - public void test_read_exception_resource() throws IOException { - URL urlMock = Mockito.mock(URL.class); - Mockito.when(urlMock.openStream()).thenThrow(IOException.class); - Mockito.when(urlMock.toString()).thenReturn("MyURL"); - assertThatThrownBy(() -> CustomPythonRuleRepository.readResource(urlMock)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Failed to read resource: MyURL"); - } -} diff --git a/docs/python-custom-rule-examples/pom.xml b/docs/python-custom-rules-example/pom.xml similarity index 69% rename from docs/python-custom-rule-examples/pom.xml rename to docs/python-custom-rules-example/pom.xml index 1f5156404a..1f61f30c59 100644 --- a/docs/python-custom-rule-examples/pom.xml +++ b/docs/python-custom-rules-example/pom.xml @@ -9,44 +9,51 @@ 4.25-SNAPSHOT - python-custom-rule-examples + python-custom-rules-example sonar-plugin SonarQube Python :: Documentation :: Custom Rules Example Python custom rule examples for SonarQube - 3.15.0.9787 + 4.24.0.18631 - org.sonarsource.sonarqube + org.sonarsource.api.plugin sonar-plugin-api - 7.9 provided + + org.sonarsource.analyzer-commons + sonar-analyzer-commons + + + org.sonarsource.sonarqube + sonar-plugin-api-impl + test + org.sonarsource.python sonar-python-plugin sonar-plugin - ${sonar.python.version} + ${project.version} provided org.sonarsource.python python-checks-testkit - ${sonar.python.version} + ${project.version} test - junit - junit - 4.13.1 + org.junit.jupiter + junit-jupiter test - org.mockito - mockito-core + org.junit.jupiter + junit-jupiter-api test @@ -56,13 +63,12 @@ org.sonarsource.sonar-packaging-maven-plugin sonar-packaging-maven-plugin - 1.21.0.505 true org.sonar.samples.python.CustomPythonRulesPlugin - python:${sonar.python.version} - true - true + python:${project.version} + ${pluginApiMinVersion} + py,ipynb @@ -70,8 +76,8 @@ maven-compiler-plugin 3.7.0 - 1.8 - 1.8 + 17 + 17 diff --git a/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java new file mode 100644 index 0000000000..93628d386b --- /dev/null +++ b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python; + +import java.util.ArrayList; +import java.util.List; +import org.sonar.api.SonarRuntime; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.plugins.python.api.PythonCustomRuleRepository; +import org.sonarsource.analyzer.commons.RuleMetadataLoader; + +public class CustomPythonRuleRepository implements RulesDefinition, PythonCustomRuleRepository { + public static final String RESOURCE_BASE_PATH = "/org/sonar/l10n/python/rules/python"; + public static final String REPOSITORY_KEY = "python-custom-rules-example"; + public static final String REPOSITORY_NAME = "MyCompany Custom Repository"; + + private final SonarRuntime runtime; + + public CustomPythonRuleRepository(SonarRuntime runtime) { + this.runtime = runtime; + } + + @Override + public void define(Context context) { + NewRepository repository = context.createRepository(REPOSITORY_KEY, "py").setName(REPOSITORY_NAME); + RuleMetadataLoader ruleMetadataLoader = new RuleMetadataLoader(RESOURCE_BASE_PATH, runtime); + ruleMetadataLoader.addRulesByAnnotatedClass(repository, new ArrayList<>(RulesList.getChecks())); + repository.done(); + } + + @Override + public String repositoryKey() { + return REPOSITORY_KEY; + } + + @Override + public List> checkClasses() { + return new ArrayList<>(RulesList.getChecks()); + } +} diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRulesPlugin.java b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/CustomPythonRulesPlugin.java similarity index 100% rename from docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRulesPlugin.java rename to docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/CustomPythonRulesPlugin.java diff --git a/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/RulesList.java b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/RulesList.java new file mode 100644 index 0000000000..d154374843 --- /dev/null +++ b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/RulesList.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.samples.python.checks.CustomPythonSubscriptionCheck; +import org.sonar.samples.python.checks.CustomPythonVisitorCheck; + +public final class RulesList { + + private RulesList() { + } + + public static List> getChecks() { + return new ArrayList<>(Stream.concat( + getPythonChecks().stream(), + getPythonTestChecks().stream() + ).toList()); + } + + /** + * These rules are going to target MAIN code only + */ + public static List> getPythonChecks() { + return new ArrayList<>(List.of( + CustomPythonSubscriptionCheck.class + )); + } + + /** + * These rules are going to target TEST code only + */ + public static List> getPythonTestChecks() { + return new ArrayList<>(List.of( + CustomPythonVisitorCheck.class + )); + } +} diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java similarity index 80% rename from docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java rename to docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java index e5d19099f2..12cce48a29 100644 --- a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java +++ b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheck.java @@ -4,17 +4,12 @@ */ package org.sonar.samples.python.checks; -import org.sonar.check.Priority; import org.sonar.check.Rule; import org.sonar.plugins.python.api.PythonSubscriptionCheck; import org.sonar.plugins.python.api.tree.ForStatement; import org.sonar.plugins.python.api.tree.Tree; -@Rule( - key = CustomPythonSubscriptionCheck.RULE_KEY, - priority = Priority.MINOR, - name = "Python subscription visitor check", - description = "desc") +@Rule(key = CustomPythonSubscriptionCheck.RULE_KEY) public class CustomPythonSubscriptionCheck extends PythonSubscriptionCheck { public static final String RULE_KEY = "subscription"; diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java similarity index 79% rename from docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java rename to docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java index cdfd46cd43..2590444699 100644 --- a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java +++ b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/checks/CustomPythonVisitorCheck.java @@ -4,16 +4,11 @@ */ package org.sonar.samples.python.checks; -import org.sonar.check.Priority; import org.sonar.check.Rule; import org.sonar.plugins.python.api.PythonVisitorCheck; import org.sonar.plugins.python.api.tree.FunctionDef; -@Rule( - key = CustomPythonVisitorCheck.RULE_KEY, - priority = Priority.MINOR, - name = "Python visitor check", - description = "desc") +@Rule(key = CustomPythonVisitorCheck.RULE_KEY) public class CustomPythonVisitorCheck extends PythonVisitorCheck { public static final String RULE_KEY = "visitor"; diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/package-info.java b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/checks/package-info.java similarity index 100% rename from docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/checks/package-info.java rename to docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/checks/package-info.java diff --git a/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/package-info.java b/docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/package-info.java similarity index 100% rename from docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/package-info.java rename to docs/python-custom-rules-example/src/main/java/org/sonar/samples/python/package-info.java diff --git a/docs/python-custom-rule-examples/src/main/resources/license-header.txt b/docs/python-custom-rules-example/src/main/resources/license-header.txt similarity index 100% rename from docs/python-custom-rule-examples/src/main/resources/license-header.txt rename to docs/python-custom-rules-example/src/main/resources/license-header.txt diff --git a/docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/subscription.html b/docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/subscription.html similarity index 100% rename from docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/subscription.html rename to docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/subscription.html diff --git a/docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/subscription.json b/docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/subscription.json new file mode 100644 index 0000000000..ae2fe92c00 --- /dev/null +++ b/docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/subscription.json @@ -0,0 +1,21 @@ +{ + "title": "Python subscription visitor check", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "10min" + }, + "tags": [ + "custom-python" + ], + "code": { + "impacts": { + "MAINTAINABILITY": "LOW", + "RELIABILITY": "LOW", + "SECURITY": "LOW" + }, + "attribute": "CLEAR" + }, + "defaultSeverity": "Minor" +} \ No newline at end of file diff --git a/docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/visitor.html b/docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/visitor.html similarity index 100% rename from docs/python-custom-rule-examples/src/main/resources/org/sonar/l10n/python/rules/python/visitor.html rename to docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/visitor.html diff --git a/docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/visitor.json b/docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/visitor.json new file mode 100644 index 0000000000..8ba45d565c --- /dev/null +++ b/docs/python-custom-rules-example/src/main/resources/org/sonar/l10n/python/rules/python/visitor.json @@ -0,0 +1,21 @@ +{ + "title": "Python visitor check", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "custom-python" + ], + "code": { + "impacts": { + "MAINTAINABILITY": "LOW", + "RELIABILITY": "LOW", + "SECURITY": "LOW" + }, + "attribute": "CLEAR" + }, + "defaultSeverity": "Minor" +} \ No newline at end of file diff --git a/docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java b/docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java new file mode 100644 index 0000000000..bb5182d48e --- /dev/null +++ b/docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011-2024 SonarSource SA - mailto:info AT sonarsource DOT com + * This code is released under [MIT No Attribution](https://opensource.org/licenses/MIT-0) license. + */ +package org.sonar.samples.python; + +import org.junit.jupiter.api.Test; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.SonarRuntime; +import org.sonar.api.internal.SonarRuntimeImpl; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.api.utils.Version; + +import static org.assertj.core.api.Assertions.assertThat; + +class CustomPythonRuleRepositoryTest { + + @Test + void test_rule_repository() { + SonarRuntime sonarRuntime = SonarRuntimeImpl.forSonarQube(Version.create(9, 9), SonarQubeSide.SCANNER, SonarEdition.DEVELOPER); + CustomPythonRuleRepository customPythonRuleRepository = new CustomPythonRuleRepository(sonarRuntime); + RulesDefinition.Context context = new RulesDefinition.Context(); + customPythonRuleRepository.define(context); + assertThat(customPythonRuleRepository.repositoryKey()).isEqualTo("python-custom-rules-example"); + assertThat(context.repositories()).hasSize(1).extracting("key").containsExactly(customPythonRuleRepository.repositoryKey()); + var rules = context.repositories().get(0).rules(); + assertThat(rules).hasSize(2); + assertThat(customPythonRuleRepository.checkClasses()).hasSize(2); + } +} diff --git a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java b/docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java similarity index 84% rename from docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java rename to docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java index 565b627791..b17d80f659 100644 --- a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java +++ b/docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/CustomPythonRulesPluginTest.java @@ -4,7 +4,7 @@ */ package org.sonar.samples.python; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.sonar.api.Plugin; import org.sonar.api.SonarEdition; import org.sonar.api.SonarQubeSide; @@ -15,10 +15,10 @@ import static org.assertj.core.api.Assertions.assertThat; -public class CustomPythonRulesPluginTest { +class CustomPythonRulesPluginTest { @Test - public void test() { - SonarRuntime sonarRuntime = SonarRuntimeImpl.forSonarQube(Version.create(7, 9), SonarQubeSide.SCANNER, SonarEdition.DEVELOPER); + void test() { + SonarRuntime sonarRuntime = SonarRuntimeImpl.forSonarQube(Version.create(9, 9), SonarQubeSide.SCANNER, SonarEdition.DEVELOPER); Plugin.Context context = new PluginContextImpl.Builder().setSonarRuntime(sonarRuntime).build(); new CustomPythonRulesPlugin().define(context); assertThat(context.getExtensions()).hasSize(1); diff --git a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java b/docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java similarity index 81% rename from docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java rename to docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java index 5073b54229..70b8480113 100644 --- a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java +++ b/docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.java @@ -4,12 +4,12 @@ */ package org.sonar.samples.python.checks; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.sonar.python.checks.utils.PythonCheckVerifier; -public class CustomPythonSubscriptionCheckTest { +class CustomPythonSubscriptionCheckTest { @Test - public void test() { + void test() { PythonCheckVerifier.verify("src/test/resources/checks/customPythonSubscriptionCheck.py", new CustomPythonSubscriptionCheck()); } } diff --git a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java b/docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java similarity index 82% rename from docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java rename to docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java index c7f1ce24ba..37e23e378e 100644 --- a/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java +++ b/docs/python-custom-rules-example/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java @@ -4,12 +4,12 @@ */ package org.sonar.samples.python.checks; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.sonar.python.checks.utils.PythonCheckVerifier; -public class CustomPythonVisitorCheckTest { +class CustomPythonVisitorCheckTest { @Test - public void test() { + void test() { PythonCheckVerifier.verify("src/test/resources/checks/customPythonVisitorCheck.py", new CustomPythonVisitorCheck()); } } diff --git a/docs/python-custom-rule-examples/src/test/resources/checks/customPythonSubscriptionCheck.py b/docs/python-custom-rules-example/src/test/resources/checks/customPythonSubscriptionCheck.py similarity index 100% rename from docs/python-custom-rule-examples/src/test/resources/checks/customPythonSubscriptionCheck.py rename to docs/python-custom-rules-example/src/test/resources/checks/customPythonSubscriptionCheck.py diff --git a/docs/python-custom-rule-examples/src/test/resources/checks/customPythonVisitorCheck.py b/docs/python-custom-rules-example/src/test/resources/checks/customPythonVisitorCheck.py similarity index 100% rename from docs/python-custom-rule-examples/src/test/resources/checks/customPythonVisitorCheck.py rename to docs/python-custom-rules-example/src/test/resources/checks/customPythonVisitorCheck.py diff --git a/its/plugin/it-python-plugin-test/profiles/profile-python-custom-rules-example.xml b/its/plugin/it-python-plugin-test/profiles/profile-python-custom-rules-example.xml new file mode 100644 index 0000000000..76fe4b4e97 --- /dev/null +++ b/its/plugin/it-python-plugin-test/profiles/profile-python-custom-rules-example.xml @@ -0,0 +1,15 @@ + + + python-custom-rules-example-profile + py + + + python-custom-rules-example + visitor + + + python-custom-rules-example + subscription + + + diff --git a/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/CustomRulesExampleTest.java b/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/CustomRulesExampleTest.java new file mode 100644 index 0000000000..87f6307437 --- /dev/null +++ b/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/CustomRulesExampleTest.java @@ -0,0 +1,77 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2012-2024 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 Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package com.sonar.python.it.plugin; + +import com.sonar.orchestrator.build.SonarScanner; +import com.sonar.orchestrator.junit5.OrchestratorExtension; +import java.io.File; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.client.issues.SearchRequest; + +import static com.sonar.python.it.plugin.TestsUtils.newWsClient; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +class CustomRulesExampleTest { + + @RegisterExtension + public static final OrchestratorExtension orchestrator = TestsUtils.ORCHESTRATOR; + + private static final String PROJECT_KEY = "custom-rules"; + private static final String PROJECT_NAME = "Custom Rules"; + + @BeforeAll + static void prepare() { + orchestrator.getServer().provisionProject(PROJECT_KEY, PROJECT_NAME); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT_KEY, "py", "python-custom-rules-example-profile"); + SonarScanner build = SonarScanner.create() + .setProjectDir(new File("projects/custom_rules")) + .setProjectKey(PROJECT_KEY) + .setProjectName(PROJECT_NAME) + .setProjectVersion("1.0") + .setSourceDirs("src"); + orchestrator.executeBuild(build); + } + + @Test + void base_tree_visitor_check() { + List issues = issues("python-custom-rules-example:visitor"); + assertSingleIssue(issues, 4, "Function def.", "5min"); + } + + @Test + void subscription_base_visitor_check() { + List issues = issues("python-custom-rules-example:subscription"); + assertSingleIssue(issues, 7, "For statement.", "10min"); + } + + private void assertSingleIssue(List issues, int expectedLine, String expectedMessage, String expectedDebt) { + assertThat(issues).hasSize(1); + Issues.Issue issue = issues.get(0); + assertThat(issue.getLine()).isEqualTo(expectedLine); + assertThat(issue.getMessage()).isEqualTo(expectedMessage); + assertThat(issue.getDebt()).isEqualTo(expectedDebt); + } + + private static List issues(String rulekey) { + return newWsClient().issues().search(new SearchRequest().setRules(singletonList(rulekey))).getIssuesList(); + } +} diff --git a/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/TestsUtils.java b/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/TestsUtils.java index c1c15d0eed..e115b6908f 100644 --- a/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/TestsUtils.java +++ b/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/TestsUtils.java @@ -52,6 +52,8 @@ public final class TestsUtils { .addPlugin(PLUGIN_LOCATION) // Custom rules plugin .addPlugin(FileLocation.byWildcardMavenFilename(new File("../python-custom-rules-plugin/target"), "python-custom-rules-plugin-*.jar")) + .addPlugin(FileLocation.byWildcardMavenFilename(new File("../../../docs/python-custom-rules-example/target"), "python-custom-rules-example-*.jar")) + .restoreProfileAtStartup(FileLocation.of("profiles/profile-python-custom-rules-example.xml")) .restoreProfileAtStartup(FileLocation.of("profiles/profile-python-custom-rules.xml")) .restoreProfileAtStartup(FileLocation.of("profiles/profile-python-test-rules.xml")) .restoreProfileAtStartup(FileLocation.of("profiles/no_rule.xml")) diff --git a/its/pom.xml b/its/pom.xml index b604774de0..b30545c0a2 100644 --- a/its/pom.xml +++ b/its/pom.xml @@ -68,6 +68,27 @@ true + + copy-python-custom-rules-example + generate-test-resources + + copy + + + + + ${project.groupId} + python-custom-rules-example + ${project.version} + sonar-plugin + true + + + ../../docs/python-custom-rules-example/target + true + true + + diff --git a/pom.xml b/pom.xml index ccb0ae6222..55978fdae7 100644 --- a/pom.xml +++ b/pom.xml @@ -49,9 +49,9 @@ python-frontend python-checks sonar-python-plugin + docs its python-checks-testkit - docs