Skip to content
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
8 changes: 7 additions & 1 deletion DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";

Expand Down Expand Up @@ -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()
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
* Copyright (C) 2021 Eric Le Goff
* mailto:community-rust AT pm DOT me
* http://github.com/elegoff/sonar-rust
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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.
Expand Down Expand Up @@ -47,10 +47,12 @@ public class RustTokensVisitor {
private final Set<String> keywords = new HashSet<>(Arrays.asList(RustKeyword.keywordValues()));
private final SensorContext context;
private final ParserAdapter<LexerlessGrammar> lexer;
private final boolean ignoreCPDTests;

public RustTokensVisitor(SensorContext context, ParserAdapter<LexerlessGrammar> 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) {
Expand Down Expand Up @@ -79,26 +81,10 @@ public void scanFile(InputFile inputFile, RustVisitorContext visitorContext) {
Set<Token> 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);
Expand All @@ -109,15 +95,36 @@ 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));
}
}

highlighting.save();
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<Token> identifyUnitTestTokens(List<Token> parsedTokens) {
Set<Token> testTokens = new HashSet<>();
Set<String> unitTestsAttributes = getUnitTestAttributes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down