diff --git a/README.md b/README.md index d5019f3e..44769222 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ In SonarQube under Quality Profiles the used Linter can be specified by selectin | Complexity |YES |Uses [Lizard](https://github.com/terryyin/lizard)| Uses [Lizard](https://github.com/terryyin/lizard)| | Design |NO | | | | Documentation |YES | | | -| Duplications |YES | | | +| Duplications |Only on Sonarqube < 7.3 | | | | Issues |YES | Uses [SwiftLint](https://github.com/realm/SwiftLint) and/or [Tailor](https://github.com/sleekbyte/tailor) for Swift. [OCLint](http://oclint-docs.readthedocs.io/en/stable/) and [Faux Pas](http://fauxpasapp.com/) for Objective-C| Uses [Tailor](https://github.com/sleekbyte/tailor)| | Size |YES | | | | Tests |YES | Uses xcodebuild + xcpretty [xcpretty](https://github.com/supermarin/xcpretty) | Not Supported | diff --git a/commons/pom.xml b/commons/pom.xml index b8deb794..efac25cd 100644 --- a/commons/pom.xml +++ b/commons/pom.xml @@ -24,7 +24,7 @@ backelite-swift com.backelite.sonarqube - 0.4.4 + 0.4.5-develop2 4.0.0 @@ -90,11 +90,6 @@ org.slf4j slf4j-api - - commons-lang - commons-lang - 2.6 - diff --git a/commons/src/main/java/com/backelite/sonarqube/commons/MeasureUtil.java b/commons/src/main/java/com/backelite/sonarqube/commons/MeasureUtil.java index 3518743b..9408af76 100644 --- a/commons/src/main/java/com/backelite/sonarqube/commons/MeasureUtil.java +++ b/commons/src/main/java/com/backelite/sonarqube/commons/MeasureUtil.java @@ -18,8 +18,8 @@ package com.backelite.sonarqube.commons; import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.batch.measure.Metric; import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.measures.Metric; /** * Created by gillesgrousset on 29/08/2018. diff --git a/commons/src/main/java/com/backelite/sonarqube/commons/TestFileFinders.java b/commons/src/main/java/com/backelite/sonarqube/commons/TestFileFinders.java index b523407e..37216a91 100644 --- a/commons/src/main/java/com/backelite/sonarqube/commons/TestFileFinders.java +++ b/commons/src/main/java/com/backelite/sonarqube/commons/TestFileFinders.java @@ -39,7 +39,7 @@ public void addFinder(TestFileFinder finder) { finders.add(finder); } - public InputFile getUnitTestResource(FileSystem fileSystem, String classname) { + InputFile getUnitTestResource(FileSystem fileSystem, String classname) { for (TestFileFinder finder : finders) { InputFile result = finder.getUnitTestResource(fileSystem, classname); if (result != null) { diff --git a/commons/src/main/java/com/backelite/sonarqube/commons/surefire/StaxParser.java b/commons/src/main/java/com/backelite/sonarqube/commons/surefire/StaxParser.java deleted file mode 100644 index 26f41f7f..00000000 --- a/commons/src/main/java/com/backelite/sonarqube/commons/surefire/StaxParser.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * commons - Enables analysis of Swift and Objective-C projects into SonarQube. - * Copyright © 2015 Backelite (${email}) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -package com.backelite.sonarqube.commons.surefire; - -import org.codehaus.staxmate.SMInputFactory; -import com.ctc.wstx.stax.WstxInputFactory; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; -import org.codehaus.staxmate.in.SMHierarchicCursor; - -public class StaxParser { - - private SMInputFactory inf; - private SurefireStaxHandler streamHandler; - - public StaxParser(UnitTestIndex index) { - this.streamHandler = new SurefireStaxHandler(index); - XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); - if (xmlInputFactory instanceof WstxInputFactory) { - WstxInputFactory wstxInputfactory = (WstxInputFactory) xmlInputFactory; - wstxInputfactory.configureForLowMemUsage(); - wstxInputfactory.getConfig().setUndeclaredEntityResolver((String publicID, String systemID, String baseURI, String namespace) -> namespace); - } - this.inf = new SMInputFactory(xmlInputFactory); - } - - public void parse(File xmlFile) throws XMLStreamException { - try(FileInputStream input = new FileInputStream(xmlFile)) { - parse(inf.rootElementCursor(input)); - } catch (IOException e) { - throw new XMLStreamException(e); - } - } - - private void parse(SMHierarchicCursor rootCursor) throws XMLStreamException { - try { - streamHandler.stream(rootCursor); - } finally { - rootCursor.getStreamReader().closeCompletely(); - } - } -} diff --git a/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireReportParser.java b/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireReportParser.java index 608572b0..dca2ce6c 100644 --- a/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireReportParser.java +++ b/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireReportParser.java @@ -17,32 +17,24 @@ */ package com.backelite.sonarqube.commons.surefire; -import com.backelite.sonarqube.commons.TestFileFinders; +import com.backelite.sonarqube.commons.MeasureUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.api.batch.fs.FileSystem; -import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputComponent; import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.component.ResourcePerspectives; import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Metric; -import org.sonar.api.test.MutableTestPlan; -import org.sonar.api.test.TestCase; -import org.sonar.squidbridge.api.AnalysisException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; -import javax.annotation.CheckForNull; +import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.stream.XMLStreamException; +import javax.xml.parsers.ParserConfigurationException; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; -import java.io.Serializable; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; public class SurefireReportParser { private static final Logger LOGGER = LoggerFactory.getLogger(SurefireReportParser.class); @@ -52,110 +44,130 @@ public class SurefireReportParser { protected final SensorContext context; private final DocumentBuilderFactory dbfactory; private final UnitTestIndex index; - private final ResourcePerspectives perspectives; - private final FileSystem fileSystem; - protected SurefireReportParser(FileSystem fileSystem, ResourcePerspectives perspectives, SensorContext context) { - this.fileSystem = fileSystem; + protected SurefireReportParser(SensorContext context) { this.context = context; - this.perspectives = perspectives; this.dbfactory = DocumentBuilderFactory.newInstance(); this.index = new UnitTestIndex(); } - public void collect(File reportsDir) { - List xmlFiles = new ArrayList<>(); - try (DirectoryStream stream = Files.newDirectoryStream(reportsDir.toPath(), "{TEST}*.{xml}")) { - for (Path p: stream) { - LOGGER.info("Processing Surefire report {}", p.getFileName()); - xmlFiles.add(p.toFile()); - } - } catch (IOException e) { - LOGGER.error( "Error while finding test files.", e); + public void parseReport(final File xmlFile) { + try { + DocumentBuilder builder = dbfactory.newDocumentBuilder(); + Document document = builder.parse(xmlFile); + collectTestSuites(document.getElementsByTagName(TESTSUITE)); + } catch (final FileNotFoundException e) { + LOGGER.error("Cobertura Report not found {}", xmlFile, e); + } catch (final IOException e) { + LOGGER.error("Error processing file named {}", xmlFile, e); + } catch (final ParserConfigurationException e) { + LOGGER.error("Error in parser config {}", e); + } catch (final SAXException e) { + LOGGER.error("Error processing file named {}", xmlFile, e); } + } - if (!xmlFiles.isEmpty()) { - parseFiles(xmlFiles); + private void collectTestSuites(NodeList nodeList) { + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + String testSuiteClassName = element.getAttribute("name"); + if(testSuiteClassName != null && !testSuiteClassName.contains("$")){ + NodeList nl = element.getElementsByTagName(TESTCASE); + collectTestCases(testSuiteClassName, nl); + } + } } } - private void parseFiles(List reports) { - UnitTestIndex index = new UnitTestIndex(); - parseFiles(reports, index); - save(index, context); + private void collectTestCases(String testSuiteName, NodeList nodeList) { + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + + String className = element.getAttribute("classname"); + if(className != null && className.endsWith(")") && className.indexOf("(") >= 0){ + className = className.substring(0, className.indexOf("(")); + } + if(className == null || className.trim().isEmpty()) + className = testSuiteName; + + String testName = element.getAttribute("name"); + if(className != null && className.contains("$")) + testName = className.substring(className.indexOf("$")) + "/" + testName; + + UnitTestClassReport classReport = index.index(className); + UnitTestResult detail = new UnitTestResult(); + detail.setName(testName); + + String status = UnitTestResult.STATUS_OK; + Long duration = parseDuration(element.getAttribute("time")); + if(element.hasChildNodes()){ + Element item = (Element) element.getChildNodes().item(0); + if ("skipped".equals(item.getLocalName())) { + status = UnitTestResult.STATUS_SKIPPED; + duration = 0L; + + } else if ("failure".equals(item.getLocalName())) { + status = UnitTestResult.STATUS_FAILURE; + detail.setMessage(item.getAttribute("message")); + detail.setStackTrace(item.getTextContent()); + + } else if ("error".equals(item.getLocalName())) { + status = UnitTestResult.STATUS_ERROR; + detail.setMessage(item.getAttribute("message")); + detail.setStackTrace(item.getTextContent()); + } + } + detail.setDurationMilliseconds(duration); + detail.setStatus(status); + classReport.add(detail); + } + } } - private static void parseFiles(List reports, UnitTestIndex index) { - StaxParser parser = new StaxParser(index); - for (File report : reports) { - try { - parser.parse(report); - } catch (XMLStreamException e) { - throw new AnalysisException("Fail to parse the Surefire report: " + report, e); - } + private Long parseDuration(String value) { + Long duration = null; + if(value != null && !value.isEmpty()){ + Double time = Double.parseDouble(value); + duration = (long)(time * 1000); } + return duration; } - private void save(UnitTestIndex index, SensorContext context) { + public void save() { long negativeTimeTestNumber = 0; - Map indexByInputFile = mapToInputFile(index.getIndexByClassname()); - for (Map.Entry entry : indexByInputFile.entrySet()) { - UnitTestClassReport report = entry.getValue(); + int testsCount = 0; + int testsSkipped = 0; + int testsErrors = 0; + int testsFailures = 0; + long testsTime = 0; + + for (UnitTestClassReport report : index.getIndexByClassname().values()) { + testsCount += report.getTests() - report.getSkipped(); + testsSkipped += report.getSkipped(); + testsErrors += report.getErrors(); + testsFailures += report.getFailures(); + if (report.getTests() > 0) { negativeTimeTestNumber += report.getNegativeTimeTestNumber(); - save(report, entry.getKey(), context); } } - if (negativeTimeTestNumber > 0) { - LOGGER.warn("There is {} test(s) reported with negative time by surefire, total duration may not be accurate.", negativeTimeTestNumber); - } - } - private Map mapToInputFile(Map indexByClassname) { - Map result = new HashMap<>(); - indexByClassname.forEach((className, index) -> { - InputFile resource = getUnitTestResource(className, index); - if (resource != null) { - UnitTestClassReport report = result.computeIfAbsent(resource, r -> new UnitTestClassReport()); - // in case of repeated/parameterized tests (JUnit 5.x) we may end up with tests having the same name - index.getResults().forEach(report::add); - } else { - LOGGER.debug("Resource not found: {}", className); - } - }); - return result; - } - - private void save(UnitTestClassReport report, InputFile inputFile, SensorContext context) { - int testsCount = report.getTests() - report.getSkipped(); - saveMeasure(context, inputFile, CoreMetrics.SKIPPED_TESTS, report.getSkipped()); - saveMeasure(context, inputFile, CoreMetrics.TESTS, testsCount); - saveMeasure(context, inputFile, CoreMetrics.TEST_ERRORS, report.getErrors()); - saveMeasure(context, inputFile, CoreMetrics.TEST_FAILURES, report.getFailures()); - saveMeasure(context, inputFile, CoreMetrics.TEST_EXECUTION_TIME, report.getDurationMilliseconds()); - saveResults(inputFile, report); - } - protected void saveResults(InputFile testFile, UnitTestClassReport report) { - for (UnitTestResult unitTestResult : report.getResults()) { - MutableTestPlan testPlan = perspectives.as(MutableTestPlan.class, testFile); - if (testPlan != null) { - testPlan.addTestCase(unitTestResult.getName()) - .setDurationInMs(Math.max(unitTestResult.getDurationMilliseconds(), 0)) - .setStatus(TestCase.Status.of(unitTestResult.getStatus())) - .setMessage(unitTestResult.getMessage()) - .setStackTrace(unitTestResult.getStackTrace()); - } + if (negativeTimeTestNumber > 0) { + LOGGER.warn("There is {} test(s) reported with negative time by data, total duration may not be accurate.", negativeTimeTestNumber); } - } - - @CheckForNull - private InputFile getUnitTestResource(String className, UnitTestClassReport unitTestClassReport) { - return TestFileFinders.getInstance().getUnitTestResource(fileSystem, className); - } - private static void saveMeasure(SensorContext context, InputFile inputFile, Metric metric, T value) { - context.newMeasure().forMetric(metric).on(inputFile).withValue(value).save(); + if (testsCount > 0) { + InputComponent module = context.module(); + MeasureUtil.saveMeasure(context, module, CoreMetrics.TESTS, testsCount); + MeasureUtil.saveMeasure(context, module, CoreMetrics.SKIPPED_TESTS, testsSkipped); + MeasureUtil.saveMeasure(context, module, CoreMetrics.TEST_ERRORS, testsErrors); + MeasureUtil.saveMeasure(context, module, CoreMetrics.TEST_FAILURES, testsFailures); + MeasureUtil.saveMeasure(context, module, CoreMetrics.TEST_EXECUTION_TIME, testsTime); + } } - } diff --git a/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireSensor.java b/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireSensor.java index b0b204d5..e5920e87 100644 --- a/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireSensor.java +++ b/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireSensor.java @@ -20,17 +20,12 @@ import com.backelite.sonarqube.commons.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.SensorDescriptor; -import org.sonar.api.component.ResourcePerspectives; import java.io.File; -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; public class SurefireSensor implements Sensor { private static final Logger LOGGER = LoggerFactory.getLogger(SurefireSensor.class); @@ -38,12 +33,8 @@ public class SurefireSensor implements Sensor { public static final String DEFAULT_REPORT_PATH = "sonar-reports/"; private final SensorContext context; - private final ResourcePerspectives perspectives; - private final FileSystem fileSystem; - public SurefireSensor(FileSystem fileSystem, ResourcePerspectives perspectives, SensorContext context) { - this.fileSystem = fileSystem; - this.perspectives = perspectives; + public SurefireSensor(SensorContext context) { this.context = context; } @@ -57,23 +48,24 @@ protected String reportPath() { public void describe(SensorDescriptor descriptor) { descriptor .name("Surefire") - .onlyOnLanguages("swift","objc"); + .onlyOnLanguages("swift","objc") + .onlyOnFileType(InputFile.Type.MAIN); } @Override public void execute(SensorContext context) { - SurefireReportParser surefireParser = new SurefireReportParser(fileSystem, perspectives, context); - String reportFileName = context.fileSystem().baseDir().getAbsolutePath() + "/"+ reportPath(); + SurefireReportParser surefireParser = new SurefireReportParser(context); + String reportFileName = context.fileSystem().baseDir().getAbsolutePath() + File.separator + reportPath(); File reportsDir = new File(reportFileName); if (!reportsDir.isDirectory()) { LOGGER.warn("JUnit report directory not found at {}", reportsDir); return; - } else { - surefireParser.collect(reportsDir); } - - + for (File file : reportsDir.listFiles((file,name) -> (name.startsWith("TEST") && name.endsWith(".xml")) || name.endsWith(".junit"))){ + LOGGER.info("Processing Surefire report {}", file.getName()); + surefireParser.parseReport(file); + } + surefireParser.save(); } - } diff --git a/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireStaxHandler.java b/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireStaxHandler.java deleted file mode 100644 index 58787c8b..00000000 --- a/commons/src/main/java/com/backelite/sonarqube/commons/surefire/SurefireStaxHandler.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * commons - Enables analysis of Swift and Objective-C projects into SonarQube. - * Copyright © 2015 Backelite (${email}) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -package com.backelite.sonarqube.commons.surefire; - -import java.text.ParseException; -import java.util.Locale; -import javax.xml.stream.XMLStreamException; -import org.apache.commons.lang.StringUtils; -import org.codehaus.staxmate.in.ElementFilter; -import org.codehaus.staxmate.in.SMEvent; -import org.codehaus.staxmate.in.SMHierarchicCursor; -import org.codehaus.staxmate.in.SMInputCursor; -import org.sonar.api.utils.ParsingUtils; - -public class SurefireStaxHandler { - - private final UnitTestIndex index; - - public SurefireStaxHandler(UnitTestIndex index) { - this.index = index; - } - - public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException { - SMInputCursor testSuite = rootCursor.constructDescendantCursor(new ElementFilter("testsuite")); - SMEvent testSuiteEvent; - for (testSuiteEvent = testSuite.getNext(); testSuiteEvent != null; testSuiteEvent = testSuite.getNext()) { - if (testSuiteEvent.compareTo(SMEvent.START_ELEMENT) == 0) { - String testSuiteClassName = testSuite.getAttrValue("name"); - if (StringUtils.contains(testSuiteClassName, "$")) { - // test suites for inner classes are ignored - return; - } - parseTestCase(testSuiteClassName, testSuite.childCursor(new ElementFilter("testcase"))); - } - } - } - - private void parseTestCase(String testSuiteClassName, SMInputCursor testCase) throws XMLStreamException { - for (SMEvent event = testCase.getNext(); event != null; event = testCase.getNext()) { - if (event.compareTo(SMEvent.START_ELEMENT) == 0) { - String testClassName = getClassname(testCase, testSuiteClassName); - UnitTestClassReport classReport = index.index(testClassName); - parseTestCase(testCase, testSuiteClassName, classReport); - } - } - } - - private static String getClassname(SMInputCursor testCaseCursor, String defaultClassname) throws XMLStreamException { - String testClassName = testCaseCursor.getAttrValue("classname"); - if (StringUtils.isNotBlank(testClassName) && testClassName.endsWith(")")) { - int openParenthesisIndex = testClassName.indexOf('('); - if (openParenthesisIndex > 0) { - testClassName = testClassName.substring(0, openParenthesisIndex); - } - } - return StringUtils.defaultIfBlank(testClassName, defaultClassname); - } - - private static void parseTestCase(SMInputCursor testCaseCursor, String testSuiteClassName, UnitTestClassReport report) throws XMLStreamException { - report.add(parseTestResult(testCaseCursor, testSuiteClassName)); - } - - private static void setStackAndMessage(UnitTestResult result, SMInputCursor stackAndMessageCursor) throws XMLStreamException { - result.setMessage(stackAndMessageCursor.getAttrValue("message")); - String stack = stackAndMessageCursor.collectDescendantText(); - result.setStackTrace(stack); - } - - private static UnitTestResult parseTestResult(SMInputCursor testCaseCursor, String testSuiteClassName) throws XMLStreamException { - UnitTestResult detail = new UnitTestResult(); - String name = getTestCaseName(testCaseCursor); - detail.setName(name); - detail.setTestSuiteClassName(testSuiteClassName); - - String status = UnitTestResult.STATUS_OK; - String time = testCaseCursor.getAttrValue("time"); - Long duration = null; - - SMInputCursor childNode = testCaseCursor.descendantElementCursor(); - if (childNode.getNext() != null) { - String elementName = childNode.getLocalName(); - if ("skipped".equals(elementName)) { - status = UnitTestResult.STATUS_SKIPPED; - // bug with surefire reporting wrong time for skipped tests - duration = 0L; - - } else if ("failure".equals(elementName)) { - status = UnitTestResult.STATUS_FAILURE; - setStackAndMessage(detail, childNode); - - } else if ("error".equals(elementName)) { - status = UnitTestResult.STATUS_ERROR; - setStackAndMessage(detail, childNode); - } - } - while (childNode.getNext() != null) { - // make sure we loop till the end of the elements cursor - } - if (duration == null) { - duration = getTimeAttributeInMS(time); - } - detail.setDurationMilliseconds(duration); - detail.setStatus(status); - return detail; - } - - private static long getTimeAttributeInMS(String value) throws XMLStreamException { - // hardcoded to Locale.ENGLISH see http://jira.codehaus.org/browse/SONAR-602 - try { - Double time = ParsingUtils.parseNumber(value, Locale.ENGLISH); - return !Double.isNaN(time) ? (long) ParsingUtils.scaleValue(time * 1000, 3) : 0L; - } catch (ParseException e) { - throw new XMLStreamException(e); - } - } - - private static String getTestCaseName(SMInputCursor testCaseCursor) throws XMLStreamException { - String classname = testCaseCursor.getAttrValue("classname"); - String name = testCaseCursor.getAttrValue("name"); - if (StringUtils.contains(classname, "$")) { - return StringUtils.substringAfter(classname, "$") + "/" + name; - } - return name; - } - -} diff --git a/docs/sonarqube-fastlane.md b/docs/sonarqube-fastlane.md index 7b28f8f1..7b8a88d9 100644 --- a/docs/sonarqube-fastlane.md +++ b/docs/sonarqube-fastlane.md @@ -9,7 +9,7 @@ Then add the following lane. lane :metrics do scan(scheme: "[SCHEME]", code_coverage: true, derived_data_path: "./DerivedData", output_directory: "./reports") slather(cobertura_xml: true, jenkins: true, scheme: "[SCHEME]", build_directory: "./DerivedData", output_directory: "./reports", proj: "./[PROJECT].xcodeproj") - lizard(source_folder: "[SOURCE_FOLDER]", language: "swift", export_type: "xml", report_file: "report/lizard-report.xml") + lizard(source_folder: "[SOURCE_FOLDER]", language: "swift", export_type: "xml", report_file: "reports/lizard-report.xml") swiftlint(output_file: "./reports/swiftlint.txt", ignore_exit_status: true) sonar end diff --git a/objclang/pom.xml b/objclang/pom.xml index e11ab42d..62a08a2f 100644 --- a/objclang/pom.xml +++ b/objclang/pom.xml @@ -24,7 +24,7 @@ backelite-swift com.backelite.sonarqube - 0.4.4 + 0.4.5-develop2 4.0.0 @@ -37,7 +37,7 @@ com.backelite.sonarqube commons - 0.4.4 + 0.4.5-develop2 diff --git a/objclang/src/main/java/com/backelite/sonarqube/objectivec/ObjectiveCSquidSensor.java b/objclang/src/main/java/com/backelite/sonarqube/objectivec/ObjectiveCSquidSensor.java index 4491a3db..692299a3 100644 --- a/objclang/src/main/java/com/backelite/sonarqube/objectivec/ObjectiveCSquidSensor.java +++ b/objclang/src/main/java/com/backelite/sonarqube/objectivec/ObjectiveCSquidSensor.java @@ -33,7 +33,6 @@ import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.batch.sensor.issue.NewIssue; import org.sonar.api.batch.sensor.issue.NewIssueLocation; -import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.rule.RuleKey; import org.sonar.api.scan.filesystem.PathResolver; @@ -67,7 +66,7 @@ public class ObjectiveCSquidSensor implements Sensor { public ObjectiveCSquidSensor(SensorContext context, PathResolver pathResolver, CheckFactory checkFactory) { this.context = context; this.pathResolver = pathResolver; - this.checks = checkFactory.>create(CheckList.REPOSITORY_KEY).addAnnotatedChecks((Iterable)CheckList.getChecks()); + this.checks = checkFactory.>create(CheckList.REPOSITORY_KEY).addAnnotatedChecks((Iterable) CheckList.getChecks()); } private ObjectiveCConfiguration createConfiguration() { @@ -86,7 +85,6 @@ private void save(Collection squidSourceFiles) { } private void saveMeasures(InputFile inputFile, SourceFile squidFile) { - MeasureUtil.saveMeasure(context, inputFile, CoreMetrics.FILES, squidFile.getInt(ObjectiveCMetric.FILES)); MeasureUtil.saveMeasure(context, inputFile, CoreMetrics.LINES, squidFile.getInt(ObjectiveCMetric.LINES)); MeasureUtil.saveMeasure(context, inputFile, CoreMetrics.NCLOC, squidFile.getInt(ObjectiveCMetric.LINES_OF_CODE)); MeasureUtil.saveMeasure(context, inputFile, CoreMetrics.STATEMENTS, squidFile.getInt(ObjectiveCMetric.STATEMENTS)); @@ -99,16 +97,21 @@ private void saveIssues(InputFile inputFile, SourceFile squidFile) { if (inputFile != null) { for (CheckMessage message : messages) { RuleKey ruleKey = checks.ruleKey((SquidCheck) message.getCheck()); - NewIssue issue = context.newIssue() - .forRule(ruleKey); - NewIssueLocation dil = new DefaultIssueLocation() - .on(inputFile) - .at(inputFile.selectLine(message.getLine())) - .message(message.getText(Locale.ENGLISH)); - issue.at(dil); + NewIssue issue = context.newIssue(); + + NewIssueLocation issueLocation = issue.newLocation() + .message(message.getText(Locale.ENGLISH)) + .on(inputFile) + .at(inputFile.selectLine(message.getLine())); + + issue + .forRule(ruleKey) + .at(issueLocation); + if (message.getCost() != null) { issue.gap(message.getCost()); } + issue.save(); } } @@ -133,7 +136,7 @@ public void execute(SensorContext context) { FilePredicate isMain = context.fileSystem().predicates().hasType(InputFile.Type.MAIN); FilePredicate and = context.fileSystem().predicates().and(hasObjC, isMain); List files = new ArrayList<>(); - for(InputFile inf : context.fileSystem().inputFiles(and)){ + for (InputFile inf : context.fileSystem().inputFiles(and)) { files.add(inf.file()); } diff --git a/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/fauxpas/FauxPasReportParser.java b/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/fauxpas/FauxPasReportParser.java index ff4bb570..aa8daf8d 100644 --- a/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/fauxpas/FauxPasReportParser.java +++ b/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/fauxpas/FauxPasReportParser.java @@ -25,8 +25,8 @@ import org.sonar.api.batch.fs.FilePredicate; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.issue.NewIssue; import org.sonar.api.batch.sensor.issue.NewIssueLocation; -import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation; import org.sonar.api.rule.RuleKey; import java.io.File; @@ -91,15 +91,20 @@ private void recordIssue(final JSONObject diagnosticJson) { } InputFile inputFile = context.fileSystem().inputFile(fp); - NewIssueLocation dil = new DefaultIssueLocation() + NewIssue issue = context.newIssue(); + + NewIssueLocation issueLocation = issue.newLocation() + .message(info) .on(inputFile) - .at(inputFile.selectLine(lineNum)) - .message(info); - context.newIssue() + .at(inputFile.selectLine(lineNum)); + + issue .forRule(RuleKey.of(FauxPasRulesDefinition.REPOSITORY_KEY, (String) diagnosticJson.get("ruleShortName"))) - .at(dil) - .save(); + .at(issueLocation); + + issue.save(); + } } -} \ No newline at end of file +} diff --git a/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/fauxpas/FauxPasSensor.java b/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/fauxpas/FauxPasSensor.java index 75864049..408827a1 100644 --- a/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/fauxpas/FauxPasSensor.java +++ b/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/fauxpas/FauxPasSensor.java @@ -22,13 +22,10 @@ import org.apache.tools.ant.DirectoryScanner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.SensorDescriptor; -import org.sonar.api.component.ResourcePerspectives; -import org.sonar.api.config.Settings; import java.io.File; @@ -43,20 +40,6 @@ public FauxPasSensor(SensorContext context) { this.context = context; } - private void parseReportIn(final String baseDir, final FauxPasReportParser parser) { - DirectoryScanner scanner = new DirectoryScanner(); - scanner.setIncludes(new String[]{reportPath()}); - scanner.setBasedir(baseDir); - scanner.setCaseSensitive(false); - scanner.scan(); - String[] files = scanner.getIncludedFiles(); - - for (String filename : files) { - LOGGER.info("Processing FauxPas report {}", filename); - parser.parseReport(new File(filename)); - } - } - private String reportPath() { return context.config() .get(REPORT_PATH_KEY) @@ -73,9 +56,17 @@ public void describe(SensorDescriptor descriptor) { @Override public void execute(SensorContext context) { - final String projectBaseDir = context.fileSystem().baseDir().getAbsolutePath(); - FauxPasReportParser parser = new FauxPasReportParser(context); - parseReportIn(projectBaseDir, parser); + DirectoryScanner scanner = new DirectoryScanner(); + scanner.setIncludes(new String[]{reportPath()}); + scanner.setBasedir(context.fileSystem().baseDir().getAbsolutePath()); + scanner.setCaseSensitive(false); + scanner.scan(); + String[] files = scanner.getIncludedFiles(); + + for (String filename : files) { + LOGGER.info("Processing FauxPas report {}", filename); + parser.parseReport(new File(filename)); + } } } diff --git a/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/oclint/OCLintParser.java b/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/oclint/OCLintParser.java index 6d79b8b0..01360d1e 100644 --- a/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/oclint/OCLintParser.java +++ b/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/oclint/OCLintParser.java @@ -22,8 +22,8 @@ import org.sonar.api.batch.fs.FilePredicate; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.issue.NewIssue; import org.sonar.api.batch.sensor.issue.NewIssueLocation; -import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation; import org.sonar.api.rule.RuleKey; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -88,24 +88,26 @@ private void collectFileViolations(String filePath, NodeList nodeList) { FilePredicate fp = context.fileSystem().predicates().hasAbsolutePath(file.getAbsolutePath()); if(!context.fileSystem().hasFiles(fp)){ LOGGER.warn("file not included in sonar {}", filePath); - } else { - InputFile inputFile = context.fileSystem().inputFile(fp); - for (int i = 0; i < nodeList.getLength(); i++) { - Node node = nodeList.item(i); - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - NewIssueLocation dil = new DefaultIssueLocation() - .on(inputFile) - .at(inputFile.selectLine(Integer.valueOf(element.getAttribute(LINE)))) - .message(element.getTextContent()); - context.newIssue() - .forRule(RuleKey.of(OCLintRulesDefinition.REPOSITORY_KEY, element.getAttribute(RULE))) - .at(dil) - .save(); - } - } + return; } + InputFile inputFile = context.fileSystem().inputFile(fp); + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + + NewIssue issue = context.newIssue(); + NewIssueLocation issueLocation = issue.newLocation() + .on(inputFile) + .at(inputFile.selectLine(Integer.valueOf(element.getAttribute(LINE)))) + .message(element.getTextContent()); + issue + .forRule(RuleKey.of(OCLintRulesDefinition.REPOSITORY_KEY, element.getAttribute(RULE))) + .at(issueLocation) + .save(); + } + } } } diff --git a/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/oclint/OCLintXMLStreamHandler.java b/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/oclint/OCLintXMLStreamHandler.java deleted file mode 100644 index 592501f8..00000000 --- a/objclang/src/main/java/com/backelite/sonarqube/objectivec/issues/oclint/OCLintXMLStreamHandler.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Swift SonarQube Plugin - Objective-C module - Enables analysis of Swift and Objective-C projects into SonarQube. - * Copyright © 2015 Backelite (${email}) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -package com.backelite.sonarqube.objectivec.issues.oclint; - -import org.codehaus.staxmate.in.SMHierarchicCursor; -import org.codehaus.staxmate.in.SMInputCursor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.batch.fs.FileSystem; -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.component.ResourcePerspectives; -import org.sonar.api.issue.Issuable; -import org.sonar.api.issue.Issue; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.utils.StaxParser.XmlStreamHandler; - -import javax.xml.stream.XMLStreamException; -import java.io.File; - -final class OCLintXMLStreamHandler implements XmlStreamHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(OCLintXMLStreamHandler.class); - private static final int PMD_MINIMUM_PRIORITY = 5; - private final ResourcePerspectives resourcePerspectives; - private final FileSystem fileSystem; - - public OCLintXMLStreamHandler(final ResourcePerspectives resourcePerspectives, final FileSystem fileSystem) { - this.resourcePerspectives = resourcePerspectives; - this.fileSystem = fileSystem; - } - - public void stream(final SMHierarchicCursor rootCursor) throws XMLStreamException { - - final SMInputCursor file = rootCursor.advance().childElementCursor("file"); - while (null != file.getNext()) { - collectIssuesFor(file); - } - } - - private void collectIssuesFor(final SMInputCursor file) throws XMLStreamException { - - final String filePath = file.getAttrValue("name"); - LoggerFactory.getLogger(getClass()).debug("Collection issues for {}", filePath); - final InputFile inputFile = findResource(filePath); - if (fileExists(inputFile)) { - LoggerFactory.getLogger(getClass()).debug("File {} was found in the project.", filePath); - collectFileIssues(inputFile, file); - } - } - - private void collectFileIssues(final InputFile inputFile, final SMInputCursor file) throws XMLStreamException { - - final SMInputCursor line = file.childElementCursor("violation"); - - while (null != line.getNext()) { - recordViolation(inputFile, line); - } - } - - private InputFile findResource(final String filePath) { - - File file = new File(filePath); - return fileSystem.inputFile(fileSystem.predicates().hasAbsolutePath(file.getAbsolutePath())); - - } - - private void recordViolation(InputFile inputFile, final SMInputCursor line) throws XMLStreamException { - - Issuable issuable = resourcePerspectives.as(Issuable.class, inputFile); - - if (issuable != null) { - - Issue issue = issuable.newIssueBuilder() - .ruleKey(RuleKey.of(OCLintRulesDefinition.REPOSITORY_KEY, line.getAttrValue("rule"))) - .line(Integer.valueOf(line.getAttrValue("beginline"))) - .message(line.getElemStringValue()) - .build(); - - try { - issuable.addIssue(issue); - } catch (Exception e) { - // Unable to add issue : probably because does not exist in the repository - LOGGER.warn(e.getMessage()); - } - - - } - } - - private boolean fileExists(InputFile file) { - if (file == null) { - return false; - } - - return file.file().exists(); - } - -} diff --git a/objclang/src/main/resources/org/sonar/plugins/oclint/profile-oclint.xml b/objclang/src/main/resources/org/sonar/plugins/oclint/profile-oclint.xml index 3c7b24e5..e34ca67d 100755 --- a/objclang/src/main/resources/org/sonar/plugins/oclint/profile-oclint.xml +++ b/objclang/src/main/resources/org/sonar/plugins/oclint/profile-oclint.xml @@ -27,6 +27,18 @@ OCLint collapsible if statements + + OCLint + compiler error + + + OCLint + compiler warning + + + OCLint + clang static analyzer + OCLint constant conditional operator diff --git a/objclang/src/main/resources/org/sonar/plugins/oclint/rules.txt b/objclang/src/main/resources/org/sonar/plugins/oclint/rules.txt index 3304844d..e82e92bd 100755 --- a/objclang/src/main/resources/org/sonar/plugins/oclint/rules.txt +++ b/objclang/src/main/resources/org/sonar/plugins/oclint/rules.txt @@ -54,6 +54,30 @@ Category: OCLint constant conditional operator ---------- +Summary: Name: compiler error + +Severity: 2 +Category: OCLint + +compiler error +---------- + +Summary: Name: compiler warning + +Severity: 3 +Category: OCLint + +compiler warning +---------- + +Summary: Name: clang static analyzer + +Severity: 3 +Category: OCLint + +clang static analyzer +---------- + Summary: Name: constant conditional operator Severity: 3 @@ -70,7 +94,7 @@ Category: OCLint high cyclomatic complexity ---------- -Summary: +Summary: Severity: 2 Category: OCLint @@ -158,7 +182,7 @@ Category: OCLint empty while statement ---------- -Summary: +Summary: Severity: 3 Category: OCLint @@ -166,7 +190,7 @@ Category: OCLint feature envy ---------- -Summary: +Summary: Severity: 3 Category: OCLint @@ -182,7 +206,7 @@ Category: OCLint goto statement ---------- -Summary: +Summary: Severity: 3 Category: OCLint @@ -246,7 +270,7 @@ Category: OCLint misplaced nil check ---------- -Summary: +Summary: Severity: 4 Category: OCLint @@ -270,7 +294,7 @@ Category: OCLint must override hash with isEqual ---------- -Summary: +Summary: Severity: 1 Category: OCLint @@ -302,7 +326,7 @@ Category: OCLint high npath complexity ---------- -Summary: +Summary: Severity: 2 Category: OCLint @@ -358,7 +382,7 @@ Category: OCLint short variable name ---------- -Summary: +Summary: Severity: 2 Category: OCLint @@ -366,7 +390,7 @@ Category: OCLint switch statements don't need default when fully covered ---------- -Summary: +Summary: Severity: 3 Category: OCLint @@ -374,7 +398,7 @@ Category: OCLint throw exception from finally block ---------- -Summary: +Summary: Severity: 3 Category: OCLint @@ -382,7 +406,7 @@ Category: OCLint too few branches in switch statement ---------- -Summary: +Summary: Severity: 2 Category: OCLint @@ -406,7 +430,7 @@ Category: OCLint too many parameters ---------- -Summary: +Summary: Severity: 3 Category: OCLint @@ -430,7 +454,7 @@ Category: OCLint unused method parameter ---------- -Summary: +Summary: Severity: 0 Category: OCLint @@ -438,7 +462,7 @@ Category: OCLint useless parentheses ---------- -Summary: +Summary: Severity: 1 Category: OCLint @@ -526,7 +550,7 @@ Category: OCLint use object subscripting ---------- -Summary: +Summary: Severity: 1 Category: OCLint @@ -561,5 +585,4 @@ missing abstract method implementation Summary: Name: missing abstract method implementation Severity: 1 -Category: OCLint - +Category: OCLint \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0405d855..1281cc8f 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ com.backelite.sonarqube backelite-swift - 0.4.4 + 0.4.5-develop2 pom @@ -88,7 +88,7 @@ true - 6.7 + 7.0 3.5.2 17.0 @@ -96,12 +96,13 @@ 1.0.13 1.9.0 1.7.21 - 6.7 - 3.22.0.1791 - 4.0.0.2052 + 7.7 + 3.24.0.1993 + 4.1.0.2218 1.23 2.6.1 1.6 + 1.1.1 @@ -165,7 +166,7 @@ com.googlecode.json-simple json-simple - 1.1.1 + ${json-simple.version} diff --git a/sonar-swift-plugin/pom.xml b/sonar-swift-plugin/pom.xml index e941627d..e57c63ea 100644 --- a/sonar-swift-plugin/pom.xml +++ b/sonar-swift-plugin/pom.xml @@ -26,12 +26,11 @@ com.backelite.sonarqube backelite-swift - 0.4.4 + 0.4.5-develop2 - com.backelite.sonarqube backelite-sonar-swift-plugin - 0.4.4 + 0.4.5-develop2 sonar-plugin @@ -40,12 +39,12 @@ com.backelite.sonarqube swift-lang - 0.4.4 + 0.4.5-develop2 com.backelite.sonarqube objc-lang - 0.4.4 + 0.4.5-develop2 @@ -67,12 +66,12 @@ junit junit - compile + test org.mockito mockito-all - compile + test org.assertj @@ -85,7 +84,7 @@ org.sonarsource.sslr sslr-testing-harness - compile + test org.sonarsource.sonarlint.core @@ -123,6 +122,7 @@ maven-shade-plugin + 3.2.0 package diff --git a/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/complexity/LizardReportParser.java b/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/complexity/LizardReportParser.java index 3fb4b46f..8d577aa5 100644 --- a/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/complexity/LizardReportParser.java +++ b/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/complexity/LizardReportParser.java @@ -20,8 +20,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.InputComponent; import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.fs.InputModule; +import org.sonar.api.batch.fs.internal.DefaultInputComponent; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.measures.CoreMetrics; import org.w3c.dom.Document; @@ -36,6 +37,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.Optional; public class LizardReportParser { private static final Logger LOGGER = LoggerFactory.getLogger(LizardReportParser.class); @@ -96,11 +98,11 @@ private void updateIndexes(NodeList nodeList) { if (node.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) node; String label = element.getTextContent(); - if(LINE_COUNT_LABEL.equalsIgnoreCase(label)) + if (LINE_COUNT_LABEL.equalsIgnoreCase(label)) lineCountIndex = i; - else if(CYCLOMATIC_COMPLEXITY_LABEL.equalsIgnoreCase(label)) + else if (CYCLOMATIC_COMPLEXITY_LABEL.equalsIgnoreCase(label)) cyclomaticComplexityIndex = i; - else if(FUNCTION_COUNT_LABEL.equalsIgnoreCase(label)) + else if (FUNCTION_COUNT_LABEL.equalsIgnoreCase(label)) functionCountIndex = i; } } @@ -115,22 +117,62 @@ private void parseMeasure(String type, NodeList itemList) { NodeList values = itemElement.getElementsByTagName(VALUE); if (FILE_MEASURE.equalsIgnoreCase(type)) { - addComplexityFileMeasures(name,values); + Optional.ofNullable(getFile(name)).ifPresent(inputFile -> addComplexityFileMeasures(inputFile, values)); } else if (FUNCTION_MEASURE.equalsIgnoreCase(type)) { - addComplexityFunctionMeasures(new SwiftFunction(name),values); + addComplexityFunctionMeasures(new SwiftFunction(0,name), values); } } } } - private void addComplexityFileMeasures(String fileName, NodeList values) { + private InputFile getFile(String fileName){ FilePredicate fp = context.fileSystem().predicates().hasRelativePath(fileName); - if(!context.fileSystem().hasFiles(fp)){ + if (!context.fileSystem().hasFiles(fp)) { LOGGER.warn("file not included in sonar {}", fileName); - return; + return null; } - InputFile component = context.fileSystem().inputFile(fp); + return context.fileSystem().inputFile(fp); + } + + static class SwiftFunction extends DefaultInputComponent implements InputComponent { + private String name; + private String key; + private String file; + private int lineNumber; + SwiftFunction(int scannerId, String name) { + super(scannerId); + String[] vals = name.split(" at "); + if (vals.length >= 2) { + this.name = vals[0].replaceAll("\\W",""); + + if (vals[1].contains(":")) { + String[] sp = vals[1].split(":"); + this.file = sp[0].substring(0,sp[0].lastIndexOf(".")); + this.lineNumber = Integer.parseInt(sp[1]); + } else { + this.file = vals[1]; + this.lineNumber = 0; + } + + this.key = String.format("%s.%s:%d", this.file, this.name, this.lineNumber); + } else { + this.key = name; + } + } + @Override + public String key() { + return key; + } + @Override + public boolean isFile() { + return false; + } + } + + private void addComplexityFileMeasures(InputFile component, NodeList values) { + LOGGER.debug("File measures for {}",component.toString()); int complexity = Integer.parseInt(values.item(cyclomaticComplexityIndex).getTextContent()); + context.newMeasure() .on(component) .forMetric(CoreMetrics.COMPLEXITY) @@ -152,7 +194,8 @@ private void addComplexityFileMeasures(String fileName, NodeList values) { .save(); } - private void addComplexityFunctionMeasures(SwiftFunction component, NodeList values) { + private void addComplexityFunctionMeasures(InputComponent component, NodeList values) { + LOGGER.debug("Function measures for {}",component.key()); int complexity = Integer.parseInt(values.item(cyclomaticComplexityIndex).getTextContent()); context.newMeasure() .on(component) @@ -167,45 +210,4 @@ private void addComplexityFunctionMeasures(SwiftFunction component, NodeList val .withValue(numberOfLines) .save(); } - - private static class SwiftFunction implements InputModule { - private String name; - private String key; - private String file; - private int lineNumber; - - public SwiftFunction(String name) { - String[] vals = name.split(" "); - if(vals.length >= 3){ - this.name = vals[0].substring(0,vals[0].indexOf("(")); - this.file = vals[2].substring(0,vals[2].lastIndexOf(":")); - this.lineNumber = Integer.parseInt(vals[2].substring(vals[2].lastIndexOf(":")+1)); - this.key = file.substring(0,file.lastIndexOf('.')+1)+name; - }else{ - this.key = name; - } - } - - @Override - public String key() { - return key; - } - - public String getName(){ - return name; - } - - public String getFile(){ - return file; - } - - public int getLineNumber(){ - return lineNumber; - } - - @Override - public boolean isFile() { - return false; - } - } } diff --git a/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/complexity/LizardSensor.java b/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/complexity/LizardSensor.java index 56b11801..6896fcb9 100644 --- a/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/complexity/LizardSensor.java +++ b/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/complexity/LizardSensor.java @@ -56,10 +56,14 @@ public void describe(SensorDescriptor descriptor) { @Override public void execute(SensorContext context) { - String reportFileName = context.fileSystem().baseDir().getPath() + File.separator + reportPath(); - LOGGER.info("Processing complexity report: {}",reportFileName); + File reportFile = new File(context.fileSystem().baseDir(), reportPath()); + if (!reportFile.isFile()) { + LOGGER.warn("Lizard report file not found at {}", reportFile.getAbsolutePath()); + return; + } + LOGGER.info("Processing complexity report: {}", reportFile.getAbsolutePath()); LizardReportParser parser = new LizardReportParser(context); - parser.parseReport(new File(reportFileName)); + parser.parseReport(reportFile); } } diff --git a/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/coverage/CoberturaReportParser.java b/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/coverage/CoberturaReportParser.java index 1d24c1e8..453612dd 100644 --- a/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/coverage/CoberturaReportParser.java +++ b/sonar-swift-plugin/src/main/java/com/backelite/sonarqube/swift/coverage/CoberturaReportParser.java @@ -36,7 +36,8 @@ import java.io.FileNotFoundException; import java.io.IOException; -public final class CoberturaReportParser { +final class CoberturaReportParser { + private static final Logger LOGGER = LoggerFactory.getLogger(CoberturaReportParser.class); private static final String PACKAGES = "packages"; private static final String CLASSES = "class"; diff --git a/sonar-swift-plugin/src/main/shell/run-sonar-swift.sh b/sonar-swift-plugin/src/main/shell/run-sonar-swift.sh index 44640803..9a06f933 100755 --- a/sonar-swift-plugin/src/main/shell/run-sonar-swift.sh +++ b/sonar-swift-plugin/src/main/shell/run-sonar-swift.sh @@ -283,7 +283,7 @@ if [[ "$workspaceFile" != "" ]] ; then else buildCmdPrefix="-project $projectFile" fi -buildCmd=($XCODEBUILD_CMD clean build $buildCmdPrefix -scheme $appScheme) +buildCmd=($XCODEBUILD_CMD clean build $buildCmdPrefix -scheme "$appScheme") if [[ ! -z "$destinationSimulator" ]]; then buildCmd+=(-destination "$destinationSimulator" -destination-timeout 360 COMPILER_INDEX_STORE_ENABLE=NO) fi @@ -445,7 +445,7 @@ if [ "$fauxpas" = "on" ] && [ "$hasObjC" = "yes" ]; then if [ "$projectCount" = "1" ] then - fauxpas -o json check $projectFile --workspace $workspaceFile --scheme $appScheme > sonar-reports/fauxpas.json + fauxpas -o json check $projectFile --workspace $workspaceFile --scheme "$appScheme" > sonar-reports/fauxpas.json else diff --git a/sonar-swift-plugin/src/test/java/com/backelite/sonarqube/swift/LizardReportParserTest.java b/sonar-swift-plugin/src/test/java/com/backelite/sonarqube/swift/LizardReportParserTest.java new file mode 100644 index 00000000..5b6e4531 --- /dev/null +++ b/sonar-swift-plugin/src/test/java/com/backelite/sonarqube/swift/LizardReportParserTest.java @@ -0,0 +1,98 @@ +/** + * backelite-sonar-swift-plugin - Enables analysis of Swift and Objective-C projects into SonarQube. + * Copyright © 2015 Backelite (${email}) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.backelite.sonarqube.swift; + +import com.backelite.sonarqube.swift.complexity.LizardReportParser; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.sonar.api.batch.fs.*; +import org.sonar.api.batch.measure.Metric; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.measure.NewMeasure; + +import java.io.File; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class LizardReportParserTest { + + @Mock + SensorContext sensorContext; + + @Mock + FileSystem fileSystem; + + @Mock + FilePredicates filePredicates; + + @Mock + FilePredicate filePredicate; + + @Mock + InputFile inputFile; + + @Mock + NewMeasure newMeasure; + + @Captor + ArgumentCaptor hasRelativePathCaptor; + + @Captor + ArgumentCaptor onCaptor; + + @Captor + ArgumentCaptor> forMetricCaptor; + + @Captor + ArgumentCaptor withValueCaptor; + + + @Test + public void parseSimpleFile() { + + LizardReportParser parser = new LizardReportParser(sensorContext); + File xmlFile = new File("src/test/resources/lizard-report.xml"); + + when(sensorContext.newMeasure()).thenReturn(newMeasure); + when(newMeasure.on(onCaptor.capture())).thenReturn(newMeasure); + when(newMeasure.forMetric(forMetricCaptor.capture())).thenReturn(newMeasure); + when(newMeasure.withValue(withValueCaptor.capture())).thenReturn(newMeasure); + + when(sensorContext.fileSystem()).thenReturn(fileSystem); + when(fileSystem.predicates()).thenReturn(filePredicates); + when(filePredicates.hasRelativePath(hasRelativePathCaptor.capture())).thenReturn(filePredicate); + when(fileSystem.hasFiles(filePredicate)).thenReturn(true); + when(fileSystem.inputFile(filePredicate)).thenReturn(inputFile); + + parser.parseReport(xmlFile); + + assertEquals(5, onCaptor.getAllValues().size()); + assertEquals(5, forMetricCaptor.getAllValues().size()); + + assertEquals(Arrays.asList(1, 4, 8, 5, 46), withValueCaptor.getAllValues()); + assertEquals(Arrays.asList("./Folder With Space/File With Space.swift"), hasRelativePathCaptor.getAllValues()); + } + +} diff --git a/sonar-swift-plugin/src/test/java/com/backelite/sonarqube/swift/complexity/LizardReportParserTest.java b/sonar-swift-plugin/src/test/java/com/backelite/sonarqube/swift/complexity/LizardReportParserTest.java new file mode 100644 index 00000000..8ffaed19 --- /dev/null +++ b/sonar-swift-plugin/src/test/java/com/backelite/sonarqube/swift/complexity/LizardReportParserTest.java @@ -0,0 +1,97 @@ +/** + * backelite-sonar-swift-plugin - Enables analysis of Swift and Objective-C projects into SonarQube. + * Copyright © 2015 Backelite (${email}) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.backelite.sonarqube.swift.complexity; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.sonar.api.batch.fs.*; +import org.sonar.api.batch.measure.Metric; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.measure.NewMeasure; + +import java.io.File; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class LizardReportParserTest { + + @Mock + SensorContext sensorContext; + + @Mock + FileSystem fileSystem; + + @Mock + FilePredicates filePredicates; + + @Mock + FilePredicate filePredicate; + + @Mock + InputFile inputFile; + + @Mock + NewMeasure newMeasure; + + @Captor + ArgumentCaptor hasRelativePathCaptor; + + @Captor + ArgumentCaptor onCaptor; + + @Captor + ArgumentCaptor> forMetricCaptor; + + @Captor + ArgumentCaptor withValueCaptor; + + + @Test + public void parseSimpleFile() { + + LizardReportParser parser = new LizardReportParser(sensorContext); + File xmlFile = new File("src/test/resources/lizard-report.xml"); + + when(sensorContext.newMeasure()).thenReturn(newMeasure); + when(newMeasure.on(onCaptor.capture())).thenReturn(newMeasure); + when(newMeasure.forMetric(forMetricCaptor.capture())).thenReturn(newMeasure); + when(newMeasure.withValue(withValueCaptor.capture())).thenReturn(newMeasure); + + when(sensorContext.fileSystem()).thenReturn(fileSystem); + when(fileSystem.predicates()).thenReturn(filePredicates); + when(filePredicates.hasRelativePath(hasRelativePathCaptor.capture())).thenReturn(filePredicate); + when(fileSystem.hasFiles(filePredicate)).thenReturn(true); + when(fileSystem.inputFile(filePredicate)).thenReturn(inputFile); + + parser.parseReport(xmlFile); + + assertEquals(5, onCaptor.getAllValues().size()); + assertEquals(5, forMetricCaptor.getAllValues().size()); + + assertEquals(Arrays.asList(1, 4, 8, 5, 46), withValueCaptor.getAllValues()); + assertEquals(Arrays.asList("./Folder With Space/File With Space.swift"), hasRelativePathCaptor.getAllValues()); + } + +} \ No newline at end of file diff --git a/sonar-swift-plugin/src/test/resources/coverage/dir3/cobertura.xml b/sonar-swift-plugin/src/test/resources/coverage/dir3/cobertura.xml deleted file mode 100644 index e69de29b..00000000 diff --git a/sonar-swift-plugin/src/test/resources/lizard-report.xml b/sonar-swift-plugin/src/test/resources/lizard-report.xml new file mode 100644 index 00000000..04693b13 --- /dev/null +++ b/sonar-swift-plugin/src/test/resources/lizard-report.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + 1 + 4 + 1 + + + + + + + + + + + + + 1 + 46 + 8 + 5 + + + + + + + + + + + \ No newline at end of file diff --git a/swiftlang/pom.xml b/swiftlang/pom.xml index 2adbbae2..1d31dd7b 100644 --- a/swiftlang/pom.xml +++ b/swiftlang/pom.xml @@ -24,7 +24,7 @@ backelite-swift com.backelite.sonarqube - 0.4.4 + 0.4.5-develop2 4.0.0 @@ -36,7 +36,7 @@ com.backelite.sonarqube commons - 0.4.4 + 0.4.5-develop2 diff --git a/swiftlang/src/main/java/com/backelite/sonarqube/swift/SwiftSquidSensor.java b/swiftlang/src/main/java/com/backelite/sonarqube/swift/SwiftSquidSensor.java index 0fc4edf3..52433534 100644 --- a/swiftlang/src/main/java/com/backelite/sonarqube/swift/SwiftSquidSensor.java +++ b/swiftlang/src/main/java/com/backelite/sonarqube/swift/SwiftSquidSensor.java @@ -33,7 +33,6 @@ import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.batch.sensor.issue.NewIssue; import org.sonar.api.batch.sensor.issue.NewIssueLocation; -import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.rule.RuleKey; import org.sonar.api.scan.filesystem.PathResolver; @@ -82,7 +81,6 @@ private void save(Collection squidSourceFiles) { } private void saveMeasures(InputFile inputFile, SourceFile squidFile) { - MeasureUtil.saveMeasure(context, inputFile, CoreMetrics.FILES, squidFile.getInt(SwiftMetric.FILES)); MeasureUtil.saveMeasure(context, inputFile, CoreMetrics.LINES, squidFile.getInt(SwiftMetric.LINES)); MeasureUtil.saveMeasure(context, inputFile, CoreMetrics.NCLOC, squidFile.getInt(SwiftMetric.LINES_OF_CODE)); MeasureUtil.saveMeasure(context, inputFile, CoreMetrics.STATEMENTS, squidFile.getInt(SwiftMetric.STATEMENTS)); @@ -97,14 +95,17 @@ private void saveIssues(InputFile inputFile, SourceFile squidFile) { RuleKey ruleKey = checks.ruleKey((SquidCheck) message.getCheck()); NewIssue issue = context.newIssue() .forRule(ruleKey); - NewIssueLocation dil = new DefaultIssueLocation() - .on(inputFile) - .at(inputFile.selectLine(message.getLine())) - .message(message.getText(Locale.ENGLISH)); + + NewIssueLocation dil = issue.newLocation() + .message(message.getText(Locale.ENGLISH)) + .on(inputFile) + .at(inputFile.selectLine(message.getLine())); issue.at(dil); + if (message.getCost() != null) { issue.gap(message.getCost()); } + issue.save(); } } diff --git a/swiftlang/src/main/java/com/backelite/sonarqube/swift/issues/swiftlint/SwiftLintReportParser.java b/swiftlang/src/main/java/com/backelite/sonarqube/swift/issues/swiftlint/SwiftLintReportParser.java index 14bc9b98..0661345e 100644 --- a/swiftlang/src/main/java/com/backelite/sonarqube/swift/issues/swiftlint/SwiftLintReportParser.java +++ b/swiftlang/src/main/java/com/backelite/sonarqube/swift/issues/swiftlint/SwiftLintReportParser.java @@ -22,8 +22,8 @@ import org.sonar.api.batch.fs.FilePredicate; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.issue.NewIssue; import org.sonar.api.batch.sensor.issue.NewIssueLocation; -import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation; import org.sonar.api.rule.RuleKey; import java.io.*; @@ -68,13 +68,15 @@ private void recordIssue(final String line) { } InputFile inputFile = context.fileSystem().inputFile(fp); - NewIssueLocation dil = new DefaultIssueLocation() - .on(inputFile) - .at(inputFile.selectLine(lineNum)) - .message(message); - context.newIssue() - .forRule(RuleKey.of(SwiftLintRulesDefinition.REPOSITORY_KEY, ruleId)) - .at(dil) + NewIssue issue = context.newIssue(); + + NewIssueLocation issueLocation = issue.newLocation() + .message(message) + .on(inputFile) + .at(inputFile.selectLine(lineNum)); + + issue.forRule(RuleKey.of(SwiftLintRulesDefinition.REPOSITORY_KEY, ruleId)) + .at(issueLocation) .save(); } } diff --git a/swiftlang/src/main/java/com/backelite/sonarqube/swift/issues/tailor/TailorReportParser.java b/swiftlang/src/main/java/com/backelite/sonarqube/swift/issues/tailor/TailorReportParser.java index 4c3d8e53..806c202b 100644 --- a/swiftlang/src/main/java/com/backelite/sonarqube/swift/issues/tailor/TailorReportParser.java +++ b/swiftlang/src/main/java/com/backelite/sonarqube/swift/issues/tailor/TailorReportParser.java @@ -22,8 +22,8 @@ import org.sonar.api.batch.fs.FilePredicate; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.issue.NewIssue; import org.sonar.api.batch.sensor.issue.NewIssueLocation; -import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation; import org.sonar.api.rule.RuleKey; import java.io.*; @@ -73,13 +73,16 @@ private void recordIssue(final String line) { } InputFile inputFile = context.fileSystem().inputFile(fp); - NewIssueLocation dil = new DefaultIssueLocation() - .on(inputFile) - .at(inputFile.selectLine(lineNum)) - .message(message); - context.newIssue() + NewIssue issue = context.newIssue(); + + NewIssueLocation issueLocation = issue.newLocation() + .message(message) + .on(inputFile) + .at(inputFile.selectLine(lineNum)); + + issue .forRule(RuleKey.of(TailorRulesDefinition.REPOSITORY_KEY, ruleId)) - .at(dil) + .at(issueLocation) .save(); } } diff --git a/swiftlang/src/main/java/com/backelite/sonarqube/swift/lang/SwiftFileSystem.java b/swiftlang/src/main/java/com/backelite/sonarqube/swift/lang/SwiftFileSystem.java index b00f0418..82ba8ef4 100644 --- a/swiftlang/src/main/java/com/backelite/sonarqube/swift/lang/SwiftFileSystem.java +++ b/swiftlang/src/main/java/com/backelite/sonarqube/swift/lang/SwiftFileSystem.java @@ -18,18 +18,17 @@ package com.backelite.sonarqube.swift.lang; import com.backelite.sonarqube.swift.lang.core.Swift; -import org.sonar.api.batch.ScannerSide; import org.sonar.api.batch.fs.FilePredicate; import org.sonar.api.batch.fs.FilePredicates; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.scanner.ScannerSide; import javax.annotation.CheckForNull; import java.io.File; import java.util.ArrayList; import java.util.List; - @ScannerSide public class SwiftFileSystem { @@ -49,10 +48,10 @@ public boolean hasSwiftFiles() { return fileSystem.hasFiles(isSwiftLanguage); } - public List sourceFiles() { - Iterable files = fileSystem.files(predicates.and(isSwiftLanguage, isMainTypeFile)); - List list = new ArrayList<>(); - files.iterator().forEachRemaining(list::add); + public List sourceFiles() { + Iterable inputFiles = fileSystem.inputFiles(predicates.and(isSwiftLanguage, isMainTypeFile)); + List list = new ArrayList<>(); + inputFiles.iterator().forEachRemaining(list::add); return list; } diff --git a/swiftlang/src/main/resources/org/sonar/plugins/swiftlint/profile-swiftlint.xml b/swiftlang/src/main/resources/org/sonar/plugins/swiftlint/profile-swiftlint.xml index f5937220..80dfd4b6 100644 --- a/swiftlang/src/main/resources/org/sonar/plugins/swiftlint/profile-swiftlint.xml +++ b/swiftlang/src/main/resources/org/sonar/plugins/swiftlint/profile-swiftlint.xml @@ -27,6 +27,10 @@ SwiftLint closing_brace + + SwiftLint + closure_body_length + SwiftLint closure_end_indentation @@ -39,6 +43,10 @@ SwiftLint closure_spacing + + SwiftLint + collection_alignment + SwiftLint colon @@ -75,6 +83,10 @@ SwiftLint cyclomatic_complexity + + SwiftLint + deployment_target + SwiftLint discarded_notification_center_observer @@ -95,6 +107,10 @@ SwiftLint discouraged_optional_collection + + SwiftLint + duplicate_imports + SwiftLint dynamic_inline @@ -135,6 +151,10 @@ SwiftLint explicit_init + + SwiftLint + explicit_self + SwiftLint explicit_top_level_acl @@ -203,6 +223,10 @@ SwiftLint generic_type_name + + SwiftLint + identical_operands + SwiftLint identifier_name @@ -219,6 +243,10 @@ SwiftLint implicitly_unwrapped_optional + + SwiftLint + inert_defer + SwiftLint is_disjoint @@ -231,6 +259,10 @@ SwiftLint large_tuple + + SwiftLint + last_where + SwiftLint leading_whitespace @@ -247,10 +279,18 @@ SwiftLint legacy_constructor + + SwiftLint + legacy_hashing + SwiftLint legacy_nsgeometry_functions + + SwiftLint + legacy_random + SwiftLint let_var_whitespace @@ -283,14 +323,26 @@ SwiftLint multiline_arguments + + SwiftLint + multiline_arguments_brackets + SwiftLint multiline_function_chains + + SwiftLint + multiline_literal_brackets + SwiftLint multiline_parameters + + SwiftLint + multiline_parameters_brackets + SwiftLint multiple_closures_with_trailing_closure @@ -319,6 +371,10 @@ SwiftLint notification_center_detachment + + SwiftLint + nslocalizedstring_key + SwiftLint number_separator @@ -371,6 +427,10 @@ SwiftLint private_unit_test + + SwiftLint + prohibited_interface_builder + SwiftLint prohibited_super_call @@ -399,6 +459,10 @@ SwiftLint redundant_nil_coalescing + + SwiftLint + redundant_objc_attribute + SwiftLint redundant_optional_initialization @@ -419,6 +483,10 @@ SwiftLint redundant_void_return + + SwiftLint + required_deinit + SwiftLint required_enum_case @@ -447,10 +515,18 @@ SwiftLint statement_position + + SwiftLint + static_operator + SwiftLint strict_fileprivate + + SwiftLint + strong_iboutlet + SwiftLint superfluous_disable_command @@ -471,6 +547,10 @@ SwiftLint todo + + SwiftLint + toggle_bool + SwiftLint trailing_closure @@ -519,14 +599,30 @@ SwiftLint unused_closure_parameter + + SwiftLint + unused_control_flow_label + SwiftLint unused_enumerated + + SwiftLint + unused_import + SwiftLint unused_optional_binding + + SwiftLint + unused_private_declaration + + + SwiftLint + unused_setter_value + SwiftLint valid_ibinspectable @@ -543,14 +639,34 @@ SwiftLint vertical_whitespace + + SwiftLint + vertical_whitespace_between_cases + + + SwiftLint + vertical_whitespace_closing_braces + + + SwiftLint + vertical_whitespace_opening_braces + SwiftLint void_return + + SwiftLint + weak_computed_property + SwiftLint weak_delegate + + SwiftLint + xct_specific_matcher + SwiftLint xctfail_message diff --git a/swiftlang/src/main/resources/org/sonar/plugins/swiftlint/rules.json b/swiftlang/src/main/resources/org/sonar/plugins/swiftlint/rules.json index f1b08ff3..e1762d17 100644 --- a/swiftlang/src/main/resources/org/sonar/plugins/swiftlint/rules.json +++ b/swiftlang/src/main/resources/org/sonar/plugins/swiftlint/rules.json @@ -41,6 +41,13 @@ "description": "Closing brace with closing parenthesis should not have any whitespaces in the middle.", "severity": "MINOR" }, + { + "key": "closure_body_length", + "category": "SwiftLint", + "name": "Closure Body Length", + "description": "Closure bodies should not span too many lines.", + "severity": "MAJOR" + }, { "key": "closure_end_indentation", "category": "SwiftLint", @@ -62,6 +69,13 @@ "description": "Closure expressions should have a single space inside each brace.", "severity": "MINOR" }, + { + "key": "collection_alignment", + "category": "SwiftLint", + "name": "Collection Element Alignment", + "description": "All elements in a collection literal should be vertically aligned", + "severity": "MINOR" + }, { "key": "colon", "category": "SwiftLint", @@ -125,6 +139,13 @@ "description": "Complexity of function bodies should be limited.", "severity": "CRITICAL" }, + { + "key": "deployment_target", + "category": "SwiftLint", + "name": "Deployment Target", + "description": "Availability checks or attributes shouldn't be using older versions that are satisfied by the deployment target.", + "severity": "MINOR" + }, { "key": "discarded_notification_center_observer", "category": "SwiftLint", @@ -160,6 +181,13 @@ "description": "Prefer empty collection over optional collection.", "severity": "MINOR" }, + { + "key": "duplicate_imports", + "category": "SwiftLint", + "name": "Duplicate Imports", + "description": "Imports should be unique.", + "severity": "MINOR" + }, { "key": "dynamic_inline", "category": "SwiftLint", @@ -230,6 +258,13 @@ "description": "Explicitly calling .init() should be avoided.", "severity": "MINOR" }, + { + "key": "explicit_self", + "category": "SwiftLint", + "name": "Explicit Self", + "description": "Instance variables and functions should be explicitly accessed with 'self.'.", + "severity": "MINOR" + }, { "key": "explicit_top_level_acl", "category": "SwiftLint", @@ -349,6 +384,13 @@ "description": "Generic type name should only contain alphanumeric characters, start with an uppercase character and span between 1 and 20 characters in length.", "severity": "MINOR" }, + { + "key": "identical_operands", + "category": "SwiftLint", + "name": "Identical Operands", + "description": "Comparing two identical operands is likely a mistake.", + "severity": "MINOR" + }, { "key": "identifier_name", "category": "SwiftLint", @@ -377,6 +419,13 @@ "description": "Implicitly unwrapped optionals should be avoided when possible.", "severity": "MINOR" }, + { + "key": "inert_defer", + "category": "SwiftLint", + "name": "Inert Defer", + "description": "If defer is at the end of its parent scope, it will be executed right where it is anyway.", + "severity": "MINOR" + }, { "key": "is_disjoint", "category": "SwiftLint", @@ -398,6 +447,13 @@ "description": "Tuples shouldn't have too many members. Create a custom type instead.", "severity": "MINOR" }, + { + "key": "last_where", + "category": "SwiftLint", + "name": "Last Where", + "description": "Prefer using `.last(where:)` over `.filter { }.last` in collections.", + "severity": "MINOR" + }, { "key": "leading_whitespace", "category": "SwiftLint", @@ -426,6 +482,13 @@ "description": "Swift constructors are preferred over legacy convenience functions.", "severity": "MINOR" }, + { + "key": "legacy_hashing", + "category": "SwiftLint", + "name": "Legacy Hashing", + "description": "Prefer using the `hash(into:)` function instead of overriding `hashValue`", + "severity": "MINOR" + }, { "key": "legacy_nsgeometry_functions", "category": "SwiftLint", @@ -433,6 +496,13 @@ "description": "Struct extension properties and methods are preferred over legacy functions", "severity": "MINOR" }, + { + "key": "legacy_random", + "category": "SwiftLint", + "name": "Legacy Random", + "description": "Prefer using `type.random(in:)` over legacy functions.", + "severity": "MINOR" + }, { "key": "let_var_whitespace", "category": "SwiftLint", @@ -489,6 +559,13 @@ "description": "Arguments should be either on the same line, or one per line.", "severity": "MINOR" }, + { + "key": "multiline_arguments_brackets", + "category": "SwiftLint", + "name": "Multiline Arguments Brackets", + "description": "Multiline arguments should have their surrounding brackets in a new line.", + "severity": "MINOR" + }, { "key": "multiline_function_chains", "category": "SwiftLint", @@ -496,6 +573,13 @@ "description": "Chained function calls should be either on the same line, or one per line.", "severity": "MINOR" }, + { + "key": "multiline_literal_brackets", + "category": "SwiftLint", + "name": "Multiline Literal Brackets", + "description": "Multiline literals should have their surrounding brackets in a new line.", + "severity": "MINOR" + }, { "key": "multiline_parameters", "category": "SwiftLint", @@ -503,6 +587,13 @@ "description": "Functions and methods parameters should be either on the same line, or one per line.", "severity": "MINOR" }, + { + "key": "multiline_parameters_brackets", + "category": "SwiftLint", + "name": "Multiline Parameters Brackets", + "description": "Multiline parameters should have their surrounding brackets in a new line.", + "severity": "MINOR" + }, { "key": "multiple_closures_with_trailing_closure", "category": "SwiftLint", @@ -552,6 +643,13 @@ "description": "An object should only remove itself as an observer in `deinit`.", "severity": "MINOR" }, + { + "key": "nslocalizedstring_key", + "category": "SwiftLint", + "name": "NSLocalizedString Key", + "description": "Static strings should be used as key in NSLocalizedString in order to genstrings work.", + "severity": "MINOR" + }, { "key": "number_separator", "category": "SwiftLint", @@ -643,6 +741,13 @@ "description": "Unit tests marked private are silently skipped.", "severity": "MINOR" }, + { + "key": "prohibited_interface_builder", + "category": "SwiftLint", + "name": "Prohibited Interface Builder", + "description": "Creating views using Interface Builder should be avoided.", + "severity": "MINOR" + }, { "key": "prohibited_super_call", "category": "SwiftLint", @@ -692,6 +797,13 @@ "description": "nil coalescing operator is only evaluated if the lhs is nil, coalescing operator with nil as rhs is redundant", "severity": "MINOR" }, + { + "key": "redundant_objc_attribute", + "category": "SwiftLint", + "name": "Redundant @objc Attribute", + "description": "Objective-C attribute (@objc) is redundant in declaration.", + "severity": "MINOR" + }, { "key": "redundant_optional_initialization", "category": "SwiftLint", @@ -727,6 +839,13 @@ "description": "Returning Void in a function declaration is redundant.", "severity": "MINOR" }, + { + "key": "required_deinit", + "category": "SwiftLint", + "name": "Required Deinit", + "description": "Classes should have an explicit deinit method.", + "severity": "MINOR" + }, { "key": "required_enum_case", "category": "SwiftLint", @@ -776,6 +895,13 @@ "description": "Else and catch should be on the same line, one space after the previous declaration.", "severity": "MINOR" }, + { + "key": "static_operator", + "category": "SwiftLint", + "name": "Static Operator", + "description": "Operators should be declared as static functions, not free functions.", + "severity": "MINOR" + }, { "key": "strict_fileprivate", "category": "SwiftLint", @@ -783,6 +909,13 @@ "description": "`fileprivate` should be avoided.", "severity": "MINOR" }, + { + "key": "strong_iboutlet", + "category": "SwiftLint", + "name": "Strong IBOutlet", + "description": "@IBOutlets shouldn't be declared as weak.", + "severity": "MINOR" + }, { "key": "superfluous_disable_command", "category": "SwiftLint", @@ -818,6 +951,13 @@ "description": "TODOs and FIXMEs should be resolved.", "severity": "MINOR" }, + { + "key": "toggle_bool", + "category": "SwiftLint", + "name": "Toggle Bool", + "description": "Prefer `someBool.toggle()` over `someBool = !someBool`.", + "severity": "MINOR" + }, { "key": "trailing_closure", "category": "SwiftLint", @@ -902,6 +1042,13 @@ "description": "Unused parameter in a closure should be replaced with _.", "severity": "MINOR" }, + { + "key": "unused_control_flow_label", + "category": "SwiftLint", + "name": "Unused Control Flow Label", + "description": "Unused control flow label should be removed.", + "severity": "MINOR" + }, { "key": "unused_enumerated", "category": "SwiftLint", @@ -909,6 +1056,13 @@ "description": "When the index or the item is not used, `.enumerated()` can be removed.", "severity": "MINOR" }, + { + "key": "unused_import", + "category": "SwiftLint", + "name": "Unused Import", + "description": "All imported modules should be required to make the file compile.", + "severity": "MINOR" + }, { "key": "unused_optional_binding", "category": "SwiftLint", @@ -916,6 +1070,20 @@ "description": "Prefer `!= nil` over `let _ =`", "severity": "MINOR" }, + { + "key": "unused_private_declaration", + "category": "SwiftLint", + "name": "Unused Private Declaration", + "description": "Private declarations should be referenced in that file.", + "severity": "MINOR" + }, + { + "key": "unused_setter_value", + "category": "SwiftLint", + "name": "Unused Setter Value", + "description": "Setter value is not used.", + "severity": "MINOR" + }, { "key": "valid_ibinspectable", "category": "SwiftLint", @@ -944,6 +1112,27 @@ "description": "Limit vertical whitespace to a single empty line.", "severity": "MINOR" }, + { + "key": "vertical_whitespace_between_cases", + "category": "SwiftLint", + "name": "Vertical Whitespace Between Cases", + "description": "Include a single empty line between switch cases.", + "severity": "MINOR" + }, + { + "key": "vertical_whitespace_closing_braces", + "category": "SwiftLint", + "name": "Vertical Whitespace before Closing Braces", + "description": "Don't include vertical whitespace (empty line) before closing braces.", + "severity": "MINOR" + }, + { + "key": "vertical_whitespace_opening_braces", + "category": "SwiftLint", + "name": "Vertical Whitespace after Opening Braces", + "description": "Don't include vertical whitespace (empty line) after opening braces.", + "severity": "MINOR" + }, { "key": "void_return", "category": "SwiftLint", @@ -951,6 +1140,13 @@ "description": "Prefer `-> Void` over `-> ()`.", "severity": "MINOR" }, + { + "key": "weak_computed_property", + "category": "SwiftLint", + "name": "Weak Computed Property", + "description": "Adding weak to a computed property has no effect.", + "severity": "MINOR" + }, { "key": "weak_delegate", "category": "SwiftLint", @@ -958,6 +1154,13 @@ "description": "Delegates should be weak to avoid reference cycles.", "severity": "MINOR" }, + { + "key": "xct_specific_matcher", + "category": "SwiftLint", + "name": "XCTest Specific Matcher", + "description": "Prefer specific XCTest matchers over `XCTAssertEqual` and `XCTAssertNotEqual`", + "severity": "MINOR" + }, { "key": "xctfail_message", "category": "SwiftLint",