commit 454a87faa298e5d1a36fb3233b71243cb237787f Author: ArtiSmarti Date: Sun Oct 16 02:41:25 2016 +0200 Add project baseline diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d22960f06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,155 @@ +#Mac-User :> +.DS_Store + +# Created by https://www.gitignore.io + +### Eclipse ### +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.project +.classpath + +### IDEA ### +*.idea + +/local.properties +/build + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + +### Java ### +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### LaTeX ### +*.acn +*.acr +*.alg +*.aux +*.bbl +*.blg +*.dvi +*.fdb_latexmk +*.glg +*.glo +*.gls +*.ilg +*.ind +*.ist +*.lof +*.log +*.lot +*.maf +*.mtc +*.mtc0 +*.nav +*.nlo +*.out +*.pdfsync +*.ps +*.snm +*.synctex.gz +*.toc +*.vrb +*.xdy +*.tdo + + +## Intermediate documents: +*-converted-to.* +# these rules might exclude image files for figures etc. +# *.ps +# *.eps +# *.pdf + +## Bibliography auxiliary files (bibtex/biblatex/biber): +*.bcf +*-blx.aux +*-blx.bib +*.brf +*.run.xml + +## Build tool auxiliary files: +*.synctex.gz(busy) + +## Auxiliary and intermediate files from other packages: + +# algorithms +*.loa + +# amsthm +*.thm + + +#(e)ledmac/(e)ledpar +*.end +*.[1-9][0-9][0-9] +*.[1-9]R +*.[1-9][0-9]R +*.[1-9][0-9][0-9]R +*.eledsec[1-9] +*.eledsec[1-9]R +*.eledsec[1-9][0-9] +*.eledsec[1-9][0-9]R +*.eledsec[1-9][0-9][0-9] +*.eledsec[1-9][0-9][0-9]R + +# listings +*.lol + +# minted +*.pyg + +# morewrites +*.mw + +# sagetex +*.sagetex.sage +*.sagetex.py +*.sagetex.scmd + +# sympy +*.sout +*.sympy +sympy-plots-for-*.tex/ + +## Maven +target/ + +/classes/ +/out/ +/src/test/kotlin/io/gitlab/arturbosch/detekt/ReproduceSpec.kt diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..0fff7e763 --- /dev/null +++ b/build.gradle @@ -0,0 +1,40 @@ +group 'io.gitlab.arturbosch.detekt' +version '1.0.0.M1' + +buildscript { + ext.kotlin_version = '1.0.4' + ext.spek_version = '1.1.18' + + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M2' + } +} + +apply plugin: 'kotlin' +apply plugin: 'org.junit.platform.gradle.plugin' + +repositories { + mavenCentral() + maven { + url "http://dl.bintray.com/jetbrains/spek" + } +} + +junitPlatform { + engines { + include 'spek' + } +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compile "org.jetbrains.kotlin:kotlin-compiler:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + testCompile "org.jetbrains.spek:spek-api:$spek_version" + testRuntime 'org.junit.platform:junit-platform-launcher:1.0.0-M2' + testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$spek_version" +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..ca78035ef Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..4f635fd1b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Aug 10 21:02:57 CEST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..27309d923 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..832fdb607 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..f94fb56d1 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'detekt' + diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/Junk.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/Junk.kt new file mode 100644 index 000000000..77d1f958a --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/Junk.kt @@ -0,0 +1,20 @@ +package io.gitlab.arturbosch.detekt + +import io.gitlab.arturbosch.detekt.api.Finding + +/** + * @author Artur Bosch + */ + +inline fun Collection.each(action: (T) -> Unit): Unit { + for (element in this) action(element) +} + +fun Any?.print(prefix: String = "", suffix: String = ""): Unit { + println("$prefix$this$suffix") +} + +fun printFindings(result: Pair>) { + result.first.print("Ruleset: ") + result.second.each { it.compact().print("\t") } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/Main.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/Main.kt new file mode 100644 index 000000000..d703d01f8 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/Main.kt @@ -0,0 +1,17 @@ +package io.gitlab.arturbosch.detekt + +import io.gitlab.arturbosch.detekt.core.KtCompiler +import io.gitlab.arturbosch.detekt.default.CodeSmellProvider +import io.gitlab.arturbosch.detekt.default.StyleGuideProvider +import java.nio.file.Paths + +/** + * @author Artur Bosch + */ +fun main(args: Array) { + val compiler = KtCompiler() + val path = Paths.get("./src/test/resources/cases/Default.kt") + val file = compiler.compile(path) + printFindings(CodeSmellProvider().instance().acceptAll(listOf(file))) + printFindings(StyleGuideProvider().instance().acceptAll(listOf(file))) +} diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Findings.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Findings.kt new file mode 100644 index 000000000..37ba4b51e --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Findings.kt @@ -0,0 +1,54 @@ +package io.gitlab.arturbosch.detekt.api + +import org.jetbrains.kotlin.diagnostics.DiagnosticUtils +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.getTextWithLocation +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +/** + * @author Artur Bosch + */ + +interface Finding { + val id: String + fun compact(): String +} + +data class CodeSmell(override val id: String, val location: Location) : Finding { + override fun compact(): String { + return "$id - ${location.text} - ${location.file}" + } +} + +data class Location(val source: SourceLocation, + val text: TextLocation, + val locationString: String, + val file: String) { + + companion object { + fun of(element: KtElement): Location { + val start = startLineAndColumn(element) + val sourceLocation = SourceLocation(start.line, start.column) + val textLocation = TextLocation(element.startOffset, element.endOffset) + return Location(sourceLocation, textLocation, + element.getTextWithLocation(), element.getContainingKtFile().name) + } + + private fun startLineAndColumn(element: KtElement) = DiagnosticUtils.getLineAndColumnInPsiFile( + element.getContainingKtFile(), element.textRange) + } + +} + +data class SourceLocation(val line: Int, val column: Int) { + override fun toString(): String { + return "($line,$column)" + } +} + +data class TextLocation(val start: Int, val end: Int) { + override fun toString(): String { + return "($start,$end)" + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Junk.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Junk.kt new file mode 100644 index 000000000..3800b304f --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Junk.kt @@ -0,0 +1,24 @@ +package io.gitlab.arturbosch.detekt.api + +import com.intellij.lang.ASTNode +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtPsiUtil + +/** + * @author Artur Bosch + */ + +private val identifierRegex = Regex("[aA-zZ]+([-][aA-zZ]+)*") + +fun validateIdentifier(id: String) { + require(id.matches(identifierRegex), { "id must match [aA-zZ]+([-][aA-zZ]+)*" }) +} + +fun ASTNode.visitTokens(currentNode: (node: ASTNode) -> Unit) { + currentNode(this) + getChildren(null).forEach { it.visitTokens(currentNode) } +} + +fun ASTNode.visit(visitor: KastVisitor) { + KtPsiUtil.visitChildren(this.psi as KtElement, visitor, null) +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/api/KastVisitor.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/KastVisitor.kt new file mode 100644 index 000000000..a4a6a4751 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/KastVisitor.kt @@ -0,0 +1,10 @@ +package io.gitlab.arturbosch.detekt.api + +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid + +/** + * @author artur + */ +open class KastVisitor : KtTreeVisitorVoid() { + +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Rule.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Rule.kt new file mode 100644 index 000000000..2cf00242d --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Rule.kt @@ -0,0 +1,38 @@ +package io.gitlab.arturbosch.detekt.api + +import com.intellij.lang.ASTNode + +/** + * @author Artur Bosch + */ +abstract class Rule(val id: String, val severity: Severity = Rule.Severity.Minor) : KastVisitor() { + + enum class Severity { + CodeSmell, Style, Warning, Defect, Minor, Major, Security + } + + private var _findings: MutableList = mutableListOf() + val findings: List + get() = _findings.toList() + + init { + validateIdentifier(id) + } + + open fun visit(root: ASTNode) { + preVisit(root) + root.visit(this) + postVisit(root) + } + + protected open fun postVisit(root: ASTNode) { + } + + protected open fun preVisit(root: ASTNode) { + _findings = mutableListOf() + } + + protected fun addFindings(vararg finding: Finding) { + _findings.addAll(finding) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/api/RuleSet.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/RuleSet.kt new file mode 100644 index 000000000..cb80d3f82 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/RuleSet.kt @@ -0,0 +1,28 @@ +package io.gitlab.arturbosch.detekt.api + +import org.jetbrains.kotlin.psi.KtFile + +/** + * @author Artur Bosch + */ +class RuleSet(val id: String, val rules: List) { + + init { + validateIdentifier(id) + } + + fun acceptAll(files: List): Pair> { + return id to files.flatMap { accept(it) } + } + + private fun accept(file: KtFile): List { + val findings: MutableList = mutableListOf() + val root = file.node + rules.forEach { + it.visit(root) + findings += it.findings + } + return findings + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/api/RuleSetProvider.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/RuleSetProvider.kt new file mode 100644 index 000000000..7ff67bc40 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/RuleSetProvider.kt @@ -0,0 +1,9 @@ +package io.gitlab.arturbosch.detekt.api + +/** + * @author Artur Bosch + */ +interface RuleSetProvider { + + fun instance(): RuleSet +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/api/SpecificRules.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/SpecificRules.kt new file mode 100644 index 000000000..43d17e8ff --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/SpecificRules.kt @@ -0,0 +1,9 @@ +package io.gitlab.arturbosch.detekt.api + +/** + * @author Artur Bosch + */ + +abstract class MetricThresholdRule(id: String, val threshold: Int, severity: Severity = Rule.Severity.Minor) : Rule(id, severity) + +abstract class MetricThresholdCodeSmellRule(id: String, threshold: Int) : MetricThresholdRule(id, threshold, Rule.Severity.CodeSmell) diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/api/TokenRule.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/TokenRule.kt new file mode 100644 index 000000000..e3b130492 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/api/TokenRule.kt @@ -0,0 +1,17 @@ +package io.gitlab.arturbosch.detekt.api + +import com.intellij.lang.ASTNode + +/** + * @author Artur Bosch + */ +abstract class TokenRule(id: String) : Rule(id) { + + override fun visit(root: ASTNode) { + preVisit(root) + root.visitTokens { procedure(it) } + postVisit(root) + } + + abstract fun procedure(node: ASTNode): Unit +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/core/Detekt.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/Detekt.kt new file mode 100644 index 000000000..3ed15960e --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/Detekt.kt @@ -0,0 +1,38 @@ +package io.gitlab.arturbosch.detekt.core + +import io.gitlab.arturbosch.detekt.api.Finding +import io.gitlab.arturbosch.detekt.api.RuleSetProvider +import java.net.URLClassLoader +import java.nio.file.Files +import java.nio.file.Path +import java.util.ServiceLoader + +/** + * @author Artur Bosch + */ +open class Detekt(project: Path, + val ruleSets: List = listOf(), + pathFilters: List = listOf(), + parallelCompilation: Boolean = false) { + + private val compiler: KtTreeCompiler + + init { + require(Files.exists(project)) { "Given project path does not exist!" } + val filters = pathFilters.map(::PathFilter) + compiler = KtTreeCompiler(project, filters, parallelCompilation) + } + + fun run(): Map> { + val ktFiles = compiler.compile() + val providers = loadProviders() + return providers.map { it.instance().acceptAll(ktFiles) }.toMap() + } + + private fun loadProviders(): ServiceLoader { + val urls = ruleSets.map { it.toUri().toURL() }.toTypedArray() + val detektLoader = URLClassLoader(urls) + return ServiceLoader.load(RuleSetProvider::class.java, detektLoader) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/core/Junk.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/Junk.kt new file mode 100644 index 000000000..242fce18a --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/Junk.kt @@ -0,0 +1,16 @@ +package io.gitlab.arturbosch.detekt.core + +import java.nio.file.Files +import java.nio.file.Path +import java.util.stream.Collectors +import java.util.stream.Stream + +/** + * @author Artur Bosch + */ + +fun Stream.toList(): List = collect(Collectors.toList()) + +fun Path.isFile(): Boolean = Files.isRegularFile(this) +fun Path.isDirectory(): Boolean = Files.isDirectory(this) +fun Path.hasEnding(ending: String): Boolean = this.toAbsolutePath().toString().endsWith(ending) \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/core/KtCompiler.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/KtCompiler.kt new file mode 100644 index 000000000..2a9fec6ea --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/KtCompiler.kt @@ -0,0 +1,32 @@ +package io.gitlab.arturbosch.detekt.core + +import com.intellij.openapi.util.Disposer +import com.intellij.psi.PsiFileFactory +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.psi.KtFile +import java.nio.file.Files +import java.nio.file.Path + +/** + * @author Artur Bosch + */ +class KtCompiler { + + private val psiFileFactory: PsiFileFactory + + init { + val project = KotlinCoreEnvironment.createForProduction(Disposer.newDisposable(), + CompilerConfiguration(), EnvironmentConfigFiles.JVM_CONFIG_FILES).project + psiFileFactory = PsiFileFactory.getInstance(project) + } + + fun compile(path: Path): KtFile { + require(path.isFile()) { "Given path should be a regular file!" } + val file = path.normalize() + val content = String(Files.readAllBytes(file)) + return psiFileFactory.createFileFromText(file.fileName.toString(), KotlinLanguage.INSTANCE, content) as KtFile + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/core/KtTreeCompiler.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/KtTreeCompiler.kt new file mode 100644 index 000000000..936998335 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/KtTreeCompiler.kt @@ -0,0 +1,44 @@ +package io.gitlab.arturbosch.detekt.core + +import org.jetbrains.kotlin.psi.KtFile +import java.nio.file.Files +import java.nio.file.Path +import java.util.stream.Stream + +/** + * @author Artur Bosch + */ +class KtTreeCompiler(val project: Path, + val filters: List = listOf(), + val parallel: Boolean = false) { + + private val compiler = KtCompiler() + + fun compile(): List { + return if (project.isFile()) { + listOf(compiler.compile(project)) + } else if (project.isDirectory()) { + compileInternal( + if (parallel) { + Files.walk(project).parallel() + } else { + Files.walk(project) + } + ) + } else { + throw IllegalArgumentException("Provided project path $project is not a file/dir." + + " Defekt cannot work with it!") + } + } + + private fun compileInternal(sequentialStream: Stream): List { + return sequentialStream + .filter(Path::isFile) + .filter { it.hasEnding("kt") } + .filter { notIgnored(it) } + .map { compiler.compile(it) } + .toList() + } + + private fun notIgnored(path: Path) = !filters.any { it.matches(path) } +} diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/core/PathFilter.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/PathFilter.kt new file mode 100644 index 000000000..826f345b5 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/PathFilter.kt @@ -0,0 +1,12 @@ +package io.gitlab.arturbosch.detekt.core + +import java.nio.file.Path + +/** + * @author Artur Bosch + */ +class PathFilter(val regex: String) { + fun matches(path: Path): Boolean { + return path.toAbsolutePath().toString().matches(Regex(regex)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/core/debug/DebugRuleSetProvider.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/debug/DebugRuleSetProvider.kt new file mode 100644 index 000000000..d9b4674e9 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/debug/DebugRuleSetProvider.kt @@ -0,0 +1,16 @@ +package io.gitlab.arturbosch.detekt.core.debug + +import io.gitlab.arturbosch.detekt.api.RuleSet +import io.gitlab.arturbosch.detekt.api.RuleSetProvider + +/** + * @author Artur Bosch + */ +object DebugRuleSetProvider : RuleSetProvider { + override fun instance(): RuleSet { + return RuleSet("debug", listOf( + ElementPrinter(), + TokenPrinter() + )) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/core/debug/ElementPrinter.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/debug/ElementPrinter.kt new file mode 100644 index 000000000..dbc5a5484 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/debug/ElementPrinter.kt @@ -0,0 +1,16 @@ +package io.gitlab.arturbosch.detekt.core.debug + +import com.intellij.psi.PsiElement +import io.gitlab.arturbosch.detekt.api.Rule +import io.gitlab.arturbosch.detekt.print + +/** + * @author Artur Bosch + */ +class ElementPrinter : Rule("ElementPrinter") { + + override fun visitElement(element: PsiElement) { + element.print() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/core/debug/TokenPrinter.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/debug/TokenPrinter.kt new file mode 100644 index 000000000..f74771c6e --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/core/debug/TokenPrinter.kt @@ -0,0 +1,16 @@ +package io.gitlab.arturbosch.detekt.core.debug + +import com.intellij.lang.ASTNode +import io.gitlab.arturbosch.detekt.api.TokenRule +import io.gitlab.arturbosch.detekt.print + +/** + * @author Artur Bosch + */ +class TokenPrinter : TokenRule("TokenPrinter") { + + override fun procedure(node: ASTNode) { + node.print() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/default/CodeSmellProvider.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/default/CodeSmellProvider.kt new file mode 100644 index 000000000..3dbad9ca9 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/default/CodeSmellProvider.kt @@ -0,0 +1,20 @@ +package io.gitlab.arturbosch.detekt.default + +import io.gitlab.arturbosch.detekt.api.RuleSet +import io.gitlab.arturbosch.detekt.api.RuleSetProvider +import io.gitlab.arturbosch.detekt.rules.LargeClass +import io.gitlab.arturbosch.detekt.rules.LongMethod +import io.gitlab.arturbosch.detekt.rules.LongParameterList + +/** + * @author Artur Bosch + */ +class CodeSmellProvider : RuleSetProvider { + override fun instance(): RuleSet { + return RuleSet("code-smell", listOf( + LongParameterList(), + LongMethod(), + LargeClass() + )) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/default/StyleGuideProvider.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/default/StyleGuideProvider.kt new file mode 100644 index 000000000..1546199b5 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/default/StyleGuideProvider.kt @@ -0,0 +1,18 @@ +package io.gitlab.arturbosch.detekt.default + +import io.gitlab.arturbosch.detekt.api.RuleSet +import io.gitlab.arturbosch.detekt.api.RuleSetProvider +import io.gitlab.arturbosch.detekt.rules.NoElseInWhenExpression +import io.gitlab.arturbosch.detekt.rules.WildcardImport + +/** + * @author Artur Bosch + */ +class StyleGuideProvider : RuleSetProvider { + override fun instance(): RuleSet { + return RuleSet("style", listOf( + WildcardImport(), + NoElseInWhenExpression() + )) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/Junk.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/Junk.kt new file mode 100644 index 000000000..43a06c504 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/Junk.kt @@ -0,0 +1,10 @@ +package io.gitlab.arturbosch.detekt.rules + +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtExpression + +/** + * @author Artur Bosch + */ + +fun KtExpression?.asBlockExpression(): KtBlockExpression? = if (this is KtBlockExpression) this else null diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/LargeClass.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/LargeClass.kt new file mode 100644 index 000000000..4321fd1b0 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/LargeClass.kt @@ -0,0 +1,87 @@ +package io.gitlab.arturbosch.detekt.rules + +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Location +import io.gitlab.arturbosch.detekt.api.MetricThresholdCodeSmellRule +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtIfExpression +import org.jetbrains.kotlin.psi.KtLoopExpression +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtTryExpression +import org.jetbrains.kotlin.psi.KtWhenEntry +import org.jetbrains.kotlin.psi.KtWhenExpression + +/** + * @author Artur Bosch + */ +class LargeClass(threshold: Int = 70) : MetricThresholdCodeSmellRule("LargeClass", threshold) { + + private var loc = 0 + + private fun inc() { + loc += 1 + } + + override fun visitClassOrObject(classOrObject: KtClassOrObject) { + loc = 0 + classOrObject.getBody()?.let { + loc += it.declarations.size + } + inc() + super.visitClassOrObject(classOrObject) + if (loc > 70) { + addFindings(CodeSmell(id, Location.of(classOrObject))) + } + } + + override fun visitNamedFunction(function: KtNamedFunction) { + val body: KtBlockExpression? = function.bodyExpression.asBlockExpression() + body?.let { + loc += body.statements.size + } + super.visitNamedFunction(function) + } + + override fun visitIfExpression(expression: KtIfExpression) { + expression.then?.let { + loc += it.children.size + } + expression.`else`?.let { + loc += it.children.size + } + super.visitIfExpression(expression) + } + + override fun visitLoopExpression(loopExpression: KtLoopExpression) { + loopExpression.body?.let { + loc += it.children.size + } + super.visitLoopExpression(loopExpression) + } + + override fun visitWhenExpression(expression: KtWhenExpression) { + loc += expression.children.filter { it is KtWhenEntry }.size + super.visitWhenExpression(expression) + } + + override fun visitTryExpression(expression: KtTryExpression) { + loc += expression.tryBlock.statements.size + loc += expression.catchClauses.size + expression.catchClauses.map { it.catchBody?.children?.size }.forEach { loc += it ?: 0 } + expression.finallyBlock?.finalExpression?.statements?.size?.let { loc += it } + super.visitTryExpression(expression) + } + + override fun visitCallExpression(expression: KtCallExpression) { + val lambdaArguments = expression.lambdaArguments + if (lambdaArguments.size > 0) { + val lambdaArgument = lambdaArguments[0] + lambdaArgument.getLambdaExpression().bodyExpression?.let { + loc += it.statements.size + } + } + super.visitCallExpression(expression) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/LongMethod.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/LongMethod.kt new file mode 100644 index 000000000..3bb54a8b1 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/LongMethod.kt @@ -0,0 +1,22 @@ +package io.gitlab.arturbosch.detekt.rules + +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Location +import io.gitlab.arturbosch.detekt.api.MetricThresholdCodeSmellRule +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtNamedFunction + +/** + * @author Artur Bosch + */ +class LongMethod(threshold: Int = 10) : MetricThresholdCodeSmellRule("LongMethod", threshold) { + + override fun visitNamedFunction(function: KtNamedFunction) { + val body: KtBlockExpression? = function.bodyExpression.asBlockExpression() + body?.let { + val size = body.statements.size + if (size > threshold) addFindings(CodeSmell(id, Location.of(function))) + } + super.visitNamedFunction(function) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/LongParameterList.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/LongParameterList.kt new file mode 100644 index 000000000..e32f7e1ec --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/LongParameterList.kt @@ -0,0 +1,18 @@ +package io.gitlab.arturbosch.detekt.rules + +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Location +import io.gitlab.arturbosch.detekt.api.MetricThresholdCodeSmellRule +import org.jetbrains.kotlin.psi.KtParameterList + +/** + * @author Artur Bosch + */ +class LongParameterList(threshold: Int = 5) : MetricThresholdCodeSmellRule("LongParameterList", threshold) { + + override fun visitParameterList(list: KtParameterList) { + if (list.parameters.size > threshold) { + addFindings(CodeSmell(id, Location.of(list))) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/NoElseInWhenExpression.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/NoElseInWhenExpression.kt new file mode 100644 index 000000000..c85e536bc --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/NoElseInWhenExpression.kt @@ -0,0 +1,17 @@ +package io.gitlab.arturbosch.detekt.rules + +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Location +import io.gitlab.arturbosch.detekt.api.Rule +import org.jetbrains.kotlin.psi.KtWhenExpression + +/** + * @author Artur Bosch + */ +class NoElseInWhenExpression : Rule("NoElseInWhenExpression", Severity.Defect) { + + override fun visitWhenExpression(expression: KtWhenExpression) { + if (expression.elseExpression == null) addFindings(CodeSmell(id, Location.of(expression))) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/WildcardImport.kt b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/WildcardImport.kt new file mode 100644 index 000000000..f2f569798 --- /dev/null +++ b/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/WildcardImport.kt @@ -0,0 +1,20 @@ +package io.gitlab.arturbosch.detekt.rules + +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Location +import io.gitlab.arturbosch.detekt.api.Rule +import org.jetbrains.kotlin.psi.KtImportDirective + +/** + * @author Artur Bosch + */ +class WildcardImport : Rule("WildcardImport", Severity.Style) { + + override fun visitImportDirective(importDirective: KtImportDirective) { + val import = importDirective.importPath?.pathStr + if (import != null && import.contains("*")) { + addFindings(CodeSmell(id, Location.of(importDirective))) + } + } +} + diff --git a/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider b/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider new file mode 100644 index 000000000..d56e40c59 --- /dev/null +++ b/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider @@ -0,0 +1,2 @@ +io.gitlab.arturbosch.detekt.default.CodeSmellProvider +io.gitlab.arturbosch.detekt.default.StyleGuideProvider \ No newline at end of file diff --git a/src/test/kotlin/io/gitlab/arturbosch/detekt/KT.kt b/src/test/kotlin/io/gitlab/arturbosch/detekt/KT.kt new file mode 100644 index 000000000..90609bdac --- /dev/null +++ b/src/test/kotlin/io/gitlab/arturbosch/detekt/KT.kt @@ -0,0 +1,30 @@ +package io.gitlab.arturbosch.detekt + +import com.intellij.lang.ASTNode +import io.gitlab.arturbosch.detekt.api.Finding +import io.gitlab.arturbosch.detekt.core.KtCompiler +import java.nio.file.Path +import java.nio.file.Paths + +/** + * @author Artur Bosch + */ + +private val compiler = KtCompiler() + +enum class Case(val file: String) { + CasesFolder("/cases"), + Default("/cases/Default.kt"), + NestedLongMethods("/cases/NestedLongMethods.kt"); + + fun path(): Path = Paths.get(Case::class.java.getResource(file).path) +} + +fun load(case: Case): ASTNode { + return compiler.compile(case.path()).node +} + +fun Map.Entry>.printFindings() { + key.print("Ruleset: ") + value.each { it.compact().print("\t") } +} \ No newline at end of file diff --git a/src/test/kotlin/io/gitlab/arturbosch/detekt/core/DetektSpec.kt b/src/test/kotlin/io/gitlab/arturbosch/detekt/core/DetektSpec.kt new file mode 100644 index 000000000..1cd2c346d --- /dev/null +++ b/src/test/kotlin/io/gitlab/arturbosch/detekt/core/DetektSpec.kt @@ -0,0 +1,26 @@ +package io.gitlab.arturbosch.detekt.core + +import io.gitlab.arturbosch.detekt.Case +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.context +import org.jetbrains.spek.api.dsl.describe +import org.jetbrains.spek.api.dsl.it +import kotlin.test.assertTrue + +/** + * @author Artur Bosch + */ +class DetektSpec : Spek({ + + context("default providers must be registered in META-INF/services") { + describe("run detekt on test cases with no additional rulesets") { + + val detekt = Detekt(Case.CasesFolder.path()) + + it("should detect findings of more than one provider") { + assertTrue { detekt.run().size >= 1 } + } + + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/io/gitlab/arturbosch/detekt/core/KtTreeCompilerSpec.kt b/src/test/kotlin/io/gitlab/arturbosch/detekt/core/KtTreeCompilerSpec.kt new file mode 100644 index 000000000..832bb8911 --- /dev/null +++ b/src/test/kotlin/io/gitlab/arturbosch/detekt/core/KtTreeCompilerSpec.kt @@ -0,0 +1,33 @@ +package io.gitlab.arturbosch.detekt.core + +import io.gitlab.arturbosch.detekt.Case +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.describe +import org.jetbrains.spek.api.dsl.it +import kotlin.test.assertNull +import kotlin.test.assertTrue + +/** + * @author Artur Bosch + */ +class KtTreeCompilerSpec : Spek({ + + describe("tree compiler functionality") { + it("should compile all files") { + val ktFiles = KtTreeCompiler(Case.CasesFolder.path()).compile() + assertTrue(ktFiles.size >= 2, "It should compile more than two files, but did ${ktFiles.size}") + } + + it("should filter the file 'Default.kt'") { + val filter = PathFilter(".*Default.kt") + val ktFiles = KtTreeCompiler(Case.CasesFolder.path(), listOf(filter)).compile() + val ktFile = ktFiles.find { it.name == "Default.kt" } + assertNull(ktFile, "It should have no Default.kt file") + } + + it("should also compile regular files") { + assertTrue { KtTreeCompiler(Case.Default.path()).compile().size == 1 } + } + } + +}) diff --git a/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/CommonSpec.kt b/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/CommonSpec.kt new file mode 100644 index 000000000..eacf9eb66 --- /dev/null +++ b/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/CommonSpec.kt @@ -0,0 +1,25 @@ +package io.gitlab.arturbosch.detekt.rules + +import io.gitlab.arturbosch.detekt.Case +import io.gitlab.arturbosch.detekt.api.Rule +import io.gitlab.arturbosch.detekt.load +import org.jetbrains.spek.api.SubjectSpek +import org.jetbrains.spek.api.dsl.describe +import org.jetbrains.spek.api.dsl.it +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * @author Artur Bosch + */ +class CommonSpec : SubjectSpek({ + subject { WildcardImport() } + val root = load(Case.Default) + + describe("running specified rule") { + it("should detect one finding") { + subject.visit(root) + assertTrue(subject.findings.size == 1) + } + } +}) diff --git a/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/LargeClassSpec.kt b/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/LargeClassSpec.kt new file mode 100644 index 000000000..d034005bb --- /dev/null +++ b/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/LargeClassSpec.kt @@ -0,0 +1,12 @@ +package io.gitlab.arturbosch.detekt.rules + +import org.jetbrains.spek.api.SubjectSpek +import org.jetbrains.spek.api.dsl.itBehavesLike + +/** + * @author Artur Bosch + */ +class LargeClassSpec : SubjectSpek({ + subject { LargeClass() } + itBehavesLike(CommonSpec::class) +}) \ No newline at end of file diff --git a/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/LongMethodSpec.kt b/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/LongMethodSpec.kt new file mode 100644 index 000000000..8df71a265 --- /dev/null +++ b/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/LongMethodSpec.kt @@ -0,0 +1,23 @@ +package io.gitlab.arturbosch.detekt.rules + +import io.gitlab.arturbosch.detekt.Case +import io.gitlab.arturbosch.detekt.load +import org.jetbrains.spek.api.SubjectSpek +import org.jetbrains.spek.api.dsl.describe +import org.jetbrains.spek.api.dsl.it +import kotlin.test.assertEquals + +/** + * @author Artur Bosch + */ +class LongMethodSpec : SubjectSpek({ + subject { LongMethod() } + + describe("nested functions can be long") { + it("should find two long methods") { + val root = load(Case.NestedLongMethods) + subject.visit(root) + assertEquals(subject.findings.size, 2) + } + } +}) diff --git a/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/LongParameterListSpec.kt b/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/LongParameterListSpec.kt new file mode 100644 index 000000000..bc211f25d --- /dev/null +++ b/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/LongParameterListSpec.kt @@ -0,0 +1,12 @@ +package io.gitlab.arturbosch.detekt.rules + +import org.jetbrains.spek.api.SubjectSpek +import org.jetbrains.spek.api.dsl.itBehavesLike + +/** + * @author Artur Bosch + */ +class LongParameterListSpec : SubjectSpek({ + subject { LongParameterList() } + itBehavesLike(CommonSpec::class) +}) diff --git a/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/NoElseInWhenSpec.kt b/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/NoElseInWhenSpec.kt new file mode 100644 index 000000000..75b5d6f06 --- /dev/null +++ b/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/NoElseInWhenSpec.kt @@ -0,0 +1,12 @@ +package io.gitlab.arturbosch.detekt.rules + +import org.jetbrains.spek.api.SubjectSpek +import org.jetbrains.spek.api.dsl.itBehavesLike + +/** + * @author Artur Bosch + */ +class NoElseInWhenSpec : SubjectSpek({ + subject { NoElseInWhenExpression() } + itBehavesLike(CommonSpec::class) +}) \ No newline at end of file diff --git a/src/test/resources/cases/Default.kt b/src/test/resources/cases/Default.kt new file mode 100644 index 000000000..5196a6733 --- /dev/null +++ b/src/test/resources/cases/Default.kt @@ -0,0 +1,143 @@ +package cases + +import io.gitlab.arturbosch.detekt.print +import java.util.* + +/** + * @author Artur Bosch + */ +@Suppress("unused") +class Default { + + /** + * Used for wildcard import spec + */ + fun noop() { + ArrayList() + } + + /** + * 8, no else + */ + fun IfTHenElse() { + if (true) { + while (true) { + println("Loop") + } + } else { + println("NoLoop") + when ("string") { + "string" -> println("s") + else -> println("s") + } + } + } + + fun NoElseInWhen() { + // no else in when + when(true) { + false -> print() + } + } + + /** + * 3 + */ + fun letStatements() { + 5.let { + it.print() + } + } + + /** + * 8, no finally + */ + fun tryStatements() { + try { + 5.print() + } catch (e: Error) { + print(e) + } catch (ex: Error) { + print(ex) + } finally { + print("Finally") + } + } + + /** + * Used for LM and LPL spec + * 11 + */ + fun long(a: Int, b: Int, c: Int, d: Int, e: Int, s: String) { + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + } + + /** + * 55 + */ + fun forLargeClass() { + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + println() + } +} \ No newline at end of file diff --git a/src/test/resources/cases/NestedLongMethods.kt b/src/test/resources/cases/NestedLongMethods.kt new file mode 100644 index 000000000..6f8a3bee1 --- /dev/null +++ b/src/test/resources/cases/NestedLongMethods.kt @@ -0,0 +1,33 @@ +package cases + +/** + * @author Artur Bosch + */ +@Suppress("unused") +class NestedLongMethods { + fun long(a: Int, b: Int, c: Int, d: Int, e: Int, s: String) { + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + fun localLPL(a: Int, b: Int, c: Int, d: Int, e: Int, s: String) { + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + println("$a,$b,$c,$d,$e,$s") + } + } +} \ No newline at end of file