Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Renew] custom rules support sonar 9.9 #23

Merged
merged 1 commit into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,20 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sonar.version>8.6.1.40680</sonar.version>
</properties>

<dependencies>
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-plugin-api</artifactId>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would keep this:

  <dependency>
    <groupId>org.sonarsource.api.plugin</groupId>
    <artifactId>sonar-plugin-api</artifactId>
    <version>${sonar.plugin.api.version}</version>
  </dependency>
  <dependency>
    <groupId>org.sonarsource.sonarqube</groupId>
    <artifactId>sonar-plugin-api-impl</artifactId>
    <version>${sonar.version}</version>
  </dependency>
  <dependency>
    <groupId>org.sonarsource.sonarqube</groupId>
    <artifactId>sonar-testing-harness</artifactId>
    <version>${sonar.version}</version>
  </dependency>

with
<sonar.version>9.9.0.65466</sonar.version>
<sonar.plugin.api.version>9.14.0.375</sonar.plugin.api.version>

<version>${sonar.version}</version>
<version>7.9</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sonarsource.sonarqube-plugins.cxx</groupId>
<artifactId>sonar-cxx-plugin</artifactId>
<type>sonar-plugin</type>
<version>0.9.7-SNAPSHOT</version>
<version>2.1.1-SNAPSHOT</version>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think this must be the version of the plugin. The "-SNAPSHOT" is replaced with the build number.

<scope>provided</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -60,8 +59,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>10</source>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is

    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>${maven-compiler.version}</version>
      <configuration>
        <encoding>UTF-8</encoding>
        <showDeprecation>true</showDeprecation>
        <release>${java.version}</release>
        <debug>true</debug>
      </configuration>
    </plugin>

with
<java.version>11</java.version>
<maven-compiler.version>3.12.1</maven-compiler.version>

see https://github.com/SonarOpenCommunity/sonar-cxx/blob/master/pom.xml

<target>10</target>
</configuration>
</plugin>
</plugins>
Expand Down
57 changes: 53 additions & 4 deletions src/main/java/org/sonar/cxx/MyCustomRulesDefinition.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,73 @@
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")
@Override
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);
}
}
}
17 changes: 7 additions & 10 deletions src/main/java/org/sonar/cxx/MyCustomRulesPlugin.java
Original file line number Diff line number Diff line change
@@ -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
);
}

}
18 changes: 7 additions & 11 deletions src/main/java/org/sonar/cxx/checks/UsingNamespaceCheck.java
Original file line number Diff line number Diff line change
@@ -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<Grammar> {

public static final String RULE_KEY = "UsingNamespace";
@Override
public void init() {
subscribeTo(CxxGrammarImpl.usingDirective);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<p>
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.
</p>

<h2>Non-compliant Code Example</h2>
<p>To Do</p>

<pre>
class myClass {
...
}
</pre>

<h2>Compliant Solution</h2>

<pre>
class MyClass {
...
}
</pre>
4 changes: 2 additions & 2 deletions src/test/java/org/sonar/cxx/MyCustomRulesDefinitionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
16 changes: 14 additions & 2 deletions src/test/java/org/sonar/cxx/MyCustomRulesPluginTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
5 changes: 4 additions & 1 deletion src/test/java/org/sonar/cxx/checks/CxxFileTester.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@

public class CxxFileTester {
public InputFile cxxFile;
public SensorContextTester sensorContext;
public SensorContextTester context;
public InputFile asInputFile() {
return cxxFile;
}
}
68 changes: 45 additions & 23 deletions src/test/java/org/sonar/cxx/checks/CxxFileTesterHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
18 changes: 10 additions & 8 deletions src/test/java/org/sonar/cxx/checks/UsingNamespaceCheckTest.java
Original file line number Diff line number Diff line change
@@ -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();
}
Expand Down