From 85ae884e1534be64fb4ffd2fb19f5c8be8e5d187 Mon Sep 17 00:00:00 2001 From: qinyong Date: Wed, 28 Feb 2024 20:01:21 +0800 Subject: [PATCH] [Renew] custom rules support sonar 9.9 Signed-off-by: qinyong --- pom.xml | 9 ++- .../sonar/cxx/MyCustomRulesDefinition.java | 57 ++++++++++++++-- .../org/sonar/cxx/MyCustomRulesPlugin.java | 17 ++--- .../sonar/cxx/checks/UsingNamespaceCheck.java | 18 ++--- .../l10n/cxx/rules/cxx/UsingNamespace.html | 21 ++++++ .../cxx/MyCustomRulesDefinitionTest.java | 4 +- .../sonar/cxx/MyCustomRulesPluginTest.java | 16 ++++- .../org/sonar/cxx/checks/CxxFileTester.java | 5 +- .../sonar/cxx/checks/CxxFileTesterHelper.java | 68 ++++++++++++------- .../cxx/checks/UsingNamespaceCheckTest.java | 18 ++--- 10 files changed, 167 insertions(+), 66 deletions(-) create mode 100644 src/main/resources/org/sonar/l10n/cxx/rules/cxx/UsingNamespace.html diff --git a/pom.xml b/pom.xml index c18083e..bfa4d2d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,21 +10,20 @@ UTF-8 - 8.6.1.40680 org.sonarsource.sonarqube sonar-plugin-api - ${sonar.version} + 7.9 provided org.sonarsource.sonarqube-plugins.cxx sonar-cxx-plugin sonar-plugin - 0.9.7-SNAPSHOT + 2.1.1-SNAPSHOT provided @@ -60,8 +59,8 @@ maven-compiler-plugin 3.8.1 - 1.7 - 1.7 + 10 + 10 diff --git a/src/main/java/org/sonar/cxx/MyCustomRulesDefinition.java b/src/main/java/org/sonar/cxx/MyCustomRulesDefinition.java index eaed9ef..608670a 100644 --- a/src/main/java/org/sonar/cxx/MyCustomRulesDefinition.java +++ b/src/main/java/org/sonar/cxx/MyCustomRulesDefinition.java @@ -1,18 +1,38 @@ package org.sonar.cxx; -import org.sonar.cxx.checks.UsingNamespaceCheck; -import org.sonar.plugins.cxx.api.CustomCxxRulesDefinition; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.resources.Language; +import org.sonar.api.scanner.ScannerSide; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.cxx.checks.*; +import org.sonar.cxx.squidbridge.annotations.AnnotationBasedRulesDefinition; +import org.sonar.plugins.cxx.CustomCxxRulesDefinition; +import org.sonar.plugins.cxx.CxxLanguage; +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; + +@ScannerSide public class MyCustomRulesDefinition extends CustomCxxRulesDefinition { + private static final Language LANGUAGE = new CxxLanguage(new MapSettings().asConfig()); + + @Override + public Language getLanguage() { + return LANGUAGE; + } @Override public String repositoryName() { - return "Repository"; + return "Custom Repository"; } @Override public String repositoryKey() { - return "repo"; + return "customrepo"; } @SuppressWarnings("rawtypes") @@ -20,5 +40,34 @@ public String repositoryKey() { public Class[] checkClasses() { return new Class[] { UsingNamespaceCheck.class }; } + @Override + public void define(RulesDefinition.Context context) { + var repo = context.createRepository(repositoryKey(), getLanguage().getKey()) + .setName(repositoryName()); + // Load metadata from check classes' annotations + new AnnotationBasedRulesDefinition(repo, getLanguage().getKey()).addRuleClasses(false, + Arrays.asList(checkClasses())); + + // Optionally override html description from annotation with content from html files + repo.rules().forEach(rule -> rule.setHtmlDescription(loadResource("/org/sonar/l10n/cxx/rules/cxx/" + rule.key() + ".html"))); + repo.done(); + } + + 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/src/main/java/org/sonar/cxx/MyCustomRulesPlugin.java b/src/main/java/org/sonar/cxx/MyCustomRulesPlugin.java index 4fa3a3d..619cfeb 100644 --- a/src/main/java/org/sonar/cxx/MyCustomRulesPlugin.java +++ b/src/main/java/org/sonar/cxx/MyCustomRulesPlugin.java @@ -1,16 +1,13 @@ package org.sonar.cxx; +import org.sonar.api.Plugin; -import java.util.List; +public class MyCustomRulesPlugin implements Plugin { -import org.sonar.api.SonarPlugin; - -import com.google.common.collect.ImmutableList; - -public class MyCustomRulesPlugin extends SonarPlugin { - - @SuppressWarnings("rawtypes") @Override - public List getExtensions() { - return ImmutableList.of(MyCustomRulesDefinition.class); + public void define(Context context) { + context.addExtension( + MyCustomRulesDefinition.class + ); } + } diff --git a/src/main/java/org/sonar/cxx/checks/UsingNamespaceCheck.java b/src/main/java/org/sonar/cxx/checks/UsingNamespaceCheck.java index 733683b..b0db217 100644 --- a/src/main/java/org/sonar/cxx/checks/UsingNamespaceCheck.java +++ b/src/main/java/org/sonar/cxx/checks/UsingNamespaceCheck.java @@ -1,27 +1,23 @@ package org.sonar.cxx.checks; -import org.sonar.api.server.rule.RulesDefinition; import org.sonar.check.Priority; import org.sonar.check.Rule; +import org.sonar.cxx.squidbridge.annotations.ActivatedByDefault; +import org.sonar.cxx.squidbridge.annotations.SqaleConstantRemediation; +import org.sonar.cxx.squidbridge.checks.SquidCheck; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; import org.sonar.cxx.parser.CxxGrammarImpl; -import org.sonar.squidbridge.annotations.ActivatedByDefault; -import org.sonar.squidbridge.annotations.SqaleConstantRemediation; -import org.sonar.squidbridge.annotations.SqaleSubCharacteristic; -import org.sonar.squidbridge.checks.SquidCheck; - -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.Grammar; @Rule( - key = "UsingNamespace", + key = UsingNamespaceCheck.RULE_KEY, name = "Using namespace directives are not allowed", priority = Priority.BLOCKER, description = "Using namespace directives are not allowed.") @ActivatedByDefault -@SqaleSubCharacteristic(RulesDefinition.SubCharacteristics.ARCHITECTURE_RELIABILITY) @SqaleConstantRemediation("5min") public class UsingNamespaceCheck extends SquidCheck { - + public static final String RULE_KEY = "UsingNamespace"; @Override public void init() { subscribeTo(CxxGrammarImpl.usingDirective); diff --git a/src/main/resources/org/sonar/l10n/cxx/rules/cxx/UsingNamespace.html b/src/main/resources/org/sonar/l10n/cxx/rules/cxx/UsingNamespace.html new file mode 100644 index 0000000..9c35145 --- /dev/null +++ b/src/main/resources/org/sonar/l10n/cxx/rules/cxx/UsingNamespace.html @@ -0,0 +1,21 @@ +

+Sharing some naming conventions is a key point to make it possible for a team to efficiently collaborate. +This rule allows to check that all class names match a provided regular expression. +

+ +

Non-compliant Code Example

+

To Do

+ +
+class myClass {
+	...
+}
+
+ +

Compliant Solution

+ +
+class MyClass {
+	...
+}
+
diff --git a/src/test/java/org/sonar/cxx/MyCustomRulesDefinitionTest.java b/src/test/java/org/sonar/cxx/MyCustomRulesDefinitionTest.java index 773259c..288c0e6 100644 --- a/src/test/java/org/sonar/cxx/MyCustomRulesDefinitionTest.java +++ b/src/test/java/org/sonar/cxx/MyCustomRulesDefinitionTest.java @@ -10,8 +10,8 @@ public class MyCustomRulesDefinitionTest { @Test public void test() { MyCustomRulesDefinition definition = new MyCustomRulesDefinition(); - assertThat(definition.repositoryName()).isEqualTo("Repository"); - assertThat(definition.repositoryKey()).isEqualTo("repo"); + assertThat(definition.repositoryName()).isEqualTo("Custom Repository"); + assertThat(definition.repositoryKey()).isEqualTo("customrepo"); assertThat(definition.checkClasses().length).isEqualTo(1); } diff --git a/src/test/java/org/sonar/cxx/MyCustomRulesPluginTest.java b/src/test/java/org/sonar/cxx/MyCustomRulesPluginTest.java index 74ccd48..eeb36af 100644 --- a/src/test/java/org/sonar/cxx/MyCustomRulesPluginTest.java +++ b/src/test/java/org/sonar/cxx/MyCustomRulesPluginTest.java @@ -3,14 +3,26 @@ import static org.fest.assertions.Assertions.assertThat; import org.junit.Test; -import org.sonar.cxx.MyCustomRulesPlugin; +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.SonarRuntimeImpl; +import org.sonar.api.utils.Version; public class MyCustomRulesPluginTest { @Test public void test() { + SonarRuntime runtime = SonarRuntimeImpl.forSonarQube( + Version.create(8, 6), + SonarQubeSide.SCANNER, + SonarEdition.COMMUNITY + ); + Plugin.Context context = new Plugin.Context(runtime); MyCustomRulesPlugin plugin = new MyCustomRulesPlugin(); - assertThat(plugin.getExtensions().size()).isEqualTo(1); + plugin.define(context); + assertThat(context.getExtensions().size()).isEqualTo(1); } } diff --git a/src/test/java/org/sonar/cxx/checks/CxxFileTester.java b/src/test/java/org/sonar/cxx/checks/CxxFileTester.java index d15d949..6d50056 100644 --- a/src/test/java/org/sonar/cxx/checks/CxxFileTester.java +++ b/src/test/java/org/sonar/cxx/checks/CxxFileTester.java @@ -24,5 +24,8 @@ public class CxxFileTester { public InputFile cxxFile; - public SensorContextTester sensorContext; + public SensorContextTester context; + public InputFile asInputFile() { + return cxxFile; + } } diff --git a/src/test/java/org/sonar/cxx/checks/CxxFileTesterHelper.java b/src/test/java/org/sonar/cxx/checks/CxxFileTesterHelper.java index b778c89..176a2bd 100644 --- a/src/test/java/org/sonar/cxx/checks/CxxFileTesterHelper.java +++ b/src/test/java/org/sonar/cxx/checks/CxxFileTesterHelper.java @@ -20,38 +20,60 @@ package org.sonar.cxx.checks; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.nio.file.Files; +import java.nio.charset.Charset; +import java.nio.file.Path; +import org.apache.commons.io.ByteOrderMark; +import org.apache.commons.io.input.BOMInputStream; import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.api.batch.sensor.internal.SensorContextTester; -/** - * - * @author jocs - */ -public class CxxFileTesterHelper { - - public static CxxFileTester CreateCxxFileTester(String fileName, String basePath) throws UnsupportedEncodingException, IOException { - CxxFileTester tester = new CxxFileTester(); - tester.sensorContext = SensorContextTester.create(new File(basePath)); +public final class CxxFileTesterHelper { - String content = new String(Files.readAllBytes(new File(tester.sensorContext.fileSystem().baseDir(), fileName).toPath()), "UTF-8"); - tester.sensorContext.fileSystem().add(new DefaultInputFile("myProjectKey", fileName).initMetadata(content)); - tester.cxxFile = tester.sensorContext.fileSystem().inputFile(tester.sensorContext.fileSystem().predicates().hasPath(fileName)); - - return tester; + private CxxFileTesterHelper() { + // utility class } - - public static CxxFileTester CreateCxxFileTester(String fileName, String basePath, String encoding) throws UnsupportedEncodingException, IOException { + + public static CxxFileTester create(String fileName, String basePath) + throws UnsupportedEncodingException, IOException { + return create(fileName, basePath, Charset.defaultCharset()); + } + + public static CxxFileTester create(String fileName, String basePath, Charset charset) + throws UnsupportedEncodingException, IOException { CxxFileTester tester = new CxxFileTester(); - tester.sensorContext = SensorContextTester.create(new File(basePath)); - String content = new String(Files.readAllBytes(new File(tester.sensorContext.fileSystem().baseDir(), fileName).toPath()), encoding); - tester.sensorContext.fileSystem().add(new DefaultInputFile("myProjectKey", fileName).initMetadata(content)); - tester.cxxFile = tester.sensorContext.fileSystem().inputFile(tester.sensorContext.fileSystem().predicates().hasPath(fileName)); - + tester.context = SensorContextTester.create(new File(basePath)); + tester.cxxFile = createInputFile(fileName, basePath, charset); + tester.context.fileSystem().add(tester.cxxFile); + return tester; } - + + private static DefaultInputFile createInputFile(String fileName, String basePath, Charset charset) throws IOException { + TestInputFileBuilder fb = TestInputFileBuilder.create("", fileName); + + fb.setCharset(charset); + fb.setProjectBaseDir(Path.of(basePath)); + fb.setContents(getSourceCode(Path.of(basePath, fileName).toFile(), charset)); + + return fb.build(); + } + + private static String getSourceCode(File filename, Charset defaultCharset) throws IOException { + try (BOMInputStream bomInputStream = new BOMInputStream(new FileInputStream(filename), + ByteOrderMark.UTF_8, + ByteOrderMark.UTF_16LE, + ByteOrderMark.UTF_16BE, + ByteOrderMark.UTF_32LE, + ByteOrderMark.UTF_32BE)) { + ByteOrderMark bom = bomInputStream.getBOM(); + Charset charset = bom != null ? Charset.forName(bom.getCharsetName()) : defaultCharset; + byte[] bytes = bomInputStream.readAllBytes(); + return new String(bytes, charset); + } + } } diff --git a/src/test/java/org/sonar/cxx/checks/UsingNamespaceCheckTest.java b/src/test/java/org/sonar/cxx/checks/UsingNamespaceCheckTest.java index 068d899..8f827fd 100644 --- a/src/test/java/org/sonar/cxx/checks/UsingNamespaceCheckTest.java +++ b/src/test/java/org/sonar/cxx/checks/UsingNamespaceCheckTest.java @@ -1,22 +1,24 @@ package org.sonar.cxx.checks; -import java.io.IOException; -import java.io.UnsupportedEncodingException; import org.junit.Test; +import org.sonar.cxx.checks.UsingNamespaceCheck; import org.sonar.cxx.CxxAstScanner; -import org.sonar.squidbridge.api.SourceFile; -import org.sonar.squidbridge.checks.CheckMessagesVerifier; +import org.sonar.cxx.squidbridge.api.SourceFile; +import org.sonar.cxx.squidbridge.checks.CheckMessagesVerifier; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; public class UsingNamespaceCheckTest { @Test public void check() throws UnsupportedEncodingException, IOException { - UsingNamespaceCheck check = new UsingNamespaceCheck(); + UsingNamespaceCheck usingNamespaceCheck = new UsingNamespaceCheck(); - CxxFileTester tester = CxxFileTesterHelper.CreateCxxFileTester("src/test/resources/checks/UsingNamespaceCheck.cc", "."); - SourceFile file = CxxAstScanner.scanSingleFile(tester.cxxFile, tester.sensorContext, check); + CxxFileTester testerHelper = CxxFileTesterHelper.create("src/test/resources/checks/UsingNamespaceCheck.cc", "."); + SourceFile sourceFile = CxxAstScanner.scanSingleInputFile(testerHelper.asInputFile(), usingNamespaceCheck); - CheckMessagesVerifier.verify(file.getCheckMessages()) + CheckMessagesVerifier.verify(sourceFile.getCheckMessages()) .next().atLine(1).withMessage("Using namespace are not allowed.") .noMore(); }