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..b7fd73caa3 --- /dev/null +++ b/docs/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + + org.sonarsource.python + python + 4.24-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..4aa1e9cbca --- /dev/null +++ b/docs/python-custom-rule-examples/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + + org.sonarsource.python + docs + 4.24-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.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..bad9819f5c --- /dev/null +++ b/docs/python-custom-rule-examples/src/main/java/org/sonar/samples/python/CustomPythonRuleRepository.java @@ -0,0 +1,65 @@ +/* + * 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); + } + + private String loadResource(String path) { + URL resource = getClass().getResource(path); + if (resource == null) { + throw new IllegalStateException("Resource not found: " + path); + } + 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: " + path, 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..86fb7f7c14 --- /dev/null +++ b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/CustomPythonRuleRepositoryTest.java @@ -0,0 +1,24 @@ +/* + * 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.server.rule.RulesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +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); + } +} 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..85daa7d314 --- /dev/null +++ b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonSubscriptionCheckTest.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.checks; + +import org.junit.Test; +import org.sonar.python.checks.utils.PythonCheckVerifier; +import org.sonar.samples.python.checks.CustomPythonSubscriptionCheck; + +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..ebc926d5a2 --- /dev/null +++ b/docs/python-custom-rule-examples/src/test/java/org/sonar/samples/python/checks/CustomPythonVisitorCheckTest.java @@ -0,0 +1,17 @@ +/* + * 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; +import org.sonar.samples.python.checks.CustomPythonVisitorCheck; + +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 7efb4bb4af..e3c7f611ab 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,7 @@ sonar-python-plugin its python-checks-testkit + docs