From 6d88b6e182ed6a0020e2897d22d83dfccfd9ae76 Mon Sep 17 00:00:00 2001 From: Eric Le Goff Date: Mon, 17 Jan 2022 15:15:10 +0100 Subject: [PATCH 1/5] Highlight unit test lines --- .../communityrust/RustTokensVisitor.java | 156 +++++++----- .../plugins/communityrust/RustSensorTest.java | 8 + .../src/test/resources/sensor/cpd.rs | 224 ++++++++++++++++++ 3 files changed, 333 insertions(+), 55 deletions(-) create mode 100644 community-rust-plugin/src/test/resources/sensor/cpd.rs 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 7f3852c5..feb5761d 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. @@ -35,81 +35,127 @@ import org.sonar.sslr.parser.ParserAdapter; import org.sonarsource.analyzer.commons.TokenLocation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; +import java.util.*; -public class RustTokensVisitor{ +public class RustTokensVisitor { - private final Set keywords = new HashSet<>(Arrays.asList(RustKeyword.keywordValues())); - private final SensorContext context; - private final ParserAdapter lexer; + private final Set keywords = new HashSet<>(Arrays.asList(RustKeyword.keywordValues())); + private final SensorContext context; + private final ParserAdapter lexer; - public RustTokensVisitor(SensorContext context, ParserAdapter lexer) { - this.context = context; - this.lexer = lexer; - } + public RustTokensVisitor(SensorContext context, ParserAdapter lexer) { + this.context = context; + this.lexer = lexer; + } - private static String getTokenImage(Token token) { - if (token.getType().equals(RustTokenType.CHARACTER_LITERAL)) { - return RustTokenType.CHARACTER_LITERAL.getValue(); - } - return token.getValue().toLowerCase(Locale.ENGLISH); + private static String getTokenImage(Token token) { + if (token.getType().equals(RustTokenType.CHARACTER_LITERAL)) { + return RustTokenType.CHARACTER_LITERAL.getValue(); } + return token.getValue().toLowerCase(Locale.ENGLISH); + } - private static void highlight(NewHighlighting highlighting, TokenLocation tokenLocation, TypeOfText typeOfText) { - highlighting.highlight(tokenLocation.startLine(), tokenLocation.startLineOffset(), tokenLocation.endLine(), tokenLocation.endLineOffset(), typeOfText); - } + private static void highlight(NewHighlighting highlighting, TokenLocation tokenLocation, TypeOfText typeOfText) { + highlighting.highlight(tokenLocation.startLine(), tokenLocation.startLineOffset(), tokenLocation.endLine(), tokenLocation.endLineOffset(), typeOfText); + } - private static TokenLocation tokenLocation(Token token) { - return new TokenLocation(token.getLine(), token.getColumn(), token.getOriginalValue()); - } + private static TokenLocation tokenLocation(Token token) { + return new TokenLocation(token.getLine(), token.getColumn(), token.getOriginalValue()); + } - public void scanFile(InputFile inputFile, RustVisitorContext visitorContext) { - var highlighting = context.newHighlighting(); - highlighting.onFile(inputFile); + public void scanFile(InputFile inputFile, RustVisitorContext visitorContext) { + var highlighting = context.newHighlighting(); + highlighting.onFile(inputFile); - var cpdTokens = context.newCpdTokens(); - cpdTokens.onFile(inputFile); + var cpdTokens = context.newCpdTokens(); + cpdTokens.onFile(inputFile); - for (Token token : lexer.parse(visitorContext.file().content()).getTokens()) { - final String tokenImage = getTokenImage(token); - final var tokenLocation = tokenLocation(token); + List parsedTokens = lexer.parse(visitorContext.file().content()).getTokens(); - 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) + Set testAttributes = new HashSet(){{ + add("test"); + add("tokio::test"); + }}; + Set testTokens = identifyUnitTestTokens(parsedTokens,testAttributes ); - ) { - highlight(highlighting, tokenLocation, TypeOfText.STRING); + for (Token token : parsedTokens) { + final String tokenImage = getTokenImage(token); + final var tokenLocation = tokenLocation(token); - } else if (keywords.contains(tokenImage)) { - highlight(highlighting, tokenLocation, TypeOfText.KEYWORD); - } + 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) - if (token.getType().equals(RustTokenType.FLOAT_LITERAL) - ||token.getType().equals(RustTokenType.BOOLEAN_LITERAL) - ||token.getType().equals(RustTokenType.INTEGER_LITERAL) + ) { + highlight(highlighting, tokenLocation, TypeOfText.STRING); - ) { - highlight(highlighting, tokenLocation, TypeOfText.CONSTANT); + } 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); + + } + for (Trivia trivia : token.getTrivia()) { + highlight(highlighting, tokenLocation(trivia.getToken()), TypeOfText.COMMENT); + } + + if (testTokens.contains(token)) { + highlight(highlighting, tokenLocation, TypeOfText.ANNOTATION + ); + } + + if (!GenericTokenType.EOF.equals(token.getType())) { + cpdTokens.addToken(tokenLocation.startLine(), tokenLocation.startLineOffset(), tokenLocation.endLine(), tokenLocation.endLineOffset(), tokenImage); + } + } + + highlighting.save(); + cpdTokens.save(); + } + + private Set identifyUnitTestTokens(List parsedTokens, Set knownTestAttributes) { + Set testTokens = new HashSet<>(); + for (int i = 0; i < parsedTokens.size(); i++) { + if (("#".equals(getTokenImage(parsedTokens.get(i)))) + && ("[".equals(getTokenImage(parsedTokens.get(i + 1)))) + && (knownTestAttributes.contains(getTokenImage(parsedTokens.get(i + 2)))) + && ("]".equals(getTokenImage(parsedTokens.get(i + 3)))) + && ("fn".equals(getTokenImage(parsedTokens.get(i + 4))))) { + int j = i + 5; + //lookup for opening bracket + while (!"{".equals(getTokenImage(parsedTokens.get(j)))) { + j++; } - for (Trivia trivia : token.getTrivia()) { - highlight(highlighting, tokenLocation(trivia.getToken()), TypeOfText.COMMENT); + int cptOpeningBracket = 1; + //lookup for outer closing bracket (end of test function position) + while (cptOpeningBracket > 0) { + j++; + if ("{".equals(getTokenImage(parsedTokens.get(j)))) { + cptOpeningBracket++; + } + if ("}".equals(getTokenImage(parsedTokens.get(j)))) { + cptOpeningBracket--; + } } - if (!GenericTokenType.EOF.equals(token.getType())) { - cpdTokens.addToken(tokenLocation.startLine(), tokenLocation.startLineOffset(), tokenLocation.endLine(), tokenLocation.endLineOffset(), tokenImage); + //all tokens constituting a test function are added to the set + for (int k = i; k <= j; k++) { + testTokens.add(parsedTokens.get(k)); } } - - highlighting.save(); - cpdTokens.save(); } + return testTokens; + + } } \ No newline at end of file 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 cf7d4bb5..9ce8f01a 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 @@ -114,6 +114,14 @@ public void canParse() throws IOException { Assertions.assertThat(tester.allAnalysisErrors()).isEmpty(); } + @Test + public void checkDuplication() throws IOException { + DefaultInputFile inputFile = executeSensorOnSingleFile("sensor/cpd.rs"); + assertEquals(212, tester.cpdTokens(inputFile.key()).size()); + verify(fileLinesContext).save(); + Assertions.assertThat(tester.allAnalysisErrors()).isEmpty(); + } + private DefaultInputFile executeSensorOnSingleFile(String fileName) throws IOException { diff --git a/community-rust-plugin/src/test/resources/sensor/cpd.rs b/community-rust-plugin/src/test/resources/sensor/cpd.rs new file mode 100644 index 00000000..358dcd62 --- /dev/null +++ b/community-rust-plugin/src/test/resources/sensor/cpd.rs @@ -0,0 +1,224 @@ +//This file has a lot of duplication in the tests + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn it_works2() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn it_works3() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn it_works4() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn it_works5() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn it_works6() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn it_works7() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn it_works8() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn it_works9() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn it_works10() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } + + #[test] + fn it_works11() { + let result = 2 + 2; + assert_eq!(result, 4); + let result = 3 + 2; + assert_eq!(result, 5); + let result = 4 + 2; + assert_eq!(result, 6); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + let result = 2 + 2; + assert_eq!(result, 4); + } +} From 045a589d2c4725b456e112096365ec90af0d5878 Mon Sep 17 00:00:00 2001 From: Eric Le Goff Date: Mon, 17 Jan 2022 16:37:55 +0100 Subject: [PATCH 2/5] add property to identify test attributes --- DOC.md | 11 ++++++++--- .../plugins/communityrust/CommunityRustPlugin.java | 14 ++++++++++++-- .../communityrust/CommunityRustPluginTest.java | 4 ++-- .../plugins/communityrust/RustSensorTest.java | 1 + 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/DOC.md b/DOC.md index 00a09b27..8b438906 100644 --- a/DOC.md +++ b/DOC.md @@ -68,15 +68,20 @@ e.g But [other coverage tools](https://vladfilippov.com/blog/rust-code-coverage-tools/) might work as well +## Highlighting unit tests +By default, the plugin will highlight Rust unit tests for functions having attributes `#[test]` or `#[tokio::test]` +You may configure different attributes with parameter `community.rust.unitttests.attributes` + ## Adding test measures Optionally SonarQube can also display tests measures. -This Community Rust plugin doesn't run your tests or generate tests reports for you. That has to be done before analysis and provided in the form of reports. +This Community Rust plugin doesn't run your tests or generate tests reports for you. That has to be done before analysis +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. As an example, one of such tool +Insert a parameter `community.rust.test.reportPath` into you `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). -for Rust than converts `cargo test` report to `junit report` is [cargo2junit](https://crates.io/crates/cargo2junit). \ 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 50186453..09e1cc2c 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 @@ -44,6 +44,8 @@ public class CommunityRustPlugin implements Plugin { public static final String DEFAULT_LCOV_REPORT_PATHS = "lcov.info"; 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 DEFAULT_UNIT_TEST_ATTRIBUTES="test,tokio::test"; @Override public void define(Context context) { @@ -87,9 +89,17 @@ public void define(Context context) { .subCategory("Test and Coverage") .category("Rust") .multiValues(true) - .build() - + .build(), + PropertyDefinition.builder(UNIT_TEST_ATTRIBUTES) + .defaultValue(DEFAULT_UNIT_TEST_ATTRIBUTES) + .name("Unit tests") + .description("Comme separated list of Rust attributes for Unit Tests") + .onQualifiers(Qualifiers.PROJECT) + .subCategory("Test and Coverage") + .category("Rust") + .multiValues(true) + .build() ); 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 0b127037..5ab33fa4 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(15); + assertThat(extensions(runtime)).hasSize(16); assertThat(extensions(runtime)).contains(ClippyRulesDefinition.class); - assertThat(extensions(SonarRuntimeImpl.forSonarLint(v79))).hasSize(15); + assertThat(extensions(SonarRuntimeImpl.forSonarLint(v79))).hasSize(16); } 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 9ce8f01a..3ba11489 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 @@ -119,6 +119,7 @@ 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(); } From 8025560e17da8e2e39b7b1628679a866e1113062 Mon Sep 17 00:00:00 2001 From: Eric Le Goff Date: Tue, 18 Jan 2022 12:19:13 +0100 Subject: [PATCH 3/5] allow to override default test attributes --- .../communityrust/CommunityRustPlugin.java | 5 +++ .../communityrust/RustTokensVisitor.java | 42 +++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) 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 09e1cc2c..c86431d7 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.config.Configuration; import org.sonar.api.config.PropertyDefinition; import org.elegoff.plugins.communityrust.clippy.ClippySensor; import org.elegoff.plugins.communityrust.clippy.ClippyRulesDefinition; @@ -33,6 +34,10 @@ import org.elegoff.plugins.communityrust.settings.RustLanguageSettings; import org.sonar.api.resources.Qualifiers; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + /** * This class is the entry point for all extensions. It is referenced in pom.xml. */ 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 feb5761d..c72533e5 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 @@ -24,10 +24,13 @@ import com.sonar.sslr.api.GenericTokenType; import com.sonar.sslr.api.Token; import com.sonar.sslr.api.Trivia; +import org.apache.commons.lang.StringUtils; +import org.elegoff.plugins.communityrust.settings.RustLanguageSettings; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.highlighting.NewHighlighting; import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.api.config.Configuration; import org.sonar.rust.RustVisitorContext; import org.sonar.rust.api.RustKeyword; import org.sonar.rust.api.RustTokenType; @@ -36,6 +39,8 @@ import org.sonarsource.analyzer.commons.TokenLocation; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class RustTokensVisitor { @@ -72,12 +77,7 @@ public void scanFile(InputFile inputFile, RustVisitorContext visitorContext) { cpdTokens.onFile(inputFile); List parsedTokens = lexer.parse(visitorContext.file().content()).getTokens(); - - Set testAttributes = new HashSet(){{ - add("test"); - add("tokio::test"); - }}; - Set testTokens = identifyUnitTestTokens(parsedTokens,testAttributes ); + Set unitTestTokens = identifyUnitTestTokens(parsedTokens); for (Token token : parsedTokens) { final String tokenImage = getTokenImage(token); @@ -97,18 +97,15 @@ public void scanFile(InputFile inputFile, RustVisitorContext visitorContext) { if (token.getType().equals(RustTokenType.FLOAT_LITERAL) || token.getType().equals(RustTokenType.BOOLEAN_LITERAL) - || token.getType().equals(RustTokenType.INTEGER_LITERAL) - - ) { + || token.getType().equals(RustTokenType.INTEGER_LITERAL)) { highlight(highlighting, tokenLocation, TypeOfText.CONSTANT); - } for (Trivia trivia : token.getTrivia()) { highlight(highlighting, tokenLocation(trivia.getToken()), TypeOfText.COMMENT); } - if (testTokens.contains(token)) { + if (unitTestTokens.contains(token)) { highlight(highlighting, tokenLocation, TypeOfText.ANNOTATION ); } @@ -122,12 +119,13 @@ public void scanFile(InputFile inputFile, RustVisitorContext visitorContext) { cpdTokens.save(); } - private Set identifyUnitTestTokens(List parsedTokens, Set knownTestAttributes) { + private Set identifyUnitTestTokens(List parsedTokens) { Set testTokens = new HashSet<>(); + Set unitTestsAttributes = getUnitTestAttributes(); for (int i = 0; i < parsedTokens.size(); i++) { if (("#".equals(getTokenImage(parsedTokens.get(i)))) && ("[".equals(getTokenImage(parsedTokens.get(i + 1)))) - && (knownTestAttributes.contains(getTokenImage(parsedTokens.get(i + 2)))) + && (unitTestsAttributes.contains(getTokenImage(parsedTokens.get(i + 2)))) && ("]".equals(getTokenImage(parsedTokens.get(i + 3)))) && ("fn".equals(getTokenImage(parsedTokens.get(i + 4))))) { int j = i + 5; @@ -155,7 +153,25 @@ private Set identifyUnitTestTokens(List parsedTokens, Set } } return testTokens; + } + private Set getUnitTestAttributes() { + Configuration config = context.config(); + String[] attrs = filterEmptyStrings(config.getStringArray(CommunityRustPlugin.UNIT_TEST_ATTRIBUTES)); + if (attrs.length == 0) { + attrs = StringUtils.split(CommunityRustPlugin.DEFAULT_UNIT_TEST_ATTRIBUTES, ","); + } + return Arrays.stream(attrs).collect(Collectors.toSet()); + } + + private String[] filterEmptyStrings(String[] stringArray) { + List nonEmptyStrings = new ArrayList<>(); + for (String string : stringArray) { + if (StringUtils.isNotBlank(string.trim())) { + nonEmptyStrings.add(string.trim()); + } + } + return nonEmptyStrings.toArray(new String[nonEmptyStrings.size()]); } } \ No newline at end of file From 54fc6b074aedc2eace5b0d712820be7e3ecde7a0 Mon Sep 17 00:00:00 2001 From: Eric Le Goff Date: Tue, 18 Jan 2022 13:42:53 +0100 Subject: [PATCH 4/5] remove useless imports --- .../communityrust/CommunityRustPlugin.java | 14 ++--- .../communityrust/RustTokensVisitor.java | 54 +++++++++---------- 2 files changed, 32 insertions(+), 36 deletions(-) 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 c86431d7..e1138ff7 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,7 +25,6 @@ import org.elegoff.plugins.communityrust.rules.RustRulesDefinition; import org.elegoff.plugins.communityrust.xunit.XUnitSensor; import org.sonar.api.Plugin; -import org.sonar.api.config.Configuration; import org.sonar.api.config.PropertyDefinition; import org.elegoff.plugins.communityrust.clippy.ClippySensor; import org.elegoff.plugins.communityrust.clippy.ClippyRulesDefinition; @@ -34,10 +33,6 @@ import org.elegoff.plugins.communityrust.settings.RustLanguageSettings; import org.sonar.api.resources.Qualifiers; -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - /** * This class is the entry point for all extensions. It is referenced in pom.xml. */ @@ -50,6 +45,7 @@ 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 TEST_AND_COVERAGE = "Test and Coverage"; public static final String DEFAULT_UNIT_TEST_ATTRIBUTES="test,tokio::test"; @Override @@ -80,7 +76,7 @@ public void define(Context context) { .name("LCOV Files") .description("Paths (absolute or relative) to the files with LCOV data.") .onQualifiers(Qualifiers.PROJECT) - .subCategory("Test and Coverage") + .subCategory(TEST_AND_COVERAGE) .category("Rust") .multiValues(true) .build(), @@ -91,7 +87,7 @@ public void define(Context context) { .name("LCOV Files") .description("Paths (absolute or relative) to the files with LCOV data.") .onQualifiers(Qualifiers.PROJECT) - .subCategory("Test and Coverage") + .subCategory(TEST_AND_COVERAGE) .category("Rust") .multiValues(true) .build(), @@ -101,7 +97,7 @@ public void define(Context context) { .name("Unit tests") .description("Comme separated list of Rust attributes for Unit Tests") .onQualifiers(Qualifiers.PROJECT) - .subCategory("Test and Coverage") + .subCategory(TEST_AND_COVERAGE) .category("Rust") .multiValues(true) .build() @@ -113,7 +109,7 @@ public void define(Context context) { .name("Path to xunit report(s)") .description("Path to the report of test execution, relative to project's root. Ant patterns are accepted. The reports have to conform to the junitreport XML format.") .category("Rust") - .subCategory("Test and Coverage") + .subCategory(TEST_AND_COVERAGE) .onQualifiers(Qualifiers.PROJECT) .defaultValue(XUnitSensor.DEFAULT_REPORT_PATH) .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 c72533e5..f41bb1a1 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 @@ -25,7 +25,6 @@ import com.sonar.sslr.api.Token; import com.sonar.sslr.api.Trivia; import org.apache.commons.lang.StringUtils; -import org.elegoff.plugins.communityrust.settings.RustLanguageSettings; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.highlighting.NewHighlighting; @@ -40,7 +39,7 @@ import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.stream.IntStream; public class RustTokensVisitor { @@ -122,35 +121,36 @@ public void scanFile(InputFile inputFile, RustVisitorContext visitorContext) { private Set identifyUnitTestTokens(List parsedTokens) { Set testTokens = new HashSet<>(); Set unitTestsAttributes = getUnitTestAttributes(); - for (int i = 0; i < parsedTokens.size(); i++) { - if (("#".equals(getTokenImage(parsedTokens.get(i)))) - && ("[".equals(getTokenImage(parsedTokens.get(i + 1)))) - && (unitTestsAttributes.contains(getTokenImage(parsedTokens.get(i + 2)))) - && ("]".equals(getTokenImage(parsedTokens.get(i + 3)))) - && ("fn".equals(getTokenImage(parsedTokens.get(i + 4))))) { - int j = i + 5; - //lookup for opening bracket - while (!"{".equals(getTokenImage(parsedTokens.get(j)))) { - j++; - } - - int cptOpeningBracket = 1; - //lookup for outer closing bracket (end of test function position) - while (cptOpeningBracket > 0) { - j++; - if ("{".equals(getTokenImage(parsedTokens.get(j)))) { - cptOpeningBracket++; + int i = 0; + while (i < parsedTokens.size()) { + if ("#".equals(getTokenImage(parsedTokens.get(i)))) + if (("[".equals(getTokenImage(parsedTokens.get(i + 1)))) && (unitTestsAttributes.contains(getTokenImage(parsedTokens.get(i + 2)))) && ("]".equals(getTokenImage(parsedTokens.get(i + 3)))) && ("fn".equals(getTokenImage(parsedTokens.get(i + 4))))) { + int j = i + 5; + //lookup for opening bracket + while (true) { + if ("{".equals(getTokenImage(parsedTokens.get(j)))) break; + j++; } - if ("}".equals(getTokenImage(parsedTokens.get(j)))) { - cptOpeningBracket--; + + int cptOpeningBracket = 1; + //lookup for outer closing bracket (end of test function position) + while (cptOpeningBracket > 0) { + j++; + switch (getTokenImage(parsedTokens.get(j))) { + case "{": + cptOpeningBracket++; + break; + case "}": + cptOpeningBracket--; + break; + } + } - } - //all tokens constituting a test function are added to the set - for (int k = i; k <= j; k++) { - testTokens.add(parsedTokens.get(k)); + //all tokens constituting a test function are added to the set + IntStream.rangeClosed(i, j).mapToObj(parsedTokens::get).forEach(testTokens::add); } - } + i++; } return testTokens; } From 8c850a6654fa89f115a37bf58b851b76ac42c8c4 Mon Sep 17 00:00:00 2001 From: Eric Le Goff Date: Tue, 18 Jan 2022 13:51:30 +0100 Subject: [PATCH 5/5] reduce cognitive complexity --- .../communityrust/RustTokensVisitor.java | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) 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 f41bb1a1..3fcdf090 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 @@ -123,33 +123,29 @@ private Set identifyUnitTestTokens(List parsedTokens) { Set unitTestsAttributes = getUnitTestAttributes(); int i = 0; while (i < parsedTokens.size()) { - if ("#".equals(getTokenImage(parsedTokens.get(i)))) - if (("[".equals(getTokenImage(parsedTokens.get(i + 1)))) && (unitTestsAttributes.contains(getTokenImage(parsedTokens.get(i + 2)))) && ("]".equals(getTokenImage(parsedTokens.get(i + 3)))) && ("fn".equals(getTokenImage(parsedTokens.get(i + 4))))) { - int j = i + 5; - //lookup for opening bracket - while (true) { - if ("{".equals(getTokenImage(parsedTokens.get(j)))) break; - j++; - } - - int cptOpeningBracket = 1; - //lookup for outer closing bracket (end of test function position) - while (cptOpeningBracket > 0) { - j++; - switch (getTokenImage(parsedTokens.get(j))) { - case "{": - cptOpeningBracket++; - break; - case "}": - cptOpeningBracket--; - break; - } + if ("#".equals(getTokenImage(parsedTokens.get(i))) && ("[".equals(getTokenImage(parsedTokens.get(i + 1)))) && (unitTestsAttributes.contains(getTokenImage(parsedTokens.get(i + 2)))) && ("]".equals(getTokenImage(parsedTokens.get(i + 3)))) && ("fn".equals(getTokenImage(parsedTokens.get(i + 4))))) { + int j = i + 5; + //lookup for opening bracket + while (!"{".equals(getTokenImage(parsedTokens.get(j)))) { + j++; + } + int cptOpeningBracket = 1; + //lookup for outer closing bracket (end of test function position) + while (cptOpeningBracket > 0) { + j++; + String tokenImage = getTokenImage(parsedTokens.get(j)); + if ("{".equals(tokenImage)) { + cptOpeningBracket++; + } else if ("}".equals(tokenImage)) { + cptOpeningBracket--; } - //all tokens constituting a test function are added to the set - IntStream.rangeClosed(i, j).mapToObj(parsedTokens::get).forEach(testTokens::add); } + + //all tokens constituting a test function are added to the set + IntStream.rangeClosed(i, j).mapToObj(parsedTokens::get).forEach(testTokens::add); + } i++; } return testTokens;