From e9a8433e8c97c2fad661abde521ca7bdb767053a Mon Sep 17 00:00:00 2001 From: rpelmegov Date: Thu, 23 Mar 2023 16:51:28 +0300 Subject: [PATCH] add staticAnalysis gradle --- gradle/scripts/staticAnalysis.gradle | 243 +++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 gradle/scripts/staticAnalysis.gradle diff --git a/gradle/scripts/staticAnalysis.gradle b/gradle/scripts/staticAnalysis.gradle new file mode 100644 index 0000000..44a8d9b --- /dev/null +++ b/gradle/scripts/staticAnalysis.gradle @@ -0,0 +1,243 @@ +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 {}