diff --git a/DOC.md b/DOC.md
index 8b438906..1dfc53b7 100644
--- a/DOC.md
+++ b/DOC.md
@@ -82,6 +82,12 @@ and provided in the form of reports.
Currently, only `junit report` formats are supported :
-Insert a parameter `community.rust.test.reportPath` into you `sonar-project.properties` file.
+Insert a parameter `community.rust.test.reportPath` into your `sonar-project.properties` file.
As an example, one of such tool for Rust that converts `cargo test` report to `junit report` is [cargo2junit](https://crates.io/crates/cargo2junit).
+### Ignore duplications on unit tests
+
+By default the unit tests are included as part as the duplication calculation
+
+You may override the default `false` for the property `community.rust.cpd.ignoretests` either globally from the UI
+(*Administration->Configuration->Rust*) or by setting `community.rust.cpd.ignoretests=true` in your `sonar-project.properties` file.
\ No newline at end of file
diff --git a/community-rust-plugin/src/main/java/org/elegoff/plugins/communityrust/CommunityRustPlugin.java b/community-rust-plugin/src/main/java/org/elegoff/plugins/communityrust/CommunityRustPlugin.java
index e1138ff7..ded52be2 100644
--- a/community-rust-plugin/src/main/java/org/elegoff/plugins/communityrust/CommunityRustPlugin.java
+++ b/community-rust-plugin/src/main/java/org/elegoff/plugins/communityrust/CommunityRustPlugin.java
@@ -25,6 +25,7 @@
import org.elegoff.plugins.communityrust.rules.RustRulesDefinition;
import org.elegoff.plugins.communityrust.xunit.XUnitSensor;
import org.sonar.api.Plugin;
+import org.sonar.api.PropertyType;
import org.sonar.api.config.PropertyDefinition;
import org.elegoff.plugins.communityrust.clippy.ClippySensor;
import org.elegoff.plugins.communityrust.clippy.ClippyRulesDefinition;
@@ -45,6 +46,8 @@ public class CommunityRustPlugin implements Plugin {
public static final String COBERTURA_REPORT_PATHS = "community.rust.cobertura.reportPaths";
public static final String DEFAULT_COBERTURA_REPORT_PATHS = "cobertura.xml";
public static final String UNIT_TEST_ATTRIBUTES = "community.rust.unittests.attributes";
+ public static final String IGNORE_DUPLICATION_FOR_TESTS = "community.rust.cpd.ignoretests";
+
public static final String TEST_AND_COVERAGE = "Test and Coverage";
public static final String DEFAULT_UNIT_TEST_ATTRIBUTES="test,tokio::test";
@@ -95,11 +98,20 @@ public void define(Context context) {
PropertyDefinition.builder(UNIT_TEST_ATTRIBUTES)
.defaultValue(DEFAULT_UNIT_TEST_ATTRIBUTES)
.name("Unit tests")
- .description("Comme separated list of Rust attributes for Unit Tests")
+ .description("Comma separated list of Rust attributes for Unit Tests")
.onQualifiers(Qualifiers.PROJECT)
.subCategory(TEST_AND_COVERAGE)
.category("Rust")
.multiValues(true)
+ .build(),
+ PropertyDefinition.builder(IGNORE_DUPLICATION_FOR_TESTS)
+ .defaultValue(Boolean.toString(false))
+ .name("Duplications on Unit tests")
+ .description("If true, CPD ignores functions identified as unit tests (see " + UNIT_TEST_ATTRIBUTES + ")")
+ .onQualifiers(Qualifiers.PROJECT)
+ .subCategory(TEST_AND_COVERAGE)
+ .category("Rust")
+ .type(PropertyType.BOOLEAN)
.build()
);
diff --git a/community-rust-plugin/src/main/java/org/elegoff/plugins/communityrust/RustTokensVisitor.java b/community-rust-plugin/src/main/java/org/elegoff/plugins/communityrust/RustTokensVisitor.java
index 3a7adf27..6fb64493 100644
--- a/community-rust-plugin/src/main/java/org/elegoff/plugins/communityrust/RustTokensVisitor.java
+++ b/community-rust-plugin/src/main/java/org/elegoff/plugins/communityrust/RustTokensVisitor.java
@@ -3,17 +3,17 @@
* Copyright (C) 2021 Eric Le Goff
* mailto:community-rust AT pm DOT me
* http://github.com/elegoff/sonar-rust
- *
+ *
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
@@ -47,10 +47,12 @@ public class RustTokensVisitor {
private final Set keywords = new HashSet<>(Arrays.asList(RustKeyword.keywordValues()));
private final SensorContext context;
private final ParserAdapter lexer;
+ private final boolean ignoreCPDTests;
public RustTokensVisitor(SensorContext context, ParserAdapter lexer) {
this.context = context;
this.lexer = lexer;
+ this.ignoreCPDTests = context.config().getBoolean(CommunityRustPlugin.IGNORE_DUPLICATION_FOR_TESTS).orElse(false);
}
private static String getTokenImage(Token token) {
@@ -79,26 +81,10 @@ public void scanFile(InputFile inputFile, RustVisitorContext visitorContext) {
Set unitTestTokens = identifyUnitTestTokens(parsedTokens);
for (Token token : parsedTokens) {
- final String tokenImage = getTokenImage(token);
- final var tokenLocation = tokenLocation(token);
-
- if (token.getType().equals(RustTokenType.CHARACTER_LITERAL)
- || token.getType().equals(RustTokenType.STRING_LITERAL)
- || token.getType().equals(RustTokenType.RAW_STRING_LITERAL)
- || token.getType().equals(RustTokenType.RAW_BYTE_STRING_LITERAL)
- ) {
- highlight(highlighting, tokenLocation, TypeOfText.STRING);
-
- } else if (keywords.contains(tokenImage)) {
- highlight(highlighting, tokenLocation, TypeOfText.KEYWORD);
- }
+ final var tokenLocation = tokenLocation(token);
- if (token.getType().equals(RustTokenType.FLOAT_LITERAL)
- || token.getType().equals(RustTokenType.BOOLEAN_LITERAL)
- || token.getType().equals(RustTokenType.INTEGER_LITERAL)) {
- highlight(highlighting, tokenLocation, TypeOfText.CONSTANT);
- }
+ highlightToken(token,tokenLocation, highlighting );
for (Trivia trivia : token.getTrivia()) {
highlight(highlighting, tokenLocation(trivia.getToken()), TypeOfText.COMMENT);
@@ -109,8 +95,8 @@ public void scanFile(InputFile inputFile, RustVisitorContext visitorContext) {
);
}
- if (!GenericTokenType.EOF.equals(token.getType())) {
- cpdTokens.addToken(tokenLocation.startLine(), tokenLocation.startLineOffset(), tokenLocation.endLine(), tokenLocation.endLineOffset(), tokenImage);
+ if (!GenericTokenType.EOF.equals(token.getType()) && !(unitTestTokens.contains(token) && this.ignoreCPDTests)) {
+ cpdTokens.addToken(tokenLocation.startLine(), tokenLocation.startLineOffset(), tokenLocation.endLine(), tokenLocation.endLineOffset(), getTokenImage(token));
}
}
@@ -118,6 +104,27 @@ public void scanFile(InputFile inputFile, RustVisitorContext visitorContext) {
cpdTokens.save();
}
+ private void highlightToken(Token token, TokenLocation tokenLocation, NewHighlighting highlighting){
+ final String tokenImage = getTokenImage(token);
+ if (token.getType().equals(RustTokenType.CHARACTER_LITERAL)
+ || token.getType().equals(RustTokenType.STRING_LITERAL)
+ || token.getType().equals(RustTokenType.RAW_STRING_LITERAL)
+ || token.getType().equals(RustTokenType.RAW_BYTE_STRING_LITERAL)
+
+ ) {
+ highlight(highlighting, tokenLocation, TypeOfText.STRING);
+
+ } else if (keywords.contains(tokenImage)) {
+ highlight(highlighting, tokenLocation, TypeOfText.KEYWORD);
+ }
+
+ if (token.getType().equals(RustTokenType.FLOAT_LITERAL)
+ || token.getType().equals(RustTokenType.BOOLEAN_LITERAL)
+ || token.getType().equals(RustTokenType.INTEGER_LITERAL)) {
+ highlight(highlighting, tokenLocation, TypeOfText.CONSTANT);
+ }
+ }
+
private Set identifyUnitTestTokens(List parsedTokens) {
Set testTokens = new HashSet<>();
Set unitTestsAttributes = getUnitTestAttributes();
diff --git a/community-rust-plugin/src/test/java/org/elegoff/plugins/communityrust/CommunityRustPluginTest.java b/community-rust-plugin/src/test/java/org/elegoff/plugins/communityrust/CommunityRustPluginTest.java
index 5ab33fa4..9734742d 100644
--- a/community-rust-plugin/src/test/java/org/elegoff/plugins/communityrust/CommunityRustPluginTest.java
+++ b/community-rust-plugin/src/test/java/org/elegoff/plugins/communityrust/CommunityRustPluginTest.java
@@ -39,9 +39,9 @@ public class CommunityRustPluginTest extends TestCase {
public void testGetExtensions() {
Version v79 = Version.create(7, 9);
SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(v79, SonarQubeSide.SERVER, SonarEdition.DEVELOPER);
- assertThat(extensions(runtime)).hasSize(16);
+ assertThat(extensions(runtime)).hasSize(17);
assertThat(extensions(runtime)).contains(ClippyRulesDefinition.class);
- assertThat(extensions(SonarRuntimeImpl.forSonarLint(v79))).hasSize(16);
+ assertThat(extensions(SonarRuntimeImpl.forSonarLint(v79))).hasSize(17);
}
private static List extensions(SonarRuntime runtime) {
diff --git a/community-rust-plugin/src/test/java/org/elegoff/plugins/communityrust/RustSensorTest.java b/community-rust-plugin/src/test/java/org/elegoff/plugins/communityrust/RustSensorTest.java
index 3ba11489..5d39dfb8 100644
--- a/community-rust-plugin/src/test/java/org/elegoff/plugins/communityrust/RustSensorTest.java
+++ b/community-rust-plugin/src/test/java/org/elegoff/plugins/communityrust/RustSensorTest.java
@@ -117,12 +117,24 @@ public void canParse() throws IOException {
@Test
public void checkDuplication() throws IOException {
DefaultInputFile inputFile = executeSensorOnSingleFile("sensor/cpd.rs");
+
assertEquals(212, tester.cpdTokens(inputFile.key()).size());
verify(fileLinesContext).save();
assertEquals(Collections.singletonList(TypeOfText.ANNOTATION), tester.highlightingTypeAt(inputFile.key(), 5, 5));
Assertions.assertThat(tester.allAnalysisErrors()).isEmpty();
}
+ @Test
+ public void checkDuplicationIgnoringTests() throws IOException {
+ tester.settings().setProperty(CommunityRustPlugin.IGNORE_DUPLICATION_FOR_TESTS,true);
+ DefaultInputFile inputFile = executeSensorOnSingleFile("sensor/cpd.rs");
+
+ assertEquals(3, tester.cpdTokens(inputFile.key()).size());
+ verify(fileLinesContext).save();
+ assertEquals(Collections.singletonList(TypeOfText.ANNOTATION), tester.highlightingTypeAt(inputFile.key(), 5, 5));
+ Assertions.assertThat(tester.allAnalysisErrors()).isEmpty();
+ }
+
private DefaultInputFile executeSensorOnSingleFile(String fileName) throws IOException {