├── .gitignore ├── src ├── test │ ├── resources │ │ ├── kibit.clj │ │ ├── bar_in_src_cljc.cljc │ │ ├── file.clj │ │ ├── foo_in_src_clj.clj │ │ ├── cloverage-result.json │ │ ├── project.clj │ │ └── nvd-report.json │ └── java │ │ └── org │ │ └── sonar │ │ └── plugins │ │ └── clojure │ │ ├── sensors │ │ ├── CommandStreamConsumerTest.java │ │ ├── kibit │ │ │ ├── KibitRulesTest.java │ │ │ ├── KibitIssueParserTest.java │ │ │ └── KibitSensorTest.java │ │ ├── ancient │ │ │ ├── OutdatedDependencyTest.java │ │ │ ├── AncientOutputParserTest.java │ │ │ └── AncientSensorTest.java │ │ ├── eastwood │ │ │ ├── EastwoodIssueParserTest.java │ │ │ ├── EastwoodPropertiesTest.java │ │ │ └── EastwoodSensorTest.java │ │ ├── AbstractSensorTest.java │ │ ├── leinnvd │ │ │ ├── LeinNvdParserTest.java │ │ │ └── LeinNvdSensorTest.java │ │ ├── kondo │ │ │ ├── KondoIssueParserTest.java │ │ │ └── KondoSensorTest.java │ │ ├── clojure │ │ │ └── ClojureSensorTest.java │ │ ├── cloverage │ │ │ ├── CloverageMetricParserTest.java │ │ │ └── CloverageSensorTest.java │ │ └── LeiningenRunnerTest.java │ │ ├── ProjectFileTest.java │ │ ├── settings │ │ ├── KibitPropertiesTest.java │ │ ├── AncientPropertiesTest.java │ │ ├── PropertiesTest.java │ │ ├── NvdPropertiesTest.java │ │ └── CloveragePropertiesTest.java │ │ ├── rules │ │ └── ClojureLintRulesDefinitionTest.java │ │ ├── language │ │ ├── ClojureTest.java │ │ └── ClojureSonarWayProfileTest.java │ │ └── ClojurePluginTest.java └── main │ ├── resources │ └── clojure │ │ ├── sonar_way.json │ │ └── rules.xml │ └── java │ └── org │ └── sonar │ └── plugins │ └── clojure │ ├── language │ ├── JsonProfile.java │ ├── Clojure.java │ └── ClojureSonarWayProfile.java │ ├── sensors │ ├── CommandStreamConsumer.java │ ├── cloverage │ │ ├── CoverageReport.java │ │ ├── FileAnalysis.java │ │ ├── LineAnalysis.java │ │ ├── CloverageMetricParser.java │ │ └── CloverageSensor.java │ ├── Issue.java │ ├── kondo │ │ ├── Finding.java │ │ ├── KondoIssueParser.java │ │ └── KondoSensor.java │ ├── ancient │ │ ├── AncientOutputParser.java │ │ ├── OutdatedDependency.java │ │ └── AncientSensor.java │ ├── eastwood │ │ ├── EastwoodIssueParser.java │ │ ├── EastwoodProperties.java │ │ └── EastwoodSensor.java │ ├── clojure │ │ └── ClojureSensor.java │ ├── kibit │ │ ├── KibitIssueParser.java │ │ └── KibitSensor.java │ ├── leinnvd │ │ ├── LeinNvdParser.java │ │ ├── Vulnerability.java │ │ └── LeinNvdSensor.java │ ├── LeiningenRunner.java │ └── AbstractSensor.java │ ├── leiningen │ └── ProjectFile.java │ ├── rules │ └── ClojureLintRulesDefinition.java │ ├── settings │ ├── KibitProperties.java │ ├── AncientProperties.java │ ├── CloverageProperties.java │ ├── NvdProperties.java │ ├── KondoProperties.java │ └── Properties.java │ └── ClojurePlugin.java ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java ├── Dockerfile ├── travis.sh ├── start-sonarqube.sh ├── .github └── ISSUE_TEMPLATE │ ├── feature.md │ └── bug.md ├── LICENSE ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── pom.xml ├── CONTRIBUTING.md ├── README.md ├── mvnw.cmd └── mvnw /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | sonar-clojure-plugin.iml 3 | target 4 | -------------------------------------------------------------------------------- /src/test/resources/kibit.clj: -------------------------------------------------------------------------------- 1 | (ns kibit) 2 | 3 | (def x 1) 4 | 5 | (> 0 x) -------------------------------------------------------------------------------- /src/test/resources/bar_in_src_cljc.cljc: -------------------------------------------------------------------------------- 1 | (ns bar-in-src-cljc) 2 | 3 | (def bar 1) 4 | -------------------------------------------------------------------------------- /src/test/resources/file.clj: -------------------------------------------------------------------------------- 1 | (defn -main [& args] 2 | (println "Hello, World!")) -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsantiag/sonar-clojure/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/foo_in_src_clj.clj: -------------------------------------------------------------------------------- 1 | (ns foo-in-src-clj) 2 | 3 | (def bar 1) 4 | 5 | (def miss 2) 6 | (def partial 3) -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM sonarqube:8.6.1-community 2 | 3 | COPY target/sonar-clojure-plugin-*-SNAPSHOT.jar $SONARQUBE_HOME/extensions/plugins/ -------------------------------------------------------------------------------- /travis.sh: -------------------------------------------------------------------------------- 1 | if [ ! -z "$TRAVIS_TAG" ]; then 2 | sed -i "s/[0-9].\+-SNAPSHOT/${TRAVIS_TAG:1}/g" pom.xml 3 | fi 4 | 5 | mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package package org.jacoco:jacoco-maven-plugin:report sonar:sonar 6 | -------------------------------------------------------------------------------- /start-sonarqube.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | ./mvnw clean package 4 | docker build . --tag sonarqube_local_image 5 | docker rm -f sonarqube_local_image || true 6 | docker run --name sonarqube_local_image -p 9000:9000 sonarqube_local_image 7 | 8 | 9 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/clojure/sonar_way.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sonar way", 3 | "ruleKeys": [ 4 | "kibit", 5 | "eastwood", 6 | "nvd-critical", 7 | "nvd-high", 8 | "nvd-medium", 9 | "nvd-low", 10 | "ancient-clj-dependency", 11 | "kondo" 12 | ] 13 | } -------------------------------------------------------------------------------- /src/test/resources/cloverage-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage": { 3 | "foo.clj": [ 4 | null, 5 | 1, 6 | null, 7 | 1, 8 | null, 9 | 0, 10 | true 11 | ], 12 | "bar.cljc": [ 13 | null, 14 | 1 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/language/JsonProfile.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.language; 2 | 3 | import java.util.List; 4 | 5 | public class JsonProfile { 6 | private List ruleKeys; 7 | 8 | public List getRuleKeys() { 9 | return this.ruleKeys; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature 3 | about: Suggest a feature you would like to see. 4 | title: '' 5 | labels: 'feature' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/CommandStreamConsumer.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors; 2 | 3 | import org.sonar.api.utils.command.StreamConsumer; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class CommandStreamConsumer implements StreamConsumer { 9 | private List data = new ArrayList<>(); 10 | 11 | @Override 12 | public void consumeLine(String line) { 13 | data.add(line); 14 | } 15 | 16 | public List getData() { 17 | return data; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/cloverage/CoverageReport.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.cloverage; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class CoverageReport { 7 | 8 | private List files = new ArrayList<>(); 9 | 10 | public void addFile(FileAnalysis e){ 11 | files.add(e); 12 | } 13 | 14 | public int filesCount(){ 15 | return files.size(); 16 | } 17 | 18 | public List getFileEntries(){ 19 | return this.files; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources/project.clj: -------------------------------------------------------------------------------- 1 | (defproject clojure-sonar-example "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"] 7 | [org.tukaani/xz "1.7"]] 8 | :plugins [[lein-ancient "0.6.15"] 9 | [jonase/eastwood "0.3.3"] 10 | [lein-cloverage "1.0.13"] 11 | [lein-kibit "0.1.6"] 12 | [lein-nvd "0.6.0"]]) 13 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: Create a bug report to help us improve. 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **How to reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | - SonarQube Version [e.g. 7.4] 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/cloverage/FileAnalysis.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.cloverage; 2 | 3 | import org.sonar.api.batch.fs.InputFile; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class FileAnalysis { 9 | private InputFile file = null; 10 | private List entries = new ArrayList<>(); 11 | 12 | public InputFile getFile() { 13 | return file; 14 | } 15 | 16 | public void setInputFile(InputFile file) { 17 | this.file = file; 18 | } 19 | 20 | public void addLine(int number, int hits){ 21 | entries.add(new LineAnalysis().setLineNumber(number).setHits(hits)); 22 | } 23 | 24 | public List getEntries(){ 25 | return this.entries; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/CommandStreamConsumerTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.*; 7 | 8 | public class CommandStreamConsumerTest { 9 | 10 | private CommandStreamConsumer commandStreamConsumer; 11 | 12 | @Before 13 | public void setUp() { 14 | commandStreamConsumer = new CommandStreamConsumer(); 15 | } 16 | 17 | @Test 18 | public void testIfDataIsSavedWhenLineIsConsumed() { 19 | String line = "new line of information"; 20 | 21 | commandStreamConsumer.consumeLine(line); 22 | 23 | assertEquals(1, commandStreamConsumer.getData().size()); 24 | assertEquals(line, commandStreamConsumer.getData().get(0)); 25 | } 26 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/ProjectFileTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure; 2 | 3 | import org.junit.Test; 4 | import org.sonar.plugins.clojure.leiningen.ProjectFile; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class ProjectFileTest { 9 | 10 | @Test 11 | public void lineNumberIsReturnedIfStringMatches(){ 12 | String input = "There is match\nIn\nlast line (3) if checking for foobar"; 13 | ProjectFile prn = new ProjectFile(input); 14 | assertEquals(3, prn.findLineNumber("foobar")); 15 | } 16 | 17 | @Test 18 | public void firstLineIsReturnedForNoMatch(){ 19 | String input = "This\ninput\ncontains bar"; 20 | ProjectFile prn = new ProjectFile(input); 21 | assertEquals(1, prn.findLineNumber("foo")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/Issue.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors; 2 | 3 | public class Issue { 4 | 5 | private String externalRuleId; 6 | private String issueMessage; 7 | private String filePath; 8 | private int line; 9 | 10 | public Issue(String externalRuleId, String description, String filePath, int line) { 11 | this.externalRuleId = externalRuleId; 12 | this.issueMessage = description; 13 | this.filePath = filePath; 14 | this.line = line; 15 | } 16 | 17 | public String getExternalRuleId() { 18 | return externalRuleId; 19 | } 20 | 21 | public String getDescription() { 22 | return issueMessage; 23 | } 24 | 25 | public String getFilePath() { 26 | return filePath; 27 | } 28 | 29 | public int getLine() { 30 | return line; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/leiningen/ProjectFile.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.leiningen; 2 | import org.sonar.api.utils.log.Logger; 3 | import org.sonar.api.utils.log.Loggers; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | 9 | public class ProjectFile { 10 | private static final Logger LOG = Loggers.get(ProjectFile.class); 11 | private List contents; 12 | 13 | public ProjectFile(String contents){ 14 | this.contents = Arrays.asList(contents.split("\\r?\\n")); 15 | } 16 | 17 | public int findLineNumber(String matchToFind){ 18 | int lineNumber = 1; 19 | for (String line: 20 | contents) { 21 | if (line.contains(matchToFind)){ 22 | return lineNumber; 23 | } 24 | lineNumber++; 25 | } 26 | LOG.warn("Match not found!"); 27 | return 1; 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/language/Clojure.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.language; 2 | 3 | import org.sonar.api.config.Configuration; 4 | import org.sonar.api.resources.AbstractLanguage; 5 | import org.sonar.plugins.clojure.settings.Properties; 6 | 7 | public class Clojure extends AbstractLanguage { 8 | public static final String KEY = "clj"; 9 | public static final String NAME = "Clojure"; 10 | 11 | private final Configuration config; 12 | 13 | public Clojure(Configuration config) { 14 | super(KEY, NAME); 15 | this.config = config; 16 | } 17 | 18 | @Override 19 | public String[] getFileSuffixes() { 20 | String[] suffixes = config.getStringArray(Properties.FILE_SUFFIXES_PROPERTY); 21 | if (suffixes.length == 0) { 22 | suffixes = Properties.FILE_SUFFIXES_PROPERTY_DEFAULT.split(","); 23 | } 24 | return suffixes; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/settings/KibitPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.junit.Test; 4 | import org.sonar.api.config.PropertyDefinition; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class KibitPropertiesTest { 10 | @Test 11 | public void shouldHaveKibitDisabledProperty() { 12 | PropertyDefinition kibitDisabled = KibitProperties.getEnabledProperty(); 13 | assertThat(kibitDisabled.key(), is("sonar.clojure.kibit.enabled")); 14 | assertThat(kibitDisabled.name(), is("Kibit Disabled")); 15 | assertThat(kibitDisabled.category(), is("SonarClojure")); 16 | assertThat(kibitDisabled.subCategory(), is("Sensors")); 17 | assertThat(kibitDisabled.defaultValue(), is("true")); 18 | assertThat(kibitDisabled.description(), is("Indicates if kibit sensor should be disabled")); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/settings/AncientPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.junit.Test; 4 | import org.sonar.api.config.PropertyDefinition; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class AncientPropertiesTest { 10 | @Test 11 | public void shouldHaveAncientDisabledProperty() { 12 | PropertyDefinition ancientDisabled = AncientProperties.getAncientDisabled(); 13 | assertThat(ancientDisabled.key(), is("sonar.clojure.ancient.enabled")); 14 | assertThat(ancientDisabled.name(), is("Ancient Disabled")); 15 | assertThat(ancientDisabled.category(), is("SonarClojure")); 16 | assertThat(ancientDisabled.subCategory(), is("Sensors")); 17 | assertThat(ancientDisabled.defaultValue(), is("true")); 18 | assertThat(ancientDisabled.description(), is("Indicates the ancient sensor should be disabled")); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/cloverage/LineAnalysis.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.cloverage; 2 | 3 | public class LineAnalysis { 4 | 5 | private int lineNumber; 6 | private int hits; 7 | 8 | public int getLineNumber() { 9 | return lineNumber; 10 | } 11 | 12 | public LineAnalysis setLineNumber(int lineNumber) { 13 | this.lineNumber = lineNumber; 14 | return this; 15 | } 16 | 17 | public int getHits() { 18 | return hits; 19 | } 20 | 21 | public LineAnalysis setHits(int hits) { 22 | this.hits = hits; 23 | return this; 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) return true; 29 | if (o == null || getClass() != o.getClass()) return false; 30 | LineAnalysis lineEntry = (LineAnalysis) o; 31 | return lineNumber == lineEntry.lineNumber && 32 | hits == lineEntry.hits; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/resources/nvd-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "reportSchema": "1.1", 3 | "dependencies": [ 4 | { 5 | "fileName": "jackson-databind-2.9.3.jar", 6 | "vulnerabilities": [ 7 | { 8 | "name": "CVE-2018-5968", 9 | "severity": "HIGH", 10 | "cwes": ["CWE-502", "CWE-184"], 11 | "description": "FasterXML jackson-databind through 2.8.11 and 2.9.x through 2.9.3 allows unauthenticated remote code execution because of an incomplete fix for the CVE-2017-7525 and CVE-2017-17485 deserialization flaws. This is exploitable via two different gadgets that bypass a blacklist." 12 | }, 13 | { 14 | "name": "CVE-2018-19362", 15 | "severity": "CRITICAL", 16 | "cwes": ["CWE-502"], 17 | "description": "FasterXML jackson-databind 2.x before 2.9.8 might allow attackers to have unspecified impact by leveraging failure to block the jboss-common-core class from polymorphic deserialization." 18 | } 19 | ] 20 | }, 21 | { 22 | "fileName": "ring-1.6.3.jar" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Felipe Santiago 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/rules/ClojureLintRulesDefinition.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.rules; 2 | 3 | import org.sonar.api.server.rule.RulesDefinition; 4 | import org.sonar.api.server.rule.RulesDefinitionXmlLoader; 5 | import org.sonar.plugins.clojure.language.Clojure; 6 | 7 | import java.nio.charset.StandardCharsets; 8 | 9 | public final class ClojureLintRulesDefinition implements RulesDefinition { 10 | 11 | private static final String RULES_PATH = "/clojure/rules.xml"; 12 | public static final String REPOSITORY_NAME = "ClojureLint"; 13 | public static final String REPOSITORY_KEY = REPOSITORY_NAME; 14 | 15 | private final RulesDefinitionXmlLoader xmlLoader; 16 | 17 | public ClojureLintRulesDefinition(RulesDefinitionXmlLoader xmlLoader) { 18 | this.xmlLoader = xmlLoader; 19 | } 20 | 21 | @Override 22 | public void define(Context context) { 23 | NewRepository repository = context.createRepository(REPOSITORY_KEY, Clojure.KEY).setName(REPOSITORY_NAME); 24 | xmlLoader.load(repository, getClass().getResourceAsStream(RULES_PATH), StandardCharsets.UTF_8.name()); 25 | repository.done(); 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/kondo/Finding.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.kondo; 2 | 3 | public class Finding { 4 | private String type, message, filename, level; 5 | private int row, col, endRow, endCol; 6 | 7 | public Finding(String type, String message, String filename, String level, int row, int col, int endRow, int endCol) { 8 | this.type = type; 9 | this.message = message; 10 | this.filename = filename; 11 | this.level = level; 12 | this.row = row; 13 | this.col = col; 14 | this.endRow = endRow; 15 | this.endCol = endCol; 16 | } 17 | 18 | public String getType() { 19 | return type; 20 | } 21 | 22 | public String getMessage() { 23 | return message; 24 | } 25 | 26 | public String getFilename() { 27 | return filename; 28 | } 29 | 30 | public int getRow() { 31 | return row; 32 | } 33 | 34 | public int getCol() { 35 | return col; 36 | } 37 | 38 | public int getEndRow() { 39 | return endRow; 40 | } 41 | 42 | public int getEndCol() { 43 | return endCol; 44 | } 45 | 46 | public String getLevel() { 47 | return level; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/settings/KibitProperties.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.sonar.api.config.PropertyDefinition; 4 | 5 | import java.util.List; 6 | 7 | import static java.lang.String.valueOf; 8 | import static java.util.Collections.singletonList; 9 | import static org.sonar.plugins.clojure.settings.Properties.MAIN_CATEGORY; 10 | import static org.sonar.plugins.clojure.settings.Properties.SUB_CATEGORY; 11 | 12 | public class KibitProperties { 13 | public static final String ENABLED_PROPERTY = "sonar.clojure.kibit.enabled"; 14 | public static final boolean ENABLED_PROPERTY_DEFAULT = true; 15 | 16 | private KibitProperties() { 17 | } 18 | 19 | static PropertyDefinition getEnabledProperty() { 20 | return PropertyDefinition.builder(ENABLED_PROPERTY) 21 | .category(MAIN_CATEGORY) 22 | .subCategory(SUB_CATEGORY) 23 | .defaultValue(valueOf(ENABLED_PROPERTY_DEFAULT)) 24 | .name("Kibit Disabled") 25 | .description("Indicates if kibit sensor should be disabled") 26 | .build(); 27 | } 28 | 29 | static List getProperties() { 30 | return singletonList(getEnabledProperty()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/settings/AncientProperties.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.sonar.api.config.PropertyDefinition; 4 | 5 | import java.util.List; 6 | 7 | import static java.lang.String.valueOf; 8 | import static java.util.Collections.singletonList; 9 | import static org.sonar.plugins.clojure.settings.Properties.MAIN_CATEGORY; 10 | import static org.sonar.plugins.clojure.settings.Properties.SUB_CATEGORY; 11 | 12 | public class AncientProperties { 13 | public static final String ENABLED_PROPERTY = "sonar.clojure.ancient.enabled"; 14 | public static final boolean ENABLED_PROPERTY_DEFAULT = true; 15 | 16 | private AncientProperties() { 17 | } 18 | 19 | static PropertyDefinition getAncientDisabled() { 20 | return PropertyDefinition.builder(ENABLED_PROPERTY) 21 | .category(MAIN_CATEGORY) 22 | .subCategory(SUB_CATEGORY) 23 | .defaultValue(valueOf(ENABLED_PROPERTY_DEFAULT)) 24 | .name("Ancient Disabled") 25 | .description("Indicates the ancient sensor should be disabled") 26 | .build(); 27 | } 28 | 29 | static List getProperties() { 30 | return singletonList(getAncientDisabled()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/language/ClojureSonarWayProfile.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.language; 2 | 3 | import com.google.gson.Gson; 4 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; 5 | import org.sonar.plugins.clojure.rules.ClojureLintRulesDefinition; 6 | 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | 10 | 11 | public final class ClojureSonarWayProfile implements BuiltInQualityProfilesDefinition { 12 | 13 | public static final String CLOJURE_SONAR_WAY_PATH = "/clojure/sonar_way.json"; 14 | 15 | @Override 16 | public void define(Context context) { 17 | NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile("Sonar way", Clojure.KEY); 18 | profile.setDefault(true); 19 | 20 | JsonProfile jsonProfile = readProfile(); 21 | 22 | jsonProfile.getRuleKeys() 23 | .forEach(key -> profile.activateRule(ClojureLintRulesDefinition.REPOSITORY_KEY, key)); 24 | 25 | profile.done(); 26 | } 27 | 28 | private JsonProfile readProfile() { 29 | InputStream resourceAsStream = getClass().getResourceAsStream(CLOJURE_SONAR_WAY_PATH); 30 | InputStreamReader inputStreamReader = new InputStreamReader(resourceAsStream); 31 | return new Gson().fromJson(inputStreamReader, JsonProfile.class); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/kibit/KibitRulesTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.kibit; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; 6 | import org.sonar.plugins.clojure.language.ClojureSonarWayProfile; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import static java.util.Arrays.asList; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | public class KibitRulesTest { 15 | 16 | private BuiltInQualityProfilesDefinition.Context context; 17 | private ClojureSonarWayProfile clojureSonarWayProfile; 18 | 19 | @Before 20 | public void setUp() { 21 | context = new BuiltInQualityProfilesDefinition.Context(); 22 | clojureSonarWayProfile = new ClojureSonarWayProfile(); 23 | clojureSonarWayProfile.define(context); 24 | } 25 | 26 | @Test 27 | public void testIfSonarwayProfileIsCreatedWithAllEastwoodRules() { 28 | BuiltInQualityProfilesDefinition.BuiltInQualityProfile profile = context.profile("clj", "Sonar way"); 29 | List rules = profile.rules(); 30 | List ruleKeys = new ArrayList<>(); 31 | ruleKeys.addAll(asList("kibit")); 32 | 33 | ruleKeys.stream().forEach(kibitRule -> assertTrue(ruleKeys.contains(kibitRule))); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/ancient/AncientOutputParser.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.ancient; 2 | 3 | import java.util.List; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | import java.util.stream.Collectors; 7 | 8 | public class AncientOutputParser { 9 | 10 | private static final Pattern ANCIENT_PATTERN = Pattern.compile("\\[([^\\s]+)\\s\"([^\"]+)\"]([^\"]+)\"([^\"]+)\""); 11 | 12 | private AncientOutputParser() {} 13 | 14 | private static Matcher parseString(String str){ 15 | Matcher matcher = ANCIENT_PATTERN.matcher(str); 16 | matcher.find(); 17 | return matcher; 18 | } 19 | 20 | private static boolean removeNonMatches(String str){ 21 | Matcher matcher = ANCIENT_PATTERN.matcher(str); 22 | return matcher.find(); 23 | 24 | } 25 | 26 | public static List parse(List output){ 27 | 28 | return output.stream() 29 | .map(e -> e.replaceAll("\u001B\\[[;\\d]*m", "")) 30 | .filter(AncientOutputParser::removeNonMatches) 31 | .map(AncientOutputParser::parseString) 32 | .map(dep -> new OutdatedDependency() 33 | .setName(dep.group(1)) 34 | .setCurrentVersion(dep.group(4)) 35 | .setAvailableVersion(dep.group(2))) 36 | .collect(Collectors.toList()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/ancient/OutdatedDependencyTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.ancient; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | public class OutdatedDependencyTest { 9 | 10 | @Test 11 | public void testToString() { 12 | 13 | OutdatedDependency outdatedDependency1 = new OutdatedDependency() 14 | .setName("dependency") 15 | .setAvailableVersion("1.0.0") 16 | .setCurrentVersion("0.0.1"); 17 | 18 | assertEquals("dependency is using version: 0.0.1 but version: 1.0.0 is available.", 19 | outdatedDependency1.toString()); 20 | } 21 | 22 | @Test 23 | public void testEquals() { 24 | OutdatedDependency outdatedDependency1 = new OutdatedDependency() 25 | .setName("dependency") 26 | .setAvailableVersion("1.0.0") 27 | .setCurrentVersion("0.0.1"); 28 | 29 | OutdatedDependency outdatedDependency2 = new OutdatedDependency() 30 | .setName("dependency") 31 | .setAvailableVersion("1.0.0") 32 | .setCurrentVersion("0.0.1"); 33 | 34 | assertTrue(outdatedDependency1.equals(outdatedDependency2) && outdatedDependency2.equals(outdatedDependency1)); 35 | assertTrue(outdatedDependency1.hashCode() == outdatedDependency2.hashCode()); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/eastwood/EastwoodIssueParser.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.eastwood; 2 | 3 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 4 | import org.sonar.plugins.clojure.sensors.Issue; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.Objects; 9 | import java.util.Optional; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | import java.util.stream.Collectors; 13 | 14 | public class EastwoodIssueParser { 15 | private static final Pattern EASTWOOD_PATTERN = Pattern.compile("([^:]+):(\\d+):(\\d+):([\\s\\w-]+):(.*)"); 16 | public static final String EASTWOOD_KEY = "eastwood"; 17 | 18 | private EastwoodIssueParser() {} 19 | 20 | public static List parse(CommandStreamConsumer output) { 21 | if (output != null) { 22 | return output.getData().stream().map(line -> { 23 | Matcher matcher = EASTWOOD_PATTERN.matcher(line); 24 | if (matcher.find()) { 25 | String description = matcher.group(5); 26 | String filePath = matcher.group(1); 27 | int lineNumber = Integer.parseInt(matcher.group(2)); 28 | return new Issue(EASTWOOD_KEY, description, filePath, lineNumber); 29 | } 30 | return null; 31 | }).filter(Objects::nonNull).collect(Collectors.toList()); 32 | } 33 | return Collections.emptyList(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/rules/ClojureLintRulesDefinitionTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.rules; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.mockito.Mockito; 6 | import org.sonar.api.server.rule.RulesDefinition; 7 | import org.sonar.api.server.rule.RulesDefinitionXmlLoader; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class ClojureLintRulesDefinitionTest { 12 | 13 | //TODO Fiz this test. A dependency seems to be broken. 14 | // private ClojureLintRulesDefinition clojureLintRulesDefinition; 15 | // private RulesDefinition.Context context; 16 | // 17 | // @Before 18 | // public void setUp() { 19 | // RulesDefinitionXmlLoader xmlLoader = Mockito.mock(RulesDefinitionXmlLoader.class); 20 | // Mockito.mock(RulesDefinitionXmlLoader.class); 21 | 22 | // clojureLintRulesDefinition = new ClojureLintRulesDefinition(xmlLoader); 23 | // context = new RulesDefinition.Context(); 24 | // clojureLintRulesDefinition.define(context); 25 | // for (RulesDefinition.Rule rule : context.repositories().get(0).rules()) { 26 | // assertThat(rule.tags()).isEmpty(); 27 | // } 28 | // } 29 | // 30 | // @Test 31 | // public void testIfRepositoryOfRulesIsProperlyCreated() { 32 | // RulesDefinition.Repository repository = context.repository("ClojureLint"); 33 | // 34 | // assertThat(repository.name()).isEqualTo("ClojureLint"); 35 | // assertThat(repository.language()).isEqualTo("clj"); 36 | // } 37 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/settings/PropertiesTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.junit.Test; 4 | import org.sonar.api.config.PropertyDefinition; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | import static org.sonar.plugins.clojure.settings.Properties.getFileSuffix; 9 | import static org.sonar.plugins.clojure.settings.Properties.getSensorsTimeout; 10 | 11 | public class PropertiesTest { 12 | 13 | @Test 14 | public void shouldGetFileSuffixProperty() { 15 | PropertyDefinition fileSuffix = getFileSuffix(); 16 | assertThat(fileSuffix.key(), is("sonar.clojure.file.suffixes")); 17 | assertThat(fileSuffix.name(), is("File Suffixes")); 18 | assertThat(fileSuffix.category(), is("SonarClojure")); 19 | assertThat(fileSuffix.defaultValue(), is("clj,cljs,cljc")); 20 | assertThat(fileSuffix.description(), is("Comma-separated list of file suffixes to analyze")); 21 | } 22 | 23 | @Test 24 | public void shouldGetSensorTimeout() { 25 | PropertyDefinition fileSuffix = getSensorsTimeout(); 26 | assertThat(fileSuffix.key(), is("sonar.clojure.sensors.timeout")); 27 | assertThat(fileSuffix.name(), is("Sensors Timeout")); 28 | assertThat(fileSuffix.category(), is("SonarClojure")); 29 | assertThat(fileSuffix.defaultValue(), is("300")); 30 | assertThat(fileSuffix.description(), 31 | is("Defines the maximum timeout (per sensor, in seconds) when sensors are executing")); 32 | } 33 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/language/ClojureTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.language; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.sonar.api.config.internal.MapSettings; 6 | import org.sonar.plugins.clojure.settings.Properties; 7 | 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class ClojureTest { 12 | 13 | private MapSettings settings; 14 | private Clojure language; 15 | 16 | @Before 17 | public void setUp() { 18 | settings = new MapSettings(); 19 | language = new Clojure(settings.asConfig()); 20 | } 21 | 22 | @Test 23 | public void testClojureLanguageFileSuffixesWhenKeyIsDefault() { 24 | assertThat(language.getFileSuffixes(), is(new String[]{"clj","cljs","cljc"})); 25 | } 26 | 27 | @Test 28 | public void testClojureLanguageFileSuffixesWhenEmptyKeyIsSet() { 29 | settings.setProperty(Properties.FILE_SUFFIXES_PROPERTY, ""); 30 | assertThat(language.getFileSuffixes(), is(new String[]{"clj","cljs","cljc"})); 31 | } 32 | 33 | @Test 34 | public void testClojureLanguageFileSuffixesWhenKeyIsCustom() { 35 | settings.setProperty(Properties.FILE_SUFFIXES_PROPERTY, ".foo"); 36 | assertThat(language.getFileSuffixes(), is(new String[]{".foo"})); 37 | } 38 | 39 | @Test 40 | public void testClojureLanguageFileSuffixesWhenKeyIsMultiple() { 41 | settings.setProperty(Properties.FILE_SUFFIXES_PROPERTY, ".foo,.bar,.baz"); 42 | assertThat(language.getFileSuffixes(), is(new String[]{".foo", ".bar", ".baz"})); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/settings/NvdPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.junit.Test; 4 | import org.sonar.api.config.PropertyDefinition; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class NvdPropertiesTest { 10 | @Test 11 | public void shouldHaveNvdDisabledProperty() { 12 | PropertyDefinition nvdDisabled = NvdProperties.getEnabledProperty(); 13 | assertThat(nvdDisabled.key(), is("sonar.clojure.nvd.enabled")); 14 | assertThat(nvdDisabled.name(), is("Lein NVD Disabled")); 15 | assertThat(nvdDisabled.category(), is("SonarClojure")); 16 | assertThat(nvdDisabled.subCategory(), is("Sensors")); 17 | assertThat(nvdDisabled.defaultValue(), is("true")); 18 | assertThat(nvdDisabled.description(), is("Indicates if lein-nvd sensor should be disabled")); 19 | } 20 | 21 | @Test 22 | public void shouldHaveNvdReportLocationProperty() { 23 | PropertyDefinition nvdReportLocation = NvdProperties.getReportLocationProperty(); 24 | assertThat(nvdReportLocation.key(), is("sonar.clojure.nvd.reportPath")); 25 | assertThat(nvdReportLocation.name(), is("Lein NVD Report Location")); 26 | assertThat(nvdReportLocation.category(), is("SonarClojure")); 27 | assertThat(nvdReportLocation.subCategory(), is("Sensors")); 28 | assertThat(nvdReportLocation.defaultValue(), is("target/nvd/dependency-check-report.json")); 29 | assertThat(nvdReportLocation.description(), is("Indicates the location of the Lein NVD report file")); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/ClojurePlugin.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure; 2 | 3 | import org.sonar.api.Plugin; 4 | import org.sonar.plugins.clojure.language.Clojure; 5 | import org.sonar.plugins.clojure.language.ClojureSonarWayProfile; 6 | import org.sonar.plugins.clojure.rules.ClojureLintRulesDefinition; 7 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 8 | import org.sonar.plugins.clojure.sensors.ancient.AncientSensor; 9 | import org.sonar.plugins.clojure.sensors.clojure.ClojureSensor; 10 | import org.sonar.plugins.clojure.sensors.cloverage.CloverageSensor; 11 | import org.sonar.plugins.clojure.sensors.eastwood.EastwoodSensor; 12 | import org.sonar.plugins.clojure.sensors.kibit.KibitSensor; 13 | import org.sonar.plugins.clojure.sensors.kondo.KondoSensor; 14 | import org.sonar.plugins.clojure.sensors.leinnvd.LeinNvdSensor; 15 | import org.sonar.plugins.clojure.settings.Properties; 16 | 17 | public class ClojurePlugin implements Plugin { 18 | 19 | @Override 20 | public void define(Context context) { 21 | context.addExtension(Properties.getAllProperties()); 22 | context.addExtension(Clojure.class); 23 | context.addExtension(ClojureSonarWayProfile.class); 24 | context.addExtension(ClojureLintRulesDefinition.class); 25 | context.addExtension(LeiningenRunner.class); 26 | context.addExtension(EastwoodSensor.class); 27 | context.addExtension(KibitSensor.class); 28 | context.addExtension(AncientSensor.class); 29 | context.addExtension(CloverageSensor.class); 30 | context.addExtension(LeinNvdSensor.class); 31 | context.addExtension(ClojureSensor.class); 32 | context.addExtension(KondoSensor.class); 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/eastwood/EastwoodIssueParserTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.eastwood; 2 | 3 | import org.junit.Test; 4 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 5 | import org.sonar.plugins.clojure.sensors.Issue; 6 | 7 | 8 | import java.util.List; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.junit.Assert.assertNull; 12 | import static org.junit.Assert.assertThat; 13 | 14 | public class EastwoodIssueParserTest { 15 | 16 | @Test 17 | public void testNoIssuesGeneratedForInvalidStreamConsumer() { 18 | CommandStreamConsumer output = new CommandStreamConsumer(); 19 | output.consumeLine("invalid issue"); 20 | 21 | List issues = EastwoodIssueParser.parse(output); 22 | 23 | assertThat(issues.size(), is(0)); 24 | } 25 | 26 | @Test 27 | public void testIssueGenerateForValidStreamConsumer() { 28 | CommandStreamConsumer output = new CommandStreamConsumer(); 29 | output.consumeLine("path:1:2:some-key:description"); 30 | 31 | List issues = EastwoodIssueParser.parse(output); 32 | 33 | assertThat(issues.size(), is(1)); 34 | assertThat(issues.get(0).getLine(), is(1)); 35 | assertThat(issues.get(0).getFilePath(), is("path")); 36 | assertThat(issues.get(0).getDescription(), is("description")); 37 | assertThat(issues.get(0).getExternalRuleId(), is("eastwood")); 38 | } 39 | 40 | @Test 41 | public void testNoIssuesGeneratedForNullStreamConsumer() { 42 | CommandStreamConsumer output = null; 43 | 44 | List issues = EastwoodIssueParser.parse(output); 45 | 46 | assertThat(issues.size(), is(0)); 47 | } 48 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/eastwood/EastwoodPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.eastwood; 2 | 3 | import org.junit.Test; 4 | import org.sonar.api.config.PropertyDefinition; 5 | import org.sonar.plugins.clojure.sensors.eastwood.EastwoodProperties; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.junit.Assert.assertThat; 9 | 10 | public class EastwoodPropertiesTest { 11 | @Test 12 | public void shouldHaveEastwoodDisabledProperty() { 13 | PropertyDefinition eastwoodDisabled = EastwoodProperties.getEnabledProperty(); 14 | assertThat(eastwoodDisabled.key(), is("sonar.clojure.eastwood.enabled")); 15 | assertThat(eastwoodDisabled.name(), is("Eastwood Disabled")); 16 | assertThat(eastwoodDisabled.category(), is("SonarClojure")); 17 | assertThat(eastwoodDisabled.subCategory(), is("Sensors")); 18 | assertThat(eastwoodDisabled.defaultValue(), is("true")); 19 | assertThat(eastwoodDisabled.description(), is("Indicates if eastwood sensor should be disabled")); 20 | } 21 | 22 | @Test 23 | public void shouldHaveEastwoodOptionsProperty() { 24 | PropertyDefinition eastwoodOptions = EastwoodProperties.getEastwoodOptions(); 25 | assertThat(eastwoodOptions.key(), is("sonar.clojure.eastwood.options")); 26 | assertThat(eastwoodOptions.name(), is("Eastwood Options")); 27 | assertThat(eastwoodOptions.category(), is("SonarClojure")); 28 | assertThat(eastwoodOptions.subCategory(), is("Sensors")); 29 | assertThat(eastwoodOptions.defaultValue(), is("")); 30 | assertThat(eastwoodOptions.description(), is("Provide options for eastwood plugin (e.g {:continue-on-exception true})")); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/AbstractSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors; 2 | 3 | import org.junit.Before; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.MockitoJUnitRunner; 9 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 10 | import org.sonar.api.utils.log.LogTester; 11 | 12 | import java.io.File; 13 | 14 | import static junit.framework.TestCase.assertTrue; 15 | import static org.hamcrest.CoreMatchers.equalTo; 16 | import static org.hamcrest.CoreMatchers.hasItem; 17 | import static org.hamcrest.MatcherAssert.assertThat; 18 | 19 | @RunWith(MockitoJUnitRunner.class) 20 | public class AbstractSensorTest { 21 | 22 | @Mock 23 | private LeiningenRunner leiningenRunner; 24 | 25 | @Rule 26 | public LogTester logTester = new LogTester(); 27 | 28 | private DummySensor dummySensor; 29 | 30 | @Before 31 | public void setUp() { 32 | this.dummySensor = new DummySensor(leiningenRunner); 33 | } 34 | 35 | @Test 36 | public void shouldDisablePluginWhenPropertyIsSet() { 37 | SensorContextTester context = SensorContextTester.create(new File("/")); 38 | context.settings().appendProperty("property.enabled", "false"); 39 | 40 | boolean isDisabled = dummySensor.isPluginEnabled(context, "SOME_PLUGIN", "property.enabled", true); 41 | 42 | assertThat(isDisabled, equalTo(false)); 43 | assertThat(logTester.logs(), hasItem("SOME_PLUGIN disabled")); 44 | } 45 | 46 | private class DummySensor extends AbstractSensor { 47 | DummySensor(LeiningenRunner leiningenRunner) { 48 | super(leiningenRunner); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/ancient/AncientOutputParserTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.ancient; 2 | 3 | import org.junit.Test; 4 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 5 | 6 | import java.util.List; 7 | 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.assertThat; 11 | 12 | public class AncientOutputParserTest { 13 | 14 | @Test 15 | public void testParse() { 16 | CommandStreamConsumer output = new CommandStreamConsumer(); 17 | output.consumeLine("This is some non related line which should not end to report"); 18 | output.consumeLine("[metosin/reitit \"0.2.10\"] is available but we use \"0.2.1\""); 19 | output.consumeLine("[metosin/ring-http-response \"0.9.1\"] is available but we use \"0.9.0\""); 20 | 21 | List outdated = AncientOutputParser.parse(output.getData()); 22 | 23 | assertThat(outdated.size(), is(2)); 24 | OutdatedDependency reitit = outdated.get(0); 25 | OutdatedDependency expected = new OutdatedDependency(); 26 | expected.setName("metosin/reitit"); 27 | expected.setAvailableVersion("0.2.10"); 28 | expected.setCurrentVersion("0.2.1"); 29 | assertEquals(reitit, expected); 30 | } 31 | 32 | @Test 33 | public void testNoMatchParseCase() { 34 | CommandStreamConsumer output = new CommandStreamConsumer(); 35 | output.consumeLine("This output doesnt contain any any outdated dependencies"); 36 | 37 | List outdated = AncientOutputParser.parse(output.getData()); 38 | 39 | assertThat(outdated.size(), is(0)); 40 | } 41 | 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/settings/CloveragePropertiesTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.junit.Test; 4 | import org.sonar.api.config.PropertyDefinition; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | 9 | public class CloveragePropertiesTest { 10 | @Test 11 | public void shouldHaveCloverageDisabledProperty() { 12 | PropertyDefinition cloverageDisabled = CloverageProperties.getEnabledProperty(); 13 | assertThat(cloverageDisabled.key(), is("sonar.clojure.cloverage.enabled")); 14 | assertThat(cloverageDisabled.name(), is("Cloverage Disabled")); 15 | assertThat(cloverageDisabled.category(), is("SonarClojure")); 16 | assertThat(cloverageDisabled.subCategory(), is("Sensors")); 17 | assertThat(cloverageDisabled.defaultValue(), is("true")); 18 | assertThat(cloverageDisabled.description(), is("Indicates if cloverage sensor should be disabled")); 19 | } 20 | 21 | @Test 22 | public void shouldHaveCloverageReportLocationProperty() { 23 | PropertyDefinition cloverageReportLocation = CloverageProperties.getReportLocationProperty(); 24 | assertThat(cloverageReportLocation.key(), is("sonar.clojure.cloverage.reportPath")); 25 | assertThat(cloverageReportLocation.name(), is("Cloverage Report Location")); 26 | assertThat(cloverageReportLocation.category(), is("SonarClojure")); 27 | assertThat(cloverageReportLocation.subCategory(), is("Sensors")); 28 | assertThat(cloverageReportLocation.defaultValue(), is("target/coverage/codecov.json")); 29 | assertThat(cloverageReportLocation.description(), is("Indicates the location of the cloverage report file")); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/eastwood/EastwoodProperties.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.eastwood; 2 | 3 | import org.sonar.api.config.PropertyDefinition; 4 | 5 | import java.util.List; 6 | 7 | import static java.lang.String.valueOf; 8 | import static java.util.Arrays.asList; 9 | import static org.sonar.plugins.clojure.settings.Properties.MAIN_CATEGORY; 10 | import static org.sonar.plugins.clojure.settings.Properties.SUB_CATEGORY; 11 | 12 | public class EastwoodProperties { 13 | public static final String ENABLED_PROPERTY = "sonar.clojure.eastwood.enabled"; 14 | public static final boolean ENABLED_PROPERTY_DEFAULT = true; 15 | public static final String EASTWOOD_OPTIONS = "sonar.clojure.eastwood.options"; 16 | 17 | private EastwoodProperties() { 18 | } 19 | 20 | public static PropertyDefinition getEnabledProperty() { 21 | return PropertyDefinition.builder(ENABLED_PROPERTY) 22 | .category(MAIN_CATEGORY) 23 | .subCategory(SUB_CATEGORY) 24 | .defaultValue(valueOf(ENABLED_PROPERTY_DEFAULT)) 25 | .name("Eastwood Disabled") 26 | .description("Indicates if eastwood sensor should be disabled") 27 | .build(); 28 | } 29 | 30 | public static PropertyDefinition getEastwoodOptions() { 31 | return PropertyDefinition.builder(EASTWOOD_OPTIONS) 32 | .category(MAIN_CATEGORY) 33 | .subCategory(SUB_CATEGORY) 34 | .defaultValue("") 35 | .name("Eastwood Options") 36 | .description("Provide options for eastwood plugin (e.g {:continue-on-exception true})") 37 | .build(); 38 | } 39 | 40 | public static List getProperties() { 41 | return asList(getEnabledProperty(), getEastwoodOptions()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/clojure/ClojureSensor.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.clojure; 2 | 3 | 4 | import org.sonar.api.batch.fs.FilePredicate; 5 | import org.sonar.api.batch.fs.FilePredicates; 6 | import org.sonar.api.batch.fs.InputFile; 7 | import org.sonar.api.batch.sensor.Sensor; 8 | import org.sonar.api.batch.sensor.SensorContext; 9 | import org.sonar.api.batch.sensor.SensorDescriptor; 10 | import org.sonar.api.utils.log.Logger; 11 | import org.sonar.api.utils.log.Loggers; 12 | import org.sonar.plugins.clojure.language.Clojure; 13 | 14 | import static org.sonar.api.measures.CoreMetrics.NCLOC; 15 | 16 | public class ClojureSensor implements Sensor { 17 | 18 | private static final Logger LOG = Loggers.get(ClojureSensor.class); 19 | public static final String SENSOR_NAME = "ClojureSensor"; 20 | 21 | @Override 22 | public void describe(SensorDescriptor descriptor) { 23 | descriptor.name(SENSOR_NAME).onlyOnLanguage(Clojure.KEY); 24 | } 25 | 26 | @Override 27 | public void execute(SensorContext context) { 28 | LOG.info("Running ClojureSensor"); 29 | FilePredicates predicates = context.fileSystem().predicates(); 30 | FilePredicate clojure = predicates.hasLanguage(Clojure.KEY); 31 | FilePredicate main = predicates.hasType(InputFile.Type.MAIN); 32 | 33 | //TODO This is inaccurate. We need to properly count the lines of code, excluding spaces, comments, etc. 34 | //TODO This is here to make sure analysis data will show up in the Sonar UI. 35 | Iterable sources = context.fileSystem().inputFiles(predicates.and(clojure, main)); 36 | 37 | for (InputFile source : sources) { 38 | LOG.info(source.toString()); 39 | context.newMeasure().withValue(source.lines()).forMetric(NCLOC).on(source).save(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/ancient/OutdatedDependency.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.ancient; 2 | 3 | import java.util.Objects; 4 | 5 | public class OutdatedDependency { 6 | 7 | private String name; 8 | private String currentVersion; 9 | private String availableVersion; 10 | 11 | @Override 12 | public boolean equals(Object o) { 13 | if (this == o) return true; 14 | if (o == null || getClass() != o.getClass()) return false; 15 | OutdatedDependency that = (OutdatedDependency) o; 16 | return Objects.equals(name, that.name) && 17 | Objects.equals(currentVersion, that.currentVersion) && 18 | Objects.equals(availableVersion, that.availableVersion); 19 | } 20 | 21 | @Override 22 | public int hashCode() { 23 | return Objects.hash(name, currentVersion, availableVersion); 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return name + " is using version: " + 29 | getCurrentVersion() + " but version: " + 30 | getAvailableVersion() + " is available."; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public OutdatedDependency setName(String name) { 38 | this.name = name; 39 | return this; 40 | } 41 | 42 | public String getCurrentVersion() { 43 | return currentVersion; 44 | } 45 | 46 | public OutdatedDependency setCurrentVersion(String currentVersion) { 47 | this.currentVersion = currentVersion; 48 | return this; 49 | } 50 | 51 | public String getAvailableVersion() { 52 | return availableVersion; 53 | } 54 | 55 | public OutdatedDependency setAvailableVersion(String availableVersion) { 56 | this.availableVersion = availableVersion; 57 | return this; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/settings/CloverageProperties.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.sonar.api.config.PropertyDefinition; 4 | 5 | import java.util.List; 6 | 7 | import static java.util.Arrays.asList; 8 | import static org.sonar.plugins.clojure.settings.Properties.MAIN_CATEGORY; 9 | import static org.sonar.plugins.clojure.settings.Properties.SUB_CATEGORY; 10 | 11 | public class CloverageProperties { 12 | public static final String ENABLED_PROPERTY = "sonar.clojure.cloverage.enabled"; 13 | public static final boolean ENABLED_PROPERTY_DEFAULT = true; 14 | public static final String REPORT_LOCATION_PROPERTY = "sonar.clojure.cloverage.reportPath"; 15 | public static final String REPORT_LOCATION_DEFAULT = "target/coverage/codecov.json"; 16 | 17 | private CloverageProperties() { 18 | } 19 | 20 | static PropertyDefinition getEnabledProperty() { 21 | return PropertyDefinition.builder(ENABLED_PROPERTY) 22 | .category(MAIN_CATEGORY) 23 | .subCategory(SUB_CATEGORY) 24 | .defaultValue(String.valueOf(ENABLED_PROPERTY_DEFAULT)) 25 | .name("Cloverage Disabled") 26 | .description("Indicates if cloverage sensor should be disabled") 27 | .build(); 28 | } 29 | 30 | static PropertyDefinition getReportLocationProperty() { 31 | return PropertyDefinition.builder(REPORT_LOCATION_PROPERTY) 32 | .category(MAIN_CATEGORY) 33 | .subCategory(SUB_CATEGORY) 34 | .defaultValue(REPORT_LOCATION_DEFAULT) 35 | .name("Cloverage Report Location") 36 | .description("Indicates the location of the cloverage report file") 37 | .build(); 38 | } 39 | 40 | static List getProperties() { 41 | return asList(getEnabledProperty(), getReportLocationProperty()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | # Disable shallow clone to prevent SonarCloud warning 4 | git: 5 | depth: false 6 | 7 | cache: 8 | directories: 9 | - '$HOME/.m2/repository' 10 | - '$HOME/.sonar/cache' 11 | 12 | addons: 13 | sonarcloud: 14 | organization: fsantiag 15 | token: 16 | secure: s8/d/kp0ar9ai8qCxSta6ftkMTyGanSsDU12i4BkxBuCpXhAO7AbRY3ii8jPxgzZEj3w5YM4ZTq3LNdSuns3KIB1eTA1OYmcIpb7VcrJxs5fxsLb8QtnOVEguyAQ+CoN1+h8qZnIYabz4ocRZuOjTfcg4plyFm2qjkKQQu8h5FPgKwp/jAMj7FN4b3z2nBhpc8v3q+ordmW94yC6jlU5V/gvcKj5JSTxHB1Ou+3OsnzTOx3a551dUeAcuUHrl/iDRnxE0tnGg2NCaR1uxIJy8U/t/rfK/pktLbdp3DpD9vM5KCfS6qaXCX+Da57+FMH9nOIoKZpefbUSgF1x6BfhQr0cBZXGSvD0USCJUYhlUru18uTkKvhQFC5ug8C0H+awLszFW83A6opmvIG20iDLs30LVxwd5o/iXorbfnyWpZQ04SzFTkr22MxMo3P35E/on019pOqZcrF5ZwhrwoyS+AEu89E0R2B39CX8bMiCyKm1IVkf49sBLRG8hrwJI4Jvttqz/OdmN906hOrRxveCrNocjTlRXtC0IJbEVDYHeXs6fZNkjBu9/s/EdCV+lFQkShftUdBhSIpD4gW0nnsuRFn689cdZVP7LIkNg+i/bAUZCqdvtYBd6LC5gLIT/BmRoxcWgZRmA+jKm50Jdh/ScB80AwmLgMuPI7MSoX+fG2w= 17 | 18 | jobs: 19 | include: 20 | - stage: Build, SonarQube and Deploy 21 | script: ./travis.sh 22 | deploy: 23 | provider: releases 24 | api_key: 25 | secure: IoGF1AEE/6jkNMgpYHPmEghfMgFcy/sZWjO62lqkkpFImFdSIijrGR3rjNCWkBfkRVdXHfNpCpQNXBhKuE8NA0K8qZXy+ay8xFjCIGBdUxLNGIA2/CLqrAgteUH9b+PrwvOiAyR8xCz4KnjfV9m6XeXSPmPpTz+fYjhW7Tt5m2fMY2simppuCHI4yn0x2H4bWFNR5YNzUQTIvKfduiO7+i+k5LV8GgKIYaEweBRx4xxP7kX7CXXTRgvyyCMXNSCAqbE07Y4puBDg6lNSxn4NhcBD8bhxmW9VNBkT76NL543GsY+LVZlmzi+MyeV/k8Uke3v95bfRKpDmfGFxFNMFERNyPXNPPwUjobu66tYioXjeuzUY4tbY4suDL9+0d6B49nWjOnavMPSGMFf2Z43TOYLb4lmQQZkvEWUQX/5TDvgPbCYjlPhnZanb7+LDt44VMXb8GeVxT3j+ommUgk0ES6jLd41NHYqFl9LjxdWfMg4He378RyCZBjUGq3J+UAGtQqaPIzRhMwPoCVuscWKCQw7Jd2xjP7LQxaxSuaJTM5hRnS/yIGmJR/gw96V3mRUIOcokBYtfV/CDdk3yYjdtCLk78jbtWyr5Wxz6bhAjomrAdq2dsMHFHDwgNOvpT7on2S/ObNcUhg8odkyCvdreCg258vBWOUZosvUfCGG6ZiQ= 26 | file_glob: true 27 | file: target/*.jar 28 | skip_cleanup: true 29 | on: 30 | tags: true -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/settings/NvdProperties.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.sonar.api.config.PropertyDefinition; 4 | 5 | import java.util.List; 6 | 7 | import static java.lang.String.valueOf; 8 | import static java.util.Arrays.asList; 9 | import static org.sonar.plugins.clojure.settings.Properties.MAIN_CATEGORY; 10 | import static org.sonar.plugins.clojure.settings.Properties.SUB_CATEGORY; 11 | 12 | public class NvdProperties { 13 | public static final String ENABLED_PROPERTY = "sonar.clojure.nvd.enabled"; 14 | public static final boolean ENABLED_PROPERTY_DEFAULT = true; 15 | public static final String REPORT_LOCATION_PROPERTY = "sonar.clojure.nvd.reportPath"; 16 | public static final String REPORT_LOCATION_DEFAULT = "target/nvd/dependency-check-report.json"; 17 | 18 | private NvdProperties() { 19 | } 20 | 21 | static PropertyDefinition getEnabledProperty() { 22 | return PropertyDefinition.builder(ENABLED_PROPERTY) 23 | .category(MAIN_CATEGORY) 24 | .subCategory(SUB_CATEGORY) 25 | .defaultValue(valueOf(ENABLED_PROPERTY_DEFAULT)) 26 | .name("Lein NVD Disabled") 27 | .description("Indicates if lein-nvd sensor should be disabled") 28 | .build(); 29 | } 30 | 31 | static PropertyDefinition getReportLocationProperty() { 32 | return PropertyDefinition.builder(REPORT_LOCATION_PROPERTY) 33 | .category(MAIN_CATEGORY) 34 | .subCategory(SUB_CATEGORY) 35 | .defaultValue(REPORT_LOCATION_DEFAULT) 36 | .name("Lein NVD Report Location") 37 | .description("Indicates the location of the Lein NVD report file") 38 | .build(); 39 | } 40 | 41 | static List getProperties() { 42 | return asList(getEnabledProperty(), getReportLocationProperty()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/kondo/KondoIssueParser.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.kondo; 2 | 3 | import org.sonar.api.internal.google.common.collect.Lists; 4 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 5 | import us.bpsm.edn.Keyword; 6 | import us.bpsm.edn.parser.Parseable; 7 | import us.bpsm.edn.parser.Parser; 8 | import us.bpsm.edn.parser.Parsers; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.stream.Collectors; 14 | 15 | import static us.bpsm.edn.Keyword.newKeyword; 16 | import static us.bpsm.edn.parser.Parsers.defaultConfiguration; 17 | 18 | public class KondoIssueParser { 19 | public static List parse(CommandStreamConsumer stdOut) { 20 | if (stdOut == null){ 21 | return Collections.EMPTY_LIST; 22 | } 23 | String kondoOutput = String.join("", stdOut.getData()); 24 | 25 | Parseable parseable = Parsers.newParseable(kondoOutput); 26 | Parser parser = Parsers.newParser(defaultConfiguration()); 27 | 28 | Map m = (Map) parser.nextValue(parseable); 29 | 30 | List findings = (List) m.get(newKeyword("findings")); 31 | 32 | return findings.stream() 33 | .map(f -> asFinding((Map) f)) 34 | .collect(Collectors.toList()); 35 | } 36 | 37 | private static Finding asFinding(Map finding) { 38 | String message = (String) finding.get(newKeyword("message")); 39 | String filename = (String) finding.get(newKeyword("filename")); 40 | String level = ((Keyword) finding.get(newKeyword("level"))).getName(); 41 | Long row = (Long) finding.get(newKeyword("row")); 42 | Long col = (Long) finding.get(newKeyword("col")); 43 | Long endRow = (Long) finding.get(newKeyword("end-row")); 44 | Long endCol = (Long) finding.get(newKeyword("end-col")); 45 | 46 | return new Finding("kondo", message, filename, level, 47 | row.intValue(), col.intValue(), endRow.intValue(), endCol.intValue()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/kibit/KibitIssueParserTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.kibit; 2 | 3 | import org.junit.Test; 4 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 5 | import org.sonar.plugins.clojure.sensors.Issue; 6 | import org.sonar.plugins.clojure.sensors.eastwood.EastwoodIssueParser; 7 | 8 | import java.util.List; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.junit.Assert.assertNull; 12 | import static org.junit.Assert.assertThat; 13 | 14 | public class KibitIssueParserTest { 15 | 16 | @Test 17 | public void testNoIssuesGeneratedForInvalidStreamConsumer() { 18 | CommandStreamConsumer output = new CommandStreamConsumer(); 19 | output.consumeLine("invalid issue"); 20 | 21 | List issues = KibitIssueParser.parse(output); 22 | 23 | assertThat(issues.size(), is(0)); 24 | } 25 | 26 | @Test 27 | public void testIssueGenerateForValidStreamConsumer() { 28 | CommandStreamConsumer output = new CommandStreamConsumer(); 29 | output.consumeLine("At src/clojure_sonar_example/core.clj:13:"); 30 | output.consumeLine("Consider using:..."); 31 | output.consumeLine("some consideration"); 32 | output.consumeLine("----"); 33 | output.consumeLine("At /sok_register_api/something.clj:67:"); 34 | output.consumeLine("Consider something else"); 35 | List issues = KibitIssueParser.parse(output); 36 | 37 | assertThat(issues.size(), is(2)); 38 | assertThat(issues.get(0).getLine(), is(13)); 39 | assertThat(issues.get(0).getFilePath(), is("src/clojure_sonar_example/core.clj")); 40 | assertThat(issues.get(0).getDescription(), is("Consider using:..." + "\n" + "some consideration\n")); 41 | assertThat(issues.get(0).getExternalRuleId(), is("kibit")); 42 | 43 | assertThat(issues.get(1).getLine(), is(67)); 44 | } 45 | 46 | @Test 47 | public void testNoIssuesGeneratedForNullStreamConsumer() { 48 | CommandStreamConsumer output = null; 49 | 50 | List issues = KibitIssueParser.parse(output); 51 | 52 | assertThat(issues.size(), is(0)); 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/leinnvd/LeinNvdParserTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.leinnvd; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import static java.nio.charset.StandardCharsets.UTF_8; 12 | import static java.util.Arrays.asList; 13 | import static org.hamcrest.CoreMatchers.is; 14 | import static org.junit.Assert.assertThat; 15 | import static org.junit.Assert.assertTrue; 16 | 17 | public class LeinNvdParserTest { 18 | 19 | @Test 20 | public void testParse() throws IOException { 21 | String json = new String(Files.readAllBytes(Paths.get("src/test/resources/nvd-report.json")), UTF_8); 22 | List vulnerabilities = LeinNvdParser.parseJson(json); 23 | assertThat(vulnerabilities.size(), is(2)); 24 | List expected = new ArrayList<>(); 25 | expected.addAll(asList( 26 | new Vulnerability() 27 | .setName("CVE-2018-5968") 28 | .setSeverity("HIGH") 29 | .setCwes("CWE-502,CWE-184") 30 | .setDescription("FasterXML jackson-databind through 2.8.11 and 2.9.x through 2.9.3 allows unauthenticated remote code execution because of an incomplete fix for the CVE-2017-7525 and CVE-2017-17485 deserialization flaws. This is exploitable via two different gadgets that bypass a blacklist.") 31 | .setFileName("jackson-databind-2.9.3.jar"), 32 | new Vulnerability() 33 | .setName("CVE-2018-19362") 34 | .setSeverity("CRITICAL") 35 | .setCwes("CWE-502") 36 | .setDescription("FasterXML jackson-databind 2.x before 2.9.8 might allow attackers to have unspecified impact by leveraging failure to block the jboss-common-core class from polymorphic deserialization.") 37 | .setFileName("jackson-databind-2.9.3.jar"))); 38 | expected.stream().forEach(entry -> assertTrue(vulnerabilities.contains(entry))); 39 | } 40 | 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/kondo/KondoIssueParserTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.kondo; 2 | 3 | import org.junit.Test; 4 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 5 | 6 | import java.util.List; 7 | 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class KondoIssueParserTest { 12 | 13 | @Test 14 | public void testNoIssuesGeneratedForInvalidStreamConsumer() { 15 | CommandStreamConsumer output = new CommandStreamConsumer(); 16 | output.consumeLine("{:findings [], :summary {:error 0, :warning 0, :info 0, :type :summary, :duration 173, :files 32}}"); 17 | List issues = KondoIssueParser.parse(output); 18 | assertThat(issues.size(), is(0)); 19 | } 20 | 21 | @Test 22 | public void testIssueGenerateForValidStreamConsumer() { 23 | CommandStreamConsumer output = new CommandStreamConsumer(); 24 | output.consumeLine( 25 | "{:findings [{:type :redundant-let, :message \"Redundant let expression.\", :level :warning, " + 26 | ":row 35, :end-row 36, :end-col 15, :col 5, :filename \"src/example/init.clj\"}], " + 27 | ":summary {:error 0, :warning 1, :info 0, :type :summary, :duration 157, :files 32}}"); 28 | 29 | List issues = KondoIssueParser.parse(output); 30 | 31 | assertThat(issues.size(), is(1)); 32 | assertThat(issues.get(0).getMessage(), is("Redundant let expression.")); 33 | assertThat(issues.get(0).getType(), is("kondo")); 34 | assertThat(issues.get(0).getFilename(), is("src/example/init.clj")); 35 | assertThat(issues.get(0).getLevel(), is("warning")); 36 | assertThat(issues.get(0).getCol(), is(5)); 37 | assertThat(issues.get(0).getEndCol(), is(15)); 38 | assertThat(issues.get(0).getRow(), is(35)); 39 | assertThat(issues.get(0).getEndRow(), is(36)); 40 | } 41 | 42 | @Test 43 | public void testNoIssuesGeneratedForNullStreamConsumer() { 44 | CommandStreamConsumer output = null; 45 | List issues = KondoIssueParser.parse(output); 46 | assertThat(issues.size(), is(0)); 47 | } 48 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/language/ClojureSonarWayProfileTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.language; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | import static java.util.Arrays.asList; 11 | import static java.util.Collections.singletonList; 12 | import static org.junit.Assert.assertTrue; 13 | import static org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.*; 14 | 15 | public class ClojureSonarWayProfileTest { 16 | 17 | private List profileRules; 18 | 19 | @Before 20 | public void setUp() { 21 | Context context = new Context(); 22 | ClojureSonarWayProfile clojureSonarWayProfile = new ClojureSonarWayProfile(); 23 | clojureSonarWayProfile.define(context); 24 | BuiltInQualityProfile profile = context.profile("clj", "Sonar way"); 25 | List rules = profile.rules(); 26 | this.profileRules = rules.stream().map(BuiltInActiveRule::ruleKey).collect(Collectors.toList()); 27 | 28 | } 29 | 30 | @Test 31 | public void testIfSonarwayProfileIsCreatedWithAllEastwoodRule() { 32 | List ruleKeys = new ArrayList<>(singletonList("eastwood")); 33 | ruleKeys.forEach(eastwoodRule -> assertTrue(profileRules.contains(eastwoodRule))); 34 | } 35 | 36 | @Test 37 | public void testIfSonarwayProfileIsCreatedWithAllAncientCljRule() { 38 | List ruleKeys = new ArrayList<>(singletonList("ancient-clj-dependency")); 39 | ruleKeys.forEach(ancientCljRule -> assertTrue(profileRules.contains(ancientCljRule))); 40 | } 41 | 42 | @Test 43 | public void testIfSonarwayProfileIsCreatedWithLeinNvdRules() { 44 | List ruleKeys = new ArrayList<>(asList("nvd-critical", "nvd-high", "nvd-medium", "nvd-low")); 45 | ruleKeys.forEach(leinNvdRule -> assertTrue(profileRules.contains(leinNvdRule))); 46 | } 47 | 48 | @Test 49 | public void testIfSonarwayProfileIsCreatedWithKibitRule() { 50 | List ruleKeys = new ArrayList<>(singletonList("kibit")); 51 | ruleKeys.forEach(kibitRule -> assertTrue(profileRules.contains(kibitRule))); 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/kibit/KibitIssueParser.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.kibit; 2 | 3 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 4 | import org.sonar.plugins.clojure.sensors.Issue; 5 | 6 | import java.util.ArrayDeque; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | import java.util.stream.Collectors; 12 | 13 | public class KibitIssueParser { 14 | private static final Pattern KIBIT_START = Pattern.compile("----"); 15 | private static final Pattern KIBIT_ENTRY = Pattern.compile("At\\s([^:]+):([^`]+):"); 16 | private KibitIssueParser() { 17 | } 18 | 19 | private static boolean isEntryStart(String row) { 20 | return KIBIT_START.matcher(row).find(); 21 | } 22 | 23 | public static List parse(CommandStreamConsumer output) { 24 | List issues = new ArrayList<>(); 25 | if (output != null) { 26 | List kibitReport = output.getData(); 27 | ArrayDeque filteredReport = kibitReport.stream() 28 | .filter(line -> !line.isEmpty()) 29 | .filter(line -> !isEntryStart(line)) 30 | .collect(Collectors.toCollection(ArrayDeque::new)); 31 | 32 | while (!filteredReport.isEmpty()) { 33 | String path = filteredReport.pop(); 34 | Matcher matcher = KIBIT_ENTRY.matcher(path); 35 | if (matcher.find()) { 36 | String filename = matcher.group(1); 37 | // Kibit may return line number sometimes as string "null" 38 | int lineNumber = !matcher.group(2).equals("null") ? Integer.parseInt(matcher.group(2)) : 1; 39 | StringBuilder description = new StringBuilder(); 40 | while (!filteredReport.isEmpty() && !KIBIT_ENTRY.matcher(filteredReport.peek()).find()) { 41 | description.append(filteredReport.pop()).append("\n"); 42 | } 43 | issues.add(new Issue("kibit", description.toString(), filename, lineNumber)); 44 | } 45 | } 46 | } 47 | return issues; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/kibit/KibitSensor.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.kibit; 2 | 3 | 4 | import org.sonar.api.batch.sensor.Sensor; 5 | import org.sonar.api.batch.sensor.SensorContext; 6 | import org.sonar.api.batch.sensor.SensorDescriptor; 7 | import org.sonar.api.utils.log.Logger; 8 | import org.sonar.api.utils.log.Loggers; 9 | import org.sonar.plugins.clojure.language.Clojure; 10 | import org.sonar.plugins.clojure.sensors.AbstractSensor; 11 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 12 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 13 | import org.sonar.plugins.clojure.sensors.Issue; 14 | import org.sonar.plugins.clojure.settings.KibitProperties; 15 | 16 | import java.util.List; 17 | 18 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY; 19 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY_DEFAULT; 20 | 21 | public class KibitSensor extends AbstractSensor implements Sensor { 22 | 23 | private static final Logger LOG = Loggers.get(KibitSensor.class); 24 | 25 | private static final String KIBIT_COMMAND = "kibit"; 26 | private static final String PLUGIN_NAME = "Kibit"; 27 | 28 | @SuppressWarnings("WeakerAccess") 29 | public KibitSensor(LeiningenRunner leiningenRunner) { 30 | super(leiningenRunner); 31 | } 32 | 33 | @Override 34 | public void describe(SensorDescriptor descriptor) { 35 | descriptor.name(PLUGIN_NAME) 36 | .onlyOnLanguage(Clojure.KEY); 37 | } 38 | 39 | @Override 40 | public void execute(SensorContext context) { 41 | if (isPluginEnabled(context, PLUGIN_NAME, KibitProperties.ENABLED_PROPERTY, KibitProperties.ENABLED_PROPERTY_DEFAULT)) { 42 | LOG.info("Running Kibit"); 43 | long timeOut = context.config().getLong(SENSORS_TIMEOUT_PROPERTY) 44 | .orElse(Long.valueOf(SENSORS_TIMEOUT_PROPERTY_DEFAULT)); 45 | 46 | CommandStreamConsumer stdOut = this.leiningenRunner.run(timeOut, KIBIT_COMMAND); 47 | 48 | List issues = KibitIssueParser.parse(stdOut); 49 | LOG.info("Saving issues"); 50 | for (Issue issue : issues) { 51 | saveIssue(issue, context); 52 | } 53 | 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/ClojurePluginTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure; 2 | 3 | 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.sonar.api.Plugin; 7 | import org.sonar.api.SonarEdition; 8 | import org.sonar.api.SonarQubeSide; 9 | import org.sonar.api.SonarRuntime; 10 | import org.sonar.api.config.PropertyDefinition; 11 | import org.sonar.api.internal.SonarRuntimeImpl; 12 | import org.sonar.api.utils.Version; 13 | import org.sonar.plugins.clojure.language.Clojure; 14 | import org.sonar.plugins.clojure.language.ClojureSonarWayProfile; 15 | import org.sonar.plugins.clojure.rules.ClojureLintRulesDefinition; 16 | import org.sonar.plugins.clojure.sensors.eastwood.EastwoodSensor; 17 | 18 | import java.util.List; 19 | 20 | import static org.hamcrest.CoreMatchers.is; 21 | import static org.junit.Assert.assertThat; 22 | import static org.junit.Assert.assertTrue; 23 | 24 | 25 | public class ClojurePluginTest { 26 | 27 | private Plugin.Context context; 28 | 29 | @Before 30 | public void setUp() { 31 | SonarRuntime runtime = SonarRuntimeImpl 32 | .forSonarQube(Version.create(7, 9, 3), SonarQubeSide.SERVER, SonarEdition.COMMUNITY); 33 | context = new Plugin.Context(runtime); 34 | new ClojurePlugin().define(context); 35 | } 36 | 37 | @Test 38 | public void testClojureLanguageIsAPluginExtension() { 39 | assertTrue(context.getExtensions().contains(Clojure.class)); 40 | } 41 | 42 | @Test 43 | public void testClojureQualityProfileIsAPluginExtension() { 44 | assertTrue(context.getExtensions().contains(ClojureSonarWayProfile.class)); 45 | } 46 | 47 | @Test 48 | public void testClojureLintRulesDefinitionIsAPluginExtension() { 49 | assertTrue(context.getExtensions().contains(ClojureLintRulesDefinition.class)); 50 | } 51 | 52 | @Test 53 | public void testEastwoodSensorIsInExtensions() { 54 | assertTrue(context.getExtensions().contains(EastwoodSensor.class)); 55 | } 56 | 57 | @Test 58 | public void testFileSuffixesPropertyIsInExtensions() { 59 | List propertyDefinitions = (List) context.getExtensions().get(0); 60 | PropertyDefinition suffixProperty = propertyDefinitions.get(0); 61 | assertThat(suffixProperty.key(), is("sonar.clojure.file.suffixes")); 62 | 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/settings/KondoProperties.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.sonar.api.config.PropertyDefinition; 4 | 5 | import java.util.List; 6 | 7 | import static java.lang.String.valueOf; 8 | import static java.util.Arrays.asList; 9 | import static org.sonar.plugins.clojure.settings.Properties.MAIN_CATEGORY; 10 | import static org.sonar.plugins.clojure.settings.Properties.SUB_CATEGORY; 11 | 12 | public class KondoProperties { 13 | public static final String ENABLED_PROPERTY = "sonar.clojure.kondo.enabled"; 14 | public static final String OPTIONS = "sonar.clojure.kondo.options"; 15 | public static final String CONFIG = "sonar.clojure.kondo.config"; 16 | public static final boolean ENABLED_PROPERTY_DEFAULT = false; 17 | public static final String DEFAULT_OPTIONS = "--lint src"; 18 | public static final String DEFAULT_CONFIG = "{:output {:format :edn}}"; 19 | 20 | private KondoProperties() { 21 | } 22 | 23 | static PropertyDefinition getEnabledProperty() { 24 | return PropertyDefinition.builder(ENABLED_PROPERTY) 25 | .category(MAIN_CATEGORY) 26 | .subCategory(SUB_CATEGORY) 27 | .defaultValue(valueOf(ENABLED_PROPERTY_DEFAULT)) 28 | .name("clj-kondo disabled") 29 | .description("Indicates if clj-kondo sensor should be disabled") 30 | .build(); 31 | } 32 | 33 | static PropertyDefinition getOptionsProperty() { 34 | return PropertyDefinition.builder(OPTIONS) 35 | .category(MAIN_CATEGORY) 36 | .subCategory(SUB_CATEGORY) 37 | .defaultValue(DEFAULT_OPTIONS) 38 | .name("clj-kondo options") 39 | .description("Provide options for clj-kondo plugin (e.g --lint src)") 40 | .build(); 41 | } 42 | 43 | static PropertyDefinition getConfigProperty() { 44 | return PropertyDefinition.builder(CONFIG) 45 | .category(MAIN_CATEGORY) 46 | .subCategory(SUB_CATEGORY) 47 | .defaultValue(DEFAULT_CONFIG) 48 | .name("clj-kondo config") 49 | .description("Provide config for clj-kondo plugin (e.g {:output {:format :edn}})") 50 | .build(); 51 | } 52 | 53 | static List getProperties() { 54 | return asList(getEnabledProperty(), getOptionsProperty(), getConfigProperty()); 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/leinnvd/LeinNvdParser.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.leinnvd; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import com.google.gson.Gson; 8 | import org.sonar.api.utils.log.Logger; 9 | import org.sonar.api.utils.log.Loggers; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class LeinNvdParser { 15 | private static final Logger LOG = Loggers.get(LeinNvdParser.class); 16 | 17 | private LeinNvdParser(){} 18 | 19 | public static List parseJson(String json){ 20 | List vulnerabilities = new ArrayList<>(); 21 | JsonParser parser = new JsonParser(); 22 | JsonElement jsonTree = parser.parse(json); 23 | JsonObject jsonObject = jsonTree.getAsJsonObject(); 24 | JsonArray dependencies = jsonObject.get("dependencies").getAsJsonArray(); 25 | 26 | for (JsonElement element: dependencies) { 27 | JsonObject dependency = element.getAsJsonObject(); 28 | 29 | if (dependency.has("vulnerabilities")){ 30 | LOG.debug("Found vulnerabilities in: " + dependency.getAsJsonPrimitive("fileName").getAsString()); 31 | JsonArray dependencyVulnerabilities = dependency.get("vulnerabilities").getAsJsonArray(); 32 | for (JsonElement dependencyVulnerability: dependencyVulnerabilities) { 33 | JsonObject dependencyVulnerabilityObject = dependencyVulnerability.getAsJsonObject(); 34 | JsonArray cwesArray = dependencyVulnerabilityObject.getAsJsonArray("cwes"); 35 | String[] cwes = new Gson().fromJson(cwesArray, String[].class); 36 | Vulnerability v = new Vulnerability() 37 | .setName(dependencyVulnerabilityObject.getAsJsonPrimitive("name").getAsString()) 38 | .setSeverity(dependencyVulnerabilityObject.getAsJsonPrimitive("severity").getAsString()) 39 | .setCwes(String.join(",", cwes)) 40 | .setDescription(dependencyVulnerabilityObject.getAsJsonPrimitive("description").getAsString()) 41 | .setFileName(dependency.getAsJsonPrimitive("fileName").getAsString()); 42 | vulnerabilities.add(v); 43 | } 44 | } 45 | } 46 | return vulnerabilities; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/leinnvd/Vulnerability.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.leinnvd; 2 | 3 | import java.util.Objects; 4 | 5 | public class Vulnerability { 6 | private String name; 7 | private String severity; 8 | private String cwes; 9 | private String description; 10 | private String fileName; 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public Vulnerability setName(String name) { 17 | this.name = name; 18 | return this; 19 | } 20 | 21 | public String getSeverity() { 22 | return severity; 23 | } 24 | 25 | public Vulnerability setSeverity(String severity) { 26 | this.severity = severity; 27 | return this; 28 | } 29 | 30 | public String getCwes() { 31 | return cwes; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "Vulnerability{" + 37 | "name='" + name + '\'' + 38 | ", severity='" + severity + '\'' + 39 | ", cwes='" + cwes + '\'' + 40 | ", Description='" + description + '\'' + 41 | ", fileName='" + fileName + '\'' + 42 | '}'; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) return true; 48 | if (o == null || getClass() != o.getClass()) return false; 49 | Vulnerability that = (Vulnerability) o; 50 | return Objects.equals(getName(), that.name) && 51 | Objects.equals(getSeverity(), that.severity) && 52 | Objects.equals(getCwes(), that.cwes) && 53 | Objects.equals(getDescription(), that.description) && 54 | Objects.equals(getFileName(), that.fileName); 55 | } 56 | 57 | 58 | public Vulnerability setCwes(String cwes) { 59 | this.cwes = cwes; 60 | return this; 61 | } 62 | 63 | public String getDescription() { 64 | return description; 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return Objects.hash(name, severity, cwes, description, fileName); 70 | } 71 | 72 | public Vulnerability setDescription(String description) { 73 | this.description = description; 74 | return this; 75 | } 76 | 77 | public String getFileName() { 78 | return fileName; 79 | } 80 | 81 | public Vulnerability setFileName(String fileName) { 82 | this.fileName = fileName; 83 | return this; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/settings/Properties.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.settings; 2 | 3 | import org.sonar.api.config.PropertyDefinition; 4 | import org.sonar.plugins.clojure.sensors.eastwood.EastwoodProperties; 5 | 6 | import java.util.Collection; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | import static java.util.Arrays.asList; 12 | 13 | public class Properties { 14 | 15 | public static final String FILE_SUFFIXES_PROPERTY = "sonar.clojure.file.suffixes"; 16 | public static final String FILE_SUFFIXES_PROPERTY_DEFAULT = "clj,cljs,cljc"; 17 | public static final String SENSORS_TIMEOUT_PROPERTY = "sonar.clojure.sensors.timeout"; 18 | public static final String SENSORS_TIMEOUT_PROPERTY_DEFAULT = "300"; 19 | public static final String MAIN_CATEGORY = "SonarClojure"; 20 | public static final String SUB_CATEGORY = "Sensors"; 21 | 22 | private Properties() { 23 | } 24 | 25 | public static List getAllProperties() { 26 | return Stream 27 | .of(getGeneralProperties(), 28 | EastwoodProperties.getProperties(), 29 | CloverageProperties.getProperties(), 30 | AncientProperties.getProperties(), 31 | KibitProperties.getProperties(), 32 | NvdProperties.getProperties(), 33 | KondoProperties.getProperties()) 34 | .flatMap(Collection::stream) 35 | .collect(Collectors.toList()); 36 | } 37 | 38 | static List getGeneralProperties() { 39 | return asList(getFileSuffix(), getSensorsTimeout()); 40 | } 41 | 42 | static PropertyDefinition getFileSuffix() { 43 | return PropertyDefinition.builder(FILE_SUFFIXES_PROPERTY) 44 | .defaultValue(FILE_SUFFIXES_PROPERTY_DEFAULT) 45 | .category(MAIN_CATEGORY) 46 | .name("File Suffixes") 47 | .description("Comma-separated list of file suffixes to analyze") 48 | .build(); 49 | } 50 | 51 | static PropertyDefinition getSensorsTimeout() { 52 | return PropertyDefinition.builder(SENSORS_TIMEOUT_PROPERTY) 53 | .defaultValue(SENSORS_TIMEOUT_PROPERTY_DEFAULT) 54 | .category(MAIN_CATEGORY) 55 | .name("Sensors Timeout") 56 | .description("Defines the maximum timeout (per sensor, in seconds) when sensors are executing") 57 | .build(); 58 | } 59 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/clojure/ClojureSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.clojure; 2 | 3 | import org.junit.Before; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.rules.TemporaryFolder; 7 | import org.sonar.api.batch.fs.internal.DefaultFileSystem; 8 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 9 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 10 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 11 | import org.sonar.api.batch.sensor.measure.Measure; 12 | import org.sonar.api.measures.CoreMetrics; 13 | import org.sonar.plugins.clojure.language.Clojure; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | 17 | public class ClojureSensorTest { 18 | 19 | private ClojureSensor clojureSensor; 20 | 21 | private SensorContextTester context; 22 | 23 | @Rule 24 | public final TemporaryFolder temporaryFolder = new TemporaryFolder(); 25 | 26 | @Before 27 | public void setUp() { 28 | context = prepareContext(); 29 | clojureSensor = new ClojureSensor(); 30 | } 31 | 32 | @Test 33 | public void shouldHaveAMeasureOfNonCommentedLinesOfCodePerFile() { 34 | clojureSensor.execute(context); 35 | 36 | String fooKey = "module:foo.clj"; 37 | String barKey = "module:bar.clj"; 38 | Measure fooLineCountMeasure = context.measure(fooKey, CoreMetrics.NCLOC); 39 | Measure barLineCountMeasure = context.measure(barKey, CoreMetrics.NCLOC); 40 | assertThat(fooLineCountMeasure).isNotNull(); 41 | assertThat(fooLineCountMeasure.value()).isEqualTo(2); 42 | 43 | assertThat(barLineCountMeasure).isNotNull(); 44 | assertThat(barLineCountMeasure.value()).isEqualTo(3); 45 | } 46 | 47 | private SensorContextTester prepareContext() { 48 | DefaultFileSystem fileSystem = new DefaultFileSystem(temporaryFolder.getRoot()); 49 | SensorContextTester context = SensorContextTester.create(temporaryFolder.getRoot()); 50 | context.setFileSystem(fileSystem); 51 | 52 | DefaultInputFile inputFile1 = TestInputFileBuilder.create("module", "foo.clj") 53 | .setLanguage(Clojure.KEY) 54 | .initMetadata("firstLine\nsecondLine") 55 | .build(); 56 | DefaultInputFile inputFile2 = TestInputFileBuilder.create("module", "bar.clj") 57 | .setLanguage(Clojure.KEY) 58 | .initMetadata("firstLine\nsecondLine\nthirdLine") 59 | .build(); 60 | context.fileSystem().add(inputFile1).add(inputFile2); 61 | 62 | return context; 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/LeiningenRunner.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors; 2 | 3 | import org.sonar.api.scanner.ScannerSide; 4 | import org.sonar.api.utils.command.Command; 5 | import org.sonar.api.utils.command.CommandExecutor; 6 | import org.sonar.api.utils.log.Logger; 7 | import org.sonar.api.utils.log.Loggers; 8 | 9 | import java.util.Arrays; 10 | import java.util.Objects; 11 | 12 | @ScannerSide 13 | public class LeiningenRunner { 14 | 15 | private static final Logger LOG = Loggers.get(LeiningenRunner.class); 16 | private static final String DELIMITER = "\n"; 17 | private static final int SUCCESS_RETURN_CODE = 0; 18 | 19 | private final Logger logger; 20 | private final CommandExecutor commandExecutor; 21 | 22 | public LeiningenRunner(Logger log, CommandExecutor commandExecutor) { 23 | this.logger = log; 24 | this.commandExecutor = commandExecutor; 25 | } 26 | 27 | public LeiningenRunner() { 28 | this(LOG, CommandExecutor.create()); 29 | } 30 | 31 | CommandStreamConsumer run(CommandStreamConsumer stdout, 32 | CommandStreamConsumer stderr, Long timeOut, String operatingSystem, String... args) { 33 | Command cmd = Command.create(getCommand(operatingSystem)); 34 | Arrays.stream(args).filter(Objects::nonNull).forEach(cmd::addArgument); 35 | 36 | int returnCode = commandExecutor.execute(cmd, stdout, stderr, fromSecondsToMilliseconds(timeOut)); 37 | 38 | logger.debug("plugin: " + cmd.getArguments()); 39 | logger.debug("stdout: " + String.join(DELIMITER, stdout.getData())); 40 | logger.debug("stderr: " + String.join(DELIMITER, stderr.getData())); 41 | 42 | if (SUCCESS_RETURN_CODE != returnCode) { 43 | //TODO A return code different than 0 doesn't mean error for some of the plugins we use. 44 | logger.warn("Command: " + cmd.getArguments() + " returned a non-zero code." + 45 | " Please make sure plugin is working" + 46 | " isolated before running sonar-scanner"); 47 | } 48 | 49 | return stdout; 50 | } 51 | 52 | private String getCommand(String operatingSystem) { 53 | return operatingSystem.toUpperCase().contains("WINDOWS") ? "lein.bat" : "lein"; 54 | } 55 | 56 | private Long fromSecondsToMilliseconds(long seconds) { 57 | return seconds * 1000; 58 | } 59 | 60 | public CommandStreamConsumer run(Long timeOut, String... args) { 61 | return run( 62 | new CommandStreamConsumer(), 63 | new CommandStreamConsumer(), 64 | timeOut, 65 | System.getProperty("os.name"), 66 | args 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/eastwood/EastwoodSensor.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.eastwood; 2 | 3 | 4 | import org.sonar.api.batch.sensor.Sensor; 5 | import org.sonar.api.batch.sensor.SensorContext; 6 | import org.sonar.api.batch.sensor.SensorDescriptor; 7 | import org.sonar.api.utils.log.Logger; 8 | import org.sonar.api.utils.log.Loggers; 9 | import org.sonar.plugins.clojure.language.Clojure; 10 | import org.sonar.plugins.clojure.sensors.AbstractSensor; 11 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 12 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 13 | import org.sonar.plugins.clojure.sensors.Issue; 14 | 15 | import java.util.List; 16 | 17 | import static org.sonar.plugins.clojure.sensors.eastwood.EastwoodProperties.*; 18 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY; 19 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY_DEFAULT; 20 | 21 | public class EastwoodSensor extends AbstractSensor implements Sensor { 22 | 23 | private static final Logger LOG = Loggers.get(EastwoodSensor.class); 24 | 25 | private static final String EASTWOOD_COMMAND = "eastwood"; 26 | private static final String SENSOR_NAME = "Eastwood"; 27 | 28 | @SuppressWarnings("WeakerAccess") 29 | public EastwoodSensor(LeiningenRunner leiningenRunner) { 30 | super(leiningenRunner); 31 | } 32 | 33 | @Override 34 | public void describe(SensorDescriptor descriptor) { 35 | descriptor.name(SENSOR_NAME) 36 | .onlyOnLanguage(Clojure.KEY); 37 | } 38 | 39 | @Override 40 | public void execute(SensorContext context) { 41 | if (isSensorEnabled(context)) { 42 | LOG.info("Running Eastwood"); 43 | CommandStreamConsumer stdOut = this.leiningenRunner.run( 44 | getSensorTimeout(context), EASTWOOD_COMMAND, getSensorOptions(context)); 45 | 46 | List issues = EastwoodIssueParser.parse(stdOut); 47 | 48 | LOG.debug("Saving issues: " + issues.size()); 49 | issues.forEach(issue -> saveIssue(issue, context)); 50 | } else { 51 | LOG.info("Eastwood disabled"); 52 | } 53 | } 54 | private boolean isSensorEnabled(SensorContext context) { 55 | Boolean enabled = context.config().getBoolean(ENABLED_PROPERTY).orElse(ENABLED_PROPERTY_DEFAULT); 56 | LOG.debug(String.format("Property: %s Value: %s", ENABLED_PROPERTY, enabled)); 57 | return enabled; 58 | } 59 | private String getSensorOptions(SensorContext context) { 60 | return context.config().get(EASTWOOD_OPTIONS).orElse(null); 61 | } 62 | private long getSensorTimeout(SensorContext context) { 63 | return context.config().getLong(SENSORS_TIMEOUT_PROPERTY) 64 | .orElse(Long.valueOf(SENSORS_TIMEOUT_PROPERTY_DEFAULT)); 65 | } 66 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/cloverage/CloverageMetricParserTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.cloverage; 2 | 3 | import org.junit.Test; 4 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 5 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 6 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 7 | import org.sonar.plugins.clojure.language.Clojure; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.nio.charset.Charset; 12 | import java.nio.charset.StandardCharsets; 13 | import java.nio.file.Files; 14 | import java.nio.file.Paths; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | import static java.util.Arrays.asList; 19 | import static org.hamcrest.CoreMatchers.is; 20 | import static org.junit.Assert.assertThat; 21 | import static org.junit.Assert.assertTrue; 22 | 23 | public class CloverageMetricParserTest { 24 | 25 | @Test 26 | public void testParse() throws IOException { 27 | SensorContextTester context = SensorContextTester.create(new File("/")); 28 | // Adding file to Sonar Context 29 | File baseDir = new File("src/test/resources/"); 30 | 31 | File fooSource = new File(baseDir, "foo_in_src_clj.clj"); 32 | DefaultInputFile fooFile = TestInputFileBuilder.create("", "src/clj/foo.clj") 33 | .setLanguage(Clojure.KEY) 34 | .initMetadata(new String(Files.readAllBytes(fooSource.toPath()), StandardCharsets.UTF_8)) 35 | .setContents(new String(Files.readAllBytes(fooSource.toPath()), StandardCharsets.UTF_8)) 36 | .build(); 37 | context.fileSystem().add(fooFile); 38 | 39 | File barSource = new File(baseDir, "bar_in_src_cljc.cljc"); 40 | 41 | DefaultInputFile barFile = TestInputFileBuilder.create("", "src/cljc/bar.cljc") 42 | .setLanguage(Clojure.KEY) 43 | .initMetadata(new String(Files.readAllBytes(barSource.toPath()), StandardCharsets.UTF_8)) 44 | .setContents(new String(Files.readAllBytes(barSource.toPath()), StandardCharsets.UTF_8)) 45 | .build(); 46 | 47 | context.fileSystem().add(barFile); 48 | 49 | String json = new String(Files.readAllBytes(Paths.get("src/test/resources/cloverage-result.json")),Charset.forName("UTF-8")); 50 | 51 | CoverageReport c = CloverageMetricParser.parse(context, json); 52 | assertThat(c.filesCount(), is(2)); 53 | FileAnalysis e = c.getFileEntries().get(0); 54 | 55 | List entries = e.getEntries(); 56 | List expected = new ArrayList<>(); 57 | expected.addAll(asList(new LineAnalysis().setLineNumber(1).setHits(1), 58 | 59 | new LineAnalysis().setLineNumber(3).setHits(1), 60 | new LineAnalysis().setLineNumber(5).setHits(0), 61 | new LineAnalysis().setLineNumber(6).setHits(1))); 62 | 63 | entries.stream().forEach(entry -> assertTrue(expected.contains(entry))); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/cloverage/CloverageMetricParser.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.cloverage; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParser; 6 | import org.sonar.api.batch.fs.FilePredicate; 7 | import org.sonar.api.batch.fs.FileSystem; 8 | import org.sonar.api.batch.fs.InputFile; 9 | import org.sonar.api.batch.sensor.SensorContext; 10 | import org.sonar.api.utils.log.Logger; 11 | import org.sonar.api.utils.log.Loggers; 12 | 13 | import java.util.Map; 14 | import java.util.Optional; 15 | 16 | public class CloverageMetricParser { 17 | 18 | private static final Logger LOG = Loggers.get(CloverageMetricParser.class); 19 | 20 | private CloverageMetricParser() { 21 | } 22 | 23 | private static Optional findFileBySources(SensorContext context, String filename) { 24 | FileSystem fs = context.fileSystem(); 25 | FilePredicate pattern = fs.predicates().matchesPathPattern("**/" + filename); 26 | InputFile potentialFile = fs.inputFile(pattern); 27 | 28 | if (potentialFile != null) { 29 | LOG.debug("Found file"); 30 | FileAnalysis foundSource = new FileAnalysis(); 31 | foundSource.setInputFile(potentialFile); 32 | return Optional.of(foundSource); 33 | } 34 | 35 | return Optional.empty(); 36 | } 37 | 38 | public static CoverageReport parse(SensorContext context, String json) { 39 | LOG.debug("Running CoverageReport parser"); 40 | CoverageReport report = new CoverageReport(); 41 | JsonParser parser = new JsonParser(); 42 | JsonElement jsonTree = parser.parse(json); 43 | JsonObject jsonObject = jsonTree.getAsJsonObject(); 44 | JsonObject r = jsonObject.get("coverage").getAsJsonObject(); 45 | 46 | 47 | for (Map.Entry e : r.entrySet()) { 48 | 49 | LOG.debug("Created new FileAnalysis: " + e.getKey()); 50 | 51 | Optional fileAnalysisOptional = findFileBySources(context, e.getKey()); 52 | 53 | if (fileAnalysisOptional.isPresent()) { 54 | FileAnalysis fileAnalysis = fileAnalysisOptional.get(); 55 | // first entry in csv is line number 0 which can be discarded 56 | int lineNumber = 0; 57 | for (JsonElement i : e.getValue().getAsJsonArray()) { 58 | if (lineNumber > 0 && !i.isJsonNull()) { 59 | try { 60 | fileAnalysis.addLine(lineNumber, i.getAsInt()); 61 | } catch (NumberFormatException n) { 62 | fileAnalysis.addLine(lineNumber, 1); 63 | } 64 | } 65 | lineNumber++; 66 | } 67 | report.addFile(fileAnalysis); 68 | } else { 69 | LOG.warn("Namespace: " + e.getKey() + " cannot be found."); 70 | } 71 | } 72 | 73 | return report; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/kibit/KibitSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.kibit; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.mockito.Mock; 6 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 7 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 8 | import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; 9 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 10 | import org.sonar.api.batch.sensor.issue.Issue; 11 | import org.sonar.plugins.clojure.language.Clojure; 12 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 13 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 14 | 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.nio.charset.StandardCharsets; 18 | import java.nio.file.Files; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import static org.hamcrest.CoreMatchers.is; 23 | import static org.hamcrest.MatcherAssert.assertThat; 24 | import static org.mockito.Mockito.when; 25 | import static org.mockito.MockitoAnnotations.initMocks; 26 | 27 | public class KibitSensorTest { 28 | 29 | @Mock 30 | private LeiningenRunner leiningenRunner; 31 | 32 | private KibitSensor kibitSensor; 33 | 34 | @Before 35 | public void setUp() { 36 | initMocks(this); 37 | kibitSensor = new KibitSensor(leiningenRunner); 38 | } 39 | 40 | @Test 41 | public void shouldConfigureSensor() { 42 | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); 43 | kibitSensor.describe(descriptor); 44 | assertThat(descriptor.name(), is("Kibit")); 45 | assertThat(descriptor.languages().contains("clj"), is(true)); 46 | assertThat(descriptor.languages().size(), is(1)); 47 | } 48 | 49 | @Test 50 | public void shouldExecuteKibit() throws IOException { 51 | SensorContextTester context = prepareContext(); 52 | 53 | CommandStreamConsumer stdOut = new CommandStreamConsumer(); 54 | stdOut.consumeLine("----"); 55 | stdOut.consumeLine("At kibit.clj:5:"); 56 | stdOut.consumeLine("Kibit will say that there is pos? function available"); 57 | when(leiningenRunner.run(300L, "kibit")).thenReturn(stdOut); 58 | 59 | kibitSensor.execute(context); 60 | 61 | List issuesList = new ArrayList<>(context.allIssues()); 62 | assertThat(issuesList.size(), is(1)); 63 | assertThat(issuesList.get(0).ruleKey().rule(), is("kibit")); 64 | } 65 | 66 | private SensorContextTester prepareContext() throws IOException { 67 | SensorContextTester context = SensorContextTester.create(new File("src/test/resources/")); 68 | 69 | File baseDir = new File("src/test/resources/"); 70 | File file = new File(baseDir, "kibit.clj"); 71 | DefaultInputFile inputFile = TestInputFileBuilder.create("", "kibit.clj") 72 | .setLanguage(Clojure.KEY) 73 | .initMetadata(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)) 74 | .build(); 75 | 76 | context.fileSystem().add(inputFile); 77 | 78 | return context; 79 | } 80 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting jfelipe.sfilho@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | ## Attribution 65 | 66 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 67 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 68 | 69 | [homepage]: https://www.contributor-covenant.org 70 | 71 | For answers to common questions about this code of conduct, see 72 | https://www.contributor-covenant.org/faq 73 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/cloverage/CloverageSensor.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.cloverage; 2 | 3 | 4 | import org.sonar.api.batch.sensor.Sensor; 5 | import org.sonar.api.batch.sensor.SensorContext; 6 | import org.sonar.api.batch.sensor.SensorDescriptor; 7 | import org.sonar.api.batch.sensor.coverage.NewCoverage; 8 | import org.sonar.api.utils.log.Logger; 9 | import org.sonar.api.utils.log.Loggers; 10 | import org.sonar.plugins.clojure.language.Clojure; 11 | import org.sonar.plugins.clojure.sensors.AbstractSensor; 12 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 13 | 14 | import java.util.Optional; 15 | 16 | import static org.sonar.plugins.clojure.sensors.cloverage.CloverageMetricParser.parse; 17 | import static org.sonar.plugins.clojure.settings.CloverageProperties.*; 18 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY; 19 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY_DEFAULT; 20 | 21 | public class CloverageSensor extends AbstractSensor implements Sensor { 22 | 23 | private static final Logger LOG = Loggers.get(CloverageSensor.class); 24 | private static final String PLUGIN_NAME = "Cloverage"; 25 | private static final String[] LEIN_ARGUMENTS = {"cloverage", "--codecov"}; 26 | 27 | @SuppressWarnings("WeakerAccess") 28 | public CloverageSensor(LeiningenRunner leiningenRunner) { 29 | super(leiningenRunner); 30 | } 31 | 32 | @Override 33 | public void describe(SensorDescriptor descriptor) { 34 | descriptor.name(PLUGIN_NAME) 35 | .onlyOnLanguage(Clojure.KEY); 36 | } 37 | 38 | @Override 39 | public void execute(SensorContext context) { 40 | if (isPluginEnabled(context, PLUGIN_NAME, ENABLED_PROPERTY, ENABLED_PROPERTY_DEFAULT)) { 41 | LOG.info("Running " + PLUGIN_NAME); 42 | 43 | long timeOut = context.config().getLong(SENSORS_TIMEOUT_PROPERTY) 44 | .orElse(Long.valueOf(SENSORS_TIMEOUT_PROPERTY_DEFAULT)); 45 | 46 | this.leiningenRunner.run(timeOut, LEIN_ARGUMENTS[0], LEIN_ARGUMENTS[1]); 47 | 48 | String reportPath = context.config().get(REPORT_LOCATION_PROPERTY).orElse(REPORT_LOCATION_DEFAULT); 49 | LOG.debug("Using report file: " + reportPath); 50 | Optional fileString = readFromFileSystem(reportPath); 51 | 52 | if (fileString.isPresent()) { 53 | try { 54 | CoverageReport report = parse(context, fileString.get()); 55 | saveCoverageForFile(report, context); 56 | } catch (Exception e) { //TODO this exception is too generic 57 | LOG.warn("Error while saving coverage", e); 58 | } 59 | } else { 60 | LOG.warn("Cloverage report does not exist in the given path: " + reportPath); 61 | } 62 | } 63 | } 64 | 65 | private void saveCoverageForFile(CoverageReport report, SensorContext context) { 66 | report.getFileEntries().forEach(fileAnalysis -> { 67 | NewCoverage coverage = context.newCoverage().onFile(fileAnalysis.getFile()); 68 | fileAnalysis.getEntries().forEach(lineAnalysis -> 69 | coverage.lineHits(lineAnalysis.getLineNumber(), lineAnalysis.getHits())); 70 | coverage.save(); 71 | }); 72 | } 73 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.sonar.plugins.clojure 8 | sonar-clojure-plugin 9 | 2.2.0-SNAPSHOT 10 | sonar-plugin 11 | sonar-clojure 12 | SonarClojure is a plugin for SonarQube to analyze Clojure source. 13 | https://github.com/fsantiag/sonar-clojure 14 | 15 | 16 | 17 | org.sonarsource.sonarqube 18 | sonar-plugin-api 19 | 7.9.3 20 | provided 21 | 22 | 23 | com.google.code.gson 24 | gson 25 | 2.8.4 26 | 27 | 28 | 29 | 30 | org.assertj 31 | assertj-core 32 | 3.11.1 33 | test 34 | 35 | 36 | junit 37 | junit 38 | 4.13.1 39 | test 40 | 41 | 42 | org.mockito 43 | mockito-core 44 | 3.3.3 45 | test 46 | 47 | 48 | us.bpsm 49 | edn-java 50 | 0.6.0 51 | 52 | 53 | 54 | 55 | UTF-8 56 | 57 | 58 | 59 | 60 | 61 | org.sonarsource.sonar-packaging-maven-plugin 62 | sonar-packaging-maven-plugin 63 | 1.18.0.372 64 | true 65 | 66 | clojure 67 | org.sonar.plugins.clojure.ClojurePlugin 68 | SonarClojure 69 | 7.9 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-compiler-plugin 75 | 3.7.0 76 | 77 | 1.8 78 | 1.8 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | sonatype 87 | Sonatype 88 | https://oss.sonatype.org/content/repositories/releases 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/AbstractSensor.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors; 2 | 3 | import org.sonar.api.batch.fs.FileSystem; 4 | import org.sonar.api.batch.fs.InputFile; 5 | import org.sonar.api.batch.sensor.SensorContext; 6 | import org.sonar.api.batch.sensor.issue.NewIssue; 7 | import org.sonar.api.batch.sensor.issue.NewIssueLocation; 8 | import org.sonar.api.rule.RuleKey; 9 | import org.sonar.api.utils.log.Logger; 10 | import org.sonar.api.utils.log.Loggers; 11 | import org.sonar.plugins.clojure.rules.ClojureLintRulesDefinition; 12 | 13 | import java.io.IOException; 14 | import java.nio.file.Files; 15 | import java.nio.file.Paths; 16 | import java.util.Optional; 17 | 18 | import static java.nio.charset.StandardCharsets.UTF_8; 19 | 20 | public abstract class AbstractSensor { 21 | 22 | private static final Logger LOG = Loggers.get(AbstractSensor.class); 23 | 24 | protected static final String LEIN_COMMAND = "lein"; 25 | 26 | protected LeiningenRunner leiningenRunner; 27 | 28 | public AbstractSensor(LeiningenRunner leiningenRunner) { 29 | this.leiningenRunner = leiningenRunner; 30 | } 31 | 32 | protected boolean isPluginEnabled(SensorContext context, String pluginName, String propertyName, boolean defaultValue) { 33 | Boolean pluginEnabled = context.config().getBoolean(propertyName).orElse(defaultValue); 34 | LOG.debug(String.format("Property: %s Value: %s", propertyName, pluginEnabled)); 35 | if (!pluginEnabled) { 36 | LOG.info(pluginName + " disabled"); 37 | } 38 | return pluginEnabled; 39 | } 40 | 41 | protected Optional getFile(String filePath, FileSystem fileSystem) { 42 | return Optional.ofNullable(fileSystem.inputFile( 43 | fileSystem.predicates().and( 44 | fileSystem.predicates().hasRelativePath(filePath), 45 | fileSystem.predicates().hasType(InputFile.Type.MAIN)))); 46 | } 47 | 48 | 49 | protected void saveIssue(Issue issue, SensorContext context) { 50 | try { 51 | Optional fileOptional = getFile(issue.getFilePath(), context.fileSystem()); 52 | 53 | if (fileOptional.isPresent()) { 54 | InputFile file = fileOptional.get(); 55 | RuleKey ruleKey = RuleKey.of(ClojureLintRulesDefinition.REPOSITORY_KEY, issue.getExternalRuleId().trim()); 56 | 57 | NewIssue newIssue = context.newIssue().forRule(ruleKey); 58 | 59 | NewIssueLocation primaryLocation = newIssue 60 | .newLocation() 61 | .on(file) 62 | .message(issue.getDescription().trim()); 63 | 64 | primaryLocation.at(file.selectLine(issue.getLine())); 65 | 66 | newIssue.at(primaryLocation); 67 | newIssue.save(); 68 | } else { 69 | LOG.warn("Not able to find a file with path '{}'", issue.getFilePath()); 70 | } 71 | } catch (Exception e) { 72 | LOG.error("Can not save the issue due to: " + e.getMessage()); 73 | } 74 | } 75 | 76 | /** 77 | * Gets the file directly from filesystem. This is useful when the file is needed to be read which is not wanted to 78 | * be part of SonarQube scanning 79 | */ 80 | public Optional readFromFileSystem(String filename){ 81 | try { 82 | return Optional.of(new String(Files.readAllBytes(Paths.get(filename)), UTF_8)); 83 | } catch (IOException e) { 84 | return Optional.empty(); 85 | } 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/leinnvd/LeinNvdSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.leinnvd; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.mockito.Mock; 6 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 7 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 8 | import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; 9 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 10 | import org.sonar.api.batch.sensor.issue.Issue; 11 | import org.sonar.plugins.clojure.language.Clojure; 12 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 13 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 14 | import org.sonar.plugins.clojure.settings.NvdProperties; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.nio.charset.StandardCharsets; 19 | import java.nio.file.Files; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import static org.hamcrest.CoreMatchers.is; 24 | import static org.hamcrest.MatcherAssert.assertThat; 25 | import static org.junit.Assert.assertTrue; 26 | import static org.mockito.MockitoAnnotations.initMocks; 27 | 28 | public class LeinNvdSensorTest { 29 | @Mock 30 | private LeiningenRunner leiningenRunner; 31 | 32 | private LeinNvdSensor leinNvdSensor; 33 | 34 | @Before 35 | public void setUp() { 36 | initMocks(this); 37 | leinNvdSensor = new LeinNvdSensor(leiningenRunner); 38 | } 39 | 40 | @Test 41 | public void shouldConfigureSensor() { 42 | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); 43 | leinNvdSensor.describe(descriptor); 44 | assertThat(descriptor.name(), is("NVD")); 45 | assertTrue(descriptor.languages().contains("clj")); 46 | assertThat(descriptor.languages().size(), is(1)); 47 | } 48 | 49 | @Test 50 | public void shouldExecuteLeinNvd() throws IOException { 51 | SensorContextTester context = prepareContext(); 52 | 53 | CommandStreamConsumer stdOut = new CommandStreamConsumer(); 54 | stdOut.consumeLine("We are not really interested of std out"); 55 | 56 | leinNvdSensor.execute(context); 57 | 58 | List issuesList = new ArrayList<>(context.allIssues()); 59 | assertThat(issuesList.size(), is(2)); 60 | assertThat(issuesList.get(0).ruleKey().rule(), is("nvd-high")); 61 | assertThat(issuesList.get(0).primaryLocation().message(), 62 | is("CVE-2018-5968;CWE-502,CWE-184;jackson-databind-2.9.3.jar")); 63 | assertThat(issuesList.get(1).ruleKey().rule(), is("nvd-critical")); 64 | assertThat(issuesList.get(1).primaryLocation().message(), 65 | is("CVE-2018-19362;CWE-502;jackson-databind-2.9.3.jar")); 66 | } 67 | 68 | private SensorContextTester prepareContext() throws IOException { 69 | SensorContextTester context = SensorContextTester.create(new File("/")); 70 | 71 | context.settings().appendProperty(NvdProperties.REPORT_LOCATION_PROPERTY, "src/test/resources/nvd-report.json"); 72 | File baseDir = new File("src/test/resources/"); 73 | File project = new File(baseDir, "project.clj"); 74 | 75 | DefaultInputFile projectFile = TestInputFileBuilder.create("", "project.clj") 76 | .setLanguage(Clojure.KEY) 77 | .initMetadata(new String(Files.readAllBytes(project.toPath()), StandardCharsets.UTF_8)) 78 | .setContents(new String(Files.readAllBytes(project.toPath()), StandardCharsets.UTF_8)) 79 | .build(); 80 | context.fileSystem().add(projectFile); 81 | 82 | return context; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/eastwood/EastwoodSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.eastwood; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.MockitoJUnitRunner; 8 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 9 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 10 | import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; 11 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 12 | import org.sonar.api.batch.sensor.issue.Issue; 13 | import org.sonar.plugins.clojure.language.Clojure; 14 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 15 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.nio.charset.StandardCharsets; 20 | import java.nio.file.Files; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | import static org.hamcrest.CoreMatchers.is; 25 | import static org.junit.Assert.assertThat; 26 | import static org.junit.Assert.assertTrue; 27 | import static org.mockito.Mockito.when; 28 | import static org.sonar.plugins.clojure.sensors.eastwood.EastwoodProperties.EASTWOOD_OPTIONS; 29 | 30 | @RunWith(MockitoJUnitRunner.class) 31 | public class EastwoodSensorTest { 32 | 33 | @Mock 34 | private LeiningenRunner leiningenRunner; 35 | 36 | private EastwoodSensor eastwoodSensor; 37 | 38 | @Before 39 | public void setUp() { 40 | eastwoodSensor = new EastwoodSensor(leiningenRunner); 41 | } 42 | 43 | @Test 44 | public void shouldConfigureSensor() { 45 | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); 46 | eastwoodSensor.describe(descriptor); 47 | assertThat(descriptor.name(), is("Eastwood")); 48 | assertTrue(descriptor.languages().contains("clj")); 49 | assertThat(descriptor.languages().size(), is(1)); 50 | } 51 | 52 | @Test 53 | public void shouldExecuteEastwood() throws IOException { 54 | SensorContextTester context = prepareContext(); 55 | 56 | CommandStreamConsumer stdOut = new CommandStreamConsumer(); 57 | stdOut.consumeLine("file.clj:1:0:issue-1:description-1"); 58 | stdOut.consumeLine("file.clj:2:0:issue-2:description-2"); 59 | String options = "eastwood-option"; 60 | when(leiningenRunner.run(300L, "eastwood", options)).thenReturn(stdOut); 61 | 62 | eastwoodSensor.execute(context); 63 | 64 | List issuesList = new ArrayList<>(context.allIssues()); 65 | assertThat(issuesList.size(), is(2)); 66 | assertThat(issuesList.get(0).ruleKey().rule(), is("eastwood")); 67 | assertThat(issuesList.get(0).primaryLocation().message(), is("description-1")); 68 | assertThat(issuesList.get(1).ruleKey().rule(), is("eastwood")); 69 | assertThat(issuesList.get(1).primaryLocation().message(), is("description-2")); 70 | } 71 | 72 | private SensorContextTester prepareContext() throws IOException { 73 | SensorContextTester context = SensorContextTester.create(new File("src/test/resources/")); 74 | 75 | context.settings().appendProperty(EASTWOOD_OPTIONS, "eastwood-option"); 76 | 77 | File baseDir = new File("src/test/resources/"); 78 | File file = new File(baseDir, "file.clj"); 79 | DefaultInputFile inputFile = TestInputFileBuilder.create("", "file.clj") 80 | .setLanguage(Clojure.KEY) 81 | .initMetadata(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)) 82 | .build(); 83 | context.fileSystem().add(inputFile); 84 | 85 | return context; 86 | } 87 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to SonarClojure 2 | 3 | Thanks for taking the time to contribute! 4 | 5 | The following is a set of guidelines for contributing to SonarClojure. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | #### Table of Contents 8 | 9 | - [Code of Conduct](#code-of-conduct) 10 | - [I don't want to read this whole thing I just have a question!!!](#i-don-t-want-to-read-this-whole-thing-i-just-have-a-question) 11 | - [How Can I Contribute?](#how-can-i-contribute) 12 | - [Requesting Features or Reporting Bugs](#requesting-features-or-reporting-bugs) 13 | - [Code Contributions](#code-contributions) 14 | - [What should I know before I get started with the code contributions?](#what-should-i-know-before-i-get-started-with-the-code-contributions) 15 | - [SonarQube API](#sonarqube-api) 16 | - [Running the plugin locally](#running-the-plugin-locally) 17 | - [Styleguides](#styleguides) 18 | - [Git Commit Messages](#git-commit-messages) 19 | - [Pull Requests](#pull-requests) 20 | 21 | ## Code of Conduct 22 | 23 | By partipating on this project, you are expected to uphold the following [CODE OF CONDUCT](CODE_OF_CONDUCT.md). 24 | 25 | ## I don't want to read this whole thing I just have a question!!! 26 | If you have a question, feel free to open a Github issue. Please make sure you follow the github templates provided. 27 | 28 | ## How Can I Contribute? 29 | ### Requesting Features or Reporting Bugs 30 | You can contribute to SonarClojure by submitting a feature request or reporting a bug. In both cases, please submit an 31 | issue using the appropriate template. Follow the guidelines provided in the template. 32 | 33 | ### Code Contributions 34 | Have a look in the [issues](https://github.com/fsantiag/sonar-clojure/issues) we have opened. If it is 35 | your first time, the ones with the tag `good first issue` might be a good idea. If you want to explore something more 36 | challenging, feel free to have a look into the other tags: `feature`, `help wanted`, `bug`. In any case, make sure to reply 37 | in the issue so we know you are working on it. 38 | 39 | #### What should I know before I get started with the code contributions? 40 | ##### SonarQube API 41 | SonarQube has an API for developing plugins. If you are not familar with the API, please have a look into their documentation [here](https://docs.sonarqube.org/display/DEV/Developing+a+Plugin). That will guide you through the basics. 42 | 43 | ##### Running the plugin locally 44 | 45 | This is how you can run SonarQube with SonarClojure: 46 | 47 | ```sh 48 | ./mvnw clean package 49 | ./start-sonarqube.sh # This script requires docker 50 | ``` 51 | 52 | SonarQube will be running on `http://localhost:9000`. 53 | 54 | ##### Styleguides 55 | We try to follow Jetbrains Java style guide. Please refer to their [website](https://www.jetbrains.com/help/idea/code-style-java.html) 56 | 57 | ##### Git Commit Messages 58 | * Use the present tense ("Add feature" not "Added feature") 59 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 60 | * Limit the first line to 72 characters or less 61 | * Reference issues and pull requests liberally after the first line 62 | * When only changing documentation, include `[ci skip]` in the commit title 63 | 64 | 65 | #### Pull Requests 66 | Before you open a PR, please make sure you squash the commits that should be squashed. What we mean is to squash commits 67 | that describe in progress work or minor fixes. If you have major milestones during development, it is ok to have 68 | multiple commits for them. Just make sure the build is working fine for every commit. The idea behind this decision is 69 | to keep the history clean while maintaining stable builds for each one of them. 70 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/leinnvd/LeinNvdSensor.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.leinnvd; 2 | 3 | import org.sonar.api.batch.fs.InputFile; 4 | import org.sonar.api.batch.sensor.Sensor; 5 | import org.sonar.api.batch.sensor.SensorContext; 6 | import org.sonar.api.batch.sensor.SensorDescriptor; 7 | import org.sonar.api.batch.sensor.issue.NewIssue; 8 | import org.sonar.api.batch.sensor.issue.NewIssueLocation; 9 | import org.sonar.api.rule.RuleKey; 10 | import org.sonar.api.utils.log.Logger; 11 | import org.sonar.api.utils.log.Loggers; 12 | import org.sonar.plugins.clojure.language.Clojure; 13 | import org.sonar.plugins.clojure.rules.ClojureLintRulesDefinition; 14 | import org.sonar.plugins.clojure.sensors.AbstractSensor; 15 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 16 | import org.sonar.plugins.clojure.settings.NvdProperties; 17 | 18 | import java.util.List; 19 | import java.util.Optional; 20 | 21 | import static org.sonar.plugins.clojure.settings.NvdProperties.*; 22 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY; 23 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY_DEFAULT; 24 | 25 | public class LeinNvdSensor extends AbstractSensor implements Sensor { 26 | 27 | private static final Logger LOG = Loggers.get(LeinNvdSensor.class); 28 | 29 | private static final String[] LEIN_ARGUMENTS = {"nvd", "check"}; 30 | private static final String PLUGIN_NAME = "NVD"; 31 | 32 | @SuppressWarnings("WeakerAccess") 33 | public LeinNvdSensor(LeiningenRunner leiningenRunner) { 34 | super(leiningenRunner); 35 | } 36 | 37 | @Override 38 | public void describe(SensorDescriptor descriptor) { 39 | descriptor.name(PLUGIN_NAME) 40 | .onlyOnLanguage(Clojure.KEY); 41 | } 42 | 43 | @Override 44 | public void execute(SensorContext context) { 45 | 46 | if (isPluginEnabled(context, PLUGIN_NAME, ENABLED_PROPERTY, ENABLED_PROPERTY_DEFAULT)) { 47 | LOG.info("Running Lein NVD"); 48 | String reportPath = context.config().get(NvdProperties.REPORT_LOCATION_PROPERTY).orElse(REPORT_LOCATION_DEFAULT); 49 | 50 | long timeOut = context.config().getLong(SENSORS_TIMEOUT_PROPERTY) 51 | .orElse(Long.valueOf(SENSORS_TIMEOUT_PROPERTY_DEFAULT)); 52 | this.leiningenRunner.run(timeOut, LEIN_ARGUMENTS[0], LEIN_ARGUMENTS[1]); 53 | 54 | Optional vulnerabilityContext = readFromFileSystem(reportPath); 55 | if (vulnerabilityContext.isPresent()) { 56 | List vulnerabilities = LeinNvdParser.parseJson(vulnerabilityContext.get()); 57 | saveVulnerabilities(vulnerabilities, context); 58 | } else { 59 | LOG.warn("Lein NVD dependency report does not exists. Is Lein NVD installed as a plugin?"); 60 | } 61 | } 62 | } 63 | 64 | private void saveVulnerabilities(List vulnerabilities, SensorContext context) { 65 | Optional fileOptional = getFile("project.clj", context.fileSystem()); 66 | 67 | fileOptional.ifPresent(projectFile -> { 68 | for (Vulnerability v : vulnerabilities) { 69 | LOG.debug("Processing vulnerability: " +v.toString()); 70 | RuleKey ruleKey = RuleKey.of(ClojureLintRulesDefinition.REPOSITORY_KEY, "nvd-" + v.getSeverity().toLowerCase()); 71 | NewIssue newIssue = context.newIssue().forRule(ruleKey); 72 | NewIssueLocation primaryLocation = newIssue 73 | .newLocation() 74 | .on(projectFile) 75 | .message(v.getName() 76 | + ";" + v.getCwes() 77 | + ";" + v.getFileName()) 78 | .at(projectFile.selectLine(1)); 79 | newIssue.at(primaryLocation); 80 | newIssue.save(); 81 | } 82 | }); 83 | } 84 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/kondo/KondoSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.kondo; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.MockitoJUnitRunner; 8 | import org.sonar.api.batch.fs.TextRange; 9 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 10 | import org.sonar.api.batch.fs.internal.DefaultTextPointer; 11 | import org.sonar.api.batch.fs.internal.DefaultTextRange; 12 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 13 | import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; 14 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 15 | import org.sonar.api.batch.sensor.issue.Issue; 16 | import org.sonar.plugins.clojure.language.Clojure; 17 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 18 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.nio.charset.StandardCharsets; 23 | import java.nio.file.Files; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | import static org.hamcrest.CoreMatchers.is; 28 | import static org.hamcrest.MatcherAssert.assertThat; 29 | import static org.mockito.Mockito.when; 30 | import static org.sonar.plugins.clojure.settings.KondoProperties.*; 31 | 32 | @RunWith(MockitoJUnitRunner.class) 33 | public class KondoSensorTest { 34 | 35 | @Mock 36 | private LeiningenRunner commandRunner; 37 | 38 | private KondoSensor kondoSensor; 39 | 40 | @Before 41 | public void setUp() { 42 | kondoSensor = new KondoSensor(commandRunner); 43 | } 44 | 45 | @Test 46 | public void shouldConfigureSensor() { 47 | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); 48 | kondoSensor.describe(descriptor); 49 | assertThat(descriptor.name(), is("clj-kondo")); 50 | assertThat(descriptor.languages().contains("clj"), is(true)); 51 | assertThat(descriptor.languages().size(), is(1)); 52 | } 53 | 54 | @Test 55 | public void shouldExecuteKondo() throws IOException { 56 | SensorContextTester context = prepareContext(); 57 | 58 | CommandStreamConsumer stdOut = new CommandStreamConsumer(); 59 | stdOut.consumeLine("{:findings [{:type :unused-binding, :filename \"file.clj\", " + 60 | ":message \"unused binding args\", :row 1, :col 16, :end-row 1, :end-col 20, :level :warning}], " + 61 | ":summary {:error 0, :warning 1, :info 0, :type :summary, :duration 11, :files 1}}\n"); 62 | when(commandRunner.run(300L, "run", "-m", "clj-kondo.main", "--lint", "src", "--config", "{:output {:format :edn}}")) 63 | .thenReturn(stdOut); 64 | 65 | kondoSensor.execute(context); 66 | 67 | TextRange expectedRange = new DefaultTextRange(new DefaultTextPointer(1, 15), new DefaultTextPointer(1, 19)); 68 | 69 | List issuesList = new ArrayList<>(context.allIssues()); 70 | assertThat(issuesList.size(), is(1)); 71 | assertThat(issuesList.get(0).ruleKey().rule(), is("kondo")); 72 | assertThat(issuesList.get(0).primaryLocation().message(), is("unused binding args")); 73 | assertThat(issuesList.get(0).primaryLocation().textRange(), is(expectedRange)); 74 | } 75 | 76 | private SensorContextTester prepareContext() throws IOException { 77 | SensorContextTester context = SensorContextTester.create(new File("src/test/resources/")); 78 | 79 | context.settings().appendProperty(OPTIONS, "--lint src"); 80 | context.settings().appendProperty(CONFIG, "{:output {:format :edn}}"); 81 | context.settings().appendProperty(ENABLED_PROPERTY, "true"); 82 | 83 | File baseDir = new File("src/test/resources/"); 84 | File file = new File(baseDir, "file.clj"); 85 | DefaultInputFile inputFile = TestInputFileBuilder.create("", "file.clj") 86 | .setLanguage(Clojure.KEY) 87 | .initMetadata(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)) 88 | .build(); 89 | context.fileSystem().add(inputFile); 90 | 91 | return context; 92 | } 93 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/ancient/AncientSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.ancient; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.mockito.Mock; 6 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 7 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 8 | import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; 9 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 10 | import org.sonar.api.batch.sensor.issue.Issue; 11 | import org.sonar.plugins.clojure.language.Clojure; 12 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 13 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 14 | import org.sonar.plugins.clojure.settings.AncientProperties; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.nio.charset.StandardCharsets; 19 | import java.nio.file.Files; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import static org.hamcrest.CoreMatchers.is; 24 | import static org.hamcrest.MatcherAssert.assertThat; 25 | import static org.mockito.ArgumentMatchers.any; 26 | import static org.mockito.ArgumentMatchers.eq; 27 | import static org.mockito.Mockito.when; 28 | import static org.mockito.MockitoAnnotations.initMocks; 29 | import static org.sonar.plugins.clojure.settings.KondoProperties.ENABLED_PROPERTY; 30 | 31 | public class AncientSensorTest { 32 | @Mock 33 | private LeiningenRunner leiningenRunner; 34 | 35 | private AncientSensor ancientSensor; 36 | 37 | @Before 38 | public void setUp() { 39 | initMocks(this); 40 | ancientSensor = new AncientSensor(leiningenRunner); 41 | } 42 | 43 | @Test 44 | public void shouldConfigureSensor() { 45 | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); 46 | ancientSensor.describe(descriptor); 47 | assertThat(descriptor.name(), is("Ancient")); 48 | assertThat(descriptor.languages().contains("clj"), is(true)); 49 | assertThat(descriptor.languages().size(), is(1)); 50 | } 51 | 52 | @Test 53 | public void shouldExecuteAncient() throws IOException { 54 | SensorContextTester context = prepareContext(); 55 | 56 | CommandStreamConsumer stdOut = new CommandStreamConsumer(); 57 | stdOut.consumeLine("This is some non related line which should not end to report"); 58 | stdOut.consumeLine("[metosin/reitit \"0.2.10\"] is available but we use \"0.2.1\""); 59 | stdOut.consumeLine("[metosin/ring-http-response \"0.9.1\"] is available but we use \"0.9.0\""); 60 | when(leiningenRunner.run(any(), eq("ancient"))).thenReturn(stdOut); 61 | 62 | ancientSensor.execute(context); 63 | 64 | List issuesList = new ArrayList<>(context.allIssues()); 65 | 66 | assertThat(issuesList.size(), is(2)); 67 | assertThat(issuesList.get(0).ruleKey().rule(), is("ancient-clj-dependency")); 68 | assertThat(issuesList.get(0).primaryLocation().message(), 69 | is("metosin/reitit is using version: 0.2.1 but version: 0.2.10 is available.")); 70 | 71 | assertThat(issuesList.get(1).ruleKey().rule(), is("ancient-clj-dependency")); 72 | assertThat(issuesList.get(1).primaryLocation().message(), 73 | is("metosin/ring-http-response is using version: 0.9.0 but version: 0.9.1 is available.")); 74 | } 75 | 76 | private SensorContextTester prepareContext() throws IOException { 77 | SensorContextTester context = SensorContextTester.create(new File("/")); 78 | 79 | context.settings().appendProperty(AncientProperties.ENABLED_PROPERTY, "true"); 80 | 81 | File baseDir = new File("src/test/resources/"); 82 | File file = new File(baseDir, "project.clj"); 83 | 84 | DefaultInputFile inputFile = TestInputFileBuilder.create("moduleKey", "project.clj") 85 | .setLanguage(Clojure.KEY) 86 | .initMetadata(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)) 87 | .setContents(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)) 88 | .build(); 89 | context.fileSystem().add(inputFile); 90 | 91 | return context; 92 | } 93 | } -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/ancient/AncientSensor.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.ancient; 2 | 3 | 4 | import org.sonar.api.batch.fs.InputFile; 5 | import org.sonar.api.batch.sensor.Sensor; 6 | import org.sonar.api.batch.sensor.SensorContext; 7 | import org.sonar.api.batch.sensor.SensorDescriptor; 8 | import org.sonar.api.batch.sensor.issue.NewIssue; 9 | import org.sonar.api.batch.sensor.issue.NewIssueLocation; 10 | import org.sonar.api.rule.RuleKey; 11 | import org.sonar.api.utils.log.Logger; 12 | import org.sonar.api.utils.log.Loggers; 13 | import org.sonar.plugins.clojure.language.Clojure; 14 | import org.sonar.plugins.clojure.leiningen.ProjectFile; 15 | import org.sonar.plugins.clojure.rules.ClojureLintRulesDefinition; 16 | import org.sonar.plugins.clojure.sensors.AbstractSensor; 17 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 18 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 19 | 20 | import java.io.IOException; 21 | import java.util.List; 22 | import java.util.Optional; 23 | 24 | import static org.sonar.plugins.clojure.sensors.ancient.AncientOutputParser.parse; 25 | import static org.sonar.plugins.clojure.settings.AncientProperties.ENABLED_PROPERTY; 26 | import static org.sonar.plugins.clojure.settings.AncientProperties.ENABLED_PROPERTY_DEFAULT; 27 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY; 28 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY_DEFAULT; 29 | 30 | public class AncientSensor extends AbstractSensor implements Sensor { 31 | 32 | private static final Logger LOG = Loggers.get(AncientSensor.class); 33 | 34 | private static final String LEIN_ARGUMENTS = "ancient"; 35 | private static final String PLUGIN_NAME = "Ancient"; 36 | 37 | @SuppressWarnings("WeakerAccess") 38 | public AncientSensor(LeiningenRunner leiningenRunner) { 39 | super(leiningenRunner); 40 | } 41 | 42 | @Override 43 | public void describe(SensorDescriptor descriptor) { 44 | descriptor.name(PLUGIN_NAME) 45 | .onlyOnLanguage(Clojure.KEY); 46 | } 47 | 48 | @Override 49 | public void execute(SensorContext context) { 50 | if (isPluginEnabled(context, PLUGIN_NAME, ENABLED_PROPERTY, ENABLED_PROPERTY_DEFAULT)) { 51 | LOG.info("Running Lein Ancient"); 52 | 53 | long timeOut = context.config().getLong(SENSORS_TIMEOUT_PROPERTY) 54 | .orElse(Long.valueOf(SENSORS_TIMEOUT_PROPERTY_DEFAULT)); 55 | 56 | CommandStreamConsumer stdOut = this.leiningenRunner.run(timeOut, LEIN_ARGUMENTS); 57 | 58 | List outdatedDependencies = parse(stdOut.getData()); 59 | LOG.debug("Parsed " + outdatedDependencies.size() + " dependencies"); 60 | saveOutdated(outdatedDependencies, context); 61 | } 62 | } 63 | 64 | private void saveOutdated(List outdatedDependencies, SensorContext context) { 65 | 66 | Optional fileOptional = getFile("project.clj", context.fileSystem()); 67 | 68 | fileOptional.ifPresent(projectFile -> outdatedDependencies.forEach(outdatedDependency -> { 69 | ProjectFile pr; 70 | try { 71 | pr = new ProjectFile(projectFile.contents()); 72 | } catch (IOException e) { 73 | LOG.warn("project.clj could not be read"); 74 | return; 75 | } 76 | LOG.debug("Processing outdated dependencies"); 77 | 78 | RuleKey ruleKey = RuleKey.of(ClojureLintRulesDefinition.REPOSITORY_KEY, "ancient-clj-dependency"); 79 | NewIssue newIssue = context.newIssue().forRule(ruleKey); 80 | int lineNumber = pr.findLineNumber( 81 | outdatedDependency.getName() + " \"" + outdatedDependency.getCurrentVersion() + "\""); 82 | 83 | NewIssueLocation primaryLocation = newIssue 84 | .newLocation() 85 | .on(projectFile) 86 | .message(outdatedDependency.toString()) 87 | .at(projectFile.selectLine(lineNumber)); 88 | newIssue.at(primaryLocation); 89 | newIssue.save(); 90 | })); 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/LeiningenRunnerTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.ArgumentCaptor; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.MockitoJUnitRunner; 9 | import org.sonar.api.utils.command.Command; 10 | import org.sonar.api.utils.command.CommandExecutor; 11 | import org.sonar.api.utils.log.Logger; 12 | 13 | import static org.hamcrest.CoreMatchers.is; 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.mockito.ArgumentMatchers.any; 16 | import static org.mockito.Mockito.*; 17 | 18 | @RunWith(MockitoJUnitRunner.class) 19 | public class LeiningenRunnerTest { 20 | 21 | @Mock 22 | private Logger logger; 23 | 24 | @Mock 25 | private CommandExecutor commandExecutor; 26 | 27 | private LeiningenRunner leiningenRunner; 28 | 29 | @Before 30 | public void setUp() { 31 | leiningenRunner = new LeiningenRunner(logger, commandExecutor); 32 | } 33 | 34 | @Test 35 | public void shouldTakeMultipleArgumentsForLeinPlugin() { 36 | leiningenRunner.run(300L, "eastwood", "argument1", "argument2"); 37 | 38 | ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(Command.class); 39 | verify(commandExecutor, times(1)) 40 | .execute( 41 | commandCaptor.capture(), 42 | any(CommandStreamConsumer.class), 43 | any(CommandStreamConsumer.class), 44 | anyLong()); 45 | 46 | Command cmd = commandCaptor.getValue(); 47 | assertThat(cmd.getExecutable(), is("lein")); 48 | assertThat(cmd.getArguments().size(), is(3)); 49 | assertThat(cmd.getArguments().get(0), is("eastwood")); 50 | assertThat(cmd.getArguments().get(1), is("argument1")); 51 | assertThat(cmd.getArguments().get(2), is("argument2")); 52 | } 53 | 54 | @Test 55 | public void shouldLogStdoutStderrAndCommandInDebugMode() { 56 | CommandStreamConsumer stdout = new CommandStreamConsumer(); 57 | stdout.consumeLine("line in stdout"); 58 | CommandStreamConsumer stderr = new CommandStreamConsumer(); 59 | stderr.consumeLine("line in stderr"); 60 | 61 | leiningenRunner.run( 62 | stdout, 63 | stderr, 64 | 300L, 65 | "Linux", 66 | "eastwood", 67 | "argument1", 68 | "argument2" 69 | ); 70 | 71 | verify(logger, times(1)).debug("plugin: [eastwood, argument1, argument2]"); 72 | verify(logger, times(1)).debug("stdout: line in stdout"); 73 | verify(logger, times(1)).debug("stderr: line in stderr"); 74 | } 75 | 76 | @Test 77 | public void shouldLogErrorForReturnCodeDifferentThanZero() { 78 | when(commandExecutor.execute(any(),any(),any(),anyLong())).thenReturn(1); 79 | CommandStreamConsumer dummyStreamConsumer = new CommandStreamConsumer(); 80 | 81 | leiningenRunner.run( 82 | dummyStreamConsumer, 83 | dummyStreamConsumer, 84 | 300L, 85 | "Linux", 86 | "eastwood", 87 | "argument1", 88 | "argument2" 89 | ); 90 | verify(logger, times(1)).warn( 91 | "Command: [eastwood, argument1, argument2] returned a non-zero " + 92 | "code. Please make sure plugin is working isolated before running sonar-scanner"); 93 | } 94 | 95 | @Test 96 | public void shouldUseBatFileWhenOperatingSystemIsWindows() { 97 | CommandStreamConsumer stdout = new CommandStreamConsumer(); 98 | CommandStreamConsumer stderr = new CommandStreamConsumer(); 99 | when(commandExecutor.execute(any(),any(),any(),anyLong())).thenReturn(0); 100 | 101 | leiningenRunner.run(stdout, stderr, 300L, "windows", "eastwood"); 102 | 103 | ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(Command.class); 104 | verify(commandExecutor).execute(commandCaptor.capture(), any(CommandStreamConsumer.class), any(CommandStreamConsumer.class), anyLong()); 105 | Command cmd = commandCaptor.getValue(); 106 | assertThat(cmd.getExecutable(), is("lein.bat")); 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /src/main/resources/clojure/rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | eastwood 5 | Suggestion from Eastwood 6 | eastwood 7 | The suggestion is generated using Eastwood, a clojure lint tool that uses the tools.analyzer and 8 | tools.analyzer.jvm libraries to inspect namespaces and report possible problems. 9 | 10 | MINOR 11 | SINGLE 12 | READY 13 | CODE_SMELL 14 | CONSTANT_ISSUE 15 | 2min 16 | 17 | 18 | kondo 19 | Suggestion from clj-kondo 20 | kondo 21 | The suggestion is generated using clj-kondo, a clojure lint tool. 22 | 23 | MINOR 24 | SINGLE 25 | READY 26 | CODE_SMELL 27 | CONSTANT_ISSUE 28 | 2min 29 | 30 | 31 | kibit 32 | Suggestion from Kibit 33 | kibit 34 | The suggestion is generated using core.logic to search for patterns of code that could be rewritten 35 | with a more idiomatic function or macro. 36 | 37 | MINOR 38 | SINGLE 39 | READY 40 | CODE_SMELL 41 | CONSTANT_ISSUE 42 | 2min 43 | 44 | 45 | ancient-clj-dependency 46 | Outdated Dependency 47 | ancient-dependency 48 | Dependencies can be upgraded to newer version. 49 | 50 | MINOR 51 | SINGLE 52 | READY 53 | VULNERABILITY 54 | CONSTANT_ISSUE 55 | 60min 56 | 57 | 58 | nvd-critical 59 | Critical Vulnerability 60 | nvd-critical 61 | Critical severity security problem 62 | 63 | BLOCKER 64 | SINGLE 65 | READY 66 | VULNERABILITY 67 | CONSTANT_ISSUE 68 | 60min 69 | 70 | 71 | nvd-high 72 | High Vulnerability 73 | nvd-high 74 | High severity security problem 75 | 76 | BLOCKER 77 | SINGLE 78 | READY 79 | VULNERABILITY 80 | CONSTANT_ISSUE 81 | 60min 82 | 83 | 84 | nvd-medium 85 | Medium Vulnerability 86 | nvd-medium 87 | High severity security problem 88 | 89 | MAJOR 90 | SINGLE 91 | READY 92 | VULNERABILITY 93 | CONSTANT_ISSUE 94 | 60min 95 | 96 | 97 | nvd-low 98 | Low Vulnerability 99 | nvd-low 100 | Low severity security problem 101 | 102 | MINOR 103 | SINGLE 104 | READY 105 | VULNERABILITY 106 | CONSTANT_ISSUE 107 | 60min◊ 108 | 109 | 110 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.net.*; 21 | import java.io.*; 22 | import java.nio.channels.*; 23 | import java.util.Properties; 24 | 25 | public class MavenWrapperDownloader { 26 | 27 | /** 28 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 29 | */ 30 | private static final String DEFAULT_DOWNLOAD_URL = 31 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 32 | 33 | /** 34 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 35 | * use instead of the default one. 36 | */ 37 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 38 | ".mvn/wrapper/maven-wrapper.properties"; 39 | 40 | /** 41 | * Path where the maven-wrapper.jar will be saved to. 42 | */ 43 | private static final String MAVEN_WRAPPER_JAR_PATH = 44 | ".mvn/wrapper/maven-wrapper.jar"; 45 | 46 | /** 47 | * Name of the property which should be used to override the default download url for the wrapper. 48 | */ 49 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 50 | 51 | public static void main(String args[]) { 52 | System.out.println("- Downloader started"); 53 | File baseDirectory = new File(args[0]); 54 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 55 | 56 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 57 | // wrapperUrl parameter. 58 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 59 | String url = DEFAULT_DOWNLOAD_URL; 60 | if(mavenWrapperPropertyFile.exists()) { 61 | FileInputStream mavenWrapperPropertyFileInputStream = null; 62 | try { 63 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 64 | Properties mavenWrapperProperties = new Properties(); 65 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 66 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 67 | } catch (IOException e) { 68 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 69 | } finally { 70 | try { 71 | if(mavenWrapperPropertyFileInputStream != null) { 72 | mavenWrapperPropertyFileInputStream.close(); 73 | } 74 | } catch (IOException e) { 75 | // Ignore ... 76 | } 77 | } 78 | } 79 | System.out.println("- Downloading from: : " + url); 80 | 81 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 82 | if(!outputFile.getParentFile().exists()) { 83 | if(!outputFile.getParentFile().mkdirs()) { 84 | System.out.println( 85 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 86 | } 87 | } 88 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 89 | try { 90 | downloadFileFromURL(url, outputFile); 91 | System.out.println("Done"); 92 | System.exit(0); 93 | } catch (Throwable e) { 94 | System.out.println("- Error downloading"); 95 | e.printStackTrace(); 96 | System.exit(1); 97 | } 98 | } 99 | 100 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 101 | URL website = new URL(urlString); 102 | ReadableByteChannel rbc; 103 | rbc = Channels.newChannel(website.openStream()); 104 | FileOutputStream fos = new FileOutputStream(destination); 105 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 106 | fos.close(); 107 | rbc.close(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/clojure/sensors/kondo/KondoSensor.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.kondo; 2 | 3 | import org.sonar.api.batch.fs.InputFile; 4 | import org.sonar.api.batch.fs.TextRange; 5 | import org.sonar.api.batch.rule.Severity; 6 | import org.sonar.api.batch.sensor.Sensor; 7 | import org.sonar.api.batch.sensor.SensorContext; 8 | import org.sonar.api.batch.sensor.SensorDescriptor; 9 | import org.sonar.api.batch.sensor.issue.NewIssue; 10 | import org.sonar.api.batch.sensor.issue.NewIssueLocation; 11 | import org.sonar.api.rule.RuleKey; 12 | import org.sonar.api.utils.log.Logger; 13 | import org.sonar.api.utils.log.Loggers; 14 | import org.sonar.plugins.clojure.language.Clojure; 15 | import org.sonar.plugins.clojure.rules.ClojureLintRulesDefinition; 16 | import org.sonar.plugins.clojure.sensors.AbstractSensor; 17 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 18 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | import java.util.List; 23 | import java.util.Optional; 24 | 25 | import static org.sonar.plugins.clojure.settings.KondoProperties.*; 26 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY; 27 | import static org.sonar.plugins.clojure.settings.Properties.SENSORS_TIMEOUT_PROPERTY_DEFAULT; 28 | 29 | public class KondoSensor extends AbstractSensor implements Sensor { 30 | 31 | private static final Logger LOG = Loggers.get(KondoSensor.class); 32 | 33 | private static final String PLUGIN_NAME = "clj-kondo"; 34 | private static final String[] KONDO_ARGS = {"run", "-m", "clj-kondo.main"}; 35 | 36 | public KondoSensor(LeiningenRunner leiningenRunner) { 37 | super(leiningenRunner); 38 | } 39 | 40 | @Override 41 | public void describe(SensorDescriptor descriptor) { 42 | descriptor.name(PLUGIN_NAME) 43 | .onlyOnLanguage(Clojure.KEY) 44 | .global(); 45 | } 46 | 47 | private void saveIssue(Finding finding, SensorContext context) { 48 | String filename = finding.getFilename(); 49 | try { 50 | Optional fileOptional = getFile(filename, context.fileSystem()); 51 | 52 | if (fileOptional.isPresent()) { 53 | InputFile file = fileOptional.get(); 54 | RuleKey ruleKey = RuleKey.of(ClojureLintRulesDefinition.REPOSITORY_KEY, finding.getType()); 55 | 56 | NewIssue newIssue = context.newIssue().forRule(ruleKey); 57 | 58 | NewIssueLocation primaryLocation = newIssue 59 | .newLocation() 60 | .on(file) 61 | .message(finding.getMessage()); 62 | 63 | TextRange range = file.newRange(finding.getRow(), finding.getCol() - 1, 64 | finding.getEndRow(), finding.getEndCol() - 1); 65 | primaryLocation.at(range); 66 | 67 | newIssue.at(primaryLocation); 68 | newIssue.overrideSeverity(getSeverity(finding.getLevel())); 69 | newIssue.save(); 70 | } else { 71 | LOG.warn("Not able to find a file with path '{}'", filename); 72 | } 73 | } catch (Exception e) { 74 | LOG.error("Can not save the issue due to: " + e.getMessage()); 75 | } 76 | } 77 | 78 | private Severity getSeverity(String level) { 79 | switch (level) { 80 | case "info": return Severity.INFO; 81 | case "warning": return Severity.MINOR; 82 | case "error": return Severity.MAJOR; 83 | default: return null; 84 | } 85 | } 86 | 87 | @Override 88 | public void execute(SensorContext context) { 89 | if (isPluginEnabled(context, PLUGIN_NAME, ENABLED_PROPERTY, ENABLED_PROPERTY_DEFAULT)) { 90 | LOG.info("Running clj-kondo"); 91 | 92 | String config = context.config().get(CONFIG).orElse(DEFAULT_CONFIG); 93 | String[] options = context.config().get(OPTIONS).orElse(DEFAULT_OPTIONS).split("\\s+"); 94 | List commandAsList = new ArrayList(Arrays.asList(KONDO_ARGS)); 95 | commandAsList.addAll(Arrays.asList(options)); 96 | if (config != null && !config.isEmpty()) { 97 | commandAsList.add("--config"); 98 | commandAsList.add(config); 99 | } 100 | String[] command = commandAsList.toArray(new String[0]); 101 | 102 | long timeOut = context.config().getLong(SENSORS_TIMEOUT_PROPERTY) 103 | .orElse(Long.valueOf(SENSORS_TIMEOUT_PROPERTY_DEFAULT)); 104 | 105 | CommandStreamConsumer stdOut = this.leiningenRunner.run(timeOut, command); 106 | 107 | List issues = KondoIssueParser.parse(stdOut); 108 | LOG.info("Saving issues " + issues.size()); 109 | for (Finding finding : issues) { 110 | saveIssue(finding, context); 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/clojure/sensors/cloverage/CloverageSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.clojure.sensors.cloverage; 2 | 3 | import org.junit.Before; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.MockitoJUnitRunner; 9 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 10 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 11 | import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; 12 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 13 | import org.sonar.api.utils.log.LogTester; 14 | import org.sonar.plugins.clojure.language.Clojure; 15 | import org.sonar.plugins.clojure.sensors.LeiningenRunner; 16 | import org.sonar.plugins.clojure.sensors.CommandStreamConsumer; 17 | import org.sonar.plugins.clojure.settings.CloverageProperties; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.nio.charset.StandardCharsets; 22 | import java.nio.file.Files; 23 | 24 | import static org.hamcrest.CoreMatchers.*; 25 | import static org.hamcrest.MatcherAssert.assertThat; 26 | import static org.junit.Assert.assertTrue; 27 | import static org.mockito.Mockito.verify; 28 | import static org.mockito.Mockito.when; 29 | import static org.sonar.plugins.clojure.settings.CloverageProperties.REPORT_LOCATION_PROPERTY; 30 | import static org.sonar.plugins.clojure.settings.KondoProperties.ENABLED_PROPERTY; 31 | 32 | @RunWith(MockitoJUnitRunner.class) 33 | public class CloverageSensorTest { 34 | 35 | private static final String MODULE_KEY = "moduleKey"; 36 | public static final String FOO_PATH = "src/clj/foo.clj"; 37 | public static final String BAR_PATH = "src/cljc/bar.cljc"; 38 | 39 | @Mock 40 | private LeiningenRunner leiningenRunner; 41 | 42 | @Rule 43 | public LogTester logTester = new LogTester(); 44 | 45 | private CloverageSensor cloverageSensor; 46 | 47 | @Before 48 | public void setUp() { 49 | this.cloverageSensor = new CloverageSensor(leiningenRunner); 50 | } 51 | 52 | @Test 53 | public void shouldConfigureSensor() { 54 | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); 55 | cloverageSensor.describe(descriptor); 56 | assertThat(descriptor.name(), is("Cloverage")); 57 | assertTrue(descriptor.languages().contains("clj")); 58 | assertThat(descriptor.languages().size(), is(1)); 59 | } 60 | 61 | @Test 62 | public void shouldExecuteCloverage() throws IOException { 63 | SensorContextTester context = prepareContext(); 64 | 65 | cloverageSensor.execute(context); 66 | 67 | String fooKey = MODULE_KEY + ":" + FOO_PATH; 68 | assertThat(context.lineHits(fooKey, 1), is(1)); 69 | assertThat(context.lineHits(fooKey, 3), is(1)); 70 | assertThat(context.lineHits(fooKey, 5), is(0)); 71 | assertThat(context.lineHits(fooKey, 6), is(1)); 72 | 73 | String barKey = MODULE_KEY + ":" + BAR_PATH; 74 | assertThat(context.lineHits(barKey, 1), is(1)); 75 | 76 | assertThat(logTester.logs(), hasItems("Running Cloverage")); 77 | verify(leiningenRunner).run(300L, "cloverage", "--codecov"); 78 | } 79 | 80 | @Test 81 | public void shouldLogIfCloverageReportPathIsInvalid() { 82 | SensorContextTester context = SensorContextTester.create(new File("/")); 83 | context.settings().appendProperty(REPORT_LOCATION_PROPERTY, "invalid/file/path"); 84 | 85 | cloverageSensor.execute(context); 86 | 87 | assertThat(logTester.logs(), hasItem("Cloverage report does not exist in the given path: invalid/file/path")); 88 | } 89 | 90 | private SensorContextTester prepareContext() throws IOException { 91 | SensorContextTester context = SensorContextTester.create(new File("/")); 92 | File baseDir = new File("src/test/resources/"); 93 | context.settings().appendProperty(REPORT_LOCATION_PROPERTY, "src/test/resources/cloverage-result.json"); 94 | 95 | addFileToContext(context, baseDir, FOO_PATH, "foo_in_src_clj.clj"); 96 | addFileToContext(context, baseDir, BAR_PATH, "bar_in_src_cljc.cljc"); 97 | 98 | CommandStreamConsumer stdOut = new CommandStreamConsumer(); 99 | stdOut.consumeLine("Cloverage is running just fine - please relax"); 100 | when(leiningenRunner.run(300L, "cloverage", "--codecov")).thenReturn(stdOut); 101 | return context; 102 | } 103 | 104 | private void addFileToContext(SensorContextTester context, File baseDir, String fooPath, String s) throws IOException { 105 | File fooSource1 = new File(baseDir, s); 106 | DefaultInputFile fooFile1 = TestInputFileBuilder.create(MODULE_KEY, fooPath) 107 | .setLanguage(Clojure.KEY) 108 | .initMetadata(new String(Files.readAllBytes(fooSource1.toPath()), StandardCharsets.UTF_8)) 109 | .setContents(new String(Files.readAllBytes(fooSource1.toPath()), StandardCharsets.UTF_8)) 110 | .build(); 111 | context.fileSystem().add(fooFile1); 112 | } 113 | 114 | } 115 | 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SonarClojure 2 | > A SonarQube plugin to analyze Clojure source. 3 | 4 | [![Build Status](https://travis-ci.org/fsantiag/sonar-clojure.svg?branch=master)](https://travis-ci.org/fsantiag/sonar-clojure) 5 | [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=org.sonar.plugins.clojure%3Asonar-clojure-plugin&metric=alert_status 6 | )](https://sonarcloud.io/dashboard?id=org.sonar.plugins.clojure%3Asonar-clojure-plugin) 7 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=org.sonar.plugins.clojure%3Asonar-clojure-plugin&metric=coverage 8 | )](https://sonarcloud.io/dashboard?id=org.sonar.plugins.clojure%3Asonar-clojure-plugin) 9 | 10 | ## Current State 11 | 12 | ### Features: 13 | * Static code analysis powered by [eastwood](https://github.com/jonase/eastwood), [kibit](https://github.com/jonase/kibit) and [clj-kondo](https://github.com/clj-kondo/clj-kondo). 14 | * Detection of outdated dependencies/plugins powered by [lein-ancient](https://github.com/xsc/lein-ancient). 15 | * Coverage reports powered by [cloverage](https://github.com/cloverage/cloverage). 16 | * Detection of vulnerable dependencies powered by [lein-nvd](https://github.com/rm-hull/lein-nvd). 17 | 18 | ## Installation 19 | In order to install SonarClojure: 20 | 1. Download the [latest](https://github.com/fsantiag/sonar-clojure/releases) jar of the plugin. 21 | 2. Place the jar in the SonarQube server plugins directory, usually located under: `/opt/sonarqube/extensions/plugins/` 22 | 3. Restart the SonarQube server. 23 | 24 | ## Usage 25 | 1. Change your ***project.clj*** file and add the required plugins and/or dependencies: 26 | 27 | ```clojure 28 | :plugins [[jonase/eastwood "0.3.13"] 29 | [lein-kibit "0.1.8"] 30 | [lein-ancient "0.6.15"] 31 | [lein-cloverage "1.1.2"] 32 | [lein-nvd "1.4.0"]] 33 | :dependencies [[clj-kondo "RELEASE"]] 34 | ``` 35 | 36 | > Note 1: Please make sure the plugins above are setup correctly for your project. A good way to test this is to 37 | execute each one of them individually on your project. Once they are running fine, SonarClojure should be able to 38 | parse their reports. 39 | > 40 | > Note 2: The lein plugin versions above are the ones we currently support. If you would like to test with a different 41 | version, keep in mind that it might cause errors on SonarClojure analysis. 42 | 43 | 2. Create a ***sonar-project.properties*** file in the root folder of your app: 44 | 45 | ```properties 46 | sonar.projectKey=your-project-key 47 | sonar.projectName=YourProjectName 48 | sonar.projectVersion=1.0 49 | sonar.sources=. 50 | ``` 51 | 52 | 3. Run [sonar-scanner](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner) on your project. 53 | 54 | ### Configuring Sensors 55 | 56 | #### Disabling 57 | Sensors can be disabled by setting `sonar.clojure..enabled=false` in the sonar-project.properties or 58 | by using the command line argument `-Dsonar.clojure..enabled` when running sonar-scanner. 59 | Sensor names are `eastwood`, `kibit`, `kondo`, `ancient`, `nvd` and `cloverage`. 60 | 61 | #### Report file location 62 | Some sensors use report files to parse the results. Both cloverage and lein-nvd use this report files. 63 | By default they have a path already set but you can change the file locations by setting the property in the 64 | sonar-project.properties: 65 | 66 | `sonar.clojure.cloverage.reportPath=target/coverage/codecov.json` 67 | 68 | `sonar.clojure.nvd.reportPath=target/nvd/dependency-check-report.json` 69 | 70 | #### Setting a timeout 71 | By default, sensors have a timeout value of 300 seconds. This value applies per sensor while they are executing. 72 | You can change the default value by setting the property `sonar.clojure.sensors.timeout` in the sonar-project.properties 73 | file. 74 | 75 | #### Debugging 76 | * SonarClojure is in its early days and therefore you might face problems when trying to run the plugin, especially because 77 | we rely on other plugins that are also in its early days. A nice way to try to debug 78 | a problem you might have is to make sure the particular plugin you are using is running fine before executing the 79 | sonar-scanner. For instance, if you are trying to visualize the coverage data on SonarQube, make sure to run cloverage 80 | against your project using `lein cloverage --codecov` for instance. Once you fix the cloverage issue on your project, 81 | then SonarClojure should be able to parse the results. The same idea applies to all the plugins. 82 | 83 | * In general, plugins should not stop execution in case of errors, unless an exception happens. 84 | 85 | * You can use `-X` or `--debug` when running sonar-scanner to get a detailed information of what SonarClojure is trying to do. 86 | 87 | ## Building from Source 88 | ```sh 89 | ./mvnw clean package 90 | ``` 91 | 92 | Maven will generate a SNAPSHOT under the folder ***target***. 93 | 94 | ## Compatibility 95 | At the moment, SonarClojure supports up to version 8.6.1 of SonarQube. 96 | 97 | ## License 98 | 99 | SonarClojure is open-sourced software licensed under the [MIT license](https://github.com/fsantiag/sonar-clojure/blob/master/LICENSE). 100 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | --------------------------------------------------------------------------------