import groovy.transform.InheritConstructors import groovy.transform.ToString import groovy.transform.TupleConstructor import groovy.xml.MarkupBuilder apply plugin: 'io.gitlab.arturbosch.detekt' task staticAnalysis { dependsOn 'cleanAnalyzerReports' if (static_analyzer_enable_detekt) { dependsOn ':detekt' } if (static_analyzer_enable_lint) { dependsOn ':app:lint' } doLast { def reportDir = new File(static_analyzer_report_dir) def detektErrors = collectDetektCodeAnalyzerErrors(reportDir) def lintErrors = collectLintCodeAnalyzerErrors(reportDir) def totalReportFile = new File(reportDir, 'total_report.html') totalReportFile << generateReportHtml(detektErrors, lintErrors) println("|----------------------static_analyzer--------------------------------") println("| static_analyzer_fail_on_lint_error : $static_analyzer_fail_on_lint_error") println("| static_analyzer_fail_on_detect_error : $static_analyzer_fail_on_detect_error") println("|") println("| total_detekt_errors : ${detektErrors.size()}" + (static_analyzer_enable_detekt ? "" : " [DISABLED]")) println("| total_lint_errors : ${lintErrors.size()}" + (static_analyzer_enable_lint ? "" : " [DISABLED]")) println("| total_errors : ${detektErrors.size() + lintErrors.size()}") println("|") println("| Successfully generated HtmlTotalReport at ${totalReportFile.path}") println("|-------------------------------------------------------------------") if (static_analyzer_fail_on_lint_error || static_analyzer_fail_on_detect_error) { def message = "" def hasLintError = static_analyzer_fail_on_lint_error && !lintErrors.isEmpty() def hasDetektError = static_analyzer_fail_on_detect_error && !detektErrors.isEmpty() if (hasLintError) { message += "There is ${lintErrors.size()} lint errors; " } if (hasDetektError) { message += "There is ${detektErrors.size()} detekt errors; " } if (hasLintError || hasDetektError) { message += "\n See report at ${totalReportFile.path}" throw new StaticAnalysisException(message) } } } } detekt { debug = true //filters = ".*test.*,.*androidTest.*,.*//*resources/.*,.*//*build/.*" input = files("$rootDir/app/src/main/kotlin", "$rootDir/data/src/main/kotlin", "$rootDir/domain/src/main/kotlin") config = files("$static_analyzer_config_dir/detekt-config.yml") reports { xml { enabled = true destination = file("$static_analyzer_report_dir/report_detekt.xml") } html { enabled = false } } } task cleanAnalyzerReports(type: Delete) { delete static_analyzer_report_dir } static def collectDetektCodeAnalyzerErrors(File reportDir) { def detektReport = new File("$reportDir/report_detekt.xml") if (detektReport == null) { println("can't find detekt report") return Collections.emptyList() } return new XmlSlurper() .parse(detektReport) .children() .inject(new ArrayList()) { acc, file -> println(file) def fileName = file.@name file.children().each { error -> acc.add( new CodeAnalyzerError( position: "$fileName:${error.@line ?: "-"}:${error.@column ?: "-"}", severity: error.@severity, message: error.@message, source: error.@source ) ) } acc } .sort { error -> error.position } } static def collectLintCodeAnalyzerErrors(File reportDir) { def lintReports = reportDir.listFiles().findAll { file -> file.name.matches("^lint.*xml\$") } if (lintReports == null || lintReports.isEmpty()) { println("can't find any lint report") return Collections.emptyList() } return lintReports.inject(new ArrayList()) { acc, report -> acc.addAll(parseLintReport(report)) acc }.sort { error -> error.position } } static def parseLintReport(File report) { return new XmlSlurper() .parse(report) .children() .inject(new ArrayList()) { acc, issue -> def fileName = issue.location.@file def line = issue.location.@line ?: "-" def column = issue.location.@column ?: "-" acc.add( new CodeAnalyzerError( position: "$fileName:$line:$column", severity: issue.@severity, message: issue.@message, source: issue.@id ) ) acc } } MarkupBuilder.metaClass.appendErrorsTable { errors -> delegate.table { tr { th(["class": "Position"], "Position") th(["class": "Severity"], "Severity") th(["class": "Source"], "Source") th(["class": "Message"], "Message") } errors.forEach { error -> tr { td(error.position) td(error.severity.toLowerCase()) td(error.source) td(error.message) } } } } static def generateReportHtml(List detektErrors, List lintErrors) { def sb = new StringWriter() def html = new MarkupBuilder(sb) html.doubleQuotes = true html.expandEmptyElements = true html.omitEmptyAttributes = false html.omitNullAttributes = false html.html(lang: 'en') { head { meta('http-equiv': '"Content-Type" content="text/html; charset=utf-8"') title('Static analyzer report') mkp.yieldUnescaped(getErrorTableStyle()) } body { println("class: " + owner) h2("Detekt errors: ${detektErrors.size()}") appendErrorsTable(detektErrors) h2("Lint errors: ${lintErrors.size()}") appendErrorsTable(lintErrors) } } return sb.toString() } static def getErrorTableStyle() { return """ """ } @ToString @TupleConstructor class CodeAnalyzerError { String position, severity, message, source } @InheritConstructors class StaticAnalysisException extends GradleException {}