Kotlin analysis and Idea formating (#17)

* kotlin analysis via detekt

* detekt config tuning

* IdeaFormatTask

* format wip

* idea format

* IDEA_HOME var is optional

* fix enum pattern

* analysis wip

* analysis wip

* IDEA_HOME var

* separate task for server

* fix

* name fixes

* fix unix build

* final fix!

* detekt M13.2, some new rules

* do nothing if there is no IDEA_HOME

* NewLineAtEndOfFile false

* отключил проверки unsafeCast и unsafeCall

* supress lint MissingRegistered
This commit is contained in:
Arseniy Borisov 2017-08-07 15:32:34 +03:00 committed by Gavriil
parent 4386bfeb61
commit 51cb1ddb55
8 changed files with 908 additions and 375 deletions

View File

@ -142,7 +142,7 @@
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="arrayInitIndent" value="4"/>
<property name="arrayInitIndent" value="8"/>
</module>
<module name="InnerAssignment"/>
<module name="InnerTypeLast"/>

8
code.style.schemes Normal file
View File

@ -0,0 +1,8 @@
<application>
<component name="CodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value />
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="TouchInstinct" />
</component>
</application>

5
code.style.schemes.xml Normal file
View File

@ -0,0 +1,5 @@
<application>
<component name="CodeStyleSchemeSettings">
<option name="CURRENT_SCHEME_NAME" value="TouchInstinct" />
</component>
</application>

View File

@ -0,0 +1,254 @@
<code_scheme name="TouchInstinct">
<option name="GENERATE_FINAL_LOCALS" value="true" />
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
</value>
</option>
<option name="RIGHT_MARGIN" value="150" />
<option name="JD_P_AT_EMPTY_LINES" value="false" />
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
<option name="ALIGN_MULTILINE_ANNOTATION_PARAMETERS" value="true" />
</JavaCodeStyleSettings>
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" />
<pair source="c" header="h" />
</extensions>
</Objective-C-extensions>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="JAVA">
<option name="RIGHT_MARGIN" value="150" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="RESOURCE_LIST_WRAP" value="1" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="THROWS_LIST_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="THROWS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_WRAP" value="1" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="FOR_STATEMENT_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="ASSIGNMENT_WRAP" value="1" />
<option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<option name="WRAP_LONG_LINES" value="true" />
<option name="PARAMETER_ANNOTATION_WRAP" value="1" />
<option name="VARIABLE_ANNOTATION_WRAP" value="1" />
<option name="ENUM_CONSTANTS_WRAP" value="2" />
</codeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>

View File

@ -0,0 +1,438 @@
apply plugin: 'checkstyle'
apply plugin: 'pmd'
apply plugin: 'cpd'
def getCpdTask
def getPmdTask
def getCheckstyleTask
def getLintTasks
def getKotlinDetektTask
def appendError
def appendCpdErrors
def appendKotlinErrors
def appendCheckstyleErrors
def appendPmdErrors
def appendLintErrors
def normalizeFileUrl
repositories {
maven { url "http://dl.bintray.com/touchin/touchin-tools" }
}
configurations {
pngtastic
detekt
}
import org.apache.tools.ant.taskdefs.condition.Os
ext.getIdeaFormatTask = { isAndroidProject, sources ->
def ideaPath = System.getenv("IDEA_HOME")
if (ideaPath == null) {
return tasks.create((isAndroidProject ? "android" : "server") + "do nothing")
}
return tasks.create((isAndroidProject ? "android" : "server") + "IdeaFormat_$project.name", Exec) {
def inspectionPath
def params = ["-r", "-mask", "*.java,*.kt,*.xml"]
for (String source : sources) {
params.add(source)
}
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
inspectionPath = ['cmd', '/c', "${ideaPath}\\bin\\format.bat ${params.join(" ")}"]
} else {
inspectionPath = ["$ideaPath/bin/format.sh"]
}
commandLine inspectionPath
if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
args = params
}
}
}
ext.getStaticAnalysisTaskNames = { isAndroidProject, sources ->
def tasksNames = new ArrayList<String>()
try {
tasksNames.add(getCpdTask(isAndroidProject, sources))
tasksNames.add(getKotlinDetektTask(isAndroidProject))
if (isAndroidProject) {
tasksNames.add(getCheckstyleTask(sources))
tasksNames.add(getPmdTask(sources))
tasksNames.addAll(getLintTasks())
}
} catch (Exception exception) {
println(exception.toString())
}
return tasksNames
}
ext.generateHtmlReport = { isAndroidProject ->
StringBuilder fullReport = new StringBuilder()
fullReport.append("<table cellpadding='10px' border='2px' cellspacing='0px' cols='4'>");
StringBuilder consoleReport = new StringBuilder()
consoleReport.append("STATIC ANALYSIS RESULTS:")
def count = 0
def previousCount = count
count = appendCpdErrors(fullReport, count, new File("${project.buildDir}/reports/cpd.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mCPD: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normalizeFileUrl("file://${project.buildDir}/reports/cpd.xml"))
} else {
consoleReport.append("\n\u001B[32mCPD: PASSED\u001B[0m")
}
previousCount = count
count = appendKotlinErrors(fullReport, count, new File("${project.buildDir}/reports/kotlin-detekt.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mKotlin-detekt: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normalizeFileUrl("file://${project.buildDir}/reports/kotlin-detekt.xml"))
} else {
consoleReport.append("\n\u001B[32mKotlin-detekt: PASSED\u001B[0m")
}
if (isAndroidProject) {
previousCount = count
count = appendPmdErrors(fullReport, count, new File("${project.buildDir}/reports/pmd.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mPMD: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normalizeFileUrl("file://${project.buildDir}/reports/pmd.html"))
} else {
consoleReport.append("\n\u001B[32mPMD: PASSED\u001B[0m")
}
previousCount = count
count = appendLintErrors(fullReport, count, new File("${project.buildDir}/reports/lint_report.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mLint: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normalizeFileUrl("file://${project.buildDir}/reports/lint.html"))
} else {
consoleReport.append("\n\u001B[32mLint: PASSED\u001B[0m")
}
previousCount = count
count = appendCheckstyleErrors(fullReport, count, new File("${project.buildDir}/reports/checkstyle.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mCheckstyle: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normalizeFileUrl("file://${project.buildDir}/reports/checkstyle.xml"))
} else {
consoleReport.append("\n\u001B[32mCheckstyle: PASSED\u001B[0m")
}
}
fullReport.append("\n</table>\n");
fullReport.append("<script>\n" +
"\tfunction visitPage(file, line) {\n" +
"\t\tfor (port = 63330; port < 63340; port++) {\n" +
"\t\t\tvar theUrl='http://127.0.0.1:' + port + '/file?file=' + file + '&line=' + line;\n" +
"\t\t\tvar xmlHttp = new XMLHttpRequest();\n" +
"\t\t\txmlHttp.open('GET', theUrl, true);\n" +
"\t\t\txmlHttp.send(null);\n" +
"\t\t}\n" +
"\t}\n" +
"</script>");
File fullReportFile = new File("${project.buildDir}/reports/full_report.html")
fullReportFile.write(fullReport.toString());
if (count > 0) {
consoleReport.append("\n\u001B[31mOverall: FAILED (" + count + " errors)\u001B[0m "
+ normalizeFileUrl("file://${project.buildDir}/reports/full_report.html"))
throw new Exception(consoleReport.toString())
} else {
consoleReport.append("\n\u001B[32mOverall: PASSED\u001B[0m")
println(consoleReport.toString())
}
}
normalizeFileUrl = { url ->
return url.replace("\\", "/")
}
appendError = { report, number, analyzer, file, line, errorId, errorLink, description ->
report.append("\n\t<tr>")
report.append("\n\t\t<td>" + number + "</td>")
report.append("\n\t\t<td>" + analyzer + "</td>")
report.append("\n\t\t<td>")
if (analyzer == "Lint") {
report.append("\n\t\t\t<a href='javascript:alert(\"" + errorLink.replace("'", "&apos;") + "\")'>" + description + " (" + errorId + ")</a>")
} else if (analyzer == "Detekt") {
report.append("\n\t\t\t" + description + " (" + errorId + ")")
} else {
report.append("\n\t\t\t<a target='_blank' href='" + errorLink + "'>" + description + " (" + errorId + ")</a>")
}
report.append("\n\t\t</td>")
def indexOfSrc = file.indexOf("src")
def deeplink = (indexOfSrc > 0 ? file.substring(indexOfSrc) : file).replace('\\', '/')
report.append("\n\t\t<td>")
report.append("\n\t\t\t<a href='javascript:visitPage(\"" + deeplink + "\", " + line + ")'>" + file + ":" + line + "</a>")
report.append("\n\t\t</td>")
report.append("\n\t</tr>")
println("\n\u001B[31m" + number + " " + analyzer + ": " + description + " (" + errorId + ")\n\tat " + file + ":" + line + "\u001B[0m")
}
appendKotlinErrors = { report, count, checkstyleFile ->
def rootNode = new XmlParser().parse(checkstyleFile)
for (def fileNode : rootNode.children()) {
if (!fileNode.name().equals("file")) {
continue
}
for (def errorNode : fileNode.children()) {
if (!errorNode.name().equals("error")) {
continue
}
count++
def error = errorNode.attribute("source")
appendError(report, count, "Detekt", fileNode.attribute("name"), errorNode.attribute("line"), error, "", errorNode.attribute("message"))
}
}
return count
}
appendCpdErrors = { report, count, cpdFile ->
def rootNode = new XmlParser().parse(cpdFile)
for (def duplicationNode : rootNode.children()) {
if (!duplicationNode.name().equals("duplication")) {
continue
}
count++
report.append("\n\t<tr>")
report.append("\n\t\t<td>" + count + "</td>")
report.append("\n\t\t<td>CPD</td>")
def fragment = "<b>Code duplication:</b></br></br>"
def links = ""
def duplicationIndex = 0
String duplicationPoints = "";
for (def filePointNode : duplicationNode.children()) {
if (filePointNode.name().equals("file")) {
def file = filePointNode.attribute("path")
def line = filePointNode.attribute("line");
def indexOfSrc = file.indexOf("src")
def deeplink = (indexOfSrc > 0 ? file.substring(indexOfSrc) : file).replace('\\', '/')
if (duplicationIndex > 0) {
links += "\n\t\t\t</br></br>"
}
links += "Code fragment " + (duplicationIndex + 1) + "</br>"
links += "\n\t\t\t<a href='javascript:visitPage(\"" + deeplink + "\", " + line + ")'>" + file + ":" + line + "</a>"
duplicationPoints += "\n\tat " + file + ":" + line
duplicationIndex++
} else if (filePointNode.name().equals("codefragment")) {
fragment += filePointNode.text().replace("\n", "</br>").replace(" ", "&nbsp;")
}
}
report.append("\n\t\t<td>" + fragment + "\n\t\t</td>")
report.append("\n\t\t<td>" + links + "\n\t\t</td>")
report.append("\n\t</tr>")
println("\u001B[31m" + count + " CPD: code duplication" + duplicationPoints + "\u001B[0m")
}
return count
}
appendCheckstyleErrors = { report, count, checkstyleFile ->
def rootNode = new XmlParser().parse(checkstyleFile)
for (def fileNode : rootNode.children()) {
if (!fileNode.name().equals("file")) {
continue
}
for (def errorNode : fileNode.children()) {
if (!errorNode.name().equals("error")) {
continue
}
count++;
def error = errorNode.attribute("source")
def link = "http://checkstyle.sourceforge.net/apidocs/" + error.replace('.', '/') + ".html"
appendError(report, count, "Checkstyle", fileNode.attribute("name"), errorNode.attribute("line"), error, link, errorNode.attribute("message"))
}
}
return count
}
appendPmdErrors = { report, count, pmdFile ->
def rootNode = new XmlParser().parse(pmdFile)
for (def fileNode : rootNode.children()) {
if (!fileNode.name().equals("file")) {
continue
}
for (def errorNode : fileNode.children()) {
if (!errorNode.name().equals("violation")) {
continue;
}
count++;
appendError(report, count, "PMD", fileNode.attribute("name"), errorNode.attribute("beginline"),
errorNode.attribute("rule").trim(), errorNode.attribute("externalInfoUrl").trim(), errorNode.text().trim())
}
}
return count
}
appendLintErrors = { report, count, lintFile ->
def rootNode = new XmlParser().parse(lintFile)
for (def issueNode : rootNode.children()) {
if (!issueNode.name().equals("issue")
|| !issueNode.attribute("severity").equals("Error")) {
continue
}
for (def locationNode : issueNode.children()) {
if (!locationNode.name().equals("location")) {
continue
}
count++;
appendError(report, count, "Lint", locationNode.attribute("file"), locationNode.attribute("line"),
issueNode.attribute("id"), issueNode.attribute("explanation"), issueNode.attribute("message"))
}
}
return count
}
getCpdTask = { isAndroidProject, sources ->
def taskName = (isAndroidProject ? "android" : "server") + "cpd_${project.name}"
tasks.create(taskName, tasks.findByName('cpdCheck').getClass().getSuperclass()) {
minimumTokenCount = 60
source = files(sources)
ignoreFailures = true
reports {
xml {
enabled = true
destination "${project.buildDir}/reports/cpd.xml"
}
}
}
return taskName
}
getPmdTask = { sources ->
def taskName = "pmd_${project.name}"
tasks.create(taskName, Pmd) {
pmdClasspath = configurations.pmd.asFileTree
ruleSetFiles = files("${rootDir}/libraries/BuildScripts/pmd/rulesets/java/android.xml")
ruleSets = []
source files(sources)
ignoreFailures = true
reports {
html {
enabled = true
destination "${project.buildDir}/reports/pmd.html"
}
xml {
enabled = true
destination "${project.buildDir}/reports/pmd.xml"
}
}
}
return taskName
}
getLintTasks = {
def lintTaskNames = new ArrayList<String>()
def lintReleaseTask = tasks.matching {
it.getName().contains("lint") && it.getName().contains("Release")
}.first();
//TODO return on jack lintReleaseTask.dependsOn.clear()
lintTaskNames.add(lintReleaseTask.getName())
def lintDebugTask = tasks.matching {
it.getName().contains("lint") && it.getName().contains("Debug")
}.first();
//TODO return on jack lintDebugTask.dependsOn.clear()
lintTaskNames.add(lintDebugTask.getName())
android.lintOptions.abortOnError = false
android.lintOptions.checkAllWarnings = true
android.lintOptions.warningsAsErrors = false
android.lintOptions.xmlReport = true
android.lintOptions.xmlOutput = file("${project.buildDir}/reports/lint_report.xml")
android.lintOptions.htmlReport = true
android.lintOptions.htmlOutput = file("${project.buildDir}/reports/lint_report.html")
android.lintOptions.lintConfig = file("${rootDir}/libraries/BuildScripts/lint/lint.xml")
return lintTaskNames
}
getCheckstyleTask = { sources ->
def taskName = "checkstyle_${project.name}"
def compileReleaseTask = tasks.matching {
it.getName().contains("compile") && it.getName().contains("Release") && it.getName().contains("Java") && !it.getName().contains("UnitTest")
}.last()
tasks.create(taskName, Checkstyle) {
ignoreFailures = true
showViolations = false
source files(sources)
configFile file("${rootDir}/libraries/BuildScripts/checkstyle/configuration/touchin_checkstyle.xml")
checkstyleClasspath = configurations.checkstyle.asFileTree
classpath = files(System.getenv("ANDROID_HOME") + "/platforms/" + android.compileSdkVersion + "/android.jar") +
files(System.properties.'java.home' + "/lib/rt.jar") +
compileReleaseTask.classpath
reports {
xml {
enabled = true
destination "${project.buildDir}/reports/checkstyle.xml"
}
}
}
return taskName
}
getKotlinDetektTask = { isAndroidProject ->
def taskName = (isAndroidProject ? "android" : "server") + "detektCheck_${project.name}"
tasks.create(taskName, JavaExec) {
main = "io.gitlab.arturbosch.detekt.cli.Main"
classpath = configurations.detekt
def input = "${rootDir}"
def output = "${project.buildDir}/reports/kotlin-detekt.xml"
def config = "${rootDir}/libraries/BuildScripts/kotlin/detekt-config.yml"
// TODO add excludes from rootProject.extensions.findByName("staticAnalysisExcludes")
def filters = ".*src/test.*,.*/resources/.*,.*/tmp/.*"
def params = ['-i', input, '-o', output, '-c', config, '-f', filters]
args(params)
}
return taskName
}
task optimizePng {
doFirst {
def jarArgs = new ArrayList<String>()
jarArgs.add(configurations.pngtastic.asPath)
def relatedPathIndex = "${rootDir}".length() + 1
for (def file : fileTree(dir: "${rootDir}", include: '**/src/**/res/drawable**/*.png')) {
jarArgs.add(file.absolutePath.substring(relatedPathIndex))
}
for (def file : fileTree(dir: "${rootDir}", include: '**/src/**/res/mipmap**/*.png')) {
jarArgs.add(file.absolutePath.substring(relatedPathIndex))
}
javaexec { main = "-jar"; args = jarArgs; workingDir = file("${rootDir}") }
}
}
dependencies {
pmd 'net.sourceforge.pmd:pmd-core:5.5.3'
pmd 'net.sourceforge.pmd:pmd-java:5.5.3'
checkstyle 'ru.touchin:checkstyle:7.6.2-fork'
pngtastic 'com.github.depsypher:pngtastic:1.2'
detekt 'io.gitlab.arturbosch.detekt:detekt-cli:1.0.0.M13.2'
detekt 'io.gitlab.arturbosch.detekt:detekt-formatting:1.0.0.M13.2'
}

View File

@ -1,42 +1,7 @@
apply plugin: 'checkstyle'
apply plugin: 'pmd'
apply plugin: 'cpd'
def getServerProjectSources
def getAndroidProjectSources
def getBinaries
def getSources
def getCpdTask
def getPmdTask
def getLintTasks
def getCheckstyleTask
def getStaticAnalysisTasks
def appendError
def appendCheckstyleErrors
def appendPmdErrors
def appendCpdErrors
def appendLintErrors
def normilizeFileUrl
repositories {
maven {
url "http://dl.bintray.com/touchin/touchin-tools"
}
}
configurations {
pngtastic
}
dependencies {
pmd 'net.sourceforge.pmd:pmd-core:5.5.3'
pmd 'net.sourceforge.pmd:pmd-java:5.5.3'
checkstyle 'ru.touchin:checkstyle:7.6.2-fork'
pngtastic 'com.github.depsypher:pngtastic:1.2'
}
apply from: "${rootDir}/libraries/BuildScripts/gradle/commonStaticAnalysis.gradle"
gradle.projectsEvaluated {
@ -53,243 +18,68 @@ gradle.projectsEvaluated {
"-Werror"
}
def excludes = rootProject.extensions.findByName("staticAnalysisExcludes")
def androidStaticAnalysisTasks = getStaticAnalysisTaskNames(true, getAndroidProjectSources(excludes))
def androidIdeaFormatTask = getIdeaFormatTask(true, getAndroidProjectSources(excludes))
task staticAnalysis {
def excludes = rootProject.extensions.findByName("staticAnalysisExcludes");
dependsOn getStaticAnalysisTasks(getSources(excludes), getBinaries(excludes))
androidStaticAnalysisTasks.each { task ->
tasks.findByName(task).mustRunAfter(androidIdeaFormatTask)
}
dependsOn androidIdeaFormatTask
dependsOn androidStaticAnalysisTasks
doFirst {
StringBuilder fullReport = new StringBuilder()
generateHtmlReport(true)
}
}
fullReport.append("<table cellpadding='10px' border='2px' cellspacing='0px' cols='4'>");
task staticAnalysisWithoutFormatting {
dependsOn androidStaticAnalysisTasks
doFirst {
generateHtmlReport(true)
}
}
StringBuilder consoleReport = new StringBuilder()
consoleReport.append("STATIC ANALYSIS RESULTS:")
def count = 0
def serverStaticAnalysisTasks = getStaticAnalysisTaskNames(false, getServerProjectSources(excludes))
def serverIdeaFormatTask = getIdeaFormatTask(false, getServerProjectSources(excludes))
task serverStaticAnalysis {
serverStaticAnalysisTasks.each { task ->
tasks.findByName(task).mustRunAfter(serverIdeaFormatTask)
}
dependsOn serverIdeaFormatTask
dependsOn serverStaticAnalysisTasks
doFirst {
generateHtmlReport(false)
}
}
def previousCount = count
count = appendCpdErrors(fullReport, count, new File("${project.buildDir}/reports/cpd_${project.name}.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mCPD: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normilizeFileUrl("file://${project.buildDir}/reports/cpd_${project.name}.xml"))
} else {
consoleReport.append("\n\u001B[32mCPD: PASSED\u001B[0m")
}
previousCount = count
count = appendPmdErrors(fullReport, count, new File("${project.buildDir}/reports/pmd_${project.name}.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mPMD: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normilizeFileUrl("file://${project.buildDir}/reports/pmd_${project.name}.html"))
} else {
consoleReport.append("\n\u001B[32mPMD: PASSED\u001B[0m")
}
previousCount = count
count = appendLintErrors(fullReport, count, new File("${project.buildDir}/reports/lint_${project.name}.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mLint: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normilizeFileUrl("file://${project.buildDir}/reports/lint_${project.name}.html"))
} else {
consoleReport.append("\n\u001B[32mLint: PASSED\u001B[0m")
}
previousCount = count
count = appendCheckstyleErrors(fullReport, count, new File("${project.buildDir}/reports/checkstyle_${project.name}.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mCheckstyle: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normilizeFileUrl("file://${project.buildDir}/reports/checkstyle_${project.name}.xml"))
} else {
consoleReport.append("\n\u001B[32mCheckstyle: PASSED\u001B[0m")
}
fullReport.append("\n</table>\n");
fullReport.append("<script>\n" +
"\tfunction visitPage(file, line) {\n" +
"\t\tfor (port = 63330; port < 63340; port++) {\n" +
"\t\t\tvar theUrl='http://127.0.0.1:' + port + '/file?file=' + file + '&line=' + line;\n" +
"\t\t\tvar xmlHttp = new XMLHttpRequest();\n" +
"\t\t\txmlHttp.open('GET', theUrl, true);\n" +
"\t\t\txmlHttp.send(null);\n" +
"\t\t}\n" +
"\t}\n" +
"</script>");
File fullReportFile = new File("${project.buildDir}/reports/full_report.html")
fullReportFile.write(fullReport.toString());
if (count > 0) {
consoleReport.append("\n\u001B[31mOverall: FAILED (" + count + " errors)\u001B[0m "
+ normilizeFileUrl("file://${project.buildDir}/reports/full_report.html"))
throw new Exception(consoleReport.toString())
} else {
consoleReport.append("\n\u001B[32mOverall: PASSED\u001B[0m")
println(consoleReport.toString())
}
task serverStaticAnalysisWithoutFormatting {
dependsOn serverStaticAnalysisTasks
doFirst {
generateHtmlReport(false)
}
}
}
task optimizePng {
doFirst {
def jarArgs = new ArrayList<String>()
jarArgs.add(configurations.pngtastic.asPath)
def relatedPathIndex = "${rootDir}".length() + 1
for (def file : fileTree(dir: "${rootDir}", include: '**/src/**/res/drawable**/*.png')) {
jarArgs.add(file.absolutePath.substring(relatedPathIndex))
getServerProjectSources = { excludes ->
def sources = new ArrayList<String>()
def sourcesDirectory = new File(project.projectDir.path, 'src')
for (def sourceFlavorDirectory : sourcesDirectory.listFiles()) {
def javaSourceDirectory = new File(sourceFlavorDirectory.path, 'java')
def kotlinSourceDirectory = new File(sourceFlavorDirectory.path, 'kotlin')
if (javaSourceDirectory.exists() && javaSourceDirectory.isDirectory()) {
sources.add(javaSourceDirectory.absolutePath)
}
for (def file : fileTree(dir: "${rootDir}", include: '**/src/**/res/mipmap**/*.png')) {
jarArgs.add(file.absolutePath.substring(relatedPathIndex))
}
javaexec { main = "-jar"; args = jarArgs; workingDir = file("${rootDir}") }
}
}
normilizeFileUrl = { url ->
return url.replace("\\", "/");
}
appendError = { report, number, analyzer, file, line, errorId, errorLink, description ->
report.append("\n\t<tr>")
report.append("\n\t\t<td>" + number + "</td>")
report.append("\n\t\t<td>" + analyzer + "</td>")
report.append("\n\t\t<td>")
if (!analyzer.equals("Lint")) {
report.append("\n\t\t\t<a target='_blank' href='" + errorLink + "'>" + description + " (" + errorId + ")</a>")
} else {
report.append("\n\t\t\t<a href='javascript:alert(\"" + errorLink.replace("'", "&apos;") + "\")'>" + description + " (" + errorId + ")</a>")
}
report.append("\n\t\t</td>")
def indexOfSrc = file.indexOf("src")
def deeplink = (indexOfSrc > 0 ? file.substring(indexOfSrc) : file).replace('\\', '/')
report.append("\n\t\t<td>")
report.append("\n\t\t\t<a href='javascript:visitPage(\"" + deeplink + "\", " + line + ")'>" + file + ":" + line + "</a>")
report.append("\n\t\t</td>")
report.append("\n\t</tr>")
println("\n\u001B[31m" + number + " " + analyzer + ": " + description + " (" + errorId + ")\n\tat " + file + ":" + line + "\u001B[0m")
}
appendCheckstyleErrors = { report, count, checkstyleFile ->
def rootNode = new XmlParser().parse(checkstyleFile)
for (def fileNode : rootNode.children()) {
if (!fileNode.name().equals("file")) {
continue
}
for (def errorNode : fileNode.children()) {
if (!errorNode.name().equals("error")) {
continue
}
count++;
def error = errorNode.attribute("source")
def link = "http://checkstyle.sourceforge.net/apidocs/" + error.replace('.', '/') + ".html"
appendError(report, count, "Checkstyle", fileNode.attribute("name"), errorNode.attribute("line"), error, link, errorNode.attribute("message"))
if (kotlinSourceDirectory.exists() && kotlinSourceDirectory.isDirectory()) {
sources.add(kotlinSourceDirectory.absolutePath)
}
}
return count
return sources
}
appendPmdErrors = { report, count, pmdFile ->
def rootNode = new XmlParser().parse(pmdFile)
for (def fileNode : rootNode.children()) {
if (!fileNode.name().equals("file")) {
continue
}
for (def errorNode : fileNode.children()) {
if (!errorNode.name().equals("violation")) {
continue;
}
count++;
appendError(report, count, "PMD", fileNode.attribute("name"), errorNode.attribute("beginline"),
errorNode.attribute("rule").trim(), errorNode.attribute("externalInfoUrl").trim(), errorNode.text().trim())
}
}
return count
}
appendCpdErrors = { report, count, cpdFile ->
def rootNode = new XmlParser().parse(cpdFile)
for (def duplicationNode : rootNode.children()) {
if (!duplicationNode.name().equals("duplication")) {
continue
}
count++;
report.append("\n\t<tr>")
report.append("\n\t\t<td>" + count + "</td>")
report.append("\n\t\t<td>CPD</td>")
def fragment = "<b>Code duplication:</b></br></br>"
def links = ""
def duplicationIndex = 0
String duplicationPoints = "";
for (def filePointNode : duplicationNode.children()) {
if (filePointNode.name().equals("file")) {
def file = filePointNode.attribute("path")
def line = filePointNode.attribute("line");
def indexOfSrc = file.indexOf("src")
def deeplink = (indexOfSrc > 0 ? file.substring(indexOfSrc) : file).replace('\\', '/')
if (duplicationIndex > 0) {
links += "\n\t\t\t</br></br>"
}
links += "Code fragment " + (duplicationIndex + 1) + "</br>"
links += "\n\t\t\t<a href='javascript:visitPage(\"" + deeplink + "\", " + line + ")'>" + file + ":" + line + "</a>"
duplicationPoints += "\n\tat " + file + ":" + line
duplicationIndex++
} else if (filePointNode.name().equals("codefragment")) {
fragment += filePointNode.text().replace("\n", "</br>").replace(" ", "&nbsp;")
}
}
report.append("\n\t\t<td>" + fragment + "\n\t\t</td>")
report.append("\n\t\t<td>" + links + "\n\t\t</td>")
report.append("\n\t</tr>")
println("\u001B[31m" + count + " CPD: code duplication" + duplicationPoints + "\u001B[0m")
}
return count
}
appendLintErrors = { report, count, lintFile ->
def rootNode = new XmlParser().parse(lintFile)
for (def issueNode : rootNode.children()) {
if (!issueNode.name().equals("issue")
|| !issueNode.attribute("severity").equals("Error")) {
continue
}
for (def locationNode : issueNode.children()) {
if (!locationNode.name().equals("location")) {
continue
}
count++;
appendError(report, count, "Lint", locationNode.attribute("file"), locationNode.attribute("line"),
issueNode.attribute("id"), issueNode.attribute("explanation"), issueNode.attribute("message"))
}
}
return count
}
getBinaries = { excludes ->
def binaries = new ArrayList<String>()
for (def project : rootProject.subprojects) {
if (!project.subprojects.isEmpty() || (excludes != null && excludes.contains(project.path))) {
continue
}
binaries.add("${project.projectDir}/build/intermediates/classes")
}
return binaries
}
getSources = { excludes ->
getAndroidProjectSources = { excludes ->
def sources = new ArrayList<String>()
for (def project : rootProject.subprojects) {
if (!project.subprojects.isEmpty() || (excludes != null && excludes.contains(project.path))) {
@ -302,113 +92,18 @@ getSources = { excludes ->
}
for (def sourceFlavorDirectory : sourcesDirectory.listFiles()) {
def sourceSetDirectory = new File(sourceFlavorDirectory.path, 'java');
if (!sourceSetDirectory.exists() || !sourceSetDirectory.isDirectory()) {
continue
def javaSourceDirectory = new File(sourceFlavorDirectory.path, 'java')
def kotlinSourceDirectory = new File(sourceFlavorDirectory.path, 'kotlin')
if (javaSourceDirectory.exists() && javaSourceDirectory.isDirectory()) {
sources.add(javaSourceDirectory.absolutePath)
}
if (kotlinSourceDirectory.exists() && kotlinSourceDirectory.isDirectory()) {
sources.add(kotlinSourceDirectory.absolutePath)
}
sources.add(sourceSetDirectory.absolutePath)
}
}
return sources
}
getCpdTask = { sources ->
def taskName = "cpd_${project.name}"
tasks.create(taskName, tasks.findByName('cpdCheck').getClass().getSuperclass()) {
minimumTokenCount = 60
source = files(sources)
ignoreFailures = true
reports {
xml {
enabled = true
destination "${project.buildDir}/reports/${taskName}.xml"
}
}
}
return taskName
}
getPmdTask = { sources ->
def taskName = "pmd_${project.name}"
tasks.create(taskName, Pmd) {
pmdClasspath = configurations.pmd.asFileTree
ruleSetFiles = files("${rootDir}/libraries/BuildScripts/pmd/rulesets/java/android.xml")
ruleSets = []
source files(sources)
ignoreFailures = true
reports {
html {
enabled = true
destination "${project.buildDir}/reports/${taskName}.html"
}
xml {
enabled = true
destination "${project.buildDir}/reports/${taskName}.xml"
}
}
}
return taskName
}
getCheckstyleTask = { sources ->
def taskName = "checkstyle_${project.name}"
def compileReleaseTask = tasks.matching {
it.getName().contains("compile") && it.getName().contains("Release") && it.getName().contains("Java") && !it.getName().contains("UnitTest")
}.last()
tasks.create(taskName, Checkstyle) {
ignoreFailures = true
showViolations = false
source files(sources)
configFile file("${rootDir}/libraries/BuildScripts/checkstyle/configuration/touchin_checkstyle.xml")
checkstyleClasspath = configurations.checkstyle.asFileTree
classpath = files(System.getenv("ANDROID_HOME") + "/platforms/" + android.compileSdkVersion + "/android.jar") +
files(System.properties.'java.home' + "/lib/rt.jar") +
compileReleaseTask.classpath
reports {
xml {
enabled = true
destination "${project.buildDir}/reports/${taskName}.xml"
}
}
}
return taskName
}
getLintTasks = {
def lintTaskNames = new ArrayList<String>()
def lintReleaseTask = tasks.matching {
it.getName().contains("lint") && it.getName().contains("Release")
}.first();
//TODO return on jack lintReleaseTask.dependsOn.clear()
lintTaskNames.add(lintReleaseTask.getName())
def lintDebugTask = tasks.matching {
it.getName().contains("lint") && it.getName().contains("Debug")
}.first();
//TODO return on jack lintDebugTask.dependsOn.clear()
lintTaskNames.add(lintDebugTask.getName())
android.lintOptions.abortOnError = false
android.lintOptions.checkAllWarnings = true
android.lintOptions.warningsAsErrors = false
android.lintOptions.xmlReport = true
android.lintOptions.xmlOutput = file("${project.buildDir}/reports/lint_${project.name}.xml")
android.lintOptions.htmlReport = true
android.lintOptions.htmlOutput = file("${project.buildDir}/reports/lint_${project.name}.html")
android.lintOptions.lintConfig = file("${rootDir}/libraries/BuildScripts/lint/lint.xml")
return lintTaskNames
}
getStaticAnalysisTasks = { sources, binaries ->
def tasksNames = new ArrayList<String>()
try {
tasksNames.add(getCpdTask(sources))
tasksNames.add(getCheckstyleTask(sources))
tasksNames.add(getPmdTask(sources))
tasksNames.addAll(getLintTasks())
} catch (Exception exception) {
println(exception.toString())
}
return tasksNames
}

142
kotlin/detekt-config.yml Normal file
View File

@ -0,0 +1,142 @@
autoCorrect: true
build:
warningThreshold: 1
failThreshold: 100500
weights:
complexity: 2
formatting: 0
LongParameterList: 1
comments: 0.5
potential-bugs:
active: true
DuplicateCaseInWhenExpression:
active: true
EqualsWithHashCodeExist:
active: true
ExplicitGarbageCollectionCall:
active: true
LateinitUsage:
active: false
UnsafeCallOnNullableType:
active: false
UnsafeCast:
active: false
performance:
active: true
ForEachOnRange:
active: true
SpreadOperator:
active: true
exceptions:
active: false
empty-blocks:
active: true
complexity:
active: true
LongMethod:
threshold: 40
LongParameterList:
threshold: 10
LargeClass:
threshold: 800
ComplexMethod:
threshold: 10
TooManyFunctions:
threshold: 20
ComplexCondition:
threshold: 6
code-smell:
active: true
FeatureEnvy:
threshold: 0.5
weight: 0.45
base: 0.5
formatting:
active: true
useTabs: true
Indentation:
active: false
indentSize: 4
ConsecutiveBlankLines:
active: true
autoCorrect: false
MultipleSpaces:
active: true
autoCorrect: true
SpacingAfterComma:
active: true
autoCorrect: true
SpacingAfterKeyword:
active: true
autoCorrect: true
SpacingAroundColon:
active: true
autoCorrect: true
SpacingAroundCurlyBraces:
active: true
autoCorrect: true
SpacingAroundOperator:
active: true
autoCorrect: true
TrailingSpaces:
active: true
autoCorrect: true
UnusedImports:
active: true
autoCorrect: true
OptionalSemicolon:
active: true
autoCorrect: true
OptionalUnit:
active: true
autoCorrect: true
ExpressionBodySyntax:
active: true
autoCorrect: false
ExpressionBodySyntaxLineBreaks:
active: true
autoCorrect: false
OptionalReturnKeyword:
active: true
autoCorrect: false
style:
active: true
NewLineAtEndOfFile:
active: false
ForbiddenComment:
active: true
values: 'STOPSHIP:'
WildcardImport:
active: true
MaxLineLength:
active: true
maxLineLength: 150
excludePackageStatements: false
excludeImportStatements: false
NamingConventionViolation:
active: true
variablePattern: '^[a-z][a-z0-9][a-zA-Z0-9]*$'
constantPattern: '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'
methodPattern: '^[a-z][a-z0-9][a-zA-Z0-9_]*$'
classPattern: '^[A-Z][a-zA-Z0-9]*$'
enumEntryPattern: '^[A-Z][a-zA-Z0-9_]*$'
comments:
active: false
CommentOverPrivateMethod:
active: false
CommentOverPrivateProperty:
active: false
UndocumentedPublicClass:
active: false
UndocumentedPublicFunction:
active: false

View File

@ -8,16 +8,7 @@
<ignore regexp="A newer version of de.ruedigermoeller:fst than .+ is available: .+"/>
</issue>
<!-- todo: lint bug? -->
<issue id="MissingRegistered" severity="error">
<ignore
regexp="Class referenced in the layout file, `ru.touchin.roboswag.components.views.TypefacedTextView`, was not found in the project or the libraries"/>
<ignore
regexp="Class referenced in the layout file, `ru.touchin.roboswag.components.views.TypefacedEditText`, was not found in the project or the libraries"/>
<ignore
regexp="Class referenced in the layout file, `ru.touchin.roboswag.components.views.AspectRatioFrameLayout`, was not found in the project or the libraries"/>
<ignore
regexp="Class referenced in the layout file, `ru.touchin.roboswag.components.views.MaterialLoadingBar`, was not found in the project or the libraries"/>
</issue>
<issue id="MissingRegistered" severity="ignore"/>
<!-- todo: lint bug? -->
<issue id="MissingPermission" severity="ignore"/>
<!-- todo: lint bug? -->