UselessPostfixExpression: Postfix on field in return

detekt marks the postfix expressions in the following case as useless. However the value of the field will be updated and affects the next call of the function.

```
class Test {
    private var runningId: Long = 0

    fun getId(): Long {
        return runningId++
    }
}
```

diff --git a/detekt-rules/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/UselessPostfixExpression.kt b/detekt-rules/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/UselessPostfixExpression.kt
index 6bf686276f..fc71df417a 100644
--- a/detekt-rules/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/UselessPostfixExpression.kt
+++ b/detekt-rules/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/UselessPostfixExpression.kt
@@ -6,21 +6,37 @@ import io.gitlab.arturbosch.detekt.api.Entity
 import io.gitlab.arturbosch.detekt.api.Issue
 import io.gitlab.arturbosch.detekt.api.Rule
 import io.gitlab.arturbosch.detekt.api.Severity
+import io.gitlab.arturbosch.detekt.rules.collectByType
 import org.jetbrains.kotlin.psi.KtBinaryExpression
+import org.jetbrains.kotlin.psi.KtClass
 import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.kotlin.psi.KtNamedFunction
 import org.jetbrains.kotlin.psi.KtPostfixExpression
+import org.jetbrains.kotlin.psi.KtProperty
 import org.jetbrains.kotlin.psi.KtReturnExpression
+import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType

 class UselessPostfixExpression(config: Config = Config.empty) : Rule(config) {

 	override val issue: Issue = Issue("UselessPostfixExpression", Severity.Defect,
 			"The incremented or decremented value is unused. This value is replaced with the original value.")

+	var properties = setOf<String?>()
+
+	override fun visitClass(klass: KtClass) {
+		properties = klass.getProperties()
+				.map { it.name }
+				.toSet()
+		super.visitClass(klass)
+	}
+
 	override fun visitReturnExpression(expression: KtReturnExpression) {
 		val postfixExpression = expression.returnedExpression as? KtPostfixExpression
-		if (postfixExpression != null) {
+
+		if (postfixExpression != null && postfixExpression.shouldBeReported()) {
 			report(postfixExpression)
 		}
+
 		getPostfixExpressionChilds(expression.returnedExpression)
 				?.forEach { report(it) }
 	}
@@ -39,6 +55,20 @@ class UselessPostfixExpression(config: Config = Config.empty) : Rule(config) {
 		}
 	}

+	private fun KtPostfixExpression.shouldBeReported(): Boolean {
+		val functionProperties = this.getNonStrictParentOfType(KtNamedFunction::class.java)
+				?.collectByType<KtProperty>()
+				?.map { it.name }
+				?.toSet()
+		val postfixReceiverName = this.baseExpression?.text
+
+		if (functionProperties != null && functionProperties.contains(postfixReceiverName)) {
+			return true
+		}
+		return !properties.contains(postfixReceiverName)
+
+	}
+
 	private fun report(postfixExpression: KtPostfixExpression) {
 		report(CodeSmell(issue, Entity.from(postfixExpression)))
 	}
diff --git a/detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/UselessPostfixExpressionSpec.kt b/detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/UselessPostfixExpressionSpec.kt
index 28bc2b1a2c..f28da7dd04 100644
--- a/detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/UselessPostfixExpressionSpec.kt
+++ b/detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/UselessPostfixExpressionSpec.kt
@@ -1,10 +1,10 @@
 package io.gitlab.arturbosch.detekt.rules.bugs

 import io.gitlab.arturbosch.detekt.test.lint
+import org.assertj.core.api.Assertions.assertThat
 import org.jetbrains.spek.api.dsl.describe
 import org.jetbrains.spek.api.dsl.it
 import org.jetbrains.spek.subject.SubjectSpek
-import org.assertj.core.api.Assertions.assertThat

 class UselessPostfixExpressionSpec : SubjectSpek<UselessPostfixExpression>({
 	subject { UselessPostfixExpression() }
@@ -33,5 +33,36 @@ class UselessPostfixExpressionSpec : SubjectSpek<UselessPostfixExpression>({
 				}"""
 			assertThat(subject.lint(code)).hasSize(2)
 		}
+
+		it("should not report field increments") {
+			val code = """
+				class Test {
+					private var runningId: Long = 0
+
+					fun increment() {
+						runningId++
+					}
+
+					fun getId(): Long {
+						return runningId++
+					}
+				}
+				"""
+			assertThat(subject.lint(code)).isEmpty()
+		}
+
+		it("should detect properties shadowing fields that are incremented") {
+			val code = """
+				class Test {
+					private var runningId: Long = 0
+
+					fun getId(): Long {
+						val runningId: Long = 0
+						return runningId++
+					}
+				}
+				"""
+			assertThat(subject.lint(code)).hasSize(1)
+		}
 	}
 })

UselessPostfixExpression: Postfix on field in return
detekt marks the postfix expressions in the following case as useless. However the value of the field will be updated and affects the next call of the function.

```
class Test {
    private var runningId: Long = 0

    fun getId(): Long {
        return runningId++
    }
}
```

package io.gitlab.arturbosch.detekt

import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.DefaultTask
import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
import org.gradle.api.tasks.TaskAction

/**
 * @author Artur Bosch
 */
open class DetektCheckTask : DefaultTask() {

	init {
		description = "Analyze your kotlin code with detekt."
		group = "verification"
	}

	@TaskAction
	fun check() {
		val detektExtension = project.extensions.getByName("detekt") as DetektExtension

		val configuration = project.buildscript.configurations.maybeCreate("detektCheck")
		project.buildscript.dependencies.add(configuration.name, DefaultExternalModuleDependency(
				"io.gitlab.arturbosch.detekt", "detekt-cli", detektExtension.version))

		project.javaexec {
			it.main = "io.gitlab.arturbosch.detekt.cli.Main"
			it.classpath = configuration
			it.args(detektExtension.profileArgumentsOrDefault(project))
		}
	}
}

package io.gitlab.arturbosch.detekt

import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.DefaultTask
import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
import org.gradle.api.tasks.TaskAction

/**
 * @author Artur Bosch
 */
open class DetektCreateBaselineTask : DefaultTask() {

	init {
		description = "Creates a detekt baseline on the given --baseline path."
		group = "verification"
	}

	private val createBaseline = "--create-baseline"

	@TaskAction
	fun baseline() {
		val detektExtension = project.extensions.getByName("detekt") as DetektExtension

		val configuration = project.buildscript.configurations.maybeCreate("detektBaseline")
		project.buildscript.dependencies.add(configuration.name, DefaultExternalModuleDependency(
				"io.gitlab.arturbosch.detekt", "detekt-cli", detektExtension.version))

		project.javaexec {
			it.main = "io.gitlab.arturbosch.detekt.cli.Main"
			it.classpath = configuration
			it.args(detektExtension.profileArgumentsOrDefault(project).plus(createBaseline))
		}
	}

}

package io.gitlab.arturbosch.detekt

import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.DefaultTask
import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
import org.gradle.api.tasks.TaskAction

/**
 * @author Artur Bosch
 */
open class DetektGenerateConfigTask : DefaultTask() {

	init {
		description = "Generate a detekt configuration file inside your project."
		group = "verification"
	}

	@TaskAction
	fun generateConfig() {
		val detektExtension = project.extensions.getByName("detekt") as DetektExtension

		val configuration = project.buildscript.configurations.maybeCreate("detektConfig")
		project.buildscript.dependencies.add(configuration.name, DefaultExternalModuleDependency(
				"io.gitlab.arturbosch.detekt", "detekt-cli", detektExtension.version))

		project.javaexec {
			it.main = "io.gitlab.arturbosch.detekt.cli.Main"
			it.classpath = configuration
			it.args("--input", project.projectDir.absolutePath, "--generate-config")
		}
	}
}

package io.gitlab.arturbosch.detekt

import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

/**
 * @author Artur Bosch
 */
open class DetektIdeaFormatTask : DefaultTask() {

	init {
		description = "Uses an external idea installation to format your code."
		group = "verification"
	}

	@TaskAction
	fun format() {
		with(project.extensions.getByName("detekt") as DetektExtension) {
			if (debug) println("$ideaExtension")
			startProcess(ideaFormatArgs())
		}
	}
}

package io.gitlab.arturbosch.detekt

import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

/**
 * @author Artur Bosch
 */
open class DetektIdeaInspectionTask : DefaultTask() {

	init {
		description = "Uses an external idea installation to inspect your code."
		group = "verification"
	}

	@TaskAction
	fun inspect() {
		with(project.extensions.getByName("detekt") as DetektExtension) {
			if (debug) println("$ideaExtension")
			startProcess(ideaInspectArgs())
		}
	}
}

package io.gitlab.arturbosch.detekt

import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.DefaultTask
import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
import org.gradle.api.tasks.TaskAction

/**
 * @author Artur Bosch
 */
open class DetektMigrateTask : DefaultTask() {

	init {
		description = "Migrate your kotlin code with detekt."
		group = "verification"
	}

	@TaskAction
	fun migrate() {
		val detektExtension = project.extensions.getByName("detekt") as DetektExtension

		val migration = project.buildscript.configurations.maybeCreate("detektMigrate")
		project.buildscript.dependencies.add(migration.name, DefaultExternalModuleDependency(
				"io.gitlab.arturbosch.detekt", "detekt-migration", detektExtension.version))

		project.javaexec {
			it.main = "io.gitlab.arturbosch.detekt.migration.Migration"
			it.classpath = migration
			it.args(detektExtension.profileArgumentsOrDefault(project))
		}
	}

}

package io.gitlab.arturbosch.detekt

import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.Plugin
import org.gradle.api.Project

class DetektPlugin : Plugin<Project> {

	override fun apply(project: Project) {
		project.extensions.create("detekt", DetektExtension::class.java)
		project.task(mapOf("type" to DetektCheckTask::class.java), "detektCheck")
		project.task(mapOf("type" to DetektMigrateTask::class.java), "detektMigrate")
		project.task(mapOf("type" to DetektIdeaFormatTask::class.java), "detektIdeaFormat")
		project.task(mapOf("type" to DetektIdeaInspectionTask::class.java), "detektIdeaInspect")
		project.task(mapOf("type" to DetektGenerateConfigTask::class.java), "detektGenerateConfig")
		project.task(mapOf("type" to DetektCreateBaselineTask::class.java), "detektBaseline")
	}

}

package io.gitlab.arturbosch.detekt

import io.gitlab.arturbosch.detekt.cli.console.BuildFailure
import java.io.BufferedInputStream
import java.io.BufferedReader
import java.io.InputStreamReader

/**
 * @author Artur Bosch
 */
fun startProcess(args: Array<String>) {
	val process = Runtime.getRuntime().exec(args)

	BufferedReader(InputStreamReader(BufferedInputStream(process.inputStream))).use {
		val inputs = it.readLines().joinToString("\n")
		println(inputs)
	}

	BufferedReader(InputStreamReader(BufferedInputStream(process.errorStream))).use {
		val errors = it.readLines().joinToString("\n")
		if (errors.isNotEmpty()) {
			throw BuildFailure(errors)
		}
	}

	process.destroy()
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.psi.KtFile

@Suppress("EmptyFunctionBlock")
abstract class BaseRule(protected val context: Context = DefaultContext()) : DetektVisitor(), Context by context {

	/**
	 * Before starting visiting kotlin elements, a check is performed if this rule should be triggered.
	 * Pre- and post-visit-hooks are executed before/after the visiting process.
	 */
	fun visitFile(root: KtFile) {
		if (visitCondition(root)) {
			clearFindings()
			preVisit(root)
			visit(root)
			postVisit(root)
		}
	}

	open fun visit(root: KtFile) {
		root.accept(this)
	}

	abstract fun visitCondition(root: KtFile): Boolean

	/**
	 * Could be overridden by subclasses to specify a behaviour which should be done before
	 * visiting kotlin elements.
	 */
	protected open fun postVisit(root: KtFile) {
	}

	/**
	 * Could be overridden by subclasses to specify a behaviour which should be done after
	 * visiting kotlin elements.
	 */
	protected open fun preVisit(root: KtFile) {
	}
}

package io.gitlab.arturbosch.detekt.api

/**
 * A code smell is a finding, implementing its behaviour. Use this class to store
 * rule violations.
 *
 * @author Artur Bosch
 */
open class CodeSmell(final override val issue: Issue,
					 override val entity: Entity,
					 override val metrics: List<Metric> = listOf(),
					 override val references: List<Entity> = listOf()) : Finding {

	override val id: String = issue.id

	override fun compact() = "$id - ${entity.compact()}"

	override fun compactWithSignature() = compact() + " - Signature=" + entity.signature

	override fun toString(): String {
		return "CodeSmell(issue=$issue, entity=$entity, metrics=$metrics, references=$references, id='$id')"
	}
}

/**
 * Represents a code smell for which a specific metric and reference entity can be determined which are responsible
 * for the existence of this rule violation.
 */
open class CodeSmellWithReferenceAndMetric(
		issue: Issue, entity: Entity, val reference: Entity, metric: Metric) : ThresholdedCodeSmell(
		issue, entity, metric, references = listOf(reference)) {

	override fun compact() = "$id - $metric - ref=${reference.name} - ${entity.compact()}"
}

/**
 * Represents a code smell for which a specific metric can be determined which is responsible
 * for the existence of this rule violation.
 */
open class ThresholdedCodeSmell(
		issue: Issue, entity: Entity, val metric: Metric, references: List<Entity> = emptyList()) : CodeSmell(
		issue, entity, metrics = listOf(metric), references = references) {
	val value: Int
		get() = metric.value
	val threshold: Int
		get() = metric.threshold

	override fun compact() = "$id - $metric - ${entity.compact()}"
}

package io.gitlab.arturbosch.detekt.api

/**
 * @author Artur Bosch
 */
class CompositeConfig(private val lookFirst: Config, private val lookSecond: Config) : Config {

	override fun subConfig(key: String): Config
			= CompositeConfig(lookFirst.subConfig(key), lookSecond.subConfig(key))

	override fun <T : Any> valueOrDefault(key: String, default: T): T {
		val firstResult = lookFirst.valueOrDefault(key, default)
		return if (firstResult != default) firstResult else lookSecond.valueOrDefault(key, default)
	}

	override fun toString() = "CompositeConfig(lookFirst=$lookFirst, lookSecond=$lookSecond)"

}

package io.gitlab.arturbosch.detekt.api

/**
 * A configuration holds information about how to configure specific rules.
 *
 * @author Artur Bosch
 */
interface Config {

	/**
	 * Tries to retrieve part of the configuration based on given key.
	 */
	fun subConfig(key: String): Config

	/**
	 * Retrieves a sub configuration of value based on given key. If configuration property cannot be found
	 * the specified default value is returned.
	 */
	fun <T : Any> valueOrDefault(key: String, default: T): T

	/**
	 * Is thrown when loading a configuration results in errors.
	 */
	class InvalidConfigurationError(msg: String = "Provided configuration file is invalid:" +
			" Structure must be from type Map<String,Any>!") : RuntimeException(msg)

	companion object {
		/**
		 * An empty configuration with no properties.
		 * This config should only be used in test cases.
		 * Always returns the default value except when 'active' is queried, it returns true .
		 */
		val empty: Config = EmptyConfig
	}
}

/**
 * NOP-implementation of a config object.
 */
internal object EmptyConfig : Config {
	override fun subConfig(key: String) = this
	@Suppress("UNCHECKED_CAST")
	override fun <T : Any> valueOrDefault(key: String, default: T): T = when (key) {
		"active" -> true as T
		else -> default
	}
}

/**
 * Convenient base configuration which parses/casts the configuration value based on the type of the default value.
 */
abstract class BaseConfig : Config {

	protected fun valueOrDefaultInternal(result: Any?, default: Any): Any {
		return try {
			if (result != null) {
				when (result) {
					is String -> tryParseBasedOnDefault(result, default)
					else -> result
				}
			} else {
				default
			}
		} catch (e: ClassCastException) {
			throw IllegalArgumentException("Type of value $result does not match the type of default value $default!")
		} catch (e: NumberFormatException) {
			throw IllegalArgumentException("Type of value $result does not match the type of default value $default!", e)
		}
	}

	private fun tryParseBasedOnDefault(result: String, defaultResult: Any): Any = when (defaultResult) {
		is Int -> java.lang.Integer.parseInt(result)
		is Boolean -> java.lang.Boolean.parseBoolean(result)
		is Double -> java.lang.Double.parseDouble(result)
		is String -> result
		else -> throw ClassCastException()
	}

}

package io.gitlab.arturbosch.detekt.api

/**
 * @author Artur Bosch
 */
interface ConfigAware : Config {

	/**
	 * Id which is used to retrieve the sub config for this rule.
	 */
	val id: String

	/**
	 * Wrapped configuration to use for specified id.
	 */
	val config: Config

	/**
	 * If your rule supports to automatically correct the misbehaviour of underlying smell,
	 * specify your code inside this method call, to allow the user of your rule to trigger auto correction
	 * only when needed.
	 */
	fun withAutoCorrect(block: () -> Unit) {
		if (autoCorrect) {
			block()
		}
	}

	/**
	 * Does this rule have auto correct specified in configuration?
	 */
	val autoCorrect: Boolean get() = valueOrDefault("autoCorrect", true)

	/**
	 * Is this rule specified as active in configuration?
	 * If an rule is not specified in the underlying configuration, we assume it should not be run.
	 */
	val active get() = valueOrDefault("active", false)

	override fun subConfig(key: String): Config
			= config.subConfig(id).subConfig(key)

	override fun <T : Any> valueOrDefault(key: String, default: T)
			= config.subConfig(id).valueOrDefault(key, default)

}

package io.gitlab.arturbosch.detekt.api

import java.io.PrintStream

/**
 * @author Artur Bosch
 */
abstract class ConsoleReport : Extension {

	fun print(printer: PrintStream, detektion: Detektion) {
		render(detektion)?.let {
			printer.println(it)
		}
	}

	abstract fun render(detektion: Detektion): String?
}

package io.gitlab.arturbosch.detekt.api

/**
 * @author Artur Bosch
 */
interface Context {
	val findings: List<Finding>
	fun report(finding: Finding)
	fun report(findings: List<Finding>)
	fun clearFindings()
}

open class DefaultContext : Context {

	/**
	 * Returns a copy of violations for this rule.
	 */
	override val findings
		get() = _findings.toList()

	private var _findings: MutableList<Finding> = mutableListOf()

	/**
	 * Reports a single code smell finding.
	 *
	 * Before adding a finding, it is checked if it is not suppressed
	 * by @Suppress or @SuppressWarnings annotations.
	 */
	override fun report(finding: Finding) {
		val ktElement = finding.entity.ktElement
		if (ktElement == null || !ktElement.isSuppressedBy(finding.id)) {
			_findings.add(finding)
		}
	}

	/**
	 * Reports a list of code smell findings.
	 *
	 * Before adding a finding, it is checked if it is not suppressed
	 * by @Suppress or @SuppressWarnings annotations.
	 */
	override fun report(findings: List<Finding>) {
		findings.forEach { report(it) }
	}

	final override fun clearFindings() {
		_findings = mutableListOf()
	}
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.psi.KtTreeVisitorVoid

/**
 * Base visitor for detekt rules.
 *
 * @author Artur Bosch
 */
open class DetektVisitor : KtTreeVisitorVoid()

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.com.intellij.openapi.util.Key

/**
 * @author Artur Bosch
 */
interface Detektion {
	val findings: Map<String, List<Finding>>
	val notifications: Collection<Notification>
	val metrics: Collection<ProjectMetric>

	fun <V> getData(key: Key<V>): V?
	fun <V> addData(key: Key<V>, value: V)
	fun add(notification: Notification)
	fun add(projectMetric: ProjectMetric)
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtElement

/**
 * Stores information about a specific code fragment.
 *
 * @author Artur Bosch
 */
data class Entity(val name: String,
				  val className: String,
				  val signature: String,
				  val location: Location,
				  val ktElement: KtElement? = null) : Compactable {

	override fun compact() = "[$name] at ${location.compact()}"

	companion object {
		fun from(element: PsiElement, offset: Int = 0): Entity {
			val name = element.searchName()
			val signature = element.buildFullSignature()
			val clazz = element.searchClass()
			return Entity(name, clazz, signature, Location.from(element, offset), element as? KtElement)
		}
	}
}

package io.gitlab.arturbosch.detekt.api

class Excludes(excludeParameter: String) {
	private val excludes = excludeParameter
			.split(",")
			.map { it.trim() }
			.filter { it.isNotBlank() }
			.map { it.removeSuffix("*") }

	fun contains(value: String?) = excludes.any { value?.contains(it, ignoreCase = true) == true }
	fun none(value: String) = !contains(value)
	fun matches(value: String): List<String> = excludes.filter { value.contains(it, ignoreCase = true) }
	fun startWith(name: String?) = excludes.any { name?.startsWith(it) ?: false }
}

package io.gitlab.arturbosch.detekt.api

/**
 * @author Artur Bosch
 */
@Suppress("EmptyFunctionBlock")
interface Extension {
	val id: String get() = javaClass.simpleName
	val priority: Int get() = -1

	fun init(config: Config) {}
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.psi.KtFile

/**
 * Gather additional metrics about the analyzed kotlin file.
 * Pay attention to the thread policy of each function!
 *
 * @author Artur Bosch
 */
@Suppress("EmptyFunctionBlock")
interface FileProcessListener : Extension {

	/**
	 * Use this to gather some additional information for the real onProcess function.
	 * This calculation should be lightweight as this method is called from the main thread.
	 */
	fun onStart(files: List<KtFile>) {}

	/**
	 * Called when processing of a file begins.
	 * This method is called from a thread pool thread. Heavy computations allowed.
	 */
	fun onProcess(file: KtFile) {}

	/**
	 * Called when processing of a file completes.
	 * This method is called from a thread pool thread. Heavy computations allowed.
	 */
	fun onProcessComplete(file: KtFile, findingsForFile: FindingsForFile) {}

	/**
	 * Mainly use this method to save computed metrics from KtFile's to the {@link Detektion} container.
	 * Do not do heavy computations here as this method is called from the main thread.
	 */
	fun onFinish(files: List<KtFile>, result: Detektion) {}
}

class FindingsForFile(findings: List<Pair<String, List<Finding>>>) {

	private val ruleSetFindings: Map<String, List<Finding>> by lazy { findings.toMergedMap() }

	fun queryForRuleSet(name: String) = ruleSetFindings[name]

}

package io.gitlab.arturbosch.detekt.api

import kotlin.reflect.full.memberProperties

/**
 * Base interface of detection findings. Inherits a bunch of useful behaviour
 * from sub interfaces.
 *
 * Basic behaviour of a finding is that is can be assigned to an id and a source code position described as
 * an entity. Metrics and entity references can also considered for deeper characterization.
 *
 * @author Artur Bosch
 */
interface Finding : Compactable, Reflective, HasEntity, HasMetrics {
	val id: String
	val issue: Issue
	val references: List<Entity>
}

/**
 * Describes a source code position.
 */
interface HasEntity {
	val entity: Entity
	val location: Location
		get() = entity.location
	val locationAsString: String
		get() = location.locationString
	val startPosition: SourceLocation
		get() = location.source
	val charPosition: TextLocation
		get() = location.text
	val file: String
		get() = location.file
	val signature: String
		get() = entity.signature
	val name: String
		get() = entity.name
	val inClass: String
		get() = entity.className
}

/**
 * Adds metric container behaviour.
 */
interface HasMetrics {
	val metrics: List<Metric>
	fun metricByType(type: String): Metric? = metrics.find { it.type == type }
}

/**
 * Allows to iterate over attributes reflectively.
 */
interface Reflective {
	fun findAttribute(name: String): Any? = this.javaClass.kotlin.memberProperties.find { it.name == name }?.get(this)
}

/**
 * Provides a compact string representation.
 */
interface Compactable {
	fun compact(): String
	fun compactWithSignature(): String = compact()
}

package io.gitlab.arturbosch.detekt.api

/**
 * @author Artur Bosch
 */
data class Issue(val id: String,
				 val severity: Severity,
				 val description: String = "",
				 val debt: Debt = Debt.TWENTY_MINS) {

	init {
		validateIdentifier(id)
	}

	override fun toString(): String {
		return "Issue(id='$id', severity=$severity, debt=$debt)"
	}
}

/**
 * Rules can classified into different severity grades. Maintainer can choose
 * a grade which is most harmful to their projects.
 */
enum class Severity {
	CodeSmell, Style, Warning, Defect, Minor, Maintainability, Security, Performance

}

/**
 * Debt describes the estimated amount of work needed to fix a given issue.
 */
@Suppress("MagicNumber")
data class Debt(val days: Int = 0, val hours: Int = 0, val mins: Int = 0) {

	init {
		require(days >= 0 && hours >= 0 && mins >= 0)
		require(!(days == 0 && hours == 0 && mins == 0))
	}

	companion object {
		val TWENTY_MINS = Debt(0, 0, 20)
		val TEN_MINS = Debt(0, 0, 10)
		val FIVE_MINS = Debt(0, 0, 5)
	}

	override fun toString(): String {
		return with(StringBuilder()) {
			if (days > 0) append("${days}d ")
			if (hours > 0) append("${hours}h ")
			if (mins > 0) append("${mins}min")
			toString()
		}.trim()
	}
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtStringTemplateEntry
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import java.util.HashMap
import kotlin.reflect.KClass
import kotlin.reflect.KProperty

private val identifierRegex = Regex("[aA-zZ]+([-][aA-zZ]+)*")

/**
 * Checks if given string matches the criteria of an id - [aA-zZ]+([-][aA-zZ]+)* .
 */
internal fun validateIdentifier(id: String) {
	require(id.matches(identifierRegex), { "id must match [aA-zZ]+([-][aA-zZ]+)*" })
}

internal fun ASTNode.visitTokens(currentNode: (node: ASTNode) -> Unit) {
	currentNode(this)
	getChildren(null).forEach { it.visitTokens(currentNode) }
}

/**
 * Tests if this element is part of given PsiElement.
 */
fun PsiElement.isPartOf(clazz: KClass<out PsiElement>) = getNonStrictParentOfType(clazz.java) != null

/**
 * Tests of this element is part of a kotlin string.
 */
fun PsiElement.isPartOfString() = isPartOf(KtStringTemplateEntry::class)

/*
 * When analyzing sub path 'testData' of the kotlin project, CompositeElement.getText() throws
 * a RuntimeException stating 'Underestimated text length' - #65.
 */
@Suppress("TooGenericExceptionCaught")
internal fun getTextSafe(defaultValue: () -> String, block: () -> String) = try {
	block()
} catch (e: RuntimeException) {
	val message = e.message
	if (message != null && message.contains("Underestimated text length")) {
		defaultValue() + "!<UnderestimatedTextLengthException>"
	} else {
		defaultValue()
	}
}

const val PREFIX = "\t- "

fun Any.format(prefix: String = "", suffix: String = "\n") = "$prefix$this$suffix"

class SingleAssign<T> {

	private var initialized = false
	private var _value: Any? = UNINITIALIZED_VALUE

	operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
		if (!initialized) {
			throw IllegalStateException("Property ${property.name} has not been assigned yet!")
		}
		@Suppress("UNCHECKED_CAST")
		return _value as T
	}

	operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
		if (initialized) {
			throw IllegalStateException("Property ${property.name} has already been assigned!")
		}
		_value = value
		initialized = true
	}

	companion object {
		private val UNINITIALIZED_VALUE = Any()
	}
}

fun <K, V> List<Pair<K, List<V>>>.toMergedMap(): Map<K, List<V>> {
    val map = HashMap<K, MutableList<V>>()
    this.forEach {
        map.merge(it.first, it.second.toMutableList(), { l1, l2 -> l1.apply { addAll(l2) } })
    }
    return map
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getTextWithLocation
import org.jetbrains.kotlin.psi.psiUtil.startOffset

/**
 * Specifies a position within a source code fragment.
 *
 * @author Artur Bosch
 */
data class Location(val source: SourceLocation,
					val text: TextLocation,
					val locationString: String,
					val file: String) : Compactable {

	override fun compact() = "$file:$source"

	companion object {
		fun from(element: PsiElement, offset: Int = 0): Location {
			val start = startLineAndColumn(element, offset)
			val sourceLocation = SourceLocation(start.line, start.column)
			val textLocation = TextLocation(element.startOffset + offset, element.endOffset + offset)
			val fileName = element.originalFilePath() ?: element.containingFile.name
			val locationText = element.getTextAtLocationSafe()
			return Location(sourceLocation, textLocation, locationText, fileName)
		}

		@Suppress("TooGenericExceptionCaught")
		fun startLineAndColumn(element: PsiElement, offset: Int = 0): DiagnosticUtils.LineAndColumn {
			return try {
				val range = element.textRange
				DiagnosticUtils.getLineAndColumnInPsiFile(element.containingFile,
						TextRange(range.startOffset + offset, range.endOffset + offset))
			} catch (e: IndexOutOfBoundsException) {
				// #18 - somehow the TextRange is out of bound on '}' leaf nodes, returning fail safe -1
				DiagnosticUtils.LineAndColumn(-1, -1, null)
			}
		}

		private fun PsiElement.originalFilePath()
				= (this.containingFile.viewProvider.virtualFile as LightVirtualFile).originalFile?.name

		private fun PsiElement.getTextAtLocationSafe()
				= getTextSafe({ searchName() }, { getTextWithLocation() })
	}

}

/**
 * Stores line and column information of a location.
 */
data class SourceLocation(val line: Int, val column: Int) {
	override fun toString() = "$line:$column"
}

/**
 * Stores character start and end positions of an text file.
 */
data class TextLocation(val start: Int, val end: Int) {
	override fun toString() = "$start:$end"
}

package io.gitlab.arturbosch.detekt.api

/**
 * Metric type, can be an integer or double value. Internally it is stored as an integer,
 * but the conversion factor and is double attributes can be used to retrieve it as a double value.
 *
 * @author Artur Bosch
 */
data class Metric(val type: String,
				  val value: Int,
				  val threshold: Int,
				  val isDouble: Boolean = false,
				  val conversionFactor: Int = DEFAULT_FLOAT_CONVERSION_FACTOR) {

	constructor(type: String,
				value: Double,
				threshold: Double,
				conversionFactor: Int = DEFAULT_FLOAT_CONVERSION_FACTOR) :
			this(type, value = (value * conversionFactor).toInt(),
					threshold = (threshold * conversionFactor).toInt(),
					isDouble = true, conversionFactor = conversionFactor)

	fun doubleValue(): Double = value.convertAsDouble()
	fun doubleThreshold(): Double = threshold.convertAsDouble()

	private fun Int.convertAsDouble() = if (isDouble) this.toDouble() / conversionFactor
	else throw IllegalStateException("This metric was not marked as double!")

	override fun toString() = if (isDouble) "${doubleValue()}/${doubleThreshold()}" else "$value/$threshold"
}

/**
 * To represent a value of 0.5, use the metric value 50 and the conversion factor of 100. (50 / 100 = 0.5)
 */
val DEFAULT_FLOAT_CONVERSION_FACTOR = 100

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.psi.KtFile

abstract class MultiRule : BaseRule() {

	abstract val rules: List<Rule>

	override fun visitCondition(root: KtFile) = true
}

package io.gitlab.arturbosch.detekt.api

/**
 * @author Artur Bosch
 */
interface Notification {
	val message: String
}

package io.gitlab.arturbosch.detekt.api

import java.nio.file.Files
import java.nio.file.Path

/**
 * @author Artur Bosch
 */
abstract class OutputReport : Extension {

	abstract var fileName: String
	abstract val ending: String

	fun write(report: Path, detektion: Detektion) {
		val smellData = render(detektion)
		smellData?.let {
			val filePath = report.resolve("$fileName.$ending")
			filePath.parent?.let { Files.createDirectories(it) }
			Files.write(filePath, it.toByteArray())
		}
	}

	abstract fun render(detektion: Detektion): String?
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.com.intellij.mock.MockProject
import org.jetbrains.kotlin.com.intellij.openapi.extensions.ExtensionPoint
import org.jetbrains.kotlin.com.intellij.openapi.extensions.Extensions
import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.com.intellij.openapi.util.UserDataHolderBase
import org.jetbrains.kotlin.com.intellij.pom.PomModel
import org.jetbrains.kotlin.com.intellij.pom.PomModelAspect
import org.jetbrains.kotlin.com.intellij.pom.PomTransaction
import org.jetbrains.kotlin.com.intellij.pom.impl.PomTransactionBase
import org.jetbrains.kotlin.com.intellij.pom.tree.TreeAspect
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.TreeCopyHandler
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.psi.KtPsiFactory
import sun.reflect.ReflectionFactory

val PROJECT = KotlinCoreEnvironment.createForProduction(Disposer.newDisposable(),
		CompilerConfiguration(), EnvironmentConfigFiles.JVM_CONFIG_FILES).project.apply {
	makeMutable(this as MockProject)
}

val FACTORY = KtPsiFactory(PROJECT, false)

private fun makeMutable(project: MockProject) {
	// Based on KtLint by Shyiko
	val pomModel: PomModel = object : UserDataHolderBase(), PomModel {

		override fun runTransaction(transaction: PomTransaction) {
			(transaction as PomTransactionBase).run()
		}

		@Suppress("UNCHECKED_CAST")
		override fun <T : PomModelAspect> getModelAspect(aspect: Class<T>): T? {
			if (aspect == TreeAspect::class.java) {
				// using approach described in https://git.io/vKQTo due to the magical bytecode of TreeAspect
				// (check constructor signature and compare it to the source)
				// (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.0.3)
				val constructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(
						aspect, Any::class.java.getDeclaredConstructor())
				return constructor.newInstance() as T
			}
			return null
		}

	}
	val extensionPoint = "org.jetbrains.kotlin.com.intellij.treeCopyHandler"
	val extensionClassName = TreeCopyHandler::class.java.name!!
	arrayOf(Extensions.getArea(project), Extensions.getArea(null))
			.filter { !it.hasExtensionPoint(extensionPoint) }
			.forEach { it.registerExtensionPoint(extensionPoint, extensionClassName, ExtensionPoint.Kind.INTERFACE) }
	project.registerService(PomModel::class.java, pomModel)
}

package io.gitlab.arturbosch.detekt.api

/**
 * Anything that can be expressed as a number value for projects.
 *
 * @author Artur Bosch
 */
open class ProjectMetric(val type: String,
						 val value: Int,
						 val priority: Int = -1,
						 val isDouble: Boolean = false,
						 val conversionFactor: Int = DEFAULT_FLOAT_CONVERSION_FACTOR) {

	override fun toString(): String = "$type: ${if (isDouble)
		(value.toDouble() / conversionFactor).toString() else value.toString()}"
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.psi.KtFile

/**
 * A rule defines how one specific code structure should look like. If code is found
 * which does not meet this structure, it is considered as harmful regarding maintainability
 * or readability.
 *
 * A rule is implemented using the visitor pattern and should be started using the visit(KtFile)
 * function. If calculations must be done before or after the visiting process, here are
 * two predefined (preVisit/postVisit) functions which can be overridden to setup/teardown additional data.
 *
 * @author Artur Bosch
 */
@Suppress("EmptyFunctionBlock")
abstract class Rule(override val config: Config = Config.empty,
					ruleContext: Context = DefaultContext()) :
		BaseRule(ruleContext), ConfigAware {

	abstract val issue: Issue
	final override val id: String by lazy(LazyThreadSafetyMode.NONE) { issue.id }

	override fun visitCondition(root: KtFile) = active && !root.isSuppressedBy(id)
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.psi.KtFile

/**
 * A rule set is a collection of rules and must be defined within a rule set provider implementation.
 *
 * @author Artur Bosch
 */
class RuleSet(val id: String, val rules: List<BaseRule>) {

	init {
		validateIdentifier(id)
	}

	/**
	 * Visits given file with all rules of this rule set, returning a list
	 * of all code smell findings.
	 */
	fun accept(file: KtFile): List<Finding> {
		val findings: MutableList<Finding> = mutableListOf()
		rules.forEach {
			it.visitFile(file)
			findings += it.findings
		}
		return findings
	}

}

package io.gitlab.arturbosch.detekt.api

/**
 * A rule set provider, as the name states, is responsible for creating rule sets.
 *
 * When writing own rule set providers make sure to register them according the ServiceLoader documentation.
 * http://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html
 *
 * @author Artur Bosch
 */
interface RuleSetProvider {

	/**
	 * Every rule set must be pre-configured with an ID to validate if this rule set
	 * must be created for current analysis.
	 */
	val ruleSetId: String

	/**
	 * Can return a rule set if this specific rule set is not considered as ignore.
	 *
	 * Api notice: As the rule set id is not known before creating the rule set instance,
	 * we must first create the rule set and then check if it is active.
	 */
	fun buildRuleset(config: Config): RuleSet? {
		val subConfig = config.subConfig(ruleSetId)
		val active = subConfig.valueOrDefault("active", true)
		return if (active) instance(subConfig) else null
	}

	/**
	 * This function must be implemented to provide custom rule sets.
	 * Make sure to pass the configuration to each rule to allow rules
	 * to be self configurable.
	 */
	fun instance(config: Config): RuleSet

}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.startOffset

internal fun PsiElement.searchName(): String {
	return this.namedUnwrappedElement?.name ?: "<UnknownName>"
}

internal fun PsiElement.searchClass(): String {
	val classElement = this.getNonStrictParentOfType(KtClassOrObject::class.java)
	var className = classElement?.name
	if (className != null && className == "Companion") {
		classElement?.parent?.getNonStrictParentOfType(KtClassOrObject::class.java)?.name?.let {
			className = it + ".$className"
		}
	}
	return className ?: this.containingFile.name
}

internal fun PsiElement.buildFullSignature(): String {
	val signature = this.searchSignature()
	val fullClassSignature = this.parents.filter { it is KtClassOrObject }
			.map { it.extractClassName() }
			.fold("") { sig, sig2 -> "$sig2${dotOrNot(sig, sig2)}$sig" }
	val filename = this.containingFile.name
	return (if (!fullClassSignature.startsWith(filename)) filename + "\$" else "") +
			if (fullClassSignature.isNotEmpty()) "$fullClassSignature\$$signature" else signature
}

private fun PsiElement.extractClassName() =
		this.getNonStrictParentOfType(KtClassOrObject::class.java)?.nameAsSafeName?.asString() ?: ""

private fun PsiElement.searchSignature(): String {
	return when (this) {
		is KtNamedFunction -> buildFunctionSignature(this)
		is KtClassOrObject -> buildClassSignature(this)
		is KtFile -> fileSignature()
		else -> this.text
	}.replace('\n', ' ').replace(Regex("\\s(\\s|\t)+"), " ")
}

private fun KtFile.fileSignature() = "${this.packageFqName.asString()}.${this.name}"

private fun dotOrNot(sig: String, sig2: String) = if (sig.isNotEmpty() && sig2.isNotEmpty()) "." else ""

private fun buildClassSignature(classOrObject: KtClassOrObject): String {
	var baseName = classOrObject.nameAsSafeName.asString()
	val typeParameters = classOrObject.typeParameters
	if (typeParameters.size > 0) {
		baseName += "<"
		baseName += typeParameters.joinToString(", ") { it.text }
		baseName += ">"
	}
	val extendedEntries = classOrObject.superTypeListEntries
	if (extendedEntries.isNotEmpty()) baseName += " : "
	extendedEntries.forEach { baseName += it.typeAsUserType?.referencedName ?: "" }
	return baseName
}

private fun buildFunctionSignature(element: KtNamedFunction): String {
	var methodStart = 0
	element.docComment?.let {
		methodStart = it.endOffset - it.startOffset
	}
	var methodEnd = element.endOffset - element.startOffset
	val typeReference = element.typeReference
	if (typeReference != null) {
		methodEnd = typeReference.endOffset - element.startOffset
	} else {
		element.valueParameterList?.let {
			methodEnd = it.endOffset - element.startOffset
		}
	}
	require(methodStart < methodEnd) {
		"Error building function signature with range $methodStart - $methodEnd for element: ${element.text}"
	}
	return getTextSafe(
			{ element.nameAsSafeName.identifier },
			{ element.text.substring(methodStart, methodEnd) })
}

package io.gitlab.arturbosch.detekt.api

/**
 * Provides a threshold attribute for this rule, which is specified manually for default values
 * but can be also obtained from within a configuration object.
 */
abstract class ThresholdRule(config: Config, private val defaultThreshold: Int) : Rule(config) {
	/**
	 * The used threshold for this rule is loaded from the configuration or used from the constructor value.
	 */
	protected val threshold get() = valueOrDefault("threshold", defaultThreshold)
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.preprocessor.typeReferenceName
import org.jetbrains.kotlin.psi.KtAnnotated
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile

/**
 * @author Artur Bosch
 */

/**
 * Checks if this psi element is suppressed by @Suppress or @SuppressWarnings annotations.
 * If this element cannot have annotations, the first annotative parent is searched.
 */
fun KtElement.isSuppressedBy(id: String)
		= this is KtAnnotated && this.isSuppressedBy(id) || findAnnotatedSuppressedParent(id)

private fun KtElement.findAnnotatedSuppressedParent(id: String): Boolean {
	val parent = PsiTreeUtil.getParentOfType(this, KtAnnotated::class.java, true)

	var suppressed = false
	if (parent != null && parent !is KtFile) {
		if (parent.isSuppressedBy(id)) {
			suppressed = true
		} else {
			suppressed = parent.findAnnotatedSuppressedParent(id)
		}
	}

	return suppressed
}

/**
 * Checks if this kt element is suppressed by @Suppress or @SuppressWarnings annotations.
 */
fun KtAnnotated.isSuppressedBy(id: String): Boolean {
	val valid = listOf(id, "ALL", "all")
	return annotationEntries.find { it.typeReferenceName.let { it == "Suppress" || it == "SuppressWarnings" } }
			?.valueArguments
			?.find { it.getArgumentExpression()?.text?.replace("\"", "") in valid } != null
}

package io.gitlab.arturbosch.detekt.api

import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.psi.KtFile

/**
 * A token rule overrides the visiting process and prohibits the use of standard visit functions.
 * As every ASTNode is considered as single object, this abstract rule can be used to define
 * rules which operate on spaces, tabs, semicolons etc.
 *
 * Token rules must define a procedure function which specifies what to do with each ASTNode.
 *
 * @author Artur Bosch
 */
@Suppress("EmptyFunctionBlock")
abstract class TokenRule(config: Config = Config.empty) : Rule(config) {

	override fun visit(root: KtFile) {
		root.node.visitTokens { procedure(it) }
	}

	/**
	 * Every ASTNode is considered in isolation. Use 'is' operator to search for wished elements.
	 */
	open fun procedure(node: ASTNode) {
		when (node) {
			is PsiWhiteSpace -> visitSpaces(node)
			is LeafPsiElement -> visitLeaf(node)

		}
	}

	open fun visitLeaf(leaf: LeafPsiElement) {
		if (!leaf.isPartOfString()) {
			when (leaf.text) {
				"}" -> visitRightBrace(leaf)
				"{" -> visitLeftBrace(leaf)
				":" -> visitColon(leaf)
				";" -> visitSemicolon(leaf)
				";;" -> visitDoubleSemicolon(leaf)
				"," -> visitComma(leaf)
			}
		}
	}

	protected open fun visitSemicolon(leaf: LeafPsiElement) {
	}

	protected open fun visitDoubleSemicolon(leaf: LeafPsiElement) {
	}

	protected open fun visitComma(leaf: LeafPsiElement) {
	}

	protected open fun visitColon(colon: LeafPsiElement) {
	}

	protected open fun visitLeftBrace(brace: LeafPsiElement) {
	}

	protected open fun visitRightBrace(brace: LeafPsiElement) {
	}

	protected open fun visitSpaces(space: PsiWhiteSpace) {
	}

}

package io.gitlab.arturbosch.detekt.api

import org.yaml.snakeyaml.Yaml
import java.io.BufferedReader
import java.net.URL
import java.nio.file.Files
import java.nio.file.Path

/**
 * Config implementation using the yaml format. SubConfigurations can return sub maps according to the
 * yaml specification.
 *
 * @author Artur Bosch
 */
@Suppress("UNCHECKED_CAST")
class YamlConfig internal constructor(val properties: Map<String, Any>) : BaseConfig() {

	override fun subConfig(key: String): Config {
		val subProperties = properties.getOrElse(key) { mapOf<String, Any>() }
		return YamlConfig(subProperties as Map<String, Any>)
	}

	override fun <T : Any> valueOrDefault(key: String, default: T): T {
		val result = properties[key]
		return valueOrDefaultInternal(result, default) as T
	}

	override fun toString(): String {
		return "YamlConfig(properties=$properties)"
	}

	companion object {

		val YAML = ".yml"

		/**
		 * Factory method to load a yaml configuration. Given path must exist and end with "yml".
		 */
		fun load(path: Path): Config {
			require(Files.exists(path) && path.toString().endsWith(YAML)) { "File does not exist or end with .yaml!" }
			return load(Files.newBufferedReader(path))
		}

		/**
		 * Factory method to load a yaml configuration from a URL.
		 */
		fun loadResource(url: URL): Config {
			val reader = url.openStream().bufferedReader()
			return load(reader)
		}

		private fun load(reader: BufferedReader): Config = reader.use {
			val yamlInput = it.readText()
			if (yamlInput.isEmpty()) {
				Config.empty
			} else {
				val map = Yaml().load(yamlInput)
				if (map is Map<*, *>) {
					YamlConfig(map as Map<String, Any>)
				} else {
					throw Config.InvalidConfigurationError()
				}
			}
		}

		private fun BufferedReader.readText() = lineSequence().joinToString("\n")

	}
}

package io.gitlab.arturbosch.detekt.cli

import com.beust.jcommander.Parameter
import java.nio.file.Path

/**
 * @author Artur Bosch
 */
class Args {

	@Parameter(names = arrayOf("--input", "-i"),
			required = true,
			converter = ExistingPathConverter::class, description = "Input path to analyze (path/to/project).")
	private var input: Path? = null

	@Parameter(names = arrayOf("--filters", "-f"),
			description = "Path filters defined through regex with separator ';' (\".*test.*\").")
	var filters: String? = null // Using a converter for List<PathFilter> resulted in a ClassCastException

	@Parameter(names = arrayOf("--config", "-c"),
			description = "Path to the config file (path/to/config.yml).")
	var config: String? = null

	@Parameter(names = arrayOf("--config-resource", "-cr"),
			description = "Path to the config resource on detekt's classpath (path/to/config.yml).")
	var configResource: String? = null

	@Parameter(names = arrayOf("--generate-config", "-gc"),
			description = "Export default config to default-detekt-config.yml.")
	var generateConfig: Boolean = false

	@Parameter(names = arrayOf("--plugins", "-p"),
			description = "Extra paths to plugin jars separated by ',' or ';'.")
	var plugins: String? = null

	@Parameter(names = arrayOf("--parallel"),
			description = "Enables parallel compilation of source files." +
					" Should only be used if the analyzing project has more than ~200 kotlin files.")
	var parallel: Boolean = false

	@Parameter(names = arrayOf("--baseline", "-b"),
			description = "If a baseline xml file is passed in," +
					" only new code smells not in the baseline are printed in the console.",
			converter = PathConverter::class)
	var baseline: Path? = null

	@Parameter(names = arrayOf("--create-baseline", "-cb"),
			description = "Treats current analysis findings as a smell baseline for further detekt runs.")
	var createBaseline: Boolean = false

	@Parameter(names = arrayOf("--output", "-o"),
			description = "Directory where output reports are stored.",
			converter = PathConverter::class)
	var output: Path? = null

	@Parameter(names = arrayOf("--output-name", "-on"),
			description = "The base name for output reports is derived from this parameter.")
	var outputName: String? = null

	@Parameter(names = arrayOf("--disable-default-rulesets", "-dd"),
			description = "Disables default rule sets.")
	var disableDefaultRuleSets: Boolean = false

	@Parameter(names = arrayOf("--debug"),
			description = "Debugs given ktFile by printing its elements.")
	var debug: Boolean = false

	@Parameter(names = arrayOf("--help", "-h"),
			help = true, description = "Shows the usage.")
	var help: Boolean = false

	val inputPath: Path
		get() = input ?: throw IllegalStateException("Input path was not initialized by jcommander!")
}

package io.gitlab.arturbosch.detekt.cli

import io.gitlab.arturbosch.detekt.api.CompositeConfig
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.YamlConfig
import io.gitlab.arturbosch.detekt.core.PathFilter
import java.io.File
import java.nio.file.Path

/**
 * @author Artur Bosch
 */

fun Args.createPathFilters(): List<PathFilter> = filters.letIfNonEmpty {
	split(SEPARATOR_COMMA, SEPARATOR_SEMICOLON).map(::PathFilter)
}

fun Args.createPlugins(): List<Path> = plugins.letIfNonEmpty {
	MultipleExistingPathConverter().convert(this)
}

private fun <T> String?.letIfNonEmpty(init: String.() -> List<T>): List<T> =
		if (this == null || this.isEmpty()) listOf() else this.init()

fun Args.loadConfiguration(): Config {
	var config = when {
		!config.isNullOrBlank() -> parsePathConfig(config!!)
		!configResource.isNullOrBlank() -> parseResourceConfig(configResource!!)
		else -> loadDefaultConfig()
	}

	if (config.valueOrDefault("failFast", false)) {
		config = FailFastConfig(config, loadDefaultConfig())
	}

	if (debug) println("\n$config\n")
	return config
}

private fun parseResourceConfig(configPath: String): Config {
	val urls = MultipleClasspathResourceConverter().convert(configPath)
	return if (urls.size == 1) {
		YamlConfig.loadResource(urls[0])
	} else {
		urls.map { YamlConfig.loadResource(it) }.reduce { composite, config -> CompositeConfig(config, composite) }
	}
}

private fun parsePathConfig(configPath: String): Config {
	val paths = MultipleExistingPathConverter().convert(configPath)
	return if (paths.size == 1) {
		YamlConfig.load(paths[0])
	} else {
		paths.map { YamlConfig.load(it) }.reduce { composite, config -> CompositeConfig(config, composite) }
	}
}

data class FailFastConfig(private val originalConfig: Config, private val defaultConfig: Config) : Config {
	override fun subConfig(key: String) = FailFastConfig(originalConfig.subConfig(key), defaultConfig.subConfig(key))

	override fun <T : Any> valueOrDefault(key: String, default: T): T {
		@Suppress("UNCHECKED_CAST")
		return when (key) {
			"active" -> originalConfig.valueOrDefault(key, true) as T
			"warningThreshold", "failThreshold" -> originalConfig.valueOrDefault(key, 0) as T
			else -> originalConfig.valueOrDefault(key, defaultConfig.valueOrDefault(key, default))
		}
	}
}

private fun loadDefaultConfig() = YamlConfig.loadResource(ClasspathResourceConverter().convert(DEFAULT_CONFIG))

private val DEFAULT_CONFIG = "default-detekt-config.yml"

/**
 * @author lummax
 */
class ConfigExporter : Executable {

	override fun execute() {
		val defaultConfig = ClasspathResourceConverter().convert(DEFAULT_CONFIG).openStream()
		defaultConfig.copyTo(File(DEFAULT_CONFIG).outputStream())
		println("\nSuccessfully copied $DEFAULT_CONFIG to project location.")
	}

}

package io.gitlab.arturbosch.detekt.cli

import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.FileProcessListener
import org.jetbrains.kotlin.psi.KtFile

/**
 * @author Artur Bosch
 */
class DetektProgressListener : FileProcessListener {

	override fun onStart(files: List<KtFile>) {
		kotlin.io.print("Analyzing ${files.size} kotlin files: ")
	}

	override fun onProcess(file: KtFile) {
		kotlin.io.print(".")
	}

	override fun onFinish(files: List<KtFile>, result: Detektion) {
		kotlin.io.println()
	}
}

package io.gitlab.arturbosch.detekt.cli

import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.cli.baseline.BaselineFacade

/**
 * @author Artur Bosch
 */
class FilteredDetectionResult(detektion: Detektion, baselineFacade: BaselineFacade) : Detektion by detektion {

	private val filteredFindings: Map<String, List<Finding>>

	init {
		filteredFindings = detektion.findings
				.map { (key, value) -> key to baselineFacade.filter(value) }
				.toMap()
	}

	override val findings = filteredFindings
}

package io.gitlab.arturbosch.detekt.cli

import com.beust.jcommander.JCommander
import com.beust.jcommander.ParameterException

private val jCommander = JCommander()

fun parseArguments(args: Array<String>): Args {
	val cli = Args()
	jCommander.addObject(cli)
	jCommander.programName = "detekt"

	try {
		jCommander.parse(*args)
	} catch (ex: ParameterException) {
		val message = ex.message
		failWithErrorMessages(message)
	}

	if (cli.help) {
		jCommander.usage()
		System.exit(0)
	}

	return cli
}

fun failWithErrorMessages(vararg messages: String?) {
	failWithErrorMessages(messages.asIterable())
}

fun failWithErrorMessages(messages: Iterable<String?>) {
	messages.forEach {
		println(it)
	}
	println()
	jCommander.usage()
	System.exit(-1)
}

package io.gitlab.arturbosch.detekt.cli

import io.gitlab.arturbosch.detekt.api.Finding

val Finding.baselineId: String
	get() = this.id + ":" + this.signature

val SEPARATOR_COMMA = ","
val SEPARATOR_SEMICOLON = ";"

package io.gitlab.arturbosch.detekt.cli

import java.io.PrintStream

/**
 * @author Artur Bosch
 */
object LOG {

	var printer: PrintStream = System.out
	var active: Boolean = false

	fun debug(message: String) {
		if (active) {
			printer.println(message)
		}
	}

	fun debug(message: () -> String) {
		if (active) {
			printer.println(message.invoke())
		}
	}
}

@file:JvmName("Main")

package io.gitlab.arturbosch.detekt.cli

import io.gitlab.arturbosch.detekt.core.isFile
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
import java.nio.file.Files

/**
 * @author Artur Bosch
 */
fun main(args: Array<String>) {
	val arguments = parseArgumentsCheckingReportDirectory(args)
	LOG.active = arguments.debug
	val executable = when {
		arguments.generateConfig -> ConfigExporter()
		else -> Runner(arguments)
	}
	executable.execute()
}

private fun parseArgumentsCheckingReportDirectory(args: Array<String>): Args {
	val arguments = parseArguments(args)
	val messages = validateCli(arguments)
	messages.ifNotEmpty {
		failWithErrorMessages(messages)
	}
	return arguments
}

private fun validateCli(arguments: Args): List<String> {
	val violations = ArrayList<String>()
	with(arguments) {
		output?.let {
			if (Files.exists(it) && it.isFile()) {
				violations += "Output file must be a directory."
			}
		}
		if (createBaseline && baseline == null) {
			violations += "Creating a baseline.xml requires the --baseline parameter to specify a path."
		}
	}
	return violations
}

package io.gitlab.arturbosch.detekt.cli

import io.gitlab.arturbosch.detekt.api.ConsoleReport
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.OutputReport
import io.gitlab.arturbosch.detekt.cli.baseline.BaselineFacade
import io.gitlab.arturbosch.detekt.core.ProcessingSettings

/**
 * @author Artur Bosch
 */
class OutputFacade(private val arguments: Args,
				   private val detektion: Detektion,
				   private val settings: ProcessingSettings) {

	private val printStream = System.out
	private val config = settings.config
	private val baselineFacade = arguments.baseline?.let { BaselineFacade(it) }
	private val createBaseline = arguments.createBaseline
	private val fileName = arguments.outputName

	fun run() {
		if (createBaseline) {
			val smells = detektion.findings.flatMap { it.value }
			baselineFacade?.create(smells)
		}

		val result = if (baselineFacade != null) {
			FilteredDetectionResult(detektion, baselineFacade)
		} else detektion

		val reports = ReportLocator(settings).load()
		reports.sortedBy { it.priority }.asReversed().forEach { report ->
			report.init(config)
			when (report) {
				is ConsoleReport -> handleConsoleReport(report, result)
				is OutputReport -> handleOutputReport(report, result)
			}
		}
	}

	private fun handleOutputReport(report: OutputReport, result: Detektion) {
		arguments.output?.let {
			fileName?.let { report.fileName = it }
			report.write(it, result)
			printStream.println("Successfully generated ${report.id}.")
		}
	}

	private fun handleConsoleReport(report: ConsoleReport, result: Detektion) {
		report.print(printStream, result)
	}
}

package io.gitlab.arturbosch.detekt.cli

import com.beust.jcommander.IStringConverter
import com.beust.jcommander.ParameterException
import java.io.File
import java.net.URL
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

/**
 * @author Artur Bosch
 */
class ExistingPathConverter : IStringConverter<Path> {
	override fun convert(value: String): Path {
		val config = File(value).toPath()
		if (Files.notExists(config))
			throw ParameterException("Provided path '$value' does not exist!")
		return config
	}
}

class PathConverter : IStringConverter<Path> {
	override fun convert(value: String): Path {
		return Paths.get(value)
	}
}

interface CommaSeparatedStringConverter<T> : IStringConverter<List<T>> {
	val converter: IStringConverter<T>
	override fun convert(value: String): List<T>
			= value.splitToSequence(SEPARATOR_COMMA, SEPARATOR_SEMICOLON)
			.map { it.trim() }
			.map { converter.convert(it) }
			.toList().apply {
		if (isEmpty()) throw IllegalStateException("Given input '$value' was impossible to parse!")
	}
}

class MultipleClasspathResourceConverter : CommaSeparatedStringConverter<URL> {
	override val converter = ClasspathResourceConverter()
}

class MultipleExistingPathConverter : CommaSeparatedStringConverter<Path> {
	override val converter = ExistingPathConverter()
}

/**
 * @author Sean Flanigan sflaniga@redhat.com
 */
class ClasspathResourceConverter : IStringConverter<URL> {
	override fun convert(resource: String): URL {
		val relativeResource = if (resource.startsWith("/")) resource else "/" + resource
		return javaClass.getResource(relativeResource) ?:
				throw ParameterException("Classpath resource '$resource' does not exist!")
	}
}

package io.gitlab.arturbosch.detekt.cli

import io.gitlab.arturbosch.detekt.api.ConsoleReport
import io.gitlab.arturbosch.detekt.api.Extension
import io.gitlab.arturbosch.detekt.api.OutputReport
import io.gitlab.arturbosch.detekt.core.ProcessingSettings
import java.net.URLClassLoader
import java.util.ServiceLoader

/**
 * @author Artur Bosch
 */
class ReportLocator(private val settings: ProcessingSettings) {

	private val consoleSubConfig = settings.config.subConfig("console-reports")
	private val consoleActive = consoleSubConfig.valueOrDefault(ACTIVE, true)
	private val consoleExcludes = consoleSubConfig.valueOrDefault(EXCLUDE, emptyList<String>())

	private val outputSubConfig = settings.config.subConfig("output-reports")
	private val outputActive = outputSubConfig.valueOrDefault(ACTIVE, true)
	private val outputExcludes = outputSubConfig.valueOrDefault(EXCLUDE, emptyList<String>())

	fun load(): List<Extension> {
		LOG.debug("console-report=$consoleActive")
		LOG.debug("output-report=$outputActive")
		val detektLoader = URLClassLoader(settings.pluginUrls, javaClass.classLoader)
		val consoleReports = loadConsoleReports(detektLoader)
		LOG.debug { "ConsoleReports: $consoleReports" }
		val outputReports = loadOutputReports(detektLoader)
		LOG.debug { "OutputReports: $outputReports" }
		return consoleReports.plus(outputReports)
	}

	private fun loadOutputReports(detektLoader: URLClassLoader) =
			if (outputActive) {
				ServiceLoader.load(OutputReport::class.java, detektLoader)
						.filter { it.id !in outputExcludes }
						.toList()
			} else {
				emptyList<OutputReport>()
			}

	private fun loadConsoleReports(detektLoader: URLClassLoader) =
			if (consoleActive) {
				ServiceLoader.load(ConsoleReport::class.java, detektLoader)
						.filter { it.id !in consoleExcludes }
						.toList()
			} else {
				emptyList<ConsoleReport>()
			}

	companion object {
		private const val ACTIVE = "active"
		private const val EXCLUDE = "exclude"
	}
}

package io.gitlab.arturbosch.detekt.cli

import io.gitlab.arturbosch.detekt.core.DetektFacade
import io.gitlab.arturbosch.detekt.core.ProcessingSettings
import kotlin.system.measureTimeMillis

interface Executable {
	fun execute()
}

/**
 * @author Artur Bosch
 */
class Runner(private val arguments: Args) : Executable {

	override fun execute() {
		val settings = createSettings()

		val time = measureTimeMillis {
			val detektion = DetektFacade.instance(settings).run()
			OutputFacade(arguments, detektion, settings).run()
		}

		println("\ndetekt finished in $time ms.")
	}

	private fun createSettings(): ProcessingSettings {
		with(arguments) {
			val pathFilters = createPathFilters()
			val plugins = createPlugins()
			val config = loadConfiguration()
			return ProcessingSettings(inputPath, config, pathFilters, parallel, disableDefaultRuleSets, plugins)
		}
	}
}

package io.gitlab.arturbosch.detekt.core

import io.gitlab.arturbosch.detekt.api.FileProcessListener
import io.gitlab.arturbosch.detekt.api.RuleSetProvider

/**
 * @author Artur Bosch
 */
object DetektFacade {

	fun instance(settings: ProcessingSettings): Detektor {
		val providers = RuleSetLocator(settings).load()
		val processors = FileProcessorLocator(settings).load()
		return instance(settings, providers, processors)
	}

	fun instance(settings: ProcessingSettings, vararg providers: RuleSetProvider): Detektor {
		return instance(settings, providers.toList(), emptyList())
	}

	fun instance(settings: ProcessingSettings, vararg processors: FileProcessListener): Detektor {
		return instance(settings, emptyList(), processors.toList())
	}

	fun instance(settings: ProcessingSettings,
				 providers: List<RuleSetProvider>,
				 processors: List<FileProcessListener>): Detektor {
		return Detektor(settings, providers, processors)
	}
}

package io.gitlab.arturbosch.detekt.core

import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.api.Notification
import io.gitlab.arturbosch.detekt.api.ProjectMetric
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.com.intellij.util.keyFMap.KeyFMap

/**
 * @author Artur Bosch
 */
data class DetektResult(override val findings: Map<String, List<Finding>>,
						override val notifications: MutableCollection<Notification>) : Detektion {

	private var userData = KeyFMap.EMPTY_MAP
	private val _metrics = ArrayList<ProjectMetric>()
	override val metrics: Collection<ProjectMetric> = _metrics

	override fun add(projectMetric: ProjectMetric) {
		_metrics.add(projectMetric)
	}

	override fun add(notification: Notification) {
		notifications.add(notification)
	}

	override fun <V> getData(key: Key<V>): V? = userData.get(key)

	override fun <V> addData(key: Key<V>, value: V) {
		userData = userData.plus(key, value)
	}
}

package io.gitlab.arturbosch.detekt.core

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.FileProcessListener
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.api.FindingsForFile
import io.gitlab.arturbosch.detekt.api.Notification
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.api.toMergedMap
import org.jetbrains.kotlin.psi.KtFile

/**
 * @author Artur Bosch
 */
class Detektor(private val settings: ProcessingSettings,
			   private val providers: List<RuleSetProvider>,
			   private val processors: List<FileProcessListener> = emptyList()) {

	private val config: Config = settings.config
	private val modifier: KtFileModifier = KtFileModifier(settings.project)
	private val notifications: MutableList<Notification> = mutableListOf()

	fun run(compiler: KtTreeCompiler = KtTreeCompiler.instance(settings)): Detektion = run(compiler.compile())

	fun run(ktFiles: List<KtFile>): Detektion = withExecutor {

		processors.forEach { it.onStart(ktFiles) }
		val futures = ktFiles.map { file ->
			runAsync {
				processors.forEach { it.onProcess(file) }
				file.analyze().apply {
					processors.forEach { it.onProcessComplete(file, FindingsForFile(this)) }
				}
			}
		}
		val findings = awaitAll(futures).flatMap { it }.toMergedMap()

		if (config.valueOrDefault("autoCorrect", false)) {
			modifier.saveModifiedFiles(ktFiles) {
				notifications.add(it)
			}
		}

		DetektResult(findings.toSortedMap(), notifications).apply {
			processors.forEach { it.onFinish(ktFiles, this) }
		}
	}

	private fun KtFile.analyze(): List<Pair<String, List<Finding>>> = providers
			.mapNotNull { it.buildRuleset(config) }
			.sortedBy { it.id }
			.distinctBy { it.id }
			.map { rule -> rule.id to rule.accept(this) }
}

package io.gitlab.arturbosch.detekt.core

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.FileProcessListener
import java.net.URL
import java.net.URLClassLoader
import java.util.ServiceLoader

/**
 * @author Artur Bosch
 */
class FileProcessorLocator(settings: ProcessingSettings) {

	private val plugins: Array<URL> = settings.pluginUrls
	private val config: Config = settings.config
	private val subConfig = config.subConfig("processors")
	private val processorsActive = subConfig.valueOrDefault("active", true)
	private val excludes = subConfig.valueOrDefault("exclude", emptyList<String>())

	fun load(): List<FileProcessListener> {
		val detektLoader = URLClassLoader(plugins, javaClass.classLoader)
		return if (processorsActive)
			ServiceLoader.load(FileProcessListener::class.java, detektLoader)
					.filter { it.id !in excludes }
					.onEach { it.init(config) }
					.toList()
		else
			emptyList()
	}
}

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 <T> Stream<T>.toList(): List<T> = collect(Collectors.toList<T>())

fun Path.exists(): Boolean = Files.exists(this)
fun Path.isFile(): Boolean = Files.isRegularFile(this)
fun Path.isDirectory(): Boolean = Files.isDirectory(this)

package io.gitlab.arturbosch.detekt.core

import io.gitlab.arturbosch.detekt.api.PROJECT
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.com.intellij.openapi.util.text.StringUtilRt
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
import org.jetbrains.kotlin.com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.KtFile
import java.nio.file.Path

/**
 * @author Artur Bosch
 */
open class KtCompiler(val project: Path) {

	protected val psiFileFactory: PsiFileFactory = PsiFileFactory.getInstance(PROJECT)

	fun compile(subPath: Path): KtFile {
		require(subPath.isFile()) { "Given sub path should be a regular file!" }
		val relativePath = if (project == subPath) subPath else project.relativize(subPath)
		val content = subPath.toFile().readText()
		val lineSeparator = content.determineLineSeparator()
		val normalizedContent = StringUtilRt.convertLineSeparators(content)
		val ktFile = createKtFile(normalizedContent, relativePath)
		ktFile.putExtraInformation(lineSeparator, relativePath)
		return ktFile
	}

	private fun KtFile.putExtraInformation(lineSeparator: String, relativePath: Path) {
		this.putUserData(LINE_SEPARATOR, lineSeparator)
		this.putUserData(RELATIVE_PATH, relativePath.toString())
	}

	private fun createKtFile(content: String, relativePath: Path) = psiFileFactory.createFileFromText(
			relativePath.fileName.toString(), KotlinLanguage.INSTANCE, StringUtilRt.convertLineSeparators(content),
			true, true, false, LightVirtualFile(relativePath.toString())) as KtFile

	private fun String.determineLineSeparator(): String {
		val i = this.lastIndexOf('\n')
		if (i == -1) {
			return if (this.lastIndexOf('\r') == -1) System.getProperty("line.separator") else "\r"
		}
		return if (i != 0 && this[i] == '\r') "\r\n" else "\n"
	}

	companion object {
		val LINE_SEPARATOR: Key<String> = Key("lineSeparator")
		val RELATIVE_PATH: Key<String> = Key("relativePath")
	}

}

package io.gitlab.arturbosch.detekt.core

import io.gitlab.arturbosch.detekt.api.Notification
import org.jetbrains.kotlin.com.intellij.openapi.util.text.StringUtilRt
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
import org.jetbrains.kotlin.psi.KtFile
import java.nio.file.Files
import java.nio.file.Path

/**
 * @author Artur Bosch
 */
class KtFileModifier(private val project: Path) {

	fun saveModifiedFiles(ktFiles: List<KtFile>, notification: (Notification) -> Unit) {
		ktFiles.filter { it.modificationStamp > 0 }
				.map { it.relativePath() to it.unnormalizeContent() }
				.filter { it.first != null }
				.map { project.resolve(it.first) to it.second }
				.forEach {
					notification.invoke(ModificationNotification(it.first))
					Files.write(it.first, it.second.toByteArray())
				}
	}

	private fun KtFile.unnormalizeContent(): String {
		val lineSeparator = getUserData(KtCompiler.LINE_SEPARATOR)
		require(lineSeparator != null) {
			"No line separator entry for ktFile ${javaFileFacadeFqName.asString()}"
		}
		return StringUtilRt.convertLineSeparators(text, lineSeparator!!)
	}

	private fun KtFile.relativePath(): String? = getUserData(KtCompiler.RELATIVE_PATH)

}

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(private val project: Path,
					 private val filters: List<PathFilter> = listOf(),
					 private val parallel: Boolean = false) {

	private val compiler = KtCompiler(project)

	init {
		require(Files.exists(project)) { "Given project path does not exist!" }
	}

	companion object {
		fun instance(settings: ProcessingSettings) = with(settings) {
			KtTreeCompiler(project, pathFilters, parallelCompilation)
		}
	}

	fun compile(): List<KtFile> = when {
		project.isFile() -> listOf(compiler.compile(project))
		project.isDirectory() -> compileInternal(createStream())
		else -> throw IllegalArgumentException("Provided project path $project is not a file/dir." +
				" Detekt cannot work with it!")
	}

	private fun createStream(): Stream<Path> = Files.walk(project).apply {
		if (parallel) parallel()
	}

	private fun compileInternal(stream: Stream<Path>): List<KtFile> = stream.filter(Path::isFile)
			.filter { it.isKotlinFile() }
			.filter { notIgnored(it) }
			.map { compiler.compile(it) }
			.toList()

	private fun Path.isKotlinFile(): Boolean {
		val fullPath = this.toAbsolutePath().toString()
		val kotlinEnding = fullPath.substring(fullPath.lastIndexOf('.') + 1)
		return kotlinEnding.length == 2 && kotlinEnding.endsWith("kt")
	}

	private fun notIgnored(path: Path) = !filters.any { it.matches(path) }
}

package io.gitlab.arturbosch.detekt.core

import io.gitlab.arturbosch.detekt.api.Notification
import java.nio.file.Path

/**
 * @author Artur Bosch
 */
class ModificationNotification(path: Path) : Notification {

	override val message: String = "File $path was modified."
	override fun toString(): String = message
}

package io.gitlab.arturbosch.detekt.core

import java.nio.file.Path
import java.util.regex.PatternSyntaxException

/**
 * @author Artur Bosch
 */
class PathFilter(pattern: String) {

	companion object {
		val IS_WINDOWS = System.getProperty("os.name").contains("Windows")
	}

	private val regex: Regex

	init {
		try {
			val independentPattern = if (IS_WINDOWS) pattern.replace("/", "\\\\") else pattern
			regex = Regex(independentPattern)
		} catch (exception: PatternSyntaxException) {
			throw IllegalArgumentException("Provided regex is not valid: $pattern")
		}
	}

	fun matches(path: Path): Boolean = path.toAbsolutePath().toString().matches(regex)
}

package io.gitlab.arturbosch.detekt.core

import io.gitlab.arturbosch.detekt.api.Config
import java.nio.file.Files
import java.nio.file.Path

/**
 * @author Artur Bosch
 */
@Suppress("LongParameterList")
data class ProcessingSettings(val project: Path,
							  val config: Config = Config.empty,
							  val pathFilters: List<PathFilter> = listOf(),
							  val parallelCompilation: Boolean = false,
							  val excludeDefaultRuleSets: Boolean = false,
							  val pluginPaths: List<Path> = emptyList()) {

	init {
		pluginPaths.forEach {
			require(Files.exists(it) && it.toString().endsWith("jar")) {
				"Given plugin $it does not exist or end with jar!"
			}
		}
	}

	val pluginUrls = pluginPaths.map { it.toUri().toURL() }.toTypedArray()
}

package io.gitlab.arturbosch.detekt.core

import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import java.net.URL
import java.net.URLClassLoader
import java.util.ServiceLoader

/**
 * @author Artur Bosch
 */
class RuleSetLocator(settings: ProcessingSettings) {

	private val excludeDefaultRuleSets: Boolean = settings.excludeDefaultRuleSets
	private val plugins: Array<URL> = settings.pluginUrls

	fun load(): List<RuleSetProvider> {
		val detektLoader = URLClassLoader(plugins, javaClass.classLoader)
		return ServiceLoader.load(RuleSetProvider::class.java, detektLoader).asIterable()
				.mapNotNull { it.nullIfDefaultAndExcluded() }
				.toList()
	}

	private fun RuleSetProvider.nullIfDefaultAndExcluded() = if (excludeDefaultRuleSets && provided()) null else this

	private fun RuleSetProvider.provided() = ruleSetId in defaultRuleSetIds

	private val defaultRuleSetIds = listOf("code-smell", "comments", "complexity", "empty-blocks",
			"exceptions", "potential-bugs", "performance", "style")
}

package io.gitlab.arturbosch.detekt.core

import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.function.Supplier

/**
 * @author Artur Bosch
 */

fun <T> withExecutor(block: Executor.() -> T): T {
	val executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
	return block.invoke(executor).apply {
		executor.shutdown()
	}
}

fun <T> Executor.runAsync(block: () -> T): CompletableFuture<T> {
	return task(this) { block() }
}

fun <T> task(executor: Executor, task: () -> T): CompletableFuture<T> {
	return CompletableFuture.supplyAsync(Supplier { task() }, executor)
}

fun <T> awaitAll(futures: List<CompletableFuture<T>>) = futures.map { it.join() }

package io.gitlab.arturbosch.detekt.extensions

/**
 * @author Artur Bosch
 */

const val DETEKT_PROFILE = "detekt.profile"
const val SUPPORTED_DETEKT_VERSION = "latest.release"
const val DEBUG_PARAMETER = false

const val DEFAULT_PROFILE_NAME = "main"
const val DEFAULT_TRUE = "true"
const val DEFAULT_DETEKT_CONFIG_RESOURCE = "/default-detekt-config.yml"
const val DEFAULT_PATH_EXCLUDES = ".*/test/.*,.*/resources/.*,.*/build/.*,.*/target/.*"

const val CONFIG_RESOURCE_PARAMETER = "--config-resource"
const val FILTERS_PARAMETER = "--filters"
const val INPUT_PARAMETER = "--input"
const val CONFIG_PARAMETER = "--config"
const val RULES_PARAMETER = "--rules"
const val OUTPUT_PARAMETER = "--output"
const val OUTPUT_NAME_PARAMETER = "--output-name"
const val BASELINE_PARAMETER = "--baseline"
const val PARALLEL_PARAMETER = "--parallel"
const val DISABLE_DEFAULT_RULESETS_PARAMETER = "--disable-default-rulesets"

package io.gitlab.arturbosch.detekt.extensions

import org.gradle.api.Action
import org.gradle.api.Project

/**
 * @author Artur Bosch
 */
open class DetektExtension(open var version: String = SUPPORTED_DETEKT_VERSION,
						   open var debug: Boolean = DEBUG_PARAMETER,
						   open var profile: String = DEFAULT_PROFILE_NAME,
						   open var ideaExtension: IdeaExtension = IdeaExtension()) {

	private val profiles: MutableList<ProfileExtension> = mutableListOf()

	fun systemOrDefaultProfile() = getSystemProfile() ?: getDefaultProfile()
	fun ideaFormatArgs() = ideaExtension.formatArgs(this)
	fun ideaInspectArgs() = ideaExtension.inspectArgs(this)

	fun idea(configuration: Action<in IdeaExtension>) {
		configuration.execute(ideaExtension)
	}

	fun profile(name: String, configuration: Action<in ProfileExtension>) {
		ProfileExtension(name).apply {
			profiles.add(this)
			configuration.execute(this)
		}
	}

	fun profileArgumentsOrDefault(project: Project): List<String> {
		return with(createArgumentsForProfile()) {
			if (isNotEmpty()) {
				if (!contains(INPUT_PARAMETER)) {
					add(INPUT_PARAMETER)
					add(project.projectDir.toString())
				}
				this
			} else {
				project.fallbackArguments()
			}
		}
	}

	private fun createArgumentsForProfile(): MutableList<String> {
		val defaultProfile = getDefaultProfile()
		val systemProfile = getSystemProfile()
		val mainProfile = if (defaultProfile?.name != MAIN_PROFILE_NAME && systemProfile?.name != MAIN_PROFILE_NAME) {
			searchProfileWithName(MAIN_PROFILE_NAME)
		} else null

		val allArguments = mainProfile?.arguments(debug) ?: mutableMapOf()
		val defaultArguments = defaultProfile?.arguments(debug) ?: mutableMapOf()
		val fallbackEmptyArguments = mutableMapOf<String, String>()

		val overriddenArguments = if (systemProfile?.name == defaultProfile?.name) fallbackEmptyArguments
		else systemProfile?.arguments(debug) ?: fallbackEmptyArguments

		defaultArguments.merge(allArguments)
		overriddenArguments.merge(allArguments)

		return allArguments.flatMapTo(ArrayList()) { flattenBoolValues(it.key, it.value) }.apply {
			if (debug) {
				val name = systemOrDefaultProfile()?.name ?: "_fallback_"
				println("detekt version: $version - usedProfile: $name")
				println("Arguments: $this")
			}
		}
	}

	private fun searchProfileWithName(name: String) = profiles.find { it.name == name }
	private fun getDefaultProfile() = searchProfileWithName(profile)
	private fun getSystemProfile() = searchProfileWithName(System.getProperty(DETEKT_PROFILE) ?: profile)

	private fun flattenBoolValues(key: String, value: String)
			= if (value == "true" || value == "false") listOf(key) else listOf(key, value)

	override fun toString(): String = "DetektExtension(version='$version', " +
			"debug=$debug, profile='$profile', ideaExtension=$ideaExtension, profiles=$profiles)"
}

private val MAIN_PROFILE_NAME = "main"

private fun MutableMap<String, String>.merge(other: MutableMap<String, String>) {
	for ((key, value) in this) {
		other.merge(key, value) { v1, v2 ->
			multipleConfigAware(key, v1, v2)
		}
	}
}

private fun multipleConfigAware(key: String, v1: String, v2: String)
		= if (key == CONFIG_PARAMETER || key == CONFIG_RESOURCE_PARAMETER) "$v1,$v2" else v2

internal fun Project.fallbackArguments() = listOf(
		INPUT_PARAMETER, projectDir.absolutePath,
		CONFIG_RESOURCE_PARAMETER, DEFAULT_DETEKT_CONFIG_RESOURCE,
		FILTERS_PARAMETER, DEFAULT_PATH_EXCLUDES)

package io.gitlab.arturbosch.detekt.extensions

/**
 * @author Artur Bosch
 */
open class IdeaExtension(open var path: String? = null,
						 open var codeStyleScheme: String? = null,
						 open var inspectionsProfile: String? = null,
						 open var report: String? = null,
						 open var mask: String = "*.kt") {

	fun formatArgs(ext: DetektExtension): Array<String> {
		val input = ext.profileInputPath()
		require(path != null) { IDEA_PATH_ERROR }
		require(input != null) { INPUT_PATH_ERROR }
		return if (codeStyleScheme != null) {
			arrayOf(format(path!!), "-r", input!!, "-s", codeStyleScheme!!, "-m", mask)
		} else {
			arrayOf(format(path!!), "-r", input!!, "-m", mask)
		}
	}

	fun inspectArgs(ext: DetektExtension): Array<String> {
		val input = ext.profileInputPath()
		require(path != null) { IDEA_PATH_ERROR }
		require(input != null) { INPUT_PATH_ERROR }
		require(report != null) { REPORT_PATH_ERROR }
		require(inspectionsProfile != null) { INSPECTION_PROFILE_ERROR }
		return arrayOf(inspect(path!!), input!!, inspectionsProfile!!, report!!)
	}

	private fun DetektExtension.profileInputPath() = systemOrDefaultProfile()?.input?.apply {
		if (debug) println("input: $this")
	}

	override fun toString(): String = "IdeaExtension(path=$path, " +
			"codeStyleScheme=$codeStyleScheme, inspectionsProfile=$inspectionsProfile, report=$report, mask='$mask')"
}

private val isWindows: Boolean = System.getProperty("os.name").contains("Windows")

private const val INPUT_PATH_ERROR = "Make sure the input path is specified!"
private const val IDEA_PATH_ERROR = "Make sure the idea path is specified to run idea tasks!"
private const val REPORT_PATH_ERROR =
		"Make sure the report path is specified where idea inspections are stored!"
private const val INSPECTION_PROFILE_ERROR =
		"Make sure the path to an inspection profile is provided!"

private fun inspect(path: String): String = "$path/bin/" + if (isWindows) "inspect.bat" else "inspect.sh"
private fun format(path: String): String = "$path/bin/" + if (isWindows) "format.bat" else "format.sh"

package io.gitlab.arturbosch.detekt.extensions

/**
 * @author Artur Bosch
 */
@Suppress("LongParameterList")
open class ProfileExtension(val name: String,
							open var input: String? = null,
							open var config: String? = null,
							open var configResource: String? = null,
							open var filters: String? = null,
							open var ruleSets: String? = null,
							open var output: String? = null,
							open var outputName: String? = null,
							open var baseline: String? = null,
							open var parallel: Boolean = false,
							open var disableDefaultRuleSets: Boolean = false) {

	fun arguments(debug: Boolean = false): MutableMap<String, String> {
		return mutableMapOf<String, String>().apply {
			input?.let { put(INPUT_PARAMETER, it) }
			config?.let { put(CONFIG_PARAMETER, it) }
			configResource?.let { put(CONFIG_RESOURCE_PARAMETER, it) }
			filters?.let { put(FILTERS_PARAMETER, it) }
			ruleSets?.let { put(RULES_PARAMETER, it) }
			output?.let { put(OUTPUT_PARAMETER, it) }
			outputName?.let { put(OUTPUT_NAME_PARAMETER, it) }
			baseline?.let { put(BASELINE_PARAMETER, it) }
			if (parallel) put(PARALLEL_PARAMETER, DEFAULT_TRUE)
			if (disableDefaultRuleSets) put(DISABLE_DEFAULT_RULESETS_PARAMETER, DEFAULT_TRUE)
			if (debug) put("--debug", DEFAULT_TRUE)
		}
	}

	override fun toString(): String = "ProfileExtension(name='$name', input=$input, config=$config, " +
			"configResource=$configResource, filters=$filters, ruleSets=$ruleSets, output=$output, " +
			"outputName=$outputName, baseline=$baseline, parallel=$parallel, " +
			"disableDefaultRuleSets=$disableDefaultRuleSets)"

}

package io.gitlab.arturbosch.detekt.migration

import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity

/**
 * @author Artur Bosch
 */
data class ImportMigration(private val toReplace: String,
						   private val replacement: String,
						   override val entity: Entity) : Finding {
	override val id: String = "ImportMigration"
	override val issue: Issue = Issue(id, Severity.Minor,
			"$id - $toReplace migrated to $replacement @ ${entity.location.compact()}")
	override val references: List<Entity> = emptyList()
	override val metrics: List<Metric> = emptyList()
	override fun compact(): String = issue.description
}

package io.gitlab.arturbosch.detekt.migration

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.PROJECT
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.impl.source.codeStyle.CodeEditUtil
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement
import org.jetbrains.kotlin.psi.KtImportList
import org.jetbrains.kotlin.psi.KtImportsFactory
import org.jetbrains.kotlin.resolve.ImportPath

/**
 * @author Artur Bosch
 */
class MigrateImportsRule(config: Config) : Rule(config) {

	override val issue = Issue("MigrateImports", Severity.Defect, "")

	private val replacements: HashMap<String, String> = config.valueOrDefault("imports", HashMap())
	private val toReplaces = replacements.keys

	private val factory = KtImportsFactory(PROJECT)

	override fun visitImportList(importList: KtImportList) {

		importList.imports
				.filter { it.importedFqName?.toString() in toReplaces }
				.forEach { import ->
					val key = import.importedFqName?.toString()
					val node = import.importedReference?.node as CompositeElement
					replacements[key]?.let {
						CodeEditUtil.removeChildren(node, node.firstChildNode, node.lastChildNode)
						val importPath = ImportPath.fromString(it)
						val newImport = factory.createImportDirectives(listOf(importPath)).toList()[0]
						node.addChild(newImport.importedReference?.node!!)
						report(ImportMigration(key!!, it, Entity.from(import)))
					}
				}
	}

}

package io.gitlab.arturbosch.detekt.migration

import io.gitlab.arturbosch.detekt.cli.OutputFacade
import io.gitlab.arturbosch.detekt.cli.createPathFilters
import io.gitlab.arturbosch.detekt.cli.loadConfiguration
import io.gitlab.arturbosch.detekt.cli.parseArguments
import io.gitlab.arturbosch.detekt.core.Detektor
import io.gitlab.arturbosch.detekt.core.ProcessingSettings

/**
 * @author Artur Bosch
 */
class Migration {

	companion object {

		@JvmStatic
		fun main(args: Array<String>) {
			with(parseArguments(args)) {
				val config = loadConfiguration()
				val settings = ProcessingSettings(inputPath, config, createPathFilters(), parallel, true)
				val detektor = Detektor(settings, listOf(MigrationRuleSetProvider()))
				val detektion = detektor.run()
				OutputFacade(this, detektion, settings).run()
			}

		}
	}

}

package io.gitlab.arturbosch.detekt.migration

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider

/**
 * @author Artur Bosch
 */
class MigrationRuleSetProvider : RuleSetProvider {

	override val ruleSetId: String = "migration"

	override fun instance(config: Config): RuleSet {
		return RuleSet(ruleSetId, listOf(MigrateImportsRule(config)))
	}
}

package io.gitlab.arturbosch.detekt.rules

import io.gitlab.arturbosch.detekt.api.CodeSmellWithReferenceAndMetric
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtVariableDeclaration
import java.util.IdentityHashMap

/**
 * @author Artur Bosch
 */
class FeatureEnvy(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Style, "")

	private val factor = FeatureEnvyFactor(
			valueOrDefault("threshold", DEFAULT_FEF_THRESHOLD),
			valueOrDefault("base", DEFAULT_FEF_BASE_MULTIPLIER),
			valueOrDefault("weight", DEFAULT_FEF_WEIGHT_MULTIPLIER))

	override fun visitClass(klass: KtClass) {
		val properties = klass.getProperties()
		val functions = klass.declarations.filterIsInstance(KtNamedFunction::class.java)
		val visitor = FeatureEnvyMethodInspector(properties, functions)
		visitor.run()
		super.visitClass(klass)
	}

	inner class FeatureEnvyMethodInspector(val properties: List<KtProperty>,
										   val functions: List<KtNamedFunction>) {

		fun run() {
			functions.filter { it.funKeyword != null }
					.filter { it.hasBlockBody() }
					.forEach { function -> analyzeFunction(function) }
		}

		private fun analyzeFunction(function: KtNamedFunction) {
			val entityOfFunction = Entity.from(function)
			val allCalls = function.collectByType<KtCallExpression>()
			val sumCalls = allCalls.map { 1 }.sum()
			println("${function.name} " + sumCalls)
			val parameters = function.valueParameters
			val locals = function.collectByType<KtVariableDeclaration>()
			println(locals.map { it.text })

			val elementCallMap = IdentityHashMap<KtNamedDeclaration, Int>()

			allCalls.map { it.parent }
					.filterIsInstance(KtDotQualifiedExpression::class.java)
					.map { it.receiverExpression }
					.filterIsInstance(KtNameReferenceExpression::class.java)
					.forEach {
						findCalledVariable(elementCallMap, properties, it)
						findCalledVariable(elementCallMap, parameters, it)
						findCalledVariable(elementCallMap, locals, it)
					}

			println(elementCallMap)
			elementCallMap.forEach { ktElement, i ->
				val value = factor.calc(i, sumCalls)
				val threshold = factor.threshold
				println(ktElement.text)
				println("factor: $value")
				if (threshold < value) {
					report(CodeSmellWithReferenceAndMetric(issue, entityOfFunction,
							Entity.from(ktElement), Metric("FeatureEnvyFactor", value, threshold)))
				}
			}
		}

		private fun findCalledVariable(elementCallMap: IdentityHashMap<KtNamedDeclaration, Int>,
									   list: List<KtNamedDeclaration>,
									   name: KtNameReferenceExpression) {
			list.find { name.getReferencedName() == it.name }
					?.run { elementCallMap.merge(this, 1) { v1, v2 -> v1 + v2 } }
		}

	}

	inner class FeatureEnvyFactor(val threshold: Double = DEFAULT_FEF_BASE_MULTIPLIER,
								  private val base: Double = DEFAULT_FEF_BASE_MULTIPLIER,
								  private val weight: Double = DEFAULT_FEF_WEIGHT_MULTIPLIER) {

		internal fun calc(entityCalls: Int, allCalls: Int): Double {
			if (allCalls == 0 || allCalls == 1) {
				return 0.0
			}
			return weight * (entityCalls / allCalls) + (1 - weight) * (1 - Math.pow(base, entityCalls.toDouble()))
		}
	}

	companion object {
		const val DEFAULT_FEF_THRESHOLD = 0.5
		const val DEFAULT_FEF_BASE_MULTIPLIER = 0.5
		const val DEFAULT_FEF_WEIGHT_MULTIPLIER = 0.45
	}
}

package io.gitlab.arturbosch.detekt.rules

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression

/**
 * @author Artur Bosch
 */

fun KtExpression?.asBlockExpression(): KtBlockExpression? = this as? KtBlockExpression

fun KtModifierListOwner.isPublicNotOverridden() =
		isPublic() && !isOverridden()

fun KtModifierListOwner.isOverridden() = this.hasModifier(KtTokens.OVERRIDE_KEYWORD)

fun KtModifierListOwner.isPublic(): Boolean {
	return this.hasModifier(KtTokens.PUBLIC_KEYWORD)
			|| !(this.hasModifier(KtTokens.PRIVATE_KEYWORD)
			|| this.hasModifier(KtTokens.PROTECTED_KEYWORD)
			|| this.hasModifier(KtTokens.INTERNAL_KEYWORD))
}

fun KtModifierListOwner.isInternal() = this.hasModifier(KtTokens.INTERNAL_KEYWORD)

fun KtClass.isDataClass() = this.modifierList?.hasModifier(KtTokens.DATA_KEYWORD) == true

fun KtCallExpression.isUsedForNesting(): Boolean = when (getCallNameExpression()?.text) {
	"run", "let", "apply", "with", "use", "forEach" -> true
	else -> false
}

fun KtBlockExpression.hasCommentInside(): Boolean {
	val commentKey = Key<Boolean>("comment")
	this.acceptChildren(object : DetektVisitor() {
		override fun visitComment(comment: PsiComment?) {
			if (comment != null) putUserData(commentKey, true)
		}
	})
	return getUserData(commentKey) == true
}

fun KtClass.companionObject() = this.companionObjects.singleOrNull { it.isCompanion() }

inline fun <reified T : KtElement> KtElement.collectByType(): List<T> {
	val list = mutableListOf<T>()
	this.accept(object : DetektVisitor() {
		override fun visitKtElement(element: KtElement) {
			if (element is T) {
				list.add(element)
			}
			element.children.forEach { it.accept(this) }
		}
	})
	return list
}

package io.gitlab.arturbosch.detekt.rules

import io.gitlab.arturbosch.detekt.api.Context
import io.gitlab.arturbosch.detekt.rules.style.File
import org.jetbrains.kotlin.psi.KtElement

fun <T : File> T.reportFindings(context: Context, rules: List<SubRule<T>>) {
	rules.filter { it.visitCondition(this.file) }
			.onEach { it.apply(this) }
			.flatMap { it.findings }
			.apply { context.report(this) }
}

fun <T : KtElement> T.reportFindings(rule: SubRule<T>) {
	if (rule.visitCondition(this.containingKtFile)) {
		rule.apply(this)
	}
}

package io.gitlab.arturbosch.detekt.rules

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Rule

abstract class SubRule<in Element>(config: Config) : Rule(config) {
	abstract fun apply(element: Element)
}

package io.gitlab.arturbosch.detekt.watchservice

import io.gitlab.arturbosch.detekt.cli.console.FindingsReport
import io.gitlab.arturbosch.detekt.core.DetektFacade
import io.gitlab.arturbosch.detekt.core.KtCompiler
import io.gitlab.arturbosch.detekt.core.PathFilter
import io.gitlab.arturbosch.detekt.core.ProcessingSettings
import io.gitlab.arturbosch.detekt.core.RuleSetLocator
import org.jetbrains.kotlin.psi.KtFile
import java.nio.file.Path

/**
 * @author Artur Bosch
 */
class DetektService(parameters: Parameters) {

	private val settings = with(parameters) {
		ProcessingSettings(
				extractWatchDirectory(),
				extractConfig(),
				listOf(PathFilter(".*/test/.*"),
						PathFilter(".*/resources/.*"),
						PathFilter(".*/build/.*"))
		)
	}

	private val detektor = DetektFacade.instance(settings, RuleSetLocator(settings).load(), emptyList())
	private val reporter = FindingsReport()

	fun check(dir: WatchedDir) {
		val watchedDir = dir.dir
		val paths = dir.events
				.filter { it.kind.name() != "ENTRY_DELETE" }
				.filter { it.path.toString().endsWith(".kt") }
				.map { watchedDir.resolve(it.path) }
				.distinct()
				.fold(mutableListOf<Path>()) { acc, path -> acc.add(path); acc }

		val compiler = KtCompiler(watchedDir)
		val ktFiles = paths
				.map { compiler.compile(it) }
				.fold(mutableListOf<KtFile>()) { acc, ktFile -> acc.add(ktFile); acc }

		if (ktFiles.isNotEmpty()) {
			paths.forEach { println("Change detected for $it") }
			val detektion = detektor.run(ktFiles)
			println(reporter.render(detektion))
		}
	}
}

package io.gitlab.arturbosch.detekt.watchservice

import com.beust.jcommander.JCommander
import com.beust.jcommander.ParameterException
import java.nio.file.FileSystems
import java.nio.file.Files

/**
 * @author Artur Bosch
 */
fun main(args: Array<String>) {
	with(parseArgs(args)) {
		val root = extractWatchDirectory()
		val watchService = FileSystems.getDefault().newWatchService()
		Files.walkFileTree(root, WatchServiceDirectoryRegisteringVisitor(watchService))
		println("Starting detekt watch service for $root")
		startWatching(watchService, DetektService(this))
	}
}

private fun parseArgs(args: Array<String>): Parameters = with(Parameters()) {
	val jco = JCommander(this)
	try {
		jco.parse(*args)
	} catch (error: ParameterException) {
		jco.usage()
		System.exit(0)
	}
	this
}

package io.gitlab.arturbosch.detekt.watchservice

import com.beust.jcommander.Parameter
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.YamlConfig
import io.gitlab.arturbosch.detekt.cli.ExistingPathConverter
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

/**
 * @author Artur Bosch
 */
class Parameters {

	@Parameter(names = arrayOf("--input", "-i"),
			converter = ExistingPathConverter::class,
			description = "Input path to analyze (path/to/project).")
	private var input: Path? = null

	@Parameter(names = arrayOf("--config", "-c"),
			description = "Path to the config file (path/to/config.yml).")
	private var config: String? = null

	fun extractWatchDirectory(): Path {
		return input?.apply {
			require(Files.isDirectory(input)) {
				"Make sure that given path must exist and be a directory with kotlin files to watch."
			}
		} ?: Paths.get(".")

	}

	fun extractConfig(): Config =
			config?.let { YamlConfig.load(ExistingPathConverter().convert(it)) } ?: Config.empty
}

package io.gitlab.arturbosch.detekt.watchservice

import java.nio.file.FileVisitResult
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE
import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE
import java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY
import java.nio.file.WatchService
import java.nio.file.attribute.BasicFileAttributes

/**
 * @author Artur Bosch
 */
class WatchServiceDirectoryRegisteringVisitor(private val watchService: WatchService) : SimpleFileVisitor<Path>() {

	override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {
		dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY)
		return FileVisitResult.CONTINUE
	}
}

package io.gitlab.arturbosch.detekt.watchservice

import java.nio.file.Path
import java.nio.file.WatchEvent

/**
 * @author Artur Bosch
 */
data class WatchedDir(val valid: Boolean,
					  val dir: Path,
					  val events: List<PathEvent>)

data class PathEvent(val path: Path, val kind: WatchEvent.Kind<*>)

package io.gitlab.arturbosch.detekt.watchservice

import java.nio.file.Path
import java.nio.file.WatchService
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit

/**
 * @author Artur Bosch
 */
fun startWatching(watchService: WatchService, detektService: DetektService) {
	while (true) {
		val watchKey = watchService.poll(1, TimeUnit.SECONDS)
		if (watchKey != null) {

			val events = watchKey.pollEvents()
					.map { PathEvent(it.context() as Path, it.kind()) }
			val watchedPath = watchKey.watchable() as Path
			val watchedDir = WatchedDir(watchKey.isValid, watchedPath, events)

			CompletableFuture.supplyAsync { detektService.check(watchedDir) }
			watchKey.reset()
		}
	}
}

package io.gitlab.arturbosch.detekt.api.internal

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.psi.KtCallExpression
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.KtWhenExpression
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression

/**
 * @author Artur Bosch
 */
class McCabeVisitor : DetektVisitor() {

	var mcc = 0

	private fun inc() {
		mcc++
	}

	override fun visitNamedFunction(function: KtNamedFunction) {
		inc()
		super.visitNamedFunction(function)
	}

	override fun visitIfExpression(expression: KtIfExpression) {
		inc()
		if (expression.`else` != null) {
			inc()
		}
		super.visitIfExpression(expression)
	}

	override fun visitLoopExpression(loopExpression: KtLoopExpression) {
		inc()
		super.visitLoopExpression(loopExpression)
	}

	override fun visitWhenExpression(expression: KtWhenExpression) {
		mcc += expression.entries.size
		super.visitWhenExpression(expression)
	}

	override fun visitTryExpression(expression: KtTryExpression) {
		inc()
		mcc += expression.catchClauses.size
		expression.finallyBlock?.let { inc() }
		super.visitTryExpression(expression)
	}

	override fun visitCallExpression(expression: KtCallExpression) {
		if (expression.isUsedForNesting()) {
			val lambdaArguments = expression.lambdaArguments
			if (lambdaArguments.size > 0) {
				val lambdaArgument = lambdaArguments[0]
				lambdaArgument.getLambdaExpression().bodyExpression?.let {
					inc()
				}
			}
		}
		super.visitCallExpression(expression)
	}
}

fun KtCallExpression.isUsedForNesting(): Boolean = when (getCallNameExpression()?.text) {
	"run", "let", "apply", "with", "use", "forEach" -> true
	else -> false
}

package io.gitlab.arturbosch.detekt.cli.baseline

/**
 * @author Artur Bosch
 */
data class Baseline(val blacklist: Blacklist, val whitelist: Whitelist) {

	override fun toString(): String {
		return "Baseline(blacklist=$blacklist, whitelist=$whitelist)"
	}
}

val SMELL_BASELINE = "SmellBaseline"
val BLACKLIST = "Blacklist"
val WHITELIST = "Whitelist"
val TIMESTAMP = "timestamp"
val ID = "ID"

class InvalidBaselineState(msg: String, error: Throwable) : IllegalStateException(msg, error)

package io.gitlab.arturbosch.detekt.cli.baseline

import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.cli.baselineId
import io.gitlab.arturbosch.detekt.core.exists
import io.gitlab.arturbosch.detekt.core.isFile
import java.nio.file.Files
import java.nio.file.Path
import java.time.Instant

/**
 * @author Artur Bosch
 */
class BaselineFacade(val baselineFile: Path) {

	private val listings: Pair<Whitelist, Blacklist>? =
			if (baselineExists()) {
				val format = BaselineFormat.read(baselineFile)
				format.whitelist to format.blacklist
			} else null

	fun filter(smells: List<Finding>) =
			if (listings != null) {
				val whiteFiltered = smells.filterNot { finding -> listings.first.ids.contains(finding.baselineId) }
				val blackFiltered = whiteFiltered.filterNot { finding -> listings.second.ids.contains(finding.baselineId) }
				blackFiltered
			} else smells

	fun create(smells: List<Finding>) {
		val timestamp = Instant.now().toEpochMilli().toString()
		val blacklist = if (baselineExists()) {
			BaselineFormat.read(baselineFile).blacklist
		} else {
			Blacklist(emptyList(), timestamp)
		}
		val ids = smells.map { it.baselineId }
		val smellBaseline = Baseline(blacklist, Whitelist(ids, timestamp))
		baselineFile.parent?.let { Files.createDirectories(it) }
		BaselineFormat.write(smellBaseline, baselineFile)
		println("Successfully wrote smell baseline to $baselineFile")
	}

	private fun baselineExists() = baselineFile.exists() && baselineFile.isFile()

}

package io.gitlab.arturbosch.detekt.cli.baseline

import com.sun.xml.internal.txw2.output.IndentingXMLStreamWriter
import org.xml.sax.SAXParseException
import java.io.OutputStream
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import javax.xml.parsers.SAXParserFactory
import javax.xml.stream.XMLOutputFactory
import javax.xml.stream.XMLStreamException
import javax.xml.stream.XMLStreamWriter

/**
 * @author Artur Bosch
 */
object BaselineFormat {

	private val outputFactory by lazy { XMLOutputFactory.newFactory() }
	private val inputFactory by lazy { SAXParserFactory.newInstance() }
	private fun xmlReader() = inputFactory.newSAXParser()
	private fun xmlWriter(out: OutputStream) = outputFactory.createXMLStreamWriter(out, StandardCharsets.UTF_8.name())

	fun read(path: Path): Baseline {
		try {
			Files.newInputStream(path).use {
				val reader = xmlReader()
				val handler = BaselineHandler()
				reader.parse(it, handler)
				return handler.createBaseline()
			}
		} catch (error: SAXParseException) {
			val (line, column) = error.lineNumber to error.columnNumber
			throw InvalidBaselineState("Error on position $line:$column while reading the baseline xml file!", error)
		}
	}

	fun write(baseline: Baseline, path: Path) {
		try {
			Files.newOutputStream(path).use {
				val writer = IndentingXMLStreamWriter(xmlWriter(it))
				writer.save(baseline)
			}
		} catch (error: XMLStreamException) {
			val (line, column) = error.positions
			throw InvalidBaselineState("Error on position $line:$column while writing the baseline xml file!", error)
		}
	}

	private val XMLStreamException.positions
		get() = location.lineNumber to location.columnNumber

	private fun XMLStreamWriter.save(baseline: Baseline) {
		document {
			tag(SMELL_BASELINE) {
				tag(BLACKLIST) {
					val (ids, timestamp) = baseline.blacklist
					attribute(TIMESTAMP, timestamp)
					ids.forEach { tag(ID, it) }
				}
				tag(WHITELIST) {
					val (ids, timestamp) = baseline.whitelist
					attribute(TIMESTAMP, timestamp)
					ids.forEach { tag(ID, it) }
				}
			}
		}
	}
}

package io.gitlab.arturbosch.detekt.cli.baseline

import org.xml.sax.Attributes
import org.xml.sax.helpers.DefaultHandler
import java.time.Instant

/**
 * @author Artur Bosch
 */
class BaselineHandler : DefaultHandler() {

	private var current: String? = null
	private var content: String = ""
	private var whitestamp: String? = null
	private var blackstamp: String? = null
	private val whiteIds = mutableListOf<String>()
	private val blackIds = mutableListOf<String>()

	internal fun createBaseline() = Baseline(
			Blacklist(blackIds, blackstamp ?: now()), Whitelist(whiteIds, whitestamp ?: now()))

	private fun now() = Instant.now().toEpochMilli().toString()

	override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
		when (qName) {
			BLACKLIST -> {
				current = BLACKLIST
				blackstamp = attributes.getValue(TIMESTAMP)
			}
			WHITELIST -> {
				current = WHITELIST
				whitestamp = attributes.getValue(TIMESTAMP)
			}
			ID -> content = ""
		}
	}

	override fun endElement(uri: String, localName: String, qName: String) {
		when (qName) {
			ID -> if (content.isNotBlank()) {
				when (current) {
					BLACKLIST -> blackIds.add(content)
					WHITELIST -> whiteIds.add(content)
				}
				content = ""
			}
			BLACKLIST, WHITELIST -> current == null
		}
	}

	override fun characters(ch: CharArray, start: Int, length: Int) {
		if (current != null) content += String(ch, start, length)
	}
}

package io.gitlab.arturbosch.detekt.cli.baseline

/**
 * @author Artur Bosch
 */
data class Blacklist(override val ids: List<String>,
					 override val timestamp: String) : Listing<Blacklist> {

	override fun withNewTimestamp(timestamp: String,
								  list: Blacklist) = Blacklist(list.ids, timestamp)

	override fun toString(): String {
		return "Blacklist(ids=$ids, timestamp='$timestamp')"
	}
}

package io.gitlab.arturbosch.detekt.cli.baseline

import java.time.Instant

/**
 * @author Artur Bosch
 */
interface Listing<T> {

	val timestamp: String
	val ids: List<String>

	fun isOlderThan(instant: Instant): Boolean {
		val timeMillis = Instant.ofEpochMilli(timestamp.toLong())
		return timeMillis < instant
	}

	fun withNewTimestamp(timestamp: String, list: T): T
}

package io.gitlab.arturbosch.detekt.cli.baseline

/**
 * @author Artur Bosch
 */
data class Whitelist(override val ids: List<String>,
					 override val timestamp: String) : Listing<Whitelist> {

	override fun withNewTimestamp(timestamp: String,
								  list: Whitelist) = Whitelist(list.ids, timestamp)

	override fun toString(): String {
		return "Blacklist(ids=$ids, timestamp='$timestamp')"
	}
}

package io.gitlab.arturbosch.detekt.cli.baseline

import javax.xml.stream.XMLStreamWriter

fun XMLStreamWriter.document(init: XMLStreamWriter.() -> Unit) = apply {
	writeStartDocument()
	init()
	writeEndDocument()
}

fun XMLStreamWriter.tag(name: String, init: XMLStreamWriter.() -> Unit) = apply {
	writeStartElement(name)
	init()
	writeEndElement()
}

fun XMLStreamWriter.tag(name: String, content: String) {
	tag(name) {
		writeCharacters(content)
	}
}

fun XMLStreamWriter.attribute(name: String, value: String) = writeAttribute(name, value)

package io.gitlab.arturbosch.detekt.cli.console

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.ConsoleReport
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.api.SingleAssign
import java.util.HashMap

/**
 * @author Artur Bosch
 */
class BuildFailureReport : ConsoleReport() {

	override val priority: Int = Int.MIN_VALUE

	private var weightsConfig: Config by SingleAssign()
	private var warningThreshold: Int by SingleAssign()
	private var failThreshold: Int by SingleAssign()

	override fun init(config: Config) {
		val buildConfig = config.subConfig("build")
		weightsConfig = buildConfig.subConfig("weights")
		warningThreshold = buildConfig.valueOrDefault("warningThreshold", -1)
		failThreshold = buildConfig.valueOrDefault("failThreshold", -1)
	}

	override fun render(detektion: Detektion): String? {
		val smells = detektion.findings.flatMap { it.value }
		val ruleToRuleSetId = extractRuleToRuleSetIdMap(detektion)
		val amount = smells.map { it.weighted(ruleToRuleSetId) }.sum()

		if (failThreshold.reached(amount)) {
			throw BuildFailure("Build failure threshold of $failThreshold reached with $amount weighted smells!")
		} else if (warningThreshold.reached(amount)) {
			return "Warning: $amount weighted code smells found. " +
					"Warning threshold is $warningThreshold and fail threshold is $failThreshold!"
		} else {
			return null
		}
	}

	private fun extractRuleToRuleSetIdMap(detektion: Detektion): HashMap<String, String> {
		return detektion.findings.mapValues { it.value.map { it.id }.toSet() }
				.map { map -> map.value.map { it to map.key }.toMap() }
				.fold(HashMap()) { result, map -> result.putAll(map); result }
	}

	private fun Finding.weighted(ids: Map<String, String>): Int {
		val key = ids[id] // entry of ID > entry of RuleSet ID > default weight 1
		return weightsConfig.valueOrDefault(id,
				if (key != null) weightsConfig.valueOrDefault(key, 1) else 1)
	}
}

internal fun Int.reached(amount: Int): Boolean = !(this == 0 && amount == 0) && this != -1 && this <= amount

class BuildFailure(override val message: String?) : RuntimeException(message)

package io.gitlab.arturbosch.detekt.cli.console

import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.core.processors.COMPLEXITY_KEY
import io.gitlab.arturbosch.detekt.core.processors.LLOC_KEY
import io.gitlab.arturbosch.detekt.core.processors.LOC_KEY
import io.gitlab.arturbosch.detekt.core.processors.NUMBER_OF_COMMENT_LINES_KEY
import io.gitlab.arturbosch.detekt.core.processors.SLOC_KEY

internal class ComplexityMetric(detektion: Detektion) {

	val mcc = detektion.getData(COMPLEXITY_KEY)
	val loc = detektion.getData(LOC_KEY)
	val sloc = detektion.getData(SLOC_KEY)
	val lloc = detektion.getData(LLOC_KEY)
	val cloc = detektion.getData(NUMBER_OF_COMMENT_LINES_KEY)
	val findings = detektion.findings.entries
}

package io.gitlab.arturbosch.detekt.cli.console

import io.gitlab.arturbosch.detekt.api.ConsoleReport
import io.gitlab.arturbosch.detekt.api.Detektion

/**
 * @author Artur Bosch
 */
class ComplexityReport : ConsoleReport() {

	override val priority: Int = 20

	override fun render(detektion: Detektion): String? {
		val complexityReportGenerator = ComplexityReportGenerator.create(detektion)
		return complexityReportGenerator.generate()
	}
}

package io.gitlab.arturbosch.detekt.cli.console

import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.PREFIX
import io.gitlab.arturbosch.detekt.api.format

internal class ComplexityReportGenerator(private val complexityMetric: ComplexityMetric) {

	private var numberOfSmells = 0
	private var smellPerThousandLines = 0
	private var mccPerThousandLines = 0
	private var commentSourceRatio = 0

	companion object Factory {
		fun create(detektion: Detektion): ComplexityReportGenerator = ComplexityReportGenerator(ComplexityMetric(detektion))
	}

	fun generate(): String? {
		if (cannotGenerate()) return null
		return with(StringBuilder()) {
			append("Complexity Report:".format())
			append("${complexityMetric.loc} lines of code (loc)".format(PREFIX))
			append("${complexityMetric.sloc} source lines of code (sloc)".format(PREFIX))
			append("${complexityMetric.lloc} logical lines of code (lloc)".format(PREFIX))
			append("${complexityMetric.cloc} comment lines of code (cloc)".format(PREFIX))
			append("${complexityMetric.mcc} McCabe complexity (mcc)".format(PREFIX))
			append("$numberOfSmells number of total code smells".format(PREFIX))
			append("$commentSourceRatio % comment source ratio".format(PREFIX))
			append("$mccPerThousandLines mcc per 1000 lloc".format(PREFIX))
			append("$smellPerThousandLines code smells per 1000 lloc".format(PREFIX))
			toString()
		}
	}

	private fun cannotGenerate(): Boolean {
		return when {
			complexityMetric.mcc == null -> true
			complexityMetric.lloc == null || complexityMetric.lloc == 0 -> true
			complexityMetric.sloc == null || complexityMetric.sloc == 0 -> true
			complexityMetric.cloc == null -> true
			else -> {
				numberOfSmells = complexityMetric.findings.sumBy { it.value.size }
				smellPerThousandLines = numberOfSmells * 1000 / complexityMetric.lloc
				mccPerThousandLines = complexityMetric.mcc * 1000 / complexityMetric.lloc
				commentSourceRatio = complexityMetric.cloc * 100 / complexityMetric.sloc
				false
			}
		}
	}
}

package io.gitlab.arturbosch.detekt.cli.console

import io.gitlab.arturbosch.detekt.api.ConsoleReport
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.format

/**
 * @author Artur Bosch
 */
class FindingsReport : ConsoleReport() {

	override val priority: Int = 40

	override fun render(detektion: Detektion): String? {
		val findings = detektion.findings
		return with(StringBuilder()) {
			findings.forEach {
				append(it.key.format("Ruleset: "))
				it.value.forEach {
					append(it.compact().format("\t"))
				}
			}
			toString()
		}
	}
}

package io.gitlab.arturbosch.detekt.cli.console

import io.gitlab.arturbosch.detekt.api.ConsoleReport
import io.gitlab.arturbosch.detekt.api.Detektion

/**
 * @author Artur Bosch
 */
class NotificationReport : ConsoleReport() {

	override val priority: Int = 50

	override fun render(detektion: Detektion): String? {
		val notifications = detektion.notifications
		return notifications.joinToString("\n") { it.message }
	}
}

package io.gitlab.arturbosch.detekt.cli.console

import io.gitlab.arturbosch.detekt.api.ConsoleReport
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.PREFIX
import io.gitlab.arturbosch.detekt.api.format

/**
 * @author Artur Bosch
 */
class ProjectStatisticsReport : ConsoleReport() {

	override val priority: Int = 10

	override fun render(detektion: Detektion): String? {
		val metrics = detektion.metrics
		if (metrics.isEmpty()) return null
		return with(StringBuilder()) {
			append("Project Statistics:".format())
			metrics.sortedBy { it.priority }
					.reversed()
					.forEach { append(it.toString().format(PREFIX)) }
			toString()
		}
	}
}

package io.gitlab.arturbosch.detekt.cli.out

import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.OutputReport

class PlainOutputReport : OutputReport() {

	override var fileName = "detekt-plain"
	override val ending: String = "txt"

	override fun render(detektion: Detektion): String {
		val smells = detektion.findings.flatMap { it.value }
		return smells.joinToString("\n") { it.compactWithSignature() }
	}
}

/*
 * =============================================================================
 *
 *   Copyright (c) 2014, The UNBESCAPE team (http://www.unbescape.org)
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 * =============================================================================
 */
@file:Suppress("ALL")

package io.gitlab.arturbosch.detekt.cli.out

import java.util.ArrayList
import java.util.Arrays
import java.util.Collections

/**
 * Adapted from Unbescape - https://github.com/unbescape/unbescape/
 *
 * Utility class for performing XML escape/unescape operations.
 *
 * @author Daniel Fernndez
 * @since 1.0.0
 */
object XmlEscape {

	private val REFERENCE_HEXA_PREFIX = "&#x".toCharArray()
	private val REFERENCE_SUFFIX = ';'

	/**
	 * Perform an XML 1.0 level 2 (markup-significant and all non-ASCII chars) **escape** operation
	 * on a <tt>String</tt> input.
	 *
	 * *Level 2* means this method will escape:
	 *  * The five markup-significant characters: <tt><</tt>, <tt>></tt>, <tt>&</tt>,
	 * <tt>"</tt> and <tt>'</tt>
	 *  * All non ASCII characters.
	 *
	 * This escape will be performed by replacing those chars by the corresponding XML Character Entity References
	 * (e.g. <tt>'&lt;'</tt>) when such CER exists for the replaced character, and replacing by a hexadecimal
	 * character reference (e.g. <tt>'&#x2430;'</tt>) when there there is no CER for the replaced character.
	 *
	 * This method is **thread-safe**.

	 * @param text the <tt>String</tt> to be escaped.
	 * *
	 * @return The escaped result <tt>String</tt>. As a memory-performance improvement, will return the exact
	 * *         same object as the <tt>text</tt> input argument if no escaping modifications were required (and
	 * *         no additional <tt>String</tt> objects will be created during processing). Will
	 * *         return <tt>null</tt> if input is <tt>null</tt>.
	 */
	fun escapeXml(text: String): String {
		val symbols = Xml10EscapeSymbolsInitializer.initializeXml10()
		val level = 2
		var strBuilder: StringBuilder? = null
		val offset = 0
		val max = text.length
		var readOffset = offset

		var i = offset
		while (i < max) {
			val codepoint = Character.codePointAt(text, i)
			val codepointValid = symbols.CODEPOINT_VALIDATOR.isValid(codepoint)

			/*
			 * Shortcut: most characters will be ASCII/Alphanumeric, and we won't need to do anything at
			 * all for them
			*/
			if (codepoint <= Xml10EscapeSymbolsInitializer.XmlEscapeSymbols.LEVELS_LEN - 2
					&& level < symbols.ESCAPE_LEVELS[codepoint]
					&& codepointValid) {
				i++
				continue
			}

			/*
			 * Shortcut: we might not want to escape non-ASCII chars at all either.
			 */
			if (codepoint > Xml10EscapeSymbolsInitializer.XmlEscapeSymbols.LEVELS_LEN - 2
					&& level < symbols.ESCAPE_LEVELS[Xml10EscapeSymbolsInitializer.XmlEscapeSymbols.LEVELS_LEN - 1]
					&& codepointValid) {
				if (Character.charCount(codepoint) > 1) {
					// This is to compensate that we are actually escaping two char[] positions with a single codepoint.
					i++
				}
				i++
				continue
			}

			/*
			 * At this point we know for sure we will need some kind of escape, so we
			 * can increase the offset and initialize the string builder if needed, along with
			 * copying to it all the contents pending up to this point.
			 */
			if (strBuilder == null) {
				strBuilder = StringBuilder(max + 20)
			}
			if (i - readOffset > 0) {
				strBuilder.append(text, readOffset, i)
			}
			if (Character.charCount(codepoint) > 1) {
				// This is to compensate that we are actually reading two char[] positions with a single codepoint.
				i++
			}
			readOffset = i + 1

			/*
			 * If the char is invalid, there is nothing to write, simply skip it (which we already did by
			 * incrementing the readOffset.
			 */
			if (!codepointValid) {
				i++
				continue
			}

			/*
			 * -----------------------------------------------------------------------------------------
			 *
			 * Perform the real escape, attending the different combinations of NCR, DCR and HCR needs.
			 *
			 * -----------------------------------------------------------------------------------------
			 */
			// We will try to use a CER
			val codepointIndex = Arrays.binarySearch(symbols.SORTED_CODEPOINTS, codepoint)
			if (codepointIndex >= 0) {
				// CER found! just write it and go for the next char
				strBuilder.append(symbols.SORTED_CERS_BY_CODEPOINT[codepointIndex])
				i++
				continue
			}
			/*
			 * No NCR-escape was possible (or allowed), so we need decimal/hexa escape.
			 */
			strBuilder.append(REFERENCE_HEXA_PREFIX)
			strBuilder.append(Integer.toHexString(codepoint))
			strBuilder.append(REFERENCE_SUFFIX)
			i++
		}

		/*
		 * -----------------------------------------------------------------------------------------------
		 * Final cleaning: return the original String object if no escape was actually needed. Otherwise
		 *                 append the remaining unescaped text to the string builder and return.
		 * -----------------------------------------------------------------------------------------------
		 */
		if (strBuilder == null) {
			return text
		}
		if (max - readOffset > 0) {
			strBuilder.append(text, readOffset, max)
		}
		return strBuilder.toString()

	}
}

/**
 * This class initializes the XML10_SYMBOLS structure.
 */
@Suppress("ALL")
private object Xml10EscapeSymbolsInitializer {

	internal class XmlCodepointValidator {

		/*
		 * XML 1.0 does not allow many control characters, nor unpaired surrogate chars
		 * (characters used for composing two-char codepoints, but appearing on their own).
		 */
		fun isValid(codepoint: Int): Boolean {
			if (codepoint < 0x20) {
				return codepoint == 0x9 || codepoint == 0xA || codepoint == 0xD
			}
			if (codepoint <= 0xD7FF) { // U+D800 - U+DFFF are reserved for low + high surrogates
				return true
			}
			if (codepoint < 0xE000) {
				return false
			}
			if (codepoint <= 0xFFFD) { // U+FFFE and U+FFFF are non-characters, and therefore not valid
				return true
			}
			if (codepoint < 0x10000) {
				return false
			}
			return true
		}

	}

	fun initializeXml10(): XmlEscapeSymbols {

		val xml10References = XmlEscapeSymbols.References()

		/*
		 * --------------------------------------------------------------------------------------------------
		 *   XML 1.0 CHARACTER ENTITY REFERENCES
		 *   See: http://www.w3.org/TR/xml
		 * --------------------------------------------------------------------------------------------------
		 */
		xml10References.addReference(34, """)
		xml10References.addReference(38, "&")
		xml10References.addReference(39, "'")
		xml10References.addReference(60, "<")
		xml10References.addReference(62, ">")

		/*
		 * Initialization of escape markup-significant characters plus all non-ASCII
		 */
		val escapeLevels = ByteArray(XmlEscapeSymbols.LEVELS_LEN)
		/*
		 * Everything is level 3 unless contrary indication.
		 */
		Arrays.fill(escapeLevels, 3.toByte())
		/*
		 * Everything non-ASCII is level 2 unless contrary indication.
		 */
		for (c in 0x80..XmlEscapeSymbols.LEVELS_LEN - 1) {
			escapeLevels[c] = 2
		}

		/*
		 * Alphanumeric characters are level 4.
		 */
		run {
			var c = 'A'
			while (c <= 'Z') {
				escapeLevels[c] = 4
				c++
			}
		}
		run {
			var c = 'a'
			while (c <= 'z') {
				escapeLevels[c] = 4
				c++
			}
		}
		run {
			var c = '0'
			while (c <= '9') {
				escapeLevels[c] = 4
				c++
			}
		}

		/*
		 * The five XML predefined entities will be escaped always (level 1)
		 */
		escapeLevels['\''] = 1
		escapeLevels['"'] = 1
		escapeLevels['<'] = 1
		escapeLevels['>'] = 1
		escapeLevels['&'] = 1

		/*
		 * XML 1.0 allows a series of control characters, but they should appear
		 * escaped: [#x7F-#x84] | [#x86-#x9F]
		 */
		for (c in 0x7F..0x84) {
			escapeLevels[c] = 1
		}
		for (c in 0x86..0x9F) {
			escapeLevels[c] = 1
		}

		/*
		 * Create the new symbols structure
		 */
		return XmlEscapeSymbols(xml10References, escapeLevels, XmlCodepointValidator())
	}

	private operator fun ByteArray.set(c: Char, value: Byte) {
		set(c.toInt(), value)
	}

	/**
	 * Instances of this class group all the complex data structures needed to support escape and unescape
	 * operations for XML.
	 *
	 * In contrast with HTML escape operations, the entity references to be used for XML escape/unescape operations
	 * can be defined by the user by manually creating an instance of this class containing all the entities he/she
	 * wants to escape.
	 *
	 * It is **not** recommended to use this XML class for HTML escape/unescape operations. Use the methods
	 * in [org.unbescape.html.HtmlEscape] instead, as HTML escape rules include a series of tweaks not allowed in
	 * XML, as well as being less lenient with regard to aspects such as case-sensitivity. Besides, the HTML escape
	 * infrastructure is able to apply a series of performance optimizations not possible in XML due to the fact that
	 * the number of HTML Character Entity References (*Named Character References* in HTML5 jargon) is fixed
	 * and known in advance.
	 *
	 * Objects of this class are **thread-safe**.
	 */
	class XmlEscapeSymbols
	/*
	 * Create a new XmlEscapeSymbols structure. This will initialize all the structures needed to cover the
	 * specified references and escape levels, including sorted arrays, overflow maps, etc.
	 */
	internal constructor(references: References,
						 escapeLevels: ByteArray,
			/*
			* This object will be in charge of validating each codepoint in input, in order to determine
			* whether such codepoint will be allowed in escaped output (escaped or not). Invalid codepoints
			* will be simply discarded.
			*/
						 val CODEPOINT_VALIDATOR: XmlCodepointValidator) {

		/*
		 * This array will hold the 'escape level' assigned to chars (not codepoints) up to LEVELS_LEN.
		 * - The last position of this array will be used for determining the level of all codepoints >= (LEVELS_LEN - 1)
		 */
		val ESCAPE_LEVELS = ByteArray(LEVELS_LEN)

		/*
		 * This array will contain all the codepoints that might be escaped, numerically ordered.
		 * - Positions in this array will correspond to positions in the SORTED_CERS_BY_CODEPOINT array, so that one array
		 *   (this one) holds the codepoints while the other one holds the CERs such codepoints refer to.
		 * - Gives the opportunity to store all codepoints in numerical order and therefore be able to perform
		 *   binary search operations in order to quickly find codepoints (and translate to CERs) when escaping.
		 */
		val SORTED_CODEPOINTS: IntArray

		/*
		 * This array contains all the CERs corresponding to the codepoints stored in SORTED_CODEPOINTS. This array is
		 * ordered so that each index in SORTED_CODEPOINTS can also be used to retrieve the corresponding CER when used
		 * on this array.
		 */
		val SORTED_CERS_BY_CODEPOINT: Array<CharArray?>

		/*
		 * This array will contain all the CERs that might be unescaped, alphabetically ordered.
		 * - Positions in this array will correspond to positions in the SORTED_CODEPOINTS_BY_CER array, so that one array
		 *   (this one) holds the CERs while the other one holds the codepoint(s) such CERs refer to.
		 * - Gives the opportunity to store all CERs in alphabetical order and therefore be able to perform
		 *   binary search operations in order to quickly find CERs (and translate to codepoints) when unescaping.
		 */
		val SORTED_CERS: Array<CharArray?>

		/*
		 * This array contains all the codepoints corresponding to the CERs stored in SORTED_CERS. This array is
		 * ordered so that each index in SORTED_CERS can also be used to retrieve the corresponding CODEPOINT when used
		 * on this array.
		 */
		val SORTED_CODEPOINTS_BY_CER: IntArray

		init {

			// Initialize escape levels: just copy the array
			System.arraycopy(escapeLevels, 0, ESCAPE_LEVELS, 0, LEVELS_LEN)

			// Initialize the length of the escaping structures
			val structureLen = references.references.size

			// Initialize some auxiliary structures
			val cers = ArrayList<CharArray>(structureLen + 5)
			val codepoints = ArrayList<Int>(structureLen + 5)

			// For each reference, initialize its corresponding codepoint -> CER and CER -> codepoint structures
			for (reference in references.references) {
				cers.add(reference.cer) // can be null
				codepoints.add(Integer.valueOf(reference.codepoint))
			}

			// We can initialize now the arrays
			SORTED_CODEPOINTS = IntArray(structureLen)
			SORTED_CERS_BY_CODEPOINT = arrayOfNulls(structureLen)
			SORTED_CERS = arrayOfNulls(structureLen)
			SORTED_CODEPOINTS_BY_CER = IntArray(structureLen)

			val cersOrdered = ArrayList(cers)
			Collections.sort(cersOrdered) { o1, o2 -> String(o1).compareTo(String(o2)) }

			val codepointsOrdered = ArrayList(codepoints)
			Collections.sort(codepointsOrdered)

			// Order the CODEPOINT -> CERs (escape)structures
			for (i in 0..structureLen - 1) {
				val codepoint = codepointsOrdered[i]
				SORTED_CODEPOINTS[i] = codepoint
				for (j in 0..structureLen - 1) {
					if (codepoint == codepoints[j]) {
						SORTED_CERS_BY_CODEPOINT[i] = cers[j]
						break
					}
				}
			}

			// Order the CERs -> CODEPOINT (unescape)structures
			for (i in 0..structureLen - 1) {
				val cer = cersOrdered[i]
				SORTED_CERS[i] = cer
				for (j in 0..structureLen - 1) {
					if (Arrays.equals(cer, cers[j])) {
						SORTED_CODEPOINTS_BY_CER[i] = codepoints[j]
						break
					}
				}
			}
		}

		/*
		 * Inner utility classes that model the named character references to be included in an initialized
		 * instance of the XmlEscapeSymbols class.
		 */
		class References {

			internal val references = ArrayList<Reference>(200)

			fun addReference(codepoint: Int, cer: String) {
				this.references.add(Reference(cer, codepoint))
			}
		}

		class Reference internal constructor(cer: String, internal val codepoint: Int) {
			// cer CAN be null -> codepoint should be removed from escaped output.
			internal val cer: CharArray = cer.toCharArray()
		}

		companion object {
			/*
			 * Size of the array specifying the escape levels.
			 */
			val LEVELS_LEN = (0x9f + 2)
		}
	}
}

package io.gitlab.arturbosch.detekt.cli.out

import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.api.OutputReport
import io.gitlab.arturbosch.detekt.api.Severity

/**
 * Generates an XML report following the structure of a Checkstyle report.
 */
class XmlOutputReport : OutputReport() {

	override var fileName = "detekt-checkstyle"
	override val ending: String = "xml"

	private sealed class MessageType(val label: String) {
		class Warning : MessageType("warning")
		class Info : MessageType("info")
		class Fatal : MessageType("fatal")
		class Error : MessageType("error")
	}

	override fun render(detektion: Detektion): String {
		val smells = detektion.findings.flatMap { it.value }

		val lines = ArrayList<String>()
		lines += "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
		lines += "<checkstyle version=\"4.3\">"

		smells.groupBy { it.location.file }.forEach { fileName, findings ->
			lines += "<file name=\"${fileName.toXmlString()}\">"
			findings.forEach {
				lines += arrayOf(
						"\t<error line=\"${it.location.source.line.toXmlString()}\"",
						"column=\"${it.location.source.column.toXmlString()}\"",
						"severity=\"${it.messageType.label.toXmlString()}\"",
						"message=\"${it.issue.description.toXmlString()}\"",
						"source=\"${"detekt.${it.id.toXmlString()}"}\" />"
				).joinToString(separator = " ")
			}
			lines += "</file>"
		}

		lines += "</checkstyle>"
		return lines.joinToString(separator = "\n")
	}

	private val Finding.messageType: MessageType
		get() = when (issue.severity) {
			Severity.CodeSmell -> MessageType.Warning()
			Severity.Style -> MessageType.Warning()
			Severity.Warning -> MessageType.Warning()
			Severity.Maintainability -> MessageType.Warning()
			Severity.Defect -> MessageType.Error()
			Severity.Minor -> MessageType.Info()
			Severity.Security -> MessageType.Fatal()
			Severity.Performance -> MessageType.Warning()
		}

	private fun Any.toXmlString() = XmlEscape.escapeXml(toString().trim())
}

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.FileProcessListener
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtFile

abstract class AbstractProcessor : FileProcessListener {

	protected abstract val visitor: DetektVisitor
	protected abstract val key: Key<Int>

	override fun onProcess(file: KtFile) {
		file.accept(visitor)
	}

	override fun onFinish(files: List<KtFile>, result: Detektion) {
		val count = files
				.mapNotNull { it.getUserData(key) }
				.sum()
		result.addData(key, count)
	}
}

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.ProjectMetric
import org.jetbrains.kotlin.psi.KtFile

/**
 * @author Artur Bosch
 */
abstract class AbstractProjectMetricProcessor : AbstractProcessor() {

	val type: String get() = key.toString()

	override fun onFinish(files: List<KtFile>, result: Detektion) {
		val count = files
				.mapNotNull { it.getUserData(key) }
				.sum()
		result.add(ProjectMetric(type, count))
	}
}

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.core.processors.util.collectByType
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile

class ClassCountProcessor : AbstractProjectMetricProcessor() {

	override val visitor = ClassCountVisitor()
	override val key = NUMBER_OF_CLASSES_KEY
}

val NUMBER_OF_CLASSES_KEY = Key<Int>("number of classes")

class ClassCountVisitor : DetektVisitor() {

	override fun visitKtFile(file: KtFile) {
		super.visitKtFile(file)
		file.putUserData(NUMBER_OF_CLASSES_KEY, file.collectByType<KtClass>().size)
	}
}

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.core.processors.util.collectByType
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction

class FunctionCountProcessor : AbstractProjectMetricProcessor() {

	override val visitor = FunctionCountVisitor()
	override val key = NUMBER_OF_FUNCTIONS_KEY
}

val NUMBER_OF_FUNCTIONS_KEY = Key<Int>("number of functions")

class FunctionCountVisitor : DetektVisitor() {

	override fun visitKtFile(file: KtFile) {
		super.visitKtFile(file)
		file.putUserData(NUMBER_OF_FUNCTIONS_KEY, file.collectByType<KtNamedFunction>().size)
	}
}

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtFile

class KtFileCountProcessor : AbstractProjectMetricProcessor() {

	override val visitor = KtFileCountVisitor()
	override val key = NUMBER_OF_FILES_KEY
}

val NUMBER_OF_FILES_KEY = Key<Int>("number of kt files")

class KtFileCountVisitor : DetektVisitor() {
	override fun visitKtFile(file: KtFile) {
		file.putUserData(NUMBER_OF_FILES_KEY, 1)
	}

}

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.FileProcessListener
import io.gitlab.arturbosch.detekt.api.ProjectMetric
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtFile

class PackageCountProcessor : FileProcessListener {

	private val visitor = PackageCountVisitor()
	private val key = NUMBER_OF_PACKAGES_KEY

	override fun onProcess(file: KtFile) {
		file.accept(visitor)
	}

	override fun onFinish(files: List<KtFile>, result: Detektion) {
		val count = files
				.mapNotNull { it.getUserData(key) }
				.distinct()
				.size
		result.add(ProjectMetric(key.toString(), count))
	}
}

val NUMBER_OF_PACKAGES_KEY = Key<String>("number of packages")

class PackageCountVisitor : DetektVisitor() {

	override fun visitKtFile(file: KtFile) {
		val packageName = file.packageFqNameByTree.toString()
		file.putUserData(NUMBER_OF_PACKAGES_KEY, packageName)
	}
}

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile

class ProjectCLOCProcessor : AbstractProcessor() {

	override val key = NUMBER_OF_COMMENT_LINES_KEY
	override val visitor = CLOCVisitor()
}

val NUMBER_OF_COMMENT_LINES_KEY = Key<Int>("cloc")

class CLOCVisitor : DetektVisitor() {

	override fun visitKtFile(file: KtFile) {
		with(CLOCCountVisitor()) {
			file.accept(this)
			file.putUserData(NUMBER_OF_COMMENT_LINES_KEY, count)
		}
	}
}

internal class CLOCCountVisitor : DetektVisitor() {

	internal var count = 0

	private fun increment(value: Int) {
		count += value
	}

	override fun visitComment(comment: PsiComment?) {
		if (comment != null) {
			increment(comment.text.split('\n').size)
		}
	}

	override fun visitDeclaration(dcl: KtDeclaration) {
		val text = dcl.docComment?.text
		if (text != null) {
			increment(text.split('\n').size)
		}
		super.visitDeclaration(dcl)
	}
}

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.internal.McCabeVisitor
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtFile

class ProjectComplexityProcessor : AbstractProcessor() {

	override val visitor = ComplexityVisitor()
	override val key = COMPLEXITY_KEY
}

val COMPLEXITY_KEY = Key<Int>("complexity")

class ComplexityVisitor : DetektVisitor() {

	override fun visitKtFile(file: KtFile) {
		with(McCabeVisitor()) {
			file.accept(this)
			file.putUserData(COMPLEXITY_KEY, mcc)
		}
	}
}

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.core.processors.util.LLOC
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtFile

class ProjectLLOCProcessor : AbstractProcessor() {

	override val visitor = LLOCVisitor()
	override val key = LLOC_KEY
}

val LLOC_KEY = Key<Int>("lloc")

class LLOCVisitor : DetektVisitor() {

	override fun visitKtFile(file: KtFile) {
		val lines = file.text.split("\n")
		val value = LLOC.analyze(lines)
		file.putUserData(LLOC_KEY, value)
	}
}

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtFile

class ProjectLOCProcessor : AbstractProcessor() {

	override val visitor: DetektVisitor = LOCVisitor()
	override val key = LOC_KEY
}

class LOCVisitor : DetektVisitor() {

	override fun visitKtFile(file: KtFile) {
		val lines = file.text.count { it == '\n' } + 1
		file.putUserData(LOC_KEY, lines)
	}
}

val LOC_KEY = Key<Int>("loc")

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtFile

class ProjectSLOCProcessor : AbstractProcessor() {

	override val visitor: DetektVisitor = SLOCVisitor()
	override val key: Key<Int> = SLOC_KEY
}

class SLOCVisitor : DetektVisitor() {

	override fun visitKtFile(file: KtFile) {
		val lines = file.text.split('\n')
		val sloc = SLOC().count(lines)
		file.putUserData(SLOC_KEY, sloc)
	}

	private class SLOC {

		private val comments = arrayOf("//", "/*", "*/", "*")

		fun count(lines: List<String>): Int {
			return lines
						.map { it.trim() }
						.filter { trim -> trim.isNotEmpty() && !comments.any { trim.startsWith(it) } }
						.size
		}
	}
}

val SLOC_KEY = Key<Int>("sloc")

package io.gitlab.arturbosch.detekt.core.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.core.processors.util.collectByType
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtProperty

class PropertyCountProcessor : AbstractProjectMetricProcessor() {

	override val visitor = PropertyCountVisitor()
	override val key = NUMBER_OF_FIELDS_KEY
}

val NUMBER_OF_FIELDS_KEY = Key<Int>("number of properties")

class PropertyCountVisitor : DetektVisitor() {

	override fun visitKtFile(file: KtFile) {
		super.visitKtFile(file)
		file.putUserData(NUMBER_OF_FIELDS_KEY, file.collectByType<KtProperty>().size)
	}
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtWhenExpression

/**
 * @author Artur Bosch
 */
class DuplicateCaseInWhenExpression(config: Config) : Rule(config) {

	override val issue = Issue("DuplicateCaseInWhenExpression",
			Severity.Warning,
			"Duplicated case statements in when expression. " +
					"Both cases should be merged.",
			Debt.TEN_MINS)

	override fun visitWhenExpression(expression: KtWhenExpression) {
		val numberOfEntries = expression.entries.size
		val distinctNumber = expression.entries
				.map { it.conditions }
				.fold(mutableListOf<String>(), { state, conditions ->
					state.apply { add(conditions.joinToString { it.text }) }
				})
				.distinct().size

		if (numberOfEntries > distinctNumber) {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.asBlockExpression
import io.gitlab.arturbosch.detekt.rules.bugs.util.isEqualsMethod
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtReturnExpression

class EqualsAlwaysReturnsTrueOrFalse(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("EqualsAlwaysReturnsTrueOrFalse",
			Severity.Defect,
			"Having an equals method which always returns true or false is not a good idea. " +
					"It does not follow the contract of this method. " +
					"Consider a good default implementation. " +
					"For example this == other")

	override fun visitClass(klass: KtClass) {
		if (!klass.isInterface()) {
			super.visitClass(klass)
		}
	}

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (function.isEqualsMethod() && isReturningBooleanConstant(function)) {
			report(CodeSmell(issue, Entity.from(function)))
		}
	}

	private fun isReturningBooleanConstant(function: KtNamedFunction): Boolean {
		val returnExpression = function.bodyExpression.asBlockExpression()?.statements?.lastOrNull() as? KtReturnExpression
		val text = returnExpression?.returnedExpression?.text
		return text == KtTokens.TRUE_KEYWORD.value || text == KtTokens.FALSE_KEYWORD.value
	}
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.bugs.util.isEqualsMethod
import io.gitlab.arturbosch.detekt.rules.bugs.util.isHashCodeMethod
import io.gitlab.arturbosch.detekt.rules.isDataClass
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedFunction
import java.util.ArrayDeque

/**
 * @author Artur Bosch
 */
class EqualsWithHashCodeExist(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("EqualsWithHashCodeExist",
			Severity.Defect,
			"Always override hashCode when you override equals. " +
					"All hash-based collections depend on objects meeting the equals-contract. " +
					"Two equal objects must produce the same hashcode. When inheriting equals or hashcode, " +
					"override the inherited and call the super method for clarification.")

	private val queue = ArrayDeque<ViolationHolder>(MAXIMUM_EXPECTED_NESTED_CLASSES)

	private data class ViolationHolder(var equals: Boolean = false, var hashCode: Boolean = false) {
		internal fun violation() = equals && !hashCode || !equals && hashCode
	}

	override fun visitFile(file: PsiFile?) {
		queue.clear()
		super.visitFile(file)
	}

	override fun visitClassOrObject(classOrObject: KtClassOrObject) {
		val klass = classOrObject as? KtClass
		if (klass != null && (klass.isInterface() || klass.isDataClass())) {
			return
		}
		queue.push(ViolationHolder())
		super.visitClassOrObject(classOrObject)
		if (queue.pop().violation()) report(CodeSmell(issue, Entity.from(classOrObject)))
	}

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (!function.isTopLevel) {
			function.let {
				if (it.isEqualsMethod()) queue.peek().equals = true
				if (it.isHashCodeMethod()) queue.peek().hashCode = true
			}
		}
	}

}

private const val MAXIMUM_EXPECTED_NESTED_CLASSES = 5

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression

/**
 * @author Artur Bosch
 */
class ExplicitGarbageCollectionCall(config: Config) : Rule(config) {

	override val issue = Issue("ExplicitGarbageCollectionCall",
			Severity.Defect,
			"Don't try to be smarter than the JVM. Your code should work independently if the garbage " +
					"collector is disabled or not. If you face memory issues, " +
					"try tuning the JVM options instead of relying on code itself.")

	override fun visitCallExpression(expression: KtCallExpression) {
		expression.getCallNameExpression()?.let {
			matchesGCCall(expression, it)
		}
	}

	private fun matchesGCCall(expression: KtCallExpression, it: KtSimpleNameExpression) {
		if (it.textMatches("gc") || it.textMatches("runFinalization")) {
			it.getReceiverExpression()?.let {
				when (it.text) {
					"System", "Runtime.getRuntime()" -> report(CodeSmell(issue, Entity.Companion.from(expression)))
				}
			}
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtForExpression

class InvalidLoopCondition(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Defect,
			"If a for loops condition is false before the first iteration, the loop will never get executed.",
			Debt.TEN_MINS)

	private val minimumSize = 3

	override fun visitForExpression(expression: KtForExpression) {
		val loopRange = expression.loopRange
		val range = loopRange?.children
		if (range != null && range.size >= minimumSize && hasInvalidLoopRange(range)) {
			report(CodeSmell(issue, Entity.from(loopRange)))
		}
		super.visitForExpression(expression)
	}

	private fun hasInvalidLoopRange(range: Array<PsiElement>): Boolean {
		val lowerValue = getIntValueForElement(range[0])
		val upperValue = getIntValueForElement(range[2])
		if (lowerValue == null || upperValue == null) {
			return false
		}
		return when (range[1].text) {
			".." -> checkRangeTo(lowerValue, upperValue)
			"downTo" -> checkDownTo(lowerValue, upperValue)
			"until" -> checkUntil(lowerValue, upperValue)
			else -> false
		}
	}

	private fun getIntValueForElement(element: PsiElement): Int? {
		return (element as? KtConstantExpression)?.text?.toIntOrNull()
	}

	private fun checkRangeTo(lower: Int, upper: Int) = lower > upper

	private fun checkDownTo(lower: Int, upper: Int) = lower < upper

	private fun checkUntil(lower: Int, upper: Int) = lower > upper
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.bugs.util.getMethod
import io.gitlab.arturbosch.detekt.rules.bugs.util.isImplementingIterator
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedFunction

class IteratorHasNextCallsNextMethod(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("IteratorHasNextCallsNextMethod", Severity.Defect,
			"The hasNext() method of an Iterator implementation should not call the next() method. " +
					"The state of the iterator should not be changed inside the hasNext() method. " +
					"The hasNext() method is not supposed to have any side effects.",
			Debt.TEN_MINS)

	override fun visitClassOrObject(classOrObject: KtClassOrObject) {
		if (classOrObject.isImplementingIterator()) {
			val hasNextMethod = classOrObject.getMethod("hasNext")
			if (hasNextMethod != null && callsNextMethod(hasNextMethod)) {
				report(CodeSmell(issue, Entity.from(classOrObject)))
			}
		}
		super.visitClassOrObject(classOrObject)
	}

	private fun callsNextMethod(method: KtNamedFunction): Boolean {
		return method.bodyExpression
				?.collectByType<KtCallExpression>()
				?.any { it.calleeExpression?.text == "next" } == true
	}
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.bugs.util.getMethod
import io.gitlab.arturbosch.detekt.rules.bugs.util.isImplementingIterator
import io.gitlab.arturbosch.detekt.rules.bugs.util.throwsNoSuchElementExceptionThrown
import org.jetbrains.kotlin.psi.KtClassOrObject

class IteratorNotThrowingNoSuchElementException(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("IteratorNotThrowingNoSuchElementException", Severity.Defect,
			"The next() method of an Iterator implementation should throw a NoSuchElementException " +
					"when there are no more elements to return",
			Debt.TEN_MINS)

	override fun visitClassOrObject(classOrObject: KtClassOrObject) {
		if (classOrObject.isImplementingIterator()) {
			val nextMethod = classOrObject.getMethod("next")
			if (nextMethod != null && !nextMethod.throwsNoSuchElementExceptionThrown()) {
				report(CodeSmell(issue, Entity.from(classOrObject)))
			}
		}
		super.visitClassOrObject(classOrObject)
	}
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Excludes
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.preprocessor.typeReferenceName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.containingClass

class LateinitUsage(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Defect,
			"Usage of lateinit. Using lateinit for property initialization " +
					"is error prone, try using constructor injection or delegation.")

	private val excludeAnnotatedProperties = Excludes(valueOrDefault(EXCLUDE_ANNOTATED_PROPERTIES, ""))

	private val ignoreOnClassesPattern = Regex(valueOrDefault(IGNORE_ON_CLASSES_PATTERN, ""))

	private var properties = mutableListOf<KtProperty>()

	override fun visitProperty(property: KtProperty) {
		if (isLateinitProperty(property)) {
			properties.add(property)
		}
	}

	override fun visit(root: KtFile) {
		properties = mutableListOf()

		super.visit(root)

		val resolvedAnnotations = root.importList
				?.imports
				?.filterNot { it.isAllUnder }
				?.mapNotNull { it.importedFqName?.asString() }
				?.map { Pair(it.split(".").last(), it) }
				?.toMap()

		properties.filterNot { isExcludedByAnnotation(it, resolvedAnnotations) }
				.filterNot { it.containingClass()?.name?.matches(ignoreOnClassesPattern) == true }
				.forEach {
					report(CodeSmell(issue, Entity.from(it)))
				}
	}

	private fun isLateinitProperty(property: KtProperty)
			= property.modifierList?.hasModifier(KtTokens.LATEINIT_KEYWORD) == true

	private fun isExcludedByAnnotation(property: KtProperty, resolvedAnnotations: Map<String, String>?)
			= property.annotationEntries
			.mapNotNull {
				val shortName = it.typeReferenceName
				resolvedAnnotations?.get(shortName) ?: shortName
			}
			.any { !excludeAnnotatedProperties.none(it) }

	companion object {
		const val EXCLUDE_ANNOTATED_PROPERTIES = "excludeAnnotatedProperties"
		const val IGNORE_ON_CLASSES_PATTERN = "ignoreOnClassesPattern"
	}
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtBreakExpression
import org.jetbrains.kotlin.psi.KtContinueExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtLoopExpression
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.KtThrowExpression

class UnconditionalJumpStatementInLoop(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Defect,
			"An unconditional jump statement in a loop is useless. " +
					"The loop itself is only executed once.", Debt.TEN_MINS)

	override fun visitLoopExpression(loopExpression: KtLoopExpression) {
		if (hasJumpStatement(loopExpression.body)) {
			report(CodeSmell(issue, Entity.from(loopExpression)))
		}
		super.visitLoopExpression(loopExpression)
	}

	private fun hasJumpStatement(body: KtExpression?) =
			isJumpStatement(body) || body?.children?.any { isJumpStatement(it) } == true

	private fun isJumpStatement(element: PsiElement?) =
			element is KtReturnExpression || element is KtBreakExpression
					|| element is KtContinueExpression || element is KtThrowExpression
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtBreakExpression
import org.jetbrains.kotlin.psi.KtContinueExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.KtThrowExpression

class UnreachableCode(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("UnreachableCode", Severity.Warning,
			"Unreachable code detected. This code should be removed", Debt.TEN_MINS)

	override fun visitReturnExpression(expression: KtReturnExpression) {
		followedByUnreachableCode(expression)
		super.visitReturnExpression(expression)
	}

	override fun visitThrowExpression(expression: KtThrowExpression) {
		followedByUnreachableCode(expression)
		super.visitThrowExpression(expression)
	}

	override fun visitContinueExpression(expression: KtContinueExpression) {
		followedByUnreachableCode(expression)
	}

	override fun visitBreakExpression(expression: KtBreakExpression) {
		followedByUnreachableCode(expression)
	}

	private fun followedByUnreachableCode(expression: KtExpression) {
		val statements = (expression.parent as? KtBlockExpression)?.statements ?: return
		val indexOfStatement = statements.indexOf(expression)
		if (indexOfStatement < statements.size - 1) {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtUnaryExpression

/**
 * @author Ivan Balaksha
 */
class UnsafeCallOnNullableType(config: Config = Config.empty) : Rule(config) {
	override val issue: Issue = Issue("UnsafeCallOnNullableType",
			Severity.Defect,
			"It will throw NullPointerException at runtime if your nullable value is null.")

	override fun visitUnaryExpression(expression: KtUnaryExpression) {
		super.visitUnaryExpression(expression)
		if (expression.operationToken == KtTokens.EXCLEXCL) {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS
import org.jetbrains.kotlin.psi.KtPsiUtil

/**
 * @author Ivan Balaksha
 */
class UnsafeCast(config: Config = Config.empty) : Rule(config) {
	override val issue: Issue = Issue("UnsafeCast",
			Severity.Defect,
			"Cast operator throws an exception if the cast is not possible.")

	override fun visitBinaryWithTypeRHSExpression(expression: KtBinaryExpressionWithTypeRHS) {
		if (KtPsiUtil.isUnsafeCast(expression)) {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPostfixExpression
import org.jetbrains.kotlin.psi.KtReturnExpression

class UselessPostfixExpression(config: Config = Config.empty) : Rule(config) {

	override val issue: Issue = Issue("UselessPostfixExpression", Severity.Defect,
			"The incremented or decremented value is unused. This value is replaced with the original value.")

	override fun visitReturnExpression(expression: KtReturnExpression) {
		val postfixExpression = expression.returnedExpression as? KtPostfixExpression
		if (postfixExpression != null) {
			report(postfixExpression)
		}
		getPostfixExpressionChilds(expression.returnedExpression)
				?.forEach { report(it) }
	}

	override fun visitBinaryExpression(expression: KtBinaryExpression) {
		val postfixExpression = expression.right as? KtPostfixExpression
		val leftIdentifierText = expression.left?.text
		checkPostfixExpression(postfixExpression, leftIdentifierText)
		getPostfixExpressionChilds(expression.right)
				?.forEach { checkPostfixExpression(it, leftIdentifierText) }
	}

	private fun checkPostfixExpression(postfixExpression: KtPostfixExpression?, leftIdentifierText: String?) {
		if (postfixExpression != null && leftIdentifierText == postfixExpression.firstChild?.text) {
			report(postfixExpression)
		}
	}

	private fun report(postfixExpression: KtPostfixExpression) {
		report(CodeSmell(issue, Entity.from(postfixExpression)))
	}

	private fun getPostfixExpressionChilds(expression: KtExpression?) =
			expression?.children?.filterIsInstance<KtPostfixExpression>()
}

package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.bugs.util.hasCorrectEqualsParameter
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtNamedFunction

class WrongEqualsTypeParameter(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("WrongEqualsTypeParameter", Severity.Defect,
			"Wrong parameter type for equals() method found. " +
					"To correctly override the equals() method use Any?")

	override fun visitClass(klass: KtClass) {
		if (klass.isInterface()) {
			return
		}
		super.visitClass(klass)
	}

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (function.name == "equals" && !function.hasCorrectEqualsParameter()) {
			report(CodeSmell(issue, Entity.from(function)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtDoWhileExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtWhileExpression

/**
 * @author Artur Bosch
 */
class ComplexCondition(config: Config = Config.empty,
					   threshold: Int = DEFAULT_ACCEPTED_NESTING) : ThresholdRule(config, threshold) {

	override val issue = Issue("ComplexCondition", Severity.Maintainability,
			"Complex conditions should be simplified and extracted " +
					"into well-named methods if necessary.")

	override fun visitIfExpression(expression: KtIfExpression) {
		val condition = expression.condition
		checkIfComplex(condition)
		super.visitIfExpression(expression)
	}

	override fun visitDoWhileExpression(expression: KtDoWhileExpression) {
		val condition = expression.condition
		checkIfComplex(condition)
		super.visitDoWhileExpression(expression)
	}

	override fun visitWhileExpression(expression: KtWhileExpression) {
		val condition = expression.condition
		checkIfComplex(condition)
		super.visitWhileExpression(expression)
	}

	private fun checkIfComplex(condition: KtExpression?) {
		val binaryExpressions = condition?.collectByType<KtBinaryExpression>()

		if (binaryExpressions != null && binaryExpressions.size > 1) {
			val longestBinExpr = binaryExpressions.reduce { acc, binExpr ->
				if (binExpr.text.length > acc.text.length) binExpr else acc
			}
			val conditionString = longestBinExpr.text
			val count = frequency(conditionString, "&&") + frequency(conditionString, "||") + 1
			if (count > threshold) {
				report(ThresholdedCodeSmell(issue, Entity.from(condition), Metric("SIZE", count, threshold)))
			}
		}
	}

	private fun frequency(source: String, part: String): Int {

		if (source.isEmpty() || part.isEmpty()) {
			return 0
		}

		var count = 0
		var pos = source.indexOf(part, 0)
		while (pos != -1) {
			pos += part.length
			count++
			pos = source.indexOf(part, pos)
		}

		return count
	}
}

private const val DEFAULT_ACCEPTED_NESTING = 3

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import io.gitlab.arturbosch.detekt.rules.companionObject
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassBody
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtProperty

class ComplexInterface(config: Config = Config.empty,
					   threshold: Int = DEFAULT_LARGE_INTERFACE_COUNT) : ThresholdRule(config, threshold) {

	override val issue = Issue(javaClass.simpleName, Severity.Maintainability,
			"An interface contains too many functions and properties. " +
					"Large classes tend to handle many things at once. " +
					"An interface should have one responsibility. " +
					"Split up large interfaces into smaller ones that are easier to understand.")

	private val includeStaticDeclarations = valueOrDefault(INCLUDE_STATIC_DECLARATIONS, false)

	override fun visitClass(klass: KtClass) {
		if (klass.isInterface()) {
			val body = klass.getBody() ?: return
			var size = calculateMembers(body)
			if (includeStaticDeclarations) {
				size += countStaticDeclarations(klass.companionObject())
			}
			if (size > threshold) {
				report(ThresholdedCodeSmell(issue, Entity.from(klass), Metric("SIZE: ", size, threshold)))
			}
		}
		super.visitClass(klass)
	}

	private fun countStaticDeclarations(companionObject: KtObjectDeclaration?): Int {
		val body = companionObject?.getBody()
		return if (body != null) calculateMembers(body) else 0
	}

	private fun calculateMembers(body: KtClassBody) = body.children.count { it is KtNamedFunction || it is KtProperty }

	companion object {
		const val INCLUDE_STATIC_DECLARATIONS = "includeStaticDeclarations"
	}
}

private const val DEFAULT_LARGE_INTERFACE_COUNT = 10

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import io.gitlab.arturbosch.detekt.api.internal.McCabeVisitor
import org.jetbrains.kotlin.psi.KtNamedFunction

/**
 * @author Artur Bosch
 */
class ComplexMethod(config: Config = Config.empty,
					threshold: Int = DEFAULT_ACCEPTED_METHOD_COMPLEXITY) : ThresholdRule(config, threshold) {

	override val issue = Issue("ComplexMethod",
			Severity.Maintainability,
			"Prefer splitting up complex methods into smaller, " +
					"easier to understand methods.")

	override fun visitNamedFunction(function: KtNamedFunction) {
		val visitor = McCabeVisitor()
		visitor.visitNamedFunction(function)
		val mcc = visitor.mcc
		if (mcc > threshold) {
			report(ThresholdedCodeSmell(issue, Entity.from(function), Metric("MCC", mcc, threshold)))
		}
	}
}

private const val DEFAULT_ACCEPTED_METHOD_COMPLEXITY = 10

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtExpressionWithLabel

/**
 * @author Ivan Balaksha
 */
class LabeledExpression(config: Config = Config.empty) : Rule(config) {
	override val issue: Issue = Issue("LabeledExpression",
			Severity.Maintainability,
			"Expression with labels increase complexity and affect maintainability.")

	override fun visitExpressionWithLabel(expression: KtExpressionWithLabel) {
		super.visitExpressionWithLabel(expression)
		expression.getLabelName()?.let {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import io.gitlab.arturbosch.detekt.rules.asBlockExpression
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
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.KtProperty
import org.jetbrains.kotlin.psi.KtTryExpression
import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression
import java.util.ArrayDeque

/**
 * @author Artur Bosch
 */
class LargeClass(config: Config = Config.empty,
				 threshold: Int = DEFAULT_ACCEPTED_CLASS_LENGTH) : ThresholdRule(config, threshold) {

	override val issue = Issue("LargeClass",
			Severity.Maintainability,
			"One class should have one responsibility. Large classes tend to handle many things at once. " +
					"Split up large classes into smaller classes that are easier to understand.")

	private val locStack = ArrayDeque<Int>()

	private fun incHead() {
		addToHead(1)
	}

	private fun addToHead(amount: Int) {
		locStack.push(locStack.pop() + amount)
	}

	override fun visitFile(file: PsiFile?) { //TODO
		locStack.clear()
		super.visitFile(file)
	}

	override fun visitClassOrObject(classOrObject: KtClassOrObject) {
		locStack.push(0)
		classOrObject.getBody()?.let {
			addToHead(it.declarations.size)
		}
		incHead() // for class body
		super.visitClassOrObject(classOrObject)
		val loc = locStack.pop()
		if (loc > threshold) {
			report(ThresholdedCodeSmell(issue, Entity.from(classOrObject), Metric("SIZE", loc, threshold)))
		}
	}

	/**
	 * Top level members must be skipped as loc stack can be empty - #64
	 */
	override fun visitProperty(property: KtProperty) {
		if (property.isTopLevel) return
		super.visitProperty(property)
	}

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (function.isTopLevel) return
		val body: KtBlockExpression? = function.bodyExpression.asBlockExpression()
		body?.let { addToHead(body.statements.size) }
		super.visitNamedFunction(function)
	}

	override fun visitIfExpression(expression: KtIfExpression) {
		expression.then?.let { addToHead(it.children.size) }
		expression.`else`?.let { addToHead(it.children.size) }
		super.visitIfExpression(expression)
	}

	override fun visitLoopExpression(loopExpression: KtLoopExpression) {
		loopExpression.body?.let { addToHead(it.children.size) }
		super.visitLoopExpression(loopExpression)
	}

	override fun visitWhenExpression(expression: KtWhenExpression) {
		addToHead(expression.children.filter { it is KtWhenEntry }.size)
		super.visitWhenExpression(expression)
	}

	override fun visitTryExpression(expression: KtTryExpression) {
		addToHead(expression.tryBlock.statements.size)
		addToHead(expression.catchClauses.size)
		expression.catchClauses.map { it.catchBody?.children?.size }.forEach { addToHead(it ?: 0) }
		expression.finallyBlock?.finalExpression?.statements?.size?.let { addToHead(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 {
				addToHead(it.statements.size)
			}
		}
		super.visitCallExpression(expression)
	}

}

private const val DEFAULT_ACCEPTED_CLASS_LENGTH = 70

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import io.gitlab.arturbosch.detekt.rules.asBlockExpression
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtNamedFunction

/**
 * @author Artur Bosch
 */
class LongMethod(config: Config = Config.empty,
				 threshold: Int = DEFAULT_ACCEPTED_METHOD_LENGTH) : ThresholdRule(config, threshold) {

	override val issue = Issue("LongMethod",
			Severity.Maintainability,
			"One method should have one responsibility. Long methods tend to handle many things at once. " +
					"Prefer smaller methods to make them easier to understand.")

	override fun visitNamedFunction(function: KtNamedFunction) {
		val body: KtBlockExpression? = function.bodyExpression.asBlockExpression()
		body?.let {
			val size = body.statements.size
			if (size > threshold) report(
					ThresholdedCodeSmell(issue, Entity.from(function), Metric("SIZE", size, threshold)))
		}
		super.visitNamedFunction(function)
	}

}

private const val DEFAULT_ACCEPTED_METHOD_LENGTH = 20

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtParameterList

/**
 * @author Artur Bosch
 */
class LongParameterList(config: Config = Config.empty,
						threshold: Int = DEFAULT_ACCEPTED_PARAMETER_LENGTH) : ThresholdRule(config, threshold) {

	override val issue = Issue("LongParameterList",
			Severity.Maintainability,
			"The more parameters a method has the more complex it is. Long parameter lists are often " +
					"used to control complex algorithms and violate the Single Responsibility Principle. " +
					"Prefer methods with short parameter lists.")

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (function.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return
		function.valueParameterList?.checkThreshold()
	}

	private fun KtParameterList.checkThreshold() {
		val size = parameters.size
		if (size > threshold) {
			report(ThresholdedCodeSmell(issue, Entity.from(this), Metric("SIZE", size, threshold)))
		}
	}
}

private const val DEFAULT_ACCEPTED_PARAMETER_LENGTH = 5

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction

class MethodOverloading(config: Config = Config.empty,
						threshold: Int = ACCEPTED_OVERLOAD_COUNT) : ThresholdRule(config, threshold) {

	override val issue = Issue("MethodOverloading", Severity.Maintainability,
			"Methods which are overloaded often might be harder to maintain. " +
					"Furthermore, these methods are tightly coupled. " +
					"Refactor these methods and try to use optional parameters.")

	override fun visitClass(klass: KtClass) {
		val visitor = OverloadedMethodVisitor()
		klass.accept(visitor)
		visitor.reportIfThresholdExceeded(klass)
		super.visitClass(klass)
	}

	override fun visitKtFile(file: KtFile) {
		val visitor = OverloadedMethodVisitor()
		file.children.filterIsInstance<KtNamedFunction>().forEach { it.accept(visitor) }
		visitor.reportIfThresholdExceeded(file)
		super.visitKtFile(file)
	}

	internal inner class OverloadedMethodVisitor : DetektVisitor() {

		private var methods = HashMap<String, Int>()

		fun reportIfThresholdExceeded(element: PsiElement) {
			methods.filterValues { it > threshold }.forEach {
				report(ThresholdedCodeSmell(issue, Entity.from(element), Metric("OVERLOAD SIZE: ", it.value, threshold)))
			}
		}

		override fun visitNamedFunction(function: KtNamedFunction) {
			val name = function.name ?: return
			methods.put(name, methods.getOrDefault(name, 0) + 1)
		}
	}
}

private const val ACCEPTED_OVERLOAD_COUNT = 5

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import io.gitlab.arturbosch.detekt.rules.isUsedForNesting
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtContainerNodeForControlStructureBody
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtLambdaArgument
import org.jetbrains.kotlin.psi.KtLoopExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtTryExpression
import org.jetbrains.kotlin.psi.KtWhenExpression

/**
 * @author Artur Bosch
 */
class NestedBlockDepth(config: Config = Config.empty,
					   threshold: Int = DEFAULT_ACCEPTED_NESTING) : ThresholdRule(config, threshold) {

	override val issue = Issue("NestedBlockDepth",
			Severity.Maintainability,
			"Excessive nesting leads to hidden complexity. " +
					"Prefer extracting code to make it easier to understand.")

	override fun visitNamedFunction(function: KtNamedFunction) {
		val visitor = FunctionDepthVisitor(threshold)
		visitor.visitNamedFunction(function)
		if (visitor.isTooDeep)
			report(ThresholdedCodeSmell(issue, Entity.from(function), Metric("SIZE", visitor.maxDepth, threshold)))
	}

	private class FunctionDepthVisitor(val threshold: Int) : DetektVisitor() {

		internal var depth = 0
		internal var maxDepth = 0
		internal var isTooDeep = false
		private fun inc() {
			depth++
			if (depth > threshold) {
				isTooDeep = true
				if (depth > maxDepth) maxDepth = depth
			}
		}

		private fun dec() {
			depth--
		}

		override fun visitIfExpression(expression: KtIfExpression) {
			// Prevents else if (...) to count as two - #51C
			if (expression.parent !is KtContainerNodeForControlStructureBody) {
				inc()
				super.visitIfExpression(expression)
				dec()
			}
		}

		override fun visitLoopExpression(loopExpression: KtLoopExpression) {
			inc()
			super.visitLoopExpression(loopExpression)
			dec()
		}

		override fun visitWhenExpression(expression: KtWhenExpression) {
			inc()
			super.visitWhenExpression(expression)
			dec()
		}

		override fun visitTryExpression(expression: KtTryExpression) {
			inc()
			super.visitTryExpression(expression)
			dec()
		}

		override fun visitCallExpression(expression: KtCallExpression) {
			val lambdaArguments = expression.lambdaArguments
			if (expression.isUsedForNesting()) {
				insideLambdaDo(lambdaArguments) { inc() }
				super.visitCallExpression(expression)
				insideLambdaDo(lambdaArguments) { dec() }
			}
		}

		private fun insideLambdaDo(lambdaArguments: List<KtLambdaArgument>, function: () -> Unit) {
			if (lambdaArguments.isNotEmpty()) {
				val lambdaArgument = lambdaArguments[0]
				lambdaArgument.getLambdaExpression().bodyExpression?.let {
					function()
				}
			}
		}

	}
}

private const val DEFAULT_ACCEPTED_NESTING = 3

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import io.gitlab.arturbosch.detekt.api.isPartOf
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry

class StringLiteralDuplication(config: Config = Config.empty,
							   threshold: Int = StringLiteralDuplication.DEFAULT_DUPLICATION) : ThresholdRule(config, threshold) {

	override val issue = Issue(javaClass.simpleName, Severity.Maintainability,
			"Multiple occurrences of the same string literal within a single kt file detected.",
			Debt.FIVE_MINS)

	private val ignoreAnnotation = valueOrDefault(IGNORE_ANNOTATION, true)
	private val excludeStringsWithLessThan5Characters = valueOrDefault(EXCLUDE_SHORT_STRING, true)
	private val ignoreStringsRegex = Regex(valueOrDefault(IGNORE_STRINGS_REGEX, "$^"))

	override fun visitKtFile(file: KtFile) {
		val visitor = StringLiteralVisitor()
		file.accept(visitor)
		val type = "SIZE: "
		visitor.getLiteralsOverThreshold().forEach {
			report(ThresholdedCodeSmell(issue, Entity.from(file), Metric(type + it.key, it.value, threshold)))
		}
	}

	internal inner class StringLiteralVisitor : DetektVisitor() {

		private var literals = HashMap<String, Int>()
		private val pass: Unit = Unit

		fun getLiteralsOverThreshold(): Map<String, Int> {
			return literals.filterValues { it > threshold }
		}

		override fun visitLiteralStringTemplateEntry(entry: KtLiteralStringTemplateEntry) {
			val text = entry.text
			when {
				ignoreAnnotation &&	entry.isPartOf(KtAnnotationEntry::class) -> pass
				excludeStringsWithLessThan5Characters && text.length < STRING_EXCLUSION_LENGTH -> pass
				text.matches(ignoreStringsRegex) -> pass
				else -> add(text)
			}
		}

		private fun add(text: String) {
			literals.put(text, literals.getOrDefault(text, 0) + 1)
		}
	}

	companion object {
		const val DEFAULT_DUPLICATION = 2
		const val STRING_EXCLUSION_LENGTH = 5
		const val IGNORE_ANNOTATION = "ignoreAnnotation"
		const val EXCLUDE_SHORT_STRING = "excludeStringsWithLessThan5Characters"
		const val IGNORE_STRINGS_REGEX = "ignoreStringsRegex"
	}
}

package io.gitlab.arturbosch.detekt.rules.complexity

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction

/**
 * @author Artur Bosch
 */
class TooManyFunctions(config: Config = Config.empty,
					   threshold: Int = DEFAULT_ACCEPTED_FUNCTIONS_IN_FILE) : ThresholdRule(config, threshold) {

	override val issue = Issue("TooManyFunctions",
			Severity.Maintainability,
			"Classes with many functions tend to do too many things and often come in conjunction with " +
					"large classes and can quickly become God classes. " +
					"Consider extracting methods to (new) classes better matching their responsibility.")

	private var amount: Int = 0

	override fun visitKtFile(file: KtFile) {
		super.visitKtFile(file)
		if (amount > threshold) {
			report(ThresholdedCodeSmell(issue, Entity.from(file), Metric("SIZE", amount, threshold)))
		}
		amount = 0
	}

	override fun visitClassOrObject(classOrObject: KtClassOrObject) {
		val amountOfFunctions = classOrObject.getBody()?.declarations
				?.filterIsInstance<KtNamedFunction>()
				?.size

		if (amountOfFunctions != null && amountOfFunctions > threshold) {
			report(ThresholdedCodeSmell(issue, Entity.from(classOrObject),
					Metric("SIZE", amountOfFunctions, threshold)))
		}

		super.visitClassOrObject(classOrObject)
	}

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (function.isTopLevel) {
			amount++
		}
	}

}

private val DEFAULT_ACCEPTED_FUNCTIONS_IN_FILE = 10

package io.gitlab.arturbosch.detekt.rules.documentation

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNamedFunction

/**
 * @author Artur Bosch
 */
class CommentOverPrivateFunction(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("CommentOverPrivateFunction",
			Severity.Maintainability,
			"Comments for private functions should be avoided. " +
					"Prefer giving the function an expressive name. " +
					"Split it up in smaller, self-explaining functions if necessary.")

	override fun visitNamedFunction(function: KtNamedFunction) {
		val modifierList = function.modifierList
		if (modifierList != null && function.docComment != null) {
			if (modifierList.hasModifier(KtTokens.PRIVATE_KEYWORD)) {
				report(CodeSmell(issue, Entity.from(function.docComment!!)))
			}
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.documentation

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtProperty

/**
 * @author Artur Bosch
 */
class CommentOverPrivateProperty(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("CommentOverPrivateProperty",
			Severity.Maintainability,
			"Private properties should be named such that they explain themselves even without a comment.")

	override fun visitProperty(property: KtProperty) {
		val modifierList = property.modifierList
		if (modifierList != null && property.docComment != null) {
			if (modifierList.hasModifier(KtTokens.PRIVATE_KEYWORD)) {
				report(CodeSmell(issue, Entity.from(property.docComment!!)))
			}
		}
	}

}

package io.gitlab.arturbosch.detekt.rules.documentation

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.isPublicNotOverridden
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtObjectDeclaration

/**
 * @author Artur Bosch
 */
class UndocumentedPublicClass(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Maintainability,
			"Public classes, interfaces and objects require documentation.")

	private val searchInNestedClass = valueOrDefault(SEARCH_IN_NESTED_CLASS, true)
	private val searchInInnerClass = valueOrDefault(SEARCH_IN_INNER_CLASS, true)
	private val searchInInnerObject = valueOrDefault(SEARCH_IN_INNER_OBJECT, true)
	private val searchInInnerInterface = valueOrDefault(SEARCH_IN_INNER_INTERFACE, true)

	override fun visitClass(klass: KtClass) {
		if (requiresDocumentation(klass)) {
			reportIfUndocumented(klass)
		}

		super.visitClass(klass)
	}

	private fun requiresDocumentation(
			klass: KtClass) = klass.isTopLevel() || klass.isInnerClass() || klass.isNestedClass() || klass.isInnerInterface()

	override fun visitObjectDeclaration(declaration: KtObjectDeclaration) {
		if (declaration.isCompanionWithoutName() || declaration.isLocal || !searchInInnerObject) {
			return
		}

		reportIfUndocumented(declaration)
		super.visitObjectDeclaration(declaration)
	}

	private fun reportIfUndocumented(element: KtClassOrObject) {
		if (element.isPublicNotOverridden() && element.notEnumEntry() && element.docComment == null) {
			report(CodeSmell(issue, Entity.Companion.from(element)))
		}
	}

	private fun KtObjectDeclaration.isCompanionWithoutName() =
			isCompanion() && nameAsSafeName.asString() == "Companion"

	private fun KtClass.isNestedClass() = !isInterface() && !isTopLevel() && !isInner() && searchInNestedClass

	private fun KtClass.isInnerClass() = !isInterface() && isInner() && searchInInnerClass

	private fun KtClass.isInnerInterface() = !isTopLevel() && isInterface() && searchInInnerInterface

	private fun KtClassOrObject.notEnumEntry() = this::class != KtEnumEntry::class

	companion object {
		const val SEARCH_IN_NESTED_CLASS = "searchInNestedClass"
		const val SEARCH_IN_INNER_CLASS = "searchInInnerClass"
		const val SEARCH_IN_INNER_OBJECT = "searchInInnerObject"
		const val SEARCH_IN_INNER_INTERFACE = "searchInInnerInterface"
	}
}

package io.gitlab.arturbosch.detekt.rules.documentation

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.isPublicNotOverridden
import org.jetbrains.kotlin.psi.KtNamedFunction

/**
 * @author Artur Bosch
 */
class UndocumentedPublicFunction(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Maintainability,
			"Public functions require documentation.")

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (function.funKeyword == null && function.isLocal) return

		val modifierList = function.modifierList
		if (function.docComment == null) {
			if (modifierList == null) {
				report(CodeSmell(issue, methodHeaderLocation(function)))
			}
			if (modifierList != null) {
				if (function.isPublicNotOverridden()) {
					report(CodeSmell(issue, methodHeaderLocation(function)))
				}
			}
		}
	}

	private fun methodHeaderLocation(function: KtNamedFunction) = Entity.from(function)

}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.MultiRule
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.SingleAssign
import org.jetbrains.kotlin.psi.KtCatchClause
import org.jetbrains.kotlin.psi.KtClassInitializer
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDoWhileExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFinallySection
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.kotlin.psi.KtSecondaryConstructor
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.psi.KtWhileExpression

/**
 * @author Artur Bosch
 */
@Suppress("TooManyFunctions")
class EmptyBlocks(val config: Config = Config.empty) : MultiRule() {

	private val emptyCatchBlock = EmptyCatchBlock(config)
	private val emptyClassBlock = EmptyClassBlock(config)
	private val emptyDefaultConstructor = EmptyDefaultConstructor(config)
	private val emptyDoWhileBlock = EmptyDoWhileBlock(config)
	private val emptyElseBlock = EmptyElseBlock(config)
	private val emptyFinallyBlock = EmptyFinallyBlock(config)
	private val emptyForBlock = EmptyForBlock(config)
	private val emptyFunctionBlock = EmptyFunctionBlock(config)
	private val emptyIfBlock = EmptyIfBlock(config)
	private val emptyInitBlock = EmptyInitBlock(config)
	private val emptyKtFile = EmptyKtFile(config)
	private val emptySecondaryConstructorBlock = EmptySecondaryConstructor(config)
	private val emptyWhenBlock = EmptyWhenBlock(config)
	private val emptyWhileBlock = EmptyWhileBlock(config)

	override val rules: List<Rule> = listOf(
			emptyCatchBlock,
			emptyClassBlock,
			emptyDefaultConstructor,
			emptyDoWhileBlock,
			emptyElseBlock,
			emptyFinallyBlock,
			emptyForBlock,
			emptyFunctionBlock,
			emptyIfBlock,
			emptyInitBlock,
			emptyKtFile,
			emptySecondaryConstructorBlock,
			emptyWhenBlock,
			emptyWhileBlock
	)

	private var activeRules: Set<Rule> by SingleAssign()

	override fun visitKtFile(file: KtFile) {
		activeRules = rules.filterTo(HashSet()) { it.visitCondition(file) }
		if (emptyKtFile in activeRules) {
			emptyKtFile.visitFile(file)
		}
		super.visitKtFile(file)
		report(activeRules.flatMap { it.findings })
	}

	override fun visitCatchSection(catchClause: KtCatchClause) {
		if (emptyCatchBlock in activeRules) {
			emptyCatchBlock.visitCatchSection(catchClause)
		}
		super.visitCatchSection(catchClause)
	}

	override fun visitClassOrObject(classOrObject: KtClassOrObject) {
		if (emptyClassBlock in activeRules) {
			emptyClassBlock.visitClassOrObject(classOrObject)
		}
		super.visitClassOrObject(classOrObject)
	}

	override fun visitPrimaryConstructor(constructor: KtPrimaryConstructor) {
		if (emptyDefaultConstructor in activeRules) {
			emptyDefaultConstructor.visitPrimaryConstructor(constructor)
		}
		super.visitPrimaryConstructor(constructor)
	}

	override fun visitDoWhileExpression(expression: KtDoWhileExpression) {
		if (emptyDoWhileBlock in activeRules) {
			emptyDoWhileBlock.visitDoWhileExpression(expression)
		}
		super.visitDoWhileExpression(expression)
	}

	override fun visitIfExpression(expression: KtIfExpression) {
		if (emptyIfBlock in activeRules) {
			emptyIfBlock.visitIfExpression(expression)
		}
		if (emptyElseBlock in activeRules) {
			emptyElseBlock.visitIfExpression(expression)
		}
		super.visitIfExpression(expression)
	}

	override fun visitFinallySection(finallySection: KtFinallySection) {
		if (emptyFinallyBlock in activeRules) {
			emptyFinallyBlock.visitFinallySection(finallySection)
		}
		super.visitFinallySection(finallySection)
	}

	override fun visitForExpression(expression: KtForExpression) {
		if (emptyForBlock in activeRules) {
			emptyForBlock.visitForExpression(expression)
		}
		super.visitForExpression(expression)
	}

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (emptyFunctionBlock in activeRules) {
			emptyFinallyBlock.visitNamedFunction(function)
		}
		super.visitNamedFunction(function)
	}

	override fun visitClassInitializer(initializer: KtClassInitializer) {
		if (emptyInitBlock in activeRules) {
			emptyInitBlock.visitClassInitializer(initializer)
		}
		super.visitClassInitializer(initializer)
	}

	override fun visitSecondaryConstructor(constructor: KtSecondaryConstructor) {
		if (emptySecondaryConstructorBlock in activeRules) {
			emptySecondaryConstructorBlock.visitSecondaryConstructor(constructor)
		}
		super.visitSecondaryConstructor(constructor)
	}

	override fun visitWhenExpression(expression: KtWhenExpression) {
		if (emptyWhenBlock in activeRules) {
			emptyWhenBlock.visitWhenExpression(expression)
		}
		super.visitWhenExpression(expression)
	}

	override fun visitWhileExpression(expression: KtWhileExpression) {
		if (emptyWhileBlock in activeRules) {
			emptyWhileBlock.visitWhileExpression(expression)
		}
		super.visitWhileExpression(expression)
	}
}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.Config
import org.jetbrains.kotlin.psi.KtCatchClause

/**
 * @author Artur Bosch
 */
class EmptyCatchBlock(config: Config) : EmptyRule(config = config) {

	override fun visitCatchSection(catchClause: KtCatchClause) {
		catchClause.catchBody?.addFindingIfBlockExprIsEmpty()
	}

}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import org.jetbrains.kotlin.psi.KtClassOrObject

/**
 * @author Artur Bosch
 */
class EmptyClassBlock(config: Config) : EmptyRule(config) {

	override fun visitClassOrObject(classOrObject: KtClassOrObject) {
		classOrObject.getBody()?.declarations?.let {
			if (it.isEmpty()) report(CodeSmell(issue, Entity.from(classOrObject)))
		}
	}

}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType

class EmptyDefaultConstructor(config: Config) : EmptyRule(config = config) {

	override fun visitPrimaryConstructor(constructor: KtPrimaryConstructor) {
		if (hasPublicVisibility(constructor.visibilityModifierType())
				&& constructor.annotationEntries.isEmpty()
				&& constructor.valueParameters.isEmpty()) {
			report(CodeSmell(issue, Entity.from(constructor)))
		}
	}

	private fun hasPublicVisibility(visibility: KtModifierKeywordToken?): Boolean {
		return visibility == null || visibility == KtTokens.PUBLIC_KEYWORD
	}
}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.Config
import org.jetbrains.kotlin.psi.KtDoWhileExpression

/**
 * @author Artur Bosch
 */
class EmptyDoWhileBlock(config: Config) : EmptyRule(config) {

	override fun visitDoWhileExpression(expression: KtDoWhileExpression) {
		expression.body?.addFindingIfBlockExprIsEmpty()
	}

}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.Config
import org.jetbrains.kotlin.psi.KtIfExpression

/**
 * @author Artur Bosch
 */
class EmptyElseBlock(config: Config) : EmptyRule(config) {

	override fun visitIfExpression(expression: KtIfExpression) {
		expression.`else`?.addFindingIfBlockExprIsEmpty()
	}

}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.Config
import org.jetbrains.kotlin.psi.KtFinallySection

/**
 * @author Artur Bosch
 */
class EmptyFinallyBlock(config: Config) : EmptyRule(config) {

	override fun visitFinallySection(finallySection: KtFinallySection) {
		finallySection.finalExpression?.addFindingIfBlockExprIsEmpty()
	}

}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.Config
import org.jetbrains.kotlin.psi.KtForExpression

/**
 * @author Artur Bosch
 */
class EmptyForBlock(config: Config) : EmptyRule(config) {

	override fun visitForExpression(expression: KtForExpression) {
		expression.body?.addFindingIfBlockExprIsEmpty()
	}
}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.Config
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNamedFunction

/**
 * @author Artur Bosch
 */
class EmptyFunctionBlock(config: Config) : EmptyRule(config) {

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (function.isNotOverridden() && function.notMeantForOverriding()) {
			function.bodyExpression?.addFindingIfBlockExprIsEmpty()
		}
	}

	private fun KtNamedFunction.isNotOverridden() = !hasModifier(KtTokens.OVERRIDE_KEYWORD)
	private fun KtNamedFunction.notMeantForOverriding() =
			!(hasModifier(KtTokens.OPEN_KEYWORD) && hasModifier(KtTokens.PROTECTED_KEYWORD))
}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.psi.KtIfExpression

/**
 * @author Artur Bosch
 */
class EmptyIfBlock(config: Config) : EmptyRule(config) {

	override fun visitIfExpression(expression: KtIfExpression) {
		expression.then?.addFindingIfBlockExprIsEmpty()
		checkThenBodyForLoneSemicolon(expression)
	}

	private fun checkThenBodyForLoneSemicolon(expression: KtIfExpression) {
		val valueOfNextSibling = (expression.nextSibling as? LeafPsiElement)?.elementType as? KtSingleValueToken
		if (valueOfNextSibling?.value?.trim() == ";") {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}

}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.Config
import org.jetbrains.kotlin.psi.KtClassInitializer

class EmptyInitBlock(config: Config) : EmptyRule(config) {

	override fun visitClassInitializer(initializer: KtClassInitializer) {
		initializer.body?.addFindingIfBlockExprIsEmpty()
	}
}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import org.jetbrains.kotlin.psi.KtFile

class EmptyKtFile(config: Config) : EmptyRule(config) {

	override fun visitKtFile(file: KtFile) {
		if (file.text.isNullOrBlank()) {
			report(CodeSmell(issue, Entity.from(file)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.asBlockExpression
import io.gitlab.arturbosch.detekt.rules.hasCommentInside
import org.jetbrains.kotlin.psi.KtExpression

/**
 * @author Artur Bosch
 */
abstract class EmptyRule(config: Config) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Minor,
			"Empty block of code detected. As they serve no purpose they should be removed.",
			Debt.FIVE_MINS)

	fun KtExpression.addFindingIfBlockExprIsEmpty() {
		val blockExpression = this.asBlockExpression()
		blockExpression?.statements?.let {
			if (it.isEmpty() && !blockExpression.hasCommentInside()) report(CodeSmell(issue, Entity.from(this)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.Config
import org.jetbrains.kotlin.psi.KtSecondaryConstructor

class EmptySecondaryConstructor(config: Config) : EmptyRule(config) {

	override fun visitSecondaryConstructor(constructor: KtSecondaryConstructor) {
		constructor.bodyExpression?.addFindingIfBlockExprIsEmpty()
	}
}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import org.jetbrains.kotlin.psi.KtWhenExpression

/**
 * @author Artur Bosch
 */
class EmptyWhenBlock(config: Config) : EmptyRule(config) {

	override fun visitWhenExpression(expression: KtWhenExpression) {
		if (expression.entries.isEmpty()) {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}

}

package io.gitlab.arturbosch.detekt.rules.empty

import io.gitlab.arturbosch.detekt.api.Config
import org.jetbrains.kotlin.psi.KtWhileExpression

/**
 * @author Artur Bosch
 */
class EmptyWhileBlock(config: Config) : EmptyRule(config) {

	override fun visitWhileExpression(expression: KtWhileExpression) {
		expression.body?.addFindingIfBlockExprIsEmpty()
	}

}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Excludes
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtClassInitializer
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtThrowExpression

class ExceptionRaisedInUnexpectedLocation(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("ExceptionRaisedInUnexpectedLocation", Severity.CodeSmell,
			"This method is not expected to throw exceptions. This can cause weird behavior.")

	private val methods = Excludes(valueOrDefault(METHOD_NAMES, ""))

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (isPotentialMethod(function) && hasThrowExpression(function.bodyExpression)) {
			report(CodeSmell(issue, Entity.from(function)))
		}
	}

	override fun visitClassInitializer(initializer: KtClassInitializer) {
		if (hasThrowExpression(initializer.body)) {
			report(CodeSmell(issue, Entity.from(initializer)))
		}
	}

	private fun isPotentialMethod(function: KtNamedFunction) = methods.contains(function.name)

	private fun hasThrowExpression(declaration: KtExpression?) =
			declaration?.collectByType<KtThrowExpression>()?.any() == true

	companion object {
		const val METHOD_NAMES = "methodNames"
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS
import org.jetbrains.kotlin.psi.KtCatchClause
import org.jetbrains.kotlin.psi.KtIsExpression
import org.jetbrains.kotlin.psi.KtPsiUtil

class InstanceOfCheckForException(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("InstanceOfCheckForException", Severity.CodeSmell,
			"Instead of checking for a general exception type and checking for a specific exception type " +
						"use multiple catch blocks.")

	override fun visitCatchSection(catchClause: KtCatchClause) {
		catchClause.catchBody?.collectByType<KtIsExpression>()?.forEach {
			report(CodeSmell(issue, Entity.from(it)))
		}
		catchClause.catchBody?.collectByType<KtBinaryExpressionWithTypeRHS>()?.forEach {
			if (KtPsiUtil.isUnsafeCast(it)) {
				report(CodeSmell(issue, Entity.from(it)))
			}
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtThrowExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny

class NotImplementedDeclaration(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("NotImplementedDeclaration", Severity.CodeSmell,
			"The NotImplementedDeclaration should only be used when a method stub is necessary. " +
					"This defers the development of the functionality of this function. " +
					"Hence, the NotImplementedDeclaration should only serve as a temporary declaration. " +
					"Before releasing, this type of declaration should be removed.")

	override fun visitThrowExpression(expression: KtThrowExpression) {
		val calleeExpression = expression.thrownExpression?.getCalleeExpressionIfAny()
		if (calleeExpression?.text == "NotImplementedError") {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}

	override fun visitCallExpression(expression: KtCallExpression) {
		if (expression.calleeExpression?.text == "TODO") {
			val size = expression.valueArguments.size
			if (size == 0 || size == 1) {
				report(CodeSmell(issue, Entity.from(expression)))
			}
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtCatchClause
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression

class PrintStackTrace(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("PrintStackTrace", Severity.CodeSmell,
			"Do not print an stack trace. " +
					"These debug statements should be replaced with a logger or removed.",
			Debt.FIVE_MINS)

	override fun visitCallExpression(expression: KtCallExpression) {
		val callNameExpression = expression.getCallNameExpression()
		if (callNameExpression?.text == "dumpStack"
				&& callNameExpression.getReceiverExpression()?.text == "Thread") {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}

	override fun visitCatchSection(catchClause: KtCatchClause) {
		catchClause.catchBody?.collectByType<KtNameReferenceExpression>()?.forEach {
			if (it.text == catchClause.catchParameter?.name && hasPrintStacktraceCallExpression(it)) {
				report(CodeSmell(issue, Entity.from(it)))
			}
		}
	}

	private fun hasPrintStacktraceCallExpression(expression: KtNameReferenceExpression): Boolean {
		val methodCall = expression.nextSibling?.nextSibling
		return methodCall is KtCallExpression && methodCall.text.startsWith("printStackTrace(")
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtCatchClause
import org.jetbrains.kotlin.psi.KtThrowExpression

class RethrowCaughtException(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("RethrowCaughtException", Severity.CodeSmell,
			"Do not rethrow a caught exception of the same type.",
			Debt.FIVE_MINS)

	override fun visitCatchSection(catchClause: KtCatchClause) {
		val throwExpression = catchClause.catchBody?.collectByType<KtThrowExpression>()?.firstOrNull {
			it.thrownExpression?.text == catchClause.catchParameter?.name
		}
		if (throwExpression != null) {
			report(CodeSmell(issue, Entity.from(throwExpression)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtFinallySection
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.psiUtil.parents

class ReturnFromFinally(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("ReturnFromFinally", Severity.Defect,
			"Do not return within a finally statement. This can discard exceptions.")

	override fun visitFinallySection(finallySection: KtFinallySection) {
		val returnExpressions = finallySection.finalExpression.collectByType<KtReturnExpression>()
		val innerFunctions = finallySection.finalExpression.collectByType<KtNamedFunction>()
		returnExpressions.forEach {
			if (isNotInInnerFunction(it, innerFunctions)) {
				report(CodeSmell(issue, Entity.from(it)))
			}
		}
	}

	private fun isNotInInnerFunction(it: KtReturnExpression, childFunctions: List<KtNamedFunction>): Boolean {
		return !it.parents.any { childFunctions.contains(it) }
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtCatchClause
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtThrowExpression

class SwallowedException(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("SwallowedException", Severity.CodeSmell,
			"The caught exception is swallowed. The original exception could be lost.")

	override fun visitCatchSection(catchClause: KtCatchClause) {
		if (isExceptionSwallowed(catchClause) == true) {
			report(CodeSmell(issue, Entity.from(catchClause)))
		}
	}

	private fun isExceptionSwallowed(catchClause: KtCatchClause): Boolean? {
		val parameterName = catchClause.catchParameter?.name
		val throwExpressions = catchClause.catchBody?.collectByType<KtThrowExpression>()
		throwExpressions?.forEach { throwExpr ->
			val parameterNameReferences = throwExpr.thrownExpression?.collectByType<KtNameReferenceExpression>()?.filter {
				it.text == parameterName
			}
			return hasParameterReferences(parameterNameReferences)
		}
		return false
	}

	private fun hasParameterReferences(parameterNameReferences: List<KtNameReferenceExpression>?): Boolean {
		return parameterNameReferences != null
				&& parameterNameReferences.isNotEmpty()
				&& parameterNameReferences.all { callsMemberOfCaughtException(it) }
	}

	private fun callsMemberOfCaughtException(expression: KtNameReferenceExpression): Boolean {
		return expression.nextSibling?.text == "."
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtFinallySection
import org.jetbrains.kotlin.psi.KtThrowExpression

class ThrowingExceptionFromFinally(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("ThrowingExceptionFromFinally", Severity.Defect,
			"Do not throw an exception within a finally statement. This can discard exceptions and is confusing.")

	override fun visitFinallySection(finallySection: KtFinallySection) {
		val throwExpressions = finallySection.finalExpression.collectByType<KtThrowExpression>()
		throwExpressions.forEach {
			report(CodeSmell(issue, Entity.from(it)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import io.gitlab.arturbosch.detekt.rules.isPublicNotOverridden
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtThrowExpression
import org.jetbrains.kotlin.psi.KtTypeReference

class ThrowingExceptionInMain(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("ThrowingExceptionInMain", Severity.CodeSmell,
			"The main method should not throw an exception.")

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (isMainFunction(function) && hasArgsParameter(function.valueParameters) && containsThrowExpression(function)) {
			report(CodeSmell(issue, Entity.from(function)))
		}
	}

	private fun isMainFunction(function: KtNamedFunction): Boolean {
		return function.name == "main" && function.isPublicNotOverridden() && function.isTopLevel
	}

	private fun hasArgsParameter(parameters: List<KtParameter>): Boolean {
		return parameters.size == 1 && isStringArrayParameter(parameters.first().typeReference)
	}

	private fun isStringArrayParameter(typeReference: KtTypeReference?): Boolean {
		return typeReference?.text?.replace("\\s+", "") == "Array<String>"
	}

	private fun containsThrowExpression(function: KtNamedFunction): Boolean {
		return function.bodyExpression?.collectByType<KtThrowExpression>()?.any() == true
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Excludes
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtCallExpression

class ThrowingExceptionsWithoutMessageOrCause(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("ThrowingExceptionsWithoutMessageOrCause", Severity.Warning,
			"A call to the default constructor of an exception was detected. " +
					"Instead one of the constructor overloads should be called. " +
					"This allows to provide more meaningful exceptions.",
			Debt.FIVE_MINS)

	private val exceptions = Excludes(valueOrDefault(EXCEPTIONS, ""))

	override fun visitCallExpression(expression: KtCallExpression) {
		val calleeExpressionText = expression.calleeExpression?.text
		if (exceptions.contains(calleeExpressionText) && expression.valueArguments.isEmpty()) {
			report(CodeSmell(issue, Entity.from(expression)))
		}
		super.visitCallExpression(expression)
	}

	companion object {
		const val EXCEPTIONS = "exceptions"
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtCatchClause
import org.jetbrains.kotlin.psi.KtThrowExpression
import org.jetbrains.kotlin.psi.KtValueArgument

class ThrowingNewInstanceOfSameException(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("ThrowingNewInstanceOfSameException", Severity.Defect,
			"Avoid catch blocks that rethrow a caught exception wrapped inside a new instance of the same exception.")

	override fun visitCatchSection(catchClause: KtCatchClause) {
		val parameterName = catchClause.catchParameter?.name
		val typeReference = catchClause.catchParameter?.typeReference?.text
		val throwExpression = catchClause.catchBody?.collectByType<KtThrowExpression>()?.firstOrNull {
			val thrownExpression = it.thrownExpression as? KtCallExpression
			thrownExpression != null
					&& createsSameExceptionType(thrownExpression, typeReference)
					&& hasSameExceptionParameter(thrownExpression.valueArguments, parameterName)
		}
		if (throwExpression != null) {
			report(CodeSmell(issue, Entity.from(throwExpression)))
		}
	}

	private fun createsSameExceptionType(thrownExpression: KtCallExpression, typeReference: String?): Boolean {
		return thrownExpression.calleeExpression?.text == typeReference
	}

	private fun hasSameExceptionParameter(valueArguments: List<KtValueArgument>, parameterName: String?): Boolean {
		return valueArguments.size == 1 && valueArguments.first().text == parameterName
	}
}

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtCatchClause

/**
 * @author Artur Bosch
 */
class TooGenericExceptionCaught(config: Config) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Defect,
			"Thrown exception is too generic. " +
					"Prefer throwing project specific exceptions to handle error cases.")

	private val exceptions: Set<String> = valueOrDefault(CAUGHT_EXCEPTIONS_PROPERTY, CAUGHT_EXCEPTIONS).toHashSet()

	override fun visitCatchSection(catchClause: KtCatchClause) {
		catchClause.catchParameter?.let {
			val text = it.typeReference?.text
			if (text != null && text in exceptions)
				report(CodeSmell(issue, Entity.from(it)))
		}
		super.visitCatchSection(catchClause)
	}
}

const val CAUGHT_EXCEPTIONS_PROPERTY = "exceptions"
val CAUGHT_EXCEPTIONS = listOf(
		"ArrayIndexOutOfBoundsException",
		"Error",
		"Exception",
		"IllegalMonitorStateException",
		"InterruptedException",
		"NullPointerException",
		"IndexOutOfBoundsException",
		"RuntimeException",
		"Throwable"
)

package io.gitlab.arturbosch.detekt.rules.exceptions

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtThrowExpression

/**
 * @author Artur Bosch
 */
class TooGenericExceptionThrown(config: Config) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Defect,
			"Thrown exception is too generic. " +
					"Prefer throwing project specific exceptions to handle error cases.")

	private val exceptions: Set<String> = valueOrDefault(THROWN_EXCEPTIONS_PROPERTY, THROWN_EXCEPTIONS).toHashSet()

	override fun visitThrowExpression(expression: KtThrowExpression) {
		expression.thrownExpression?.text?.substringBefore("(")?.let {
			if (it in exceptions) report(CodeSmell(issue, Entity.from(expression)))
		}
		super.visitThrowExpression(expression)
	}
}

const val THROWN_EXCEPTIONS_PROPERTY = "exceptions"

val THROWN_EXCEPTIONS = listOf(
		"Error",
		"Exception",
		"NullPointerException",
		"Throwable",
		"RuntimeException"
)

package io.gitlab.arturbosch.detekt.rules.performance

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression

class ForEachOnRange(config: Config = Config.empty) : Rule(config) {
	override val issue = Issue("ForEachOnRange",
			Severity.Performance,
			"Using the forEach method on ranges has a heavy performance cost. Prefer using simple for loops.")

	override fun visitCallExpression(expression: KtCallExpression) {
		super.visitCallExpression(expression)

		expression.getCallNameExpression()?.let {
			if (!it.textMatches("forEach")) {
				return
			}

			it.getReceiverExpression()?.text?.let {
				if (it matches rangeRegex) {
					report(CodeSmell(issue, Entity.from(expression)))
				}
			}
		}
	}

	companion object {
		val rangeRegex = Regex("\\(.*\\.\\..+\\)")
	}
}

package io.gitlab.arturbosch.detekt.rules.performance

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtValueArgumentList

/**
 * @author Ivan Balaksha
 */
class SpreadOperator(config: Config = Config.empty) : Rule(config) {
	override val issue: Issue = Issue("SpreadOperator",
			Severity.Performance,
			"Using a spread operator causes a full copy of the array to be created before calling a method " +
					"has a very high performance penalty.")

	override fun visitValueArgumentList(list: KtValueArgumentList) {
		super.visitValueArgumentList(list)
		list.arguments.filter { it.getSpreadElement() != null }
				.forEach {
					report(CodeSmell(issue, Entity.from(list)))
				}
	}
}

package io.gitlab.arturbosch.detekt.rules.performance

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtExpression

class UnnecessaryTemporaryInstantiation(config: Config = Config.empty) : Rule(config) {

	override val issue: Issue = Issue("UnnecessaryTemporaryInstantiation", Severity.Performance,
			"Avoid temporary objects when converting primitive types to String.")

	private val types: Set<String> = hashSetOf("Boolean", "Byte", "Short", "Integer", "Long", "Float", "Double")

	override fun visitCallExpression(expression: KtCallExpression) {
		if (isPrimitiveWrapperType(expression.calleeExpression)
				&& isToStringMethod(expression.nextSibling?.nextSibling)) {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}

	private fun isPrimitiveWrapperType(expression: KtExpression?) = types.contains(expression?.text)

	private fun isToStringMethod(element: PsiElement?) = element?.text == "toString()"
}

package io.gitlab.arturbosch.detekt.rules.providers

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider

/**
 * @author Artur Bosch
 */
class CodeSmellProvider : RuleSetProvider {

	override val ruleSetId: String = "code-smell"

	override fun instance(config: Config): RuleSet {
		return RuleSet(ruleSetId, listOf(
				//FeatureEnvy(config)
		))
	}
}

package io.gitlab.arturbosch.detekt.rules.providers

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.rules.documentation.CommentOverPrivateFunction
import io.gitlab.arturbosch.detekt.rules.documentation.CommentOverPrivateProperty
import io.gitlab.arturbosch.detekt.rules.documentation.UndocumentedPublicClass
import io.gitlab.arturbosch.detekt.rules.documentation.UndocumentedPublicFunction

/**
 * @author Artur Bosch
 */
class CommentSmellProvider : RuleSetProvider {

	override val ruleSetId: String = "comments"

	override fun instance(config: Config): RuleSet {
		return RuleSet(ruleSetId, listOf(
				CommentOverPrivateFunction(config),
				CommentOverPrivateProperty(config),
				UndocumentedPublicClass(config),
				UndocumentedPublicFunction(config)
		))
	}

}

package io.gitlab.arturbosch.detekt.rules.providers

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.rules.complexity.ComplexMethod
import io.gitlab.arturbosch.detekt.rules.complexity.LargeClass
import io.gitlab.arturbosch.detekt.rules.complexity.LongMethod
import io.gitlab.arturbosch.detekt.rules.complexity.LongParameterList
import io.gitlab.arturbosch.detekt.rules.complexity.NestedBlockDepth
import io.gitlab.arturbosch.detekt.rules.complexity.TooManyFunctions
import io.gitlab.arturbosch.detekt.rules.complexity.ComplexCondition
import io.gitlab.arturbosch.detekt.rules.complexity.LabeledExpression
import io.gitlab.arturbosch.detekt.rules.complexity.ComplexInterface
import io.gitlab.arturbosch.detekt.rules.complexity.MethodOverloading
import io.gitlab.arturbosch.detekt.rules.complexity.StringLiteralDuplication

/**
 * @author Artur Bosch
 */
class ComplexityProvider : RuleSetProvider {

	override val ruleSetId: String = "complexity"

	override fun instance(config: Config): RuleSet {
		return RuleSet(ruleSetId, listOf(
				LongParameterList(config),
				LongMethod(config),
				LargeClass(config),
				ComplexInterface(config),
				ComplexMethod(config),
				StringLiteralDuplication(config),
				MethodOverloading(config),
				NestedBlockDepth(config),
				TooManyFunctions(config),
				ComplexCondition(config),
				LabeledExpression(config)
		))
	}
}

package io.gitlab.arturbosch.detekt.rules.providers

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.rules.empty.EmptyBlocks

/**
 * @author Artur Bosch
 */
class EmptyCodeProvider : RuleSetProvider {

	override val ruleSetId: String = "empty-blocks"

	override fun instance(config: Config): RuleSet {
		return RuleSet(ruleSetId, listOf(
				EmptyBlocks(config)
		))
	}

}

package io.gitlab.arturbosch.detekt.rules.providers

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.rules.exceptions.ExceptionRaisedInUnexpectedLocation
import io.gitlab.arturbosch.detekt.rules.exceptions.InstanceOfCheckForException
import io.gitlab.arturbosch.detekt.rules.exceptions.NotImplementedDeclaration
import io.gitlab.arturbosch.detekt.rules.exceptions.ThrowingExceptionsWithoutMessageOrCause
import io.gitlab.arturbosch.detekt.rules.exceptions.PrintStackTrace
import io.gitlab.arturbosch.detekt.rules.exceptions.RethrowCaughtException
import io.gitlab.arturbosch.detekt.rules.exceptions.ReturnFromFinally
import io.gitlab.arturbosch.detekt.rules.exceptions.SwallowedException
import io.gitlab.arturbosch.detekt.rules.exceptions.ThrowingExceptionFromFinally
import io.gitlab.arturbosch.detekt.rules.exceptions.ThrowingExceptionInMain
import io.gitlab.arturbosch.detekt.rules.exceptions.ThrowingNewInstanceOfSameException
import io.gitlab.arturbosch.detekt.rules.exceptions.TooGenericExceptionCaught
import io.gitlab.arturbosch.detekt.rules.exceptions.TooGenericExceptionThrown

/**
 * @author Artur Bosch
 */
class ExceptionsProvider : RuleSetProvider {

	override val ruleSetId: String = "exceptions"

	override fun instance(config: Config): RuleSet {
		return RuleSet(ruleSetId, listOf(
				TooGenericExceptionCaught(config),
				ExceptionRaisedInUnexpectedLocation(config),
				TooGenericExceptionThrown(config),
				NotImplementedDeclaration(config),
				PrintStackTrace(config),
				InstanceOfCheckForException(config),
				ThrowingExceptionsWithoutMessageOrCause(config),
				ReturnFromFinally(config),
				ThrowingExceptionFromFinally(config),
				ThrowingExceptionInMain(config),
				RethrowCaughtException(config),
				ThrowingNewInstanceOfSameException(config),
				SwallowedException(config)
		))
	}

}

package io.gitlab.arturbosch.detekt.rules.providers

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.rules.performance.ForEachOnRange
import io.gitlab.arturbosch.detekt.rules.performance.SpreadOperator
import io.gitlab.arturbosch.detekt.rules.performance.UnnecessaryTemporaryInstantiation

class PerformanceProvider : RuleSetProvider {

	override val ruleSetId: String = "performance"

	override fun instance(config: Config): RuleSet {
		return RuleSet(ruleSetId, listOf(
				ForEachOnRange(config),
				SpreadOperator(config),
				UnnecessaryTemporaryInstantiation(config)
		))
	}
}

package io.gitlab.arturbosch.detekt.rules.providers

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.rules.bugs.DuplicateCaseInWhenExpression
import io.gitlab.arturbosch.detekt.rules.bugs.EqualsAlwaysReturnsTrueOrFalse
import io.gitlab.arturbosch.detekt.rules.bugs.EqualsWithHashCodeExist
import io.gitlab.arturbosch.detekt.rules.bugs.ExplicitGarbageCollectionCall
import io.gitlab.arturbosch.detekt.rules.bugs.InvalidLoopCondition
import io.gitlab.arturbosch.detekt.rules.bugs.IteratorHasNextCallsNextMethod
import io.gitlab.arturbosch.detekt.rules.bugs.IteratorNotThrowingNoSuchElementException
import io.gitlab.arturbosch.detekt.rules.bugs.LateinitUsage
import io.gitlab.arturbosch.detekt.rules.bugs.UnconditionalJumpStatementInLoop
import io.gitlab.arturbosch.detekt.rules.bugs.UnreachableCode
import io.gitlab.arturbosch.detekt.rules.bugs.UnsafeCallOnNullableType
import io.gitlab.arturbosch.detekt.rules.bugs.UnsafeCast
import io.gitlab.arturbosch.detekt.rules.bugs.UselessPostfixExpression
import io.gitlab.arturbosch.detekt.rules.bugs.WrongEqualsTypeParameter

/**
 * @author Artur Bosch
 */
class PotentialBugProvider : RuleSetProvider {

	override val ruleSetId: String = "potential-bugs"

	override fun instance(config: Config): RuleSet {
		return RuleSet(ruleSetId, listOf(
				DuplicateCaseInWhenExpression(config),
				EqualsAlwaysReturnsTrueOrFalse(config),
				EqualsWithHashCodeExist(config),
				IteratorNotThrowingNoSuchElementException(config),
				IteratorHasNextCallsNextMethod(config),
				UselessPostfixExpression(config),
				InvalidLoopCondition(config),
				WrongEqualsTypeParameter(config),
				ExplicitGarbageCollectionCall(config),
				LateinitUsage(config),
				UnconditionalJumpStatementInLoop(config),
				UnreachableCode(config),
				UnsafeCallOnNullableType(config),
				UnsafeCast(config)
		))
	}

}

package io.gitlab.arturbosch.detekt.rules.providers

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.rules.style.DataClassContainsFunctions
import io.gitlab.arturbosch.detekt.rules.style.EqualsNullCall
import io.gitlab.arturbosch.detekt.rules.style.ExpressionBodySyntax
import io.gitlab.arturbosch.detekt.rules.style.FileParsingRule
import io.gitlab.arturbosch.detekt.rules.style.ForbiddenComment
import io.gitlab.arturbosch.detekt.rules.style.ForbiddenImport
import io.gitlab.arturbosch.detekt.rules.style.LoopWithTooManyJumpStatements
import io.gitlab.arturbosch.detekt.rules.style.MagicNumber
import io.gitlab.arturbosch.detekt.rules.style.ModifierOrder
import io.gitlab.arturbosch.detekt.rules.style.NestedClassesVisibility
import io.gitlab.arturbosch.detekt.rules.style.NewLineAtEndOfFile
import io.gitlab.arturbosch.detekt.rules.style.OptionalAbstractKeyword
import io.gitlab.arturbosch.detekt.rules.style.OptionalWhenBraces
import io.gitlab.arturbosch.detekt.rules.style.PackageDeclarationStyle
import io.gitlab.arturbosch.detekt.rules.style.ProtectedMemberInFinalClass
import io.gitlab.arturbosch.detekt.rules.style.ReturnCount
import io.gitlab.arturbosch.detekt.rules.style.SafeCast
import io.gitlab.arturbosch.detekt.rules.style.SerialVersionUIDInSerializableClass
import io.gitlab.arturbosch.detekt.rules.style.ThrowsCount
import io.gitlab.arturbosch.detekt.rules.style.UnnecessaryAbstractClass
import io.gitlab.arturbosch.detekt.rules.style.UnnecessaryInheritance
import io.gitlab.arturbosch.detekt.rules.style.UnnecessaryParentheses
import io.gitlab.arturbosch.detekt.rules.style.UnusedImports
import io.gitlab.arturbosch.detekt.rules.style.UseDataClass
import io.gitlab.arturbosch.detekt.rules.style.UtilityClassWithPublicConstructor
import io.gitlab.arturbosch.detekt.rules.style.WildcardImport
import io.gitlab.arturbosch.detekt.rules.style.naming.NamingRules
import io.gitlab.arturbosch.detekt.rules.style.optional.OptionalReturnKeyword
import io.gitlab.arturbosch.detekt.rules.style.optional.OptionalUnit

/**
 * @author Artur Bosch
 */
class StyleGuideProvider : RuleSetProvider {

	override val ruleSetId: String = "style"

	override fun instance(config: Config): RuleSet {
		return RuleSet(ruleSetId, listOf(
				ReturnCount(config),
				ThrowsCount(config),
				NewLineAtEndOfFile(config),
				WildcardImport(config),
				FileParsingRule(config),
				EqualsNullCall(config),
				ForbiddenComment(config),
				ForbiddenImport(config),
				PackageDeclarationStyle(config),
				LoopWithTooManyJumpStatements(config),
				NamingRules(config),
				SafeCast(config),
				UnnecessaryAbstractClass(config),
				UnnecessaryParentheses(config),
				UnnecessaryInheritance(config),
				UtilityClassWithPublicConstructor(config),
				OptionalAbstractKeyword(config),
				OptionalWhenBraces(config),
				OptionalReturnKeyword(config),
				OptionalUnit(config),
				ProtectedMemberInFinalClass(config),
				SerialVersionUIDInSerializableClass(config),
				MagicNumber(config),
				ModifierOrder(config),
				DataClassContainsFunctions(config),
				UseDataClass(config),
				UnusedImports(config),
				ExpressionBodySyntax(config),
				NestedClassesVisibility(config)
		))
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.Excludes
import io.gitlab.arturbosch.detekt.rules.isOverridden
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtNamedFunction

class DataClassContainsFunctions(config: Config = Config.empty) : Rule(config) {

	override val issue: Issue = Issue("DataClassContainsFunctions",
			Severity.Style,
			"Data classes should mainly be used to store data and should not have any extra functions. " +
					"(Compiler will automatically generate equals, toString and hashCode functions)")

	private val conversionFunctionPrefix = Excludes(valueOrDefault(CONVERSION_FUNCTION_PREFIX, ""))

	override fun visitClass(klass: KtClass) {
		if (klass.isData()) {
			klass.getBody()?.declarations
					?.filterIsInstance<KtNamedFunction>()
					?.forEach { checkFunction(it) }
		}
		super.visitClass(klass)
	}

	private fun checkFunction(function: KtNamedFunction) {
		if (!function.isOverridden() && !conversionFunctionPrefix.startWith(function.name)) {
			report(CodeSmell(issue, Entity.from(function)))
		}
	}

	companion object {
		const val CONVERSION_FUNCTION_PREFIX = "conversionFunctionPrefix"
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtCallExpression

class EqualsNullCall(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("EqualsNullCall", Severity.Style,
			"Equals() method is called with null as parameter. Consider using == to compare to null.")

	override fun visitCallExpression(expression: KtCallExpression) {
		if (expression.calleeExpression?.text == "equals" && hasNullParameter(expression)) {
			report(CodeSmell(issue, Entity.from(expression)))
		} else {
			super.visitCallExpression(expression)
		}
	}

	private fun hasNullParameter(expression: KtCallExpression): Boolean {
		val valueArguments = expression.valueArguments
		return valueArguments.size == 1 && valueArguments.first().text == "null"
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtReturnExpression

/**
 * @author Artur Bosch
 */
class ExpressionBodySyntax(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(
			javaClass.simpleName,
			Severity.Style,
			"Functions with exact one statement, the return statement," +
					" can be rewritten with ExpressionBodySyntax.",
			Debt.FIVE_MINS)

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (function.bodyExpression != null) {
			val body = function.bodyExpression!!
			body.singleReturnStatement()?.let { returnStmt ->
				report(CodeSmell(issue, Entity.from(returnStmt)))
			}
		}
	}

	private fun KtExpression.singleReturnStatement(): KtReturnExpression? {
		val statements = (this as? KtBlockExpression)?.statements
		return statements?.size?.let {
			if (it == 1 && statements[0] is KtReturnExpression) {
				return statements[0] as KtReturnExpression
			} else null
		}
	}

}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.MultiRule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import io.gitlab.arturbosch.detekt.rules.reportFindings
import org.jetbrains.kotlin.psi.KtFile

class FileParsingRule(val config: Config = Config.empty) : MultiRule() {

	override val rules = listOf(MaxLineLength(config))

	override fun visitKtFile(file: KtFile) {
		val lines = file.text.splitToSequence("\n")
		val fileContents = KtFileContent(file, lines)
		fileContents.reportFindings(context, rules)
	}
}

abstract class File(open val file: KtFile)
data class KtFileContent(override val file: KtFile, val content: Sequence<String>) : File(file)

class MaxLineLength(config: Config = Config.empty) : SubRule<KtFileContent>(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			"Line detected that is longer than the defined maximum line length in the code style.",
			Debt.FIVE_MINS)

	private val maxLineLength: Int
			= valueOrDefault(MaxLineLength.MAX_LINE_LENGTH, MaxLineLength.DEFAULT_IDEA_LINE_LENGTH)
	private val excludePackageStatements: Boolean
			= valueOrDefault(MaxLineLength.EXCLUDE_PACKAGE_STATEMENTS, MaxLineLength.DEFAULT_VALUE_PACKAGE_EXCLUDE)
	private val excludeImportStatements: Boolean
			= valueOrDefault(MaxLineLength.EXCLUDE_IMPORT_STATEMENTS, MaxLineLength.DEFAULT_VALUE_IMPORTS_EXCLUDE)

	override fun apply(element: KtFileContent) {
		var offset = 0
		val lines = element.content
		val file = element.file
		lines.filter { filterPackageStatements(it) }
				.filter { filterImportStatements(it) }
				.map { it.length }
				.forEach {
					offset += it
					if (it > maxLineLength) {
						report(CodeSmell(issue, Entity.from(file, offset)))
					}
				}
	}

	private fun filterPackageStatements(line: String): Boolean {
		if (excludePackageStatements) {
			return !line.trim().startsWith("package ")
		}
		return true
	}

	private fun filterImportStatements(line: String): Boolean {
		if (excludeImportStatements) {
			return !line.trim().startsWith("import ")
		}
		return true
	}

	companion object {
		const val MAX_LINE_LENGTH = "maxLineLength"
		const val DEFAULT_IDEA_LINE_LENGTH = 120

		const val EXCLUDE_PACKAGE_STATEMENTS = "excludePackageStatements"
		const val DEFAULT_VALUE_PACKAGE_EXCLUDE = false

		const val EXCLUDE_IMPORT_STATEMENTS = "excludeImportStatements"
		const val DEFAULT_VALUE_IMPORTS_EXCLUDE = false
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.PsiComment

class ForbiddenComment(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			"Flags a forbidden comment. Defaults values are TODO:, FIXME: or STOPSHIP:",
			Debt.TEN_MINS)

	private val values: List<String>
			= valueOrDefault(VALUES, "TODO:,FIXME:,STOPSHIP:")
			.split(",")
			.filter { it.isNotBlank() }

	override fun visitComment(comment: PsiComment) {
		super.visitComment(comment)

		val text = comment.text

		values.forEach {
			if (text.contains(it, ignoreCase = true)) {
				report(CodeSmell(issue, Entity.from(comment)))
			}
		}
	}

	companion object {
		const val VALUES = "values"
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Excludes
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtImportList

class ForbiddenImport(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Style,
			"Mark forbidden imports. A forbidden import could be an import for an unstable / experimental api" +
					"and hence you might want to mark it as forbidden in order to get warned about the usage.", Debt.TEN_MINS)

	private val forbiddenImports = Excludes(valueOrDefault(IMPORTS, ""))

	override fun visitImportList(importList: KtImportList) {
		super.visitImportList(importList)

		importList
				.imports
				.filterNot { it.isAllUnder }
				.filter { forbiddenImports.contains(it.importedFqName?.asString() ?: "") }
				.forEach { report(CodeSmell(issue, Entity.from(it))) }
	}

	companion object {
		const val IMPORTS = "imports"
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtBreakExpression
import org.jetbrains.kotlin.psi.KtContinueExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtLoopExpression

class LoopWithTooManyJumpStatements(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Style,
			"The loop contains more than one break or continue statement. " +
					"The code should be refactored to increase readability.", Debt.TEN_MINS)

	private val maxJumpCount = valueOrDefault(MAX_JUMP_COUNT, 1)

	override fun visitLoopExpression(loopExpression: KtLoopExpression) {
		if (countBreakAndReturnStatements(loopExpression.body) > maxJumpCount) {
			report(CodeSmell(issue, Entity.from(loopExpression)))
		}
		super.visitLoopExpression(loopExpression)
	}

	private fun countBreakAndReturnStatements(body: KtExpression?): Int {
		return body?.countBreakAndReturnStatementsInLoop() ?: 0
	}

	private fun KtElement.countBreakAndReturnStatementsInLoop(): Int {
		var count = 0
		this.accept(object : DetektVisitor() {
			override fun visitKtElement(element: KtElement) {
				if (element is KtLoopExpression) {
					return
				}
				if (element is KtBreakExpression || element is KtContinueExpression) {
					count++
				}
				element.children.forEach { it.accept(this) }
			}
		})
		return count
	}

	companion object {
		const val MAX_JUMP_COUNT = "maxJumpCount"
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.isPartOf
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtOperationReferenceExpression
import org.jetbrains.kotlin.psi.KtPrefixExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import java.util.Locale

class MagicNumber(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Style,
			"Report magic numbers. Magic number is a numeric literal that is not defined as a constant " +
					"and hence it's unclear what the purpose of this number is. " +
					"It's better to declare such numbers as constants and give them a proper name. " +
					"By default, -1, 0, 1, and 2 are not considered to be magic numbers.", Debt.TEN_MINS)

	private val ignoredNumbers = valueOrDefault(IGNORE_NUMBERS, "-1,0,1,2")
			.split(",")
			.filterNot { it.isEmpty() }
			.map { parseAsDouble(it) }
			.sorted()

	private val ignoreHashCodeFunction = valueOrDefault(IGNORE_HASH_CODE, false)
	private val ignoreAnnotation = valueOrDefault(IGNORE_ANNOTATION, false)
	private val ignorePropertyDeclaration = valueOrDefault(IGNORE_PROPERTY_DECLARATION, false)
	private val ignoreNamedArgument = valueOrDefault(IGNORE_NAMED_ARGUMENT, false)
	private val ignoreEnums = valueOrDefault(IGNORE_ENUMS, false)

	override fun visitConstantExpression(expression: KtConstantExpression) {
		val parent = expression.parent

		if (isIgnoredByConfig(parent, expression)) {
			return
		}

		val rawNumber = if (parent.hasUnaryMinusPrefix()) {
			parent.text
		} else {
			expression.text
		}

		val number = parseAsDoubleOrNull(rawNumber) ?: return
		if (!ignoredNumbers.contains(number)) {
			report(CodeSmell(issue, Entity.from(expression)))
		}
	}

	private fun isIgnoredByConfig(parent: PsiElement?, expression: KtConstantExpression) = when {
		ignorePropertyDeclaration && parent is KtProperty && !parent.isLocal -> true
		ignoreAnnotation && expression.isPartOf(KtAnnotationEntry::class) -> true
		ignoreHashCodeFunction && expression.isPartOfHashCode() -> true
		ignoreEnums && expression.isPartOf(KtEnumEntry::class) -> true
		parent.isConstantProperty() -> true
		ignoreNamedArgument
				&& expression.isPartOf(KtValueArgument::class)
				&& expression.isPartOf(KtCallExpression::class) -> true
		else -> false
	}

	private fun KtConstantExpression.isPartOfHashCode(): Boolean {
		val containingFunction = getNonStrictParentOfType(KtNamedFunction::class.java)
		val name = containingFunction?.name
		val returnType = containingFunction?.typeReference?.node?.text
		return nameIsHashCode(name) && returnTypeIsInt(returnType)
	}

	private fun returnTypeIsInt(returnType: String?) = returnType != null && returnType == "Int"
	private fun nameIsHashCode(name: String?) = name != null && name == "hashCode"

	private fun PsiElement?.isConstantProperty(): Boolean =
			this is KtProperty && this.hasModifier(KtTokens.CONST_KEYWORD)

	private fun PsiElement.hasUnaryMinusPrefix(): Boolean = this is KtPrefixExpression
			&& (this.firstChild as? KtOperationReferenceExpression)?.operationSignTokenType == KtTokens.MINUS

	private fun parseAsDoubleOrNull(rawToken: String?): Double? = try {
		rawToken?.let { parseAsDouble(it) }
	} catch (e: NumberFormatException) {
		null
	}

	private fun parseAsDouble(rawNumber: String): Double {
		val normalizedText = normalizeForParsingAsDouble(rawNumber)
		return when {
			normalizedText.startsWith("0x") || normalizedText.startsWith("0X") ->
				normalizedText.substring(2).toLong(HEX_RADIX).toDouble()
			normalizedText.startsWith("0b") || normalizedText.startsWith("0B") ->
				normalizedText.substring(2).toLong(BINARY_RADIX).toDouble()
			else -> normalizedText.toDouble()
		}
	}

	private fun normalizeForParsingAsDouble(text: String): String {
		return text.trim()
				.toLowerCase(Locale.US)
				.replace("_", "")
				.removeSuffix("l")
				.removeSuffix("d")
				.removeSuffix("f")
	}

	companion object {
		const val IGNORE_NUMBERS = "ignoreNumbers"
		const val IGNORE_HASH_CODE = "ignoreHashCodeFunction"
		const val IGNORE_PROPERTY_DECLARATION = "ignorePropertyDeclaration"
		const val IGNORE_ANNOTATION = "ignoreAnnotation"
		const val IGNORE_NAMED_ARGUMENT = "ignoreNamedArgument"
		const val IGNORE_ENUMS = "ignoreEnums"

		private const val HEX_RADIX = 16
		private const val BINARY_RADIX = 2
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.lexer.KtTokens.ABSTRACT_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.ANNOTATION_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.COMPANION_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.CONST_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.DATA_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.ENUM_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.EXTERNAL_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.FINAL_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.INFIX_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.INLINE_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.INNER_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.INTERNAL_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.LATEINIT_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.OPEN_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.OPERATOR_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.OVERRIDE_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.PRIVATE_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.PROTECTED_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.PUBLIC_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.SEALED_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.SUSPEND_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.TAILREC_KEYWORD
import org.jetbrains.kotlin.psi.KtModifierList
import org.jetbrains.kotlin.psi.psiUtil.allChildren
import java.util.Arrays

/**
 * Modifier order array taken from ktlint: https://github.com/shyiko/ktlint
 */
class ModifierOrder(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt(mins = 1))

	// subset of KtTokens.MODIFIER_KEYWORDS_ARRAY
	private val order = arrayOf(
			PUBLIC_KEYWORD, PROTECTED_KEYWORD, PRIVATE_KEYWORD, INTERNAL_KEYWORD,
			FINAL_KEYWORD, OPEN_KEYWORD, ABSTRACT_KEYWORD,
			SUSPEND_KEYWORD, TAILREC_KEYWORD,
			OVERRIDE_KEYWORD,
			CONST_KEYWORD, LATEINIT_KEYWORD,
			INNER_KEYWORD, EXTERNAL_KEYWORD,
			ENUM_KEYWORD, ANNOTATION_KEYWORD, SEALED_KEYWORD, DATA_KEYWORD,
			COMPANION_KEYWORD,
			INLINE_KEYWORD,
			// NOINLINE_KEYWORD, CROSSINLINE_KEYWORD, OUT_KEYWORD, IN_KEYWORD, VARARG_KEYWORD, REIFIED_KEYWORD
			INFIX_KEYWORD,
			OPERATOR_KEYWORD
			// HEADER_KEYWORD, IMPL_KEYWORD
	)

	override fun visitModifierList(list: KtModifierList) {
		super.visitModifierList(list)

		val modifiers = list.allChildren
				.toList()
				.filter { it !is PsiWhiteSpace }
				.toTypedArray()

		val sortedModifiers = modifiers.copyOf()
				.apply { sortWith(compareBy { order.indexOf(it.node.elementType) }) }

		if (!Arrays.equals(modifiers, sortedModifiers)) {
			val modifierString = sortedModifiers.joinToString(" ") { it.text }

			report(CodeSmell(Issue(javaClass.simpleName, Severity.Style,
					"Modifier order should be: $modifierString", Debt(mins = 1)), Entity.from(list)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.isInternal
import io.gitlab.arturbosch.detekt.rules.isPublic
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtEnumEntry

class NestedClassesVisibility(config: Config = Config.empty) : Rule(config) {

	override val issue: Issue = Issue("NestedClassesVisibility", Severity.Style,
			"Nested types are often used for implementing private functionality " +
					"and therefore this should not be public.",
					Debt.FIVE_MINS)

	override fun visitClass(klass: KtClass) {
		if (klass.isTopLevel() && klass.isInternal()) {
			checkDeclarations(klass)
		}
	}

	private fun checkDeclarations(klass: KtClass) {
		klass.declarations
				.filterIsInstance<KtClass>()
				.filter { it.isPublic() && !it.isEnum() && it !is KtEnumEntry }
				.forEach { report(CodeSmell(issue, Entity.from(it)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtFile

class NewLineAtEndOfFile(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			"Checks whether files end with a line separator.",
			Debt.FIVE_MINS)

	override fun visitKtFile(file: KtFile) {
		val text = file.text
		if (text.isNotEmpty() && text.lastOrNull() != '\n') {
			report(CodeSmell(issue, Entity.from(file, text.length - 1)))
		}
	}

}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty

class OptionalAbstractKeyword(config: Config = Config.empty) : Rule(config) {

	private val visitor = AbstractDeclarationVisitor(this)

	override val issue: Issue = Issue(javaClass.simpleName, Severity.Style,
			"Unnecessary abstract modifier in interface", Debt.FIVE_MINS)

	override fun visitClass(klass: KtClass) {
		if (klass.isInterface()) {
			handleAbstractKeyword(klass)
			klass.getBody()?.declarations?.forEach { it.accept(visitor) }
		}
		super.visitClass(klass)
	}

	internal fun handleAbstractKeyword(dcl: KtDeclaration) {
		dcl.modifierList?.let {
			val abstractModifier = it.getModifier(KtTokens.ABSTRACT_KEYWORD)
			if (abstractModifier != null) {
				report(CodeSmell(issue, Entity.from(dcl)))
			}
		}
	}

	private class AbstractDeclarationVisitor(val rule: OptionalAbstractKeyword) : DetektVisitor() {

		override fun visitProperty(property: KtProperty) {
			rule.handleAbstractKeyword(property)
		}

		override fun visitNamedFunction(function: KtNamedFunction) {
			rule.handleAbstractKeyword(function)
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.hasCommentInside
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtWhenExpression

class OptionalWhenBraces(config: Config = Config.empty) : Rule(config) {

	override val issue: Issue = Issue(javaClass.simpleName, Severity.Style,
			"Optional braces in when expression", Debt.FIVE_MINS)

	override fun visitWhenExpression(expression: KtWhenExpression) {
		for (entry in expression.entries) {
			val blockExpression = entry.expression as? KtBlockExpression
			if (hasOneStatement(blockExpression) && hasOptionalBrace(blockExpression)) {
				report(CodeSmell(issue, Entity.from(entry)))
			}
		}
	}

	private fun hasOneStatement(blockExpression: KtBlockExpression?) =
			blockExpression?.statements?.size == 1 && !blockExpression.hasCommentInside()

	private fun hasOptionalBrace(blockExpression: KtBlockExpression?) =
			blockExpression != null && blockExpression.lBrace != null && blockExpression.rBrace != null
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtImportList
import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.psiUtil.siblings

class PackageDeclarationStyle(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Style,
			"Violation of the package declaration style." +
					"There should be exactly one blank line after the package and import declaration",
			Debt.FIVE_MINS)

	override fun visitImportList(importList: KtImportList) {
		if (importList.imports.isNotEmpty()) {
			checkPackageDeclaration(importList)
			checkKtElementsDeclaration(importList)
		}
	}

	private fun checkPackageDeclaration(importList: KtImportList) {
		val prevSibling = importList.prevSibling
		if (isPackageDeclaration(prevSibling) || prevSibling is PsiWhiteSpace) {
			checkLinebreakAfterElement(prevSibling)
		}
	}

	private fun isPackageDeclaration(element: PsiElement?) =
			(element is KtPackageDirective && element.text.isNotEmpty())

	private fun checkKtElementsDeclaration(importList: KtImportList) {
		val hasKtElements = importList.siblings(withItself = false).any { it is KtElement }
		val nextSibling = importList.nextSibling
		if (hasKtElements
				&& (nextSibling is PsiWhiteSpace || nextSibling is KtElement)) {
			checkLinebreakAfterElement(nextSibling)
		}
	}

	private fun checkLinebreakAfterElement(element: PsiElement?) {
		if (element is PsiWhiteSpace || element is KtElement) {
			val count = element.text.count { it == '\n' }
			if (count != 2) {
				report(CodeSmell(issue, Entity.from(element)))
			}
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDeclaration

class ProtectedMemberInFinalClass(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Warning,
			"Member with protected visibility in final class is private. Consider using private or internal as modifier.",
			Debt.TEN_MINS)

	private val visitor = DeclarationVisitor()

	/**
	 * Only classes and companion objects can contain protected members.
	 */
	override fun visitClass(klass: KtClass) {
		if (hasModifiers(klass)) {
			klass.primaryConstructor?.accept(visitor)
			klass.getBody()?.declarations?.forEach { it.accept(visitor) }
			klass.companionObjects.forEach { it.accept(visitor) }
		}
		super.visitClass(klass)
	}

	private fun hasModifiers(klass: KtClass): Boolean {
		val isNotAbstract = !klass.hasModifier(KtTokens.ABSTRACT_KEYWORD)
		val isFinal = !klass.hasModifier(KtTokens.OPEN_KEYWORD)
		val isNotSealed = !klass.hasModifier(KtTokens.SEALED_KEYWORD)
		return isNotAbstract && isFinal && isNotSealed
	}

	internal inner class DeclarationVisitor : DetektVisitor() {

		override fun visitDeclaration(dcl: KtDeclaration) {
			val isProtected = dcl.hasModifier(KtTokens.PROTECTED_KEYWORD)
			val isNotOverridden = !dcl.hasModifier(KtTokens.OVERRIDE_KEYWORD)
			if (isProtected && isNotOverridden) {
				report(CodeSmell(issue, Entity.from(dcl)))
			}
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtReturnExpression

class ReturnCount(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Style,
			"Restrict the number of return statements in methods.", Debt.TEN_MINS)

	private val max = valueOrDefault(MAX, 2)

	override fun visitNamedFunction(function: KtNamedFunction) {
		super.visitNamedFunction(function)

		val numberOfReturns = function.collectByType<KtReturnExpression>().count()

		if (numberOfReturns > max) {
			report(CodeSmell(issue, Entity.from(function)))
		}
	}

	companion object {
		const val MAX = "max"
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.asBlockExpression
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtIsExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression

class SafeCast(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Style, "Safe cast instead of if-else-null", Debt.FIVE_MINS)
	private var identifier = ""

	override fun visitIfExpression(expression: KtIfExpression) {
		val condition = expression.condition as? KtIsExpression
		if (condition != null) {
			val leftHandSide = condition.leftHandSide as? KtNameReferenceExpression
			if (leftHandSide != null) {
				identifier = leftHandSide.text
				val thenClause = expression.then
				val elseClause = expression.`else`
				val result = when (condition.isNegated) {
					true -> isIfElseNull(elseClause, thenClause)
					false -> isIfElseNull(thenClause, elseClause)
				}
				if (result) {
					addReport(expression)
				}
			}
		}
	}

	private fun isIfElseNull(thenClause: KtExpression?, elseClause: KtExpression?): Boolean {
		val hasIdentifier = thenClause?.asBlockExpression()?.statements?.firstOrNull()?.text == identifier
		val elseStatement = elseClause?.asBlockExpression()?.statements?.firstOrNull()
		val hasNull = elseStatement is KtConstantExpression && elseStatement.node.elementType == KtNodeTypes.NULL
		return hasIdentifier && hasNull
	}

	private fun addReport(expression: KtIfExpression) {
		report(CodeSmell(issue, Entity.from(expression)))
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.companionObject
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtProperty

class SerialVersionUIDInSerializableClass(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Warning,
			"A class which implements the Serializable interface does not define a correct serialVersionUID field. " +
					"The serialVersionUID field should be a constant long value inside a companion object.",
			Debt.FIVE_MINS)

	private val versionUID = "serialVersionUID"

	override fun visitClass(klass: KtClass) {
		if (!klass.isInterface() && isImplementingSerializable(klass)) {
			val companionObject = klass.companionObject()
			if (companionObject == null || !hasCorrectSerialVersionUUID(companionObject)) {
				report(CodeSmell(issue, Entity.from(klass)))
			}
		}
		super.visitClass(klass)
	}

	override fun visitObjectDeclaration(declaration: KtObjectDeclaration) {
		if (!declaration.isCompanion()
				&& isImplementingSerializable(declaration)
				&& !hasCorrectSerialVersionUUID(declaration)) {
			report(CodeSmell(issue, Entity.from(declaration)))
		}
		super.visitObjectDeclaration(declaration)
	}

	private fun isImplementingSerializable(classOrObject: KtClassOrObject) =
			classOrObject.superTypeListEntries.any { it.text == "Serializable" }

	private fun hasCorrectSerialVersionUUID(declaration: KtObjectDeclaration): Boolean {
		val property = declaration.getBody()?.properties?.firstOrNull { it.name == versionUID }
		return property != null && property.hasModifier(KtTokens.CONST_KEYWORD) && isLongProperty(property)
	}

	private fun isLongProperty(property: KtProperty) = hasLongType(property) || hasLongAssignment(property)

	private fun hasLongType(property: KtProperty) = property.typeReference?.text == "Long"

	private fun hasLongAssignment(property: KtProperty): Boolean {
		val assignmentText = property.children.singleOrNull { it is KtConstantExpression }?.text
		return assignmentText != null && assignmentText.last() == 'L'
				&& assignmentText.substring(0, assignmentText.length-1).toLongOrNull() != null
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtThrowExpression

class ThrowsCount(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Style,
			"Restrict the number of throw statements in methods.",
			Debt.FIVE_MINS)

	private val max = valueOrDefault(MAX, 2)

	override fun visitNamedFunction(function: KtNamedFunction) {
		super.visitNamedFunction(function)
		if (!function.hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
			val count = function.collectByType<KtThrowExpression>().count()
			if (count > max) {
				report(CodeSmell(issue, Entity.from(function)))
			}
		}
	}

	companion object {
		const val MAX = "max"
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.isAbstract

class UnnecessaryAbstractClass(config: Config = Config.empty) : Rule(config) {

	override val issue =
			Issue("UnnecessaryAbstractClass", Severity.Style,
			"An abstract class is unnecessary and can be refactored. " +
						"An abstract class should have both abstract and concrete properties or functions. " +
						"An abstract class without a concrete member can be refactored to an interface. " +
						"An abstract class without an abstract member can be refactored to a concrete class.")

	override fun visitClass(klass: KtClass) {
		if (!klass.isInterface() && klass.isAbstract()) {
			val namedMembers = klass.getBody()?.children
					?.filter { it is KtProperty || it is KtNamedFunction }
			if (namedMembers != null) {
				val namedClassMembers = NamedClassMembers(klass, namedMembers)
				namedClassMembers.detectAbstractAndConcreteType()
			}
		}
		super.visitClass(klass)
	}

	private inner class NamedClassMembers(val klass: KtClass, val namedMembers: List<PsiElement>) {

		fun detectAbstractAndConcreteType() {
			val indexOfFirstAbstractMember = indexOfFirstMember(true)
			if (indexOfFirstAbstractMember == -1) {
				report(CodeSmell(issue.copy(description =
						"An abstract class without an abstract member can be refactored to a concrete class."), Entity.from(klass)))
			} else if (indexOfFirstAbstractMember == 0 && hasNoConcreteMemberLeft()) {
				report(CodeSmell(issue.copy(description =
						"An abstract class without a concrete member can be refactored to an interface."), Entity.from(klass)))
			}
		}

		private fun indexOfFirstMember(isAbstract: Boolean, members: List<PsiElement> = this.namedMembers): Int {
			return members.indexOfFirst {
				val namedDeclaration = it as? KtNamedDeclaration
				namedDeclaration != null && namedDeclaration.hasModifier(KtTokens.ABSTRACT_KEYWORD) == isAbstract
			}
		}

		private fun hasNoConcreteMemberLeft() = indexOfFirstMember(false, namedMembers.drop(1)) == -1
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtClassOrObject

class UnnecessaryInheritance(config: Config = Config.empty) : Rule(config) {

	override val issue: Issue = Issue(javaClass.simpleName, Severity.Style,
			"The extended super type is unnecessary.", Debt.FIVE_MINS)

	override fun visitClassOrObject(classOrObject: KtClassOrObject) {
		for (superEntry in classOrObject.superTypeListEntries) {
			when (superEntry.text) {
				"Any()" -> report(classOrObject, "Unnecessary inheritance of 'Any'.")
				"Object()" -> report(classOrObject, "Unnecessary inheritance of 'Object'.")
			}
		}
	}

	private fun report(classOrObject: KtClassOrObject, newDescription: String) {
		report(CodeSmell(issue.copy(description = newDescription), Entity.from(classOrObject)))
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtParenthesizedExpression
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.psi.KtSuperTypeCallEntry
import org.jetbrains.kotlin.psi.KtValueArgument

/**
 * Reports unnecessary parentheses around expressions.
 *
 * Added in v1.0.0.RC4
 */
class UnnecessaryParentheses(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue("UnnecessaryParentheses", Severity.Style,
			"These parentheses are unnecessary and can be removed.")

	override fun visitParenthesizedExpression(expression: KtParenthesizedExpression) {
		super.visitParenthesizedExpression(expression)

		if (KtPsiUtil.areParenthesesUseless(expression)) {
			val description = "Parentheses in ${expression.text} are unnecessary and can be replaced with: " +
					"${KtPsiUtil.deparenthesize(expression)?.text}"
			report(CodeSmell(issue.copy(description = description), Entity.from(expression)))
		}
	}

	override fun visitArgument(argument: KtValueArgument) {
		super.visitArgument(argument)
		val isLambdaExpression = argument.children.any { it is KtLambdaExpression }
		val isOnlyArgument = argument.parent.children.size == 1

		val isSuperTypeCallEntry = argument.parent.parent is KtSuperTypeCallEntry

		if (isLambdaExpression && isOnlyArgument && !isSuperTypeCallEntry) {
			val description = "Parentheses around the lambda ${argument.parent.text} are unnecessary and can be removed."
			report(CodeSmell(issue.copy(description = description), Entity.from(argument.parent)))
		}
	}
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.isPartOf
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtImportList
import org.jetbrains.kotlin.psi.KtReferenceExpression

/**
 * @author Artur Bosch
 */
class UnusedImports(config: Config) : Rule(config) {

	override val issue = Issue(
			javaClass.simpleName,
			Severity.Style,
			"Unused Imports are dead code and should be removed.",
			Debt.FIVE_MINS)

	private val operatorSet = setOf("unaryPlus", "unaryMinus", "not", "inc", "dec", "plus", "minus", "times", "div",
			"mod", "rangeTo", "contains", "get", "set", "invoke", "plusAssign", "minusAssign", "timesAssign", "divAssign",
			"modAssign", "equals", "compareTo", "iterator", "getValue", "setValue")

	private var imports = mutableListOf<Pair<String, KtImportDirective>>()
	private val kotlinDocReferencesRegExp = Regex("\\[([^]]+)](?!\\[)")

	override fun visitFile(file: PsiFile?) {
		imports.clear()
		super.visitFile(file)
		imports.forEach { report(CodeSmell(issue, Entity.from(it.second))) }
	}

	override fun visitImportList(importList: KtImportList) {
		imports = importList.imports.filter { it.isValidImport }
				.filter { it.identifier()?.contains("*")?.not() == true }
				.filter { it.identifier() != null }
				.filter { !operatorSet.contains(it.identifier()) }
				.map { it.identifier()!! to it }
				.toMutableList()
		super.visitImportList(importList)
	}

	override fun visitReferenceExpression(expression: KtReferenceExpression) {
		if (expression.isPartOf(KtImportDirective::class)) return

		val reference = expression.text.trim('`')
		imports.find { it.first == reference }?.let {
			imports.remove(it)
		}

		super.visitReferenceExpression(expression)
	}

	override fun visitDeclaration(dcl: KtDeclaration) {
		val kdoc = dcl.docComment?.getDefaultSection()

		kdoc?.children
				?.filter { it is KDocTag }
				?.map { it.text }
				?.forEach { handleKDoc(it) }

		kdoc?.getContent()?.let {
			handleKDoc(it)
		}
		super.visitDeclaration(dcl)
	}

	private fun handleKDoc(content: String) {
		kotlinDocReferencesRegExp.findAll(content, 0)
				.map { it.groupValues[1] }
				.forEach { checkImports(it) }
	}

	private fun checkImports(it: String) {
		imports.removeIf { pair ->
			val identifier = pair.second.identifier()
			identifier != null && it.startsWith(identifier)
		}
	}

	private fun KtImportDirective.identifier() = this.importPath?.importedName?.identifier

}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.isAbstract
import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter

/**
 * @author Ivan Balaksha
 */
class UseDataClass(config: Config = Config.empty) : Rule(config) {

	override val issue: Issue = Issue("UseDataClass",
			Severity.Style,
			"Classes that do nothing but hold data should be replaced with a data class.",
			Debt.FIVE_MINS)

	private val defaultFunctionNames = hashSetOf("hashCode", "equals", "toString", "copy")

	override fun visitClass(klass: KtClass) {
		if (isIncorrectClassType(klass)) {
			return
		}
		if (klass.isClosedForExtension() && klass.doesNotExtendAnything()) {
			val declarations = klass.extractDeclarations()
			val properties = declarations.filterIsInstance<KtProperty>()
			val functions = declarations.filterIsInstance<KtNamedFunction>()

			val propertyParameters = klass.extractConstructorPropertyParameters()

			val containsFunctions = functions.none { !defaultFunctionNames.contains(it.name) }
			val containsPropertyOrPropertyParameters = properties.isNotEmpty() || propertyParameters.isNotEmpty()

			if (containsFunctions && containsPropertyOrPropertyParameters) {
				report(CodeSmell(issue, Entity.from(klass)))
			}
		}
		super.visitClass(klass)
	}

	private fun isIncorrectClassType(klass: KtClass) =
			klass.isData() || klass.isEnum() || klass.isAnnotation() || klass.isSealed()

	private fun KtClass.doesNotExtendAnything() = superTypeListEntries.isEmpty()

	private fun KtClass.isClosedForExtension() = !isAbstract() && !hasModifier(KtTokens.OPEN_KEYWORD)

	private fun KtClass.extractDeclarations(): List<KtDeclaration> = getBody()?.declarations ?: emptyList()

	private fun KtClass.extractConstructorPropertyParameters(): List<KtParameter> =
			getPrimaryConstructorParameterList()
					?.parameters
					?.filter { it.isPropertyParameter() } ?: emptyList()
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.isPublic
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassInitializer
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.kotlin.psi.KtSecondaryConstructor

class UtilityClassWithPublicConstructor(config: Config = Config.empty) : Rule(config) {

	override val issue: Issue = Issue(javaClass.simpleName,
			Severity.Style,
			"The class declaration is unnecessary because it only contains utility functions. " +
					"An object declaration should be used instead.",
			Debt.FIVE_MINS)

	override fun visitClass(klass: KtClass) {
		if (!klass.isInterface()) {
			val declarations = klass.getBody()?.declarations
			if (hasOnlyUtilityClassMembers(declarations) && hasPublicConstructor(klass)) {
				report(CodeSmell(issue, Entity.from(klass)))
			}
		}
		super.visitClass(klass)
	}

	private fun hasOnlyUtilityClassMembers(declarations: List<KtDeclaration>?): Boolean {
		return declarations?.all {
			it is KtSecondaryConstructor || it is KtClassInitializer || isCompanionObject(it)
		} == true
	}

	private fun isCompanionObject(declaration: KtDeclaration): Boolean {
		return (declaration as? KtObjectDeclaration)?.isCompanion() == true
	}

	private fun hasPublicConstructor(klass: KtClass): Boolean {
		val primaryConstructor = klass.primaryConstructor
		val secondaryConstructors = klass.secondaryConstructors
		if (primaryConstructor == null) {
			return hasPublicConstructor(secondaryConstructors)
		}
		return hasPublicConstructor(primaryConstructor)
	}

	private fun hasPublicConstructor(primaryConstructor: KtPrimaryConstructor) =
			primaryConstructor.isPublic() && primaryConstructor.valueParameters.isEmpty()

	private fun hasPublicConstructor(secondaryConstructors: List<KtSecondaryConstructor>) =
			secondaryConstructors.isEmpty() || secondaryConstructors.any { it.isPublic() }
}

package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Excludes
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtImportDirective

/**
 * @author Artur Bosch
 */
class WildcardImport(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			"Wildcard imports should be replaced with imports using fully qualified class names. " +
					"Wildcard imports can lead to naming conflicts. " +
					"A library update can introduce naming clashes with your classes which " +
					"results in compilation errors.",
			Debt.FIVE_MINS)

	private val excludedImports = Excludes(valueOrDefault(EXCLUDED_IMPORTS, ""))

	override fun visitImportDirective(importDirective: KtImportDirective) {
		val import = importDirective.importPath?.pathStr
		import?.let {
			if (!import.contains("*")) {
				return
			}

			if (excludedImports.contains(import)) {
				return
			}
			report(CodeSmell(issue, Entity.from(importDirective)))
		}
	}

	companion object {
		const val EXCLUDED_IMPORTS = "excludeImports"
	}
}

package io.gitlab.arturbosch.detekt.sample.extensions

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.sample.extensions.rules.TooManyFunctions
import io.gitlab.arturbosch.detekt.sample.extensions.rules.TooManyFunctionsTwo

/**
 * @author Artur Bosch
 */
class SampleProvider : RuleSetProvider {

	override val ruleSetId: String = "sample"

	override fun instance(config: Config): RuleSet {
		return RuleSet(ruleSetId, listOf(
				TooManyFunctions(),
				TooManyFunctionsTwo(config)
		))
	}
}

package io.gitlab.arturbosch.detekt.core.processors.util

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.psi.KtElement

/**
 * @author Artur Bosch
 */
inline fun <reified T : KtElement> KtElement.collectByType(): List<T> {
	val list = mutableListOf<T>()
	this.accept(object : DetektVisitor() {
		override fun visitKtElement(element: KtElement) {
			if (element is T) {
				list.add(element)
			}
			element.children.forEach { it.accept(this) }
		}
	})
	return list
}

package io.gitlab.arturbosch.detekt.core.processors.util

/**
 * @author Artur Bosch
 */
object LLOC {

	private val comments = arrayOf("//", "/*", "*/", "*")
	private val escapes = arrayOf("import", "package")

	fun analyze(lines: List<String>,
				isCommentMode: Boolean = false,
				isFullMode: Boolean = false): Int = Counter(lines, isCommentMode, isFullMode).run()

	private class Counter(private val lines: List<String>,
						  private val isCommentMode: Boolean = false,
						  private val isFullMode: Boolean = false) {

		private var counter = 0
		private var openedBrackets = 0
		private var closedBrackets = 0
		private var escape: Boolean = false

		internal fun run(): Int {
			for (line in lines) {

				val trimmed = line.trim()

				if (trimmed.isEmpty()) {
					continue
				}

				countOrEscapeComment(trimmed)
				if (escape) {
					continue
				}

				countOrEscapeAdditionalStatements(trimmed)
				if (escape) {
					continue
				}

				countStatementsAndDeclarations(trimmed)
			}

			return counter + if (openedBrackets - closedBrackets == 0) openedBrackets else -1
		}

		private fun countStatementsAndDeclarations(trimmed: String) {
			if (trimmed.contains(";")) {
				counter++
			}

			if (trimmed.contains("{")) {
				openedBrackets += frequency(trimmed, "{")
			} else if (trimmed.length != 1) {
				counter++
			}

			if (trimmed.contains("}")) {
				closedBrackets += frequency(trimmed, "}")
			}
		}

		private fun countOrEscapeAdditionalStatements(trimmed: String) {
			escape = isEscaped(trimmed, escapes)
			if (escape && isFullMode) {
				counter++
			}
		}

		private fun countOrEscapeComment(trimmed: String) {
			escape = isEscaped(trimmed, comments)
			if (isCommentMode && escape) {
				counter++
			}
		}

		private fun isEscaped(trimmed: String, rules: Array<String>): Boolean {
			return rules.any { trimmed.startsWith(it) }
		}

		private fun frequency(source: String, part: String): Int {

			if (source.isEmpty() || part.isEmpty()) {
				return 0
			}

			var count = 0
			var pos = source.indexOf(part, 0)
			while (pos != -1) {
				pos += part.length
				count++
				pos = source.indexOf(part, pos)
			}

			return count
		}

	}
}

package io.gitlab.arturbosch.detekt.rules.bugs.util

import io.gitlab.arturbosch.detekt.rules.collectByType
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtThrowExpression

internal fun KtClassOrObject.isImplementingIterator(): Boolean {
	val typeList = this.getSuperTypeList()?.entries
	val name = typeList?.firstOrNull()?.typeAsUserType?.referencedName
	return name == "Iterator"
}

internal fun KtClassOrObject.getMethod(name: String): KtNamedFunction? {
	val functions = this.declarations.filterIsInstance(KtNamedFunction::class.java)
	return functions.firstOrNull { it.name == name && it.valueParameters.isEmpty() }
}

internal fun KtNamedFunction.throwsNoSuchElementExceptionThrown(): Boolean {
	return this.bodyExpression
			?.collectByType<KtThrowExpression>()
			?.any { isNoSuchElementExpression(it) } ?: false
}

private fun isNoSuchElementExpression(expression: KtThrowExpression): Boolean {
	val calleeExpression = (expression.thrownExpression as? KtCallExpression)?.calleeExpression
	return calleeExpression?.text == "NoSuchElementException"
}

package io.gitlab.arturbosch.detekt.rules.bugs.util

import org.jetbrains.kotlin.psi.KtFunction

fun KtFunction.isEqualsMethod() =
	this.name == "equals" && hasCorrectEqualsParameter()

fun KtFunction.isHashCodeMethod() =
		this.name == "hashCode" && this.valueParameters.isEmpty()

fun KtFunction.hasCorrectEqualsParameter() =
		this.valueParameters.firstOrNull()?.typeReference?.text == "Any?"

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtClassOrObject

class ClassNaming(config: Config = Config.empty) : SubRule<KtClassOrObject>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val classPattern = Regex(valueOrDefault(CLASS_PATTERN, "^[A-Z$][a-zA-Z$]*$"))

	override fun apply(element: KtClassOrObject) {
		if (!element.identifierName().matches(classPattern)) {
			report(CodeSmell(
					issue.copy(description = "Class and Object names should match the pattern: $classPattern"),
					Entity.from(element)))
		}
	}

	companion object {
		const val CLASS_PATTERN = "classPattern"
	}
}

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtVariableDeclaration

class ConstantNaming(config: Config = Config.empty) : SubRule<KtVariableDeclaration>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val constantPattern = Regex(valueOrDefault(CONSTANT_PATTERN, "^([A-Z_]*|serialVersionUID)$"))

	override fun apply(element: KtVariableDeclaration) {
		if (doesntMatchPattern((element))) {
			report(CodeSmell(
					issue.copy(description = "Constant names should match the pattern: $constantPattern"),
					Entity.from(element)))
		}
	}

	fun doesntMatchPattern(element: KtVariableDeclaration) = !element.identifierName().matches(constantPattern)

	companion object {
		const val CONSTANT_PATTERN = "constantPattern"
	}
}

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtEnumEntry

class EnumNaming(config: Config = Config.empty) : SubRule<KtEnumEntry>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val enumEntryPattern = Regex(valueOrDefault(ENUM_PATTERN, "^[A-Z$][a-zA-Z_$]*$"))

	override fun apply(element: KtEnumEntry) {
		if (!element.identifierName().matches(enumEntryPattern)) {
			report(CodeSmell(
					issue.copy(description = "Enum entry names should match the pattern: $enumEntryPattern"),
					Entity.from(element)))
		}
	}

	companion object {
		const val ENUM_PATTERN = "enumEntryPattern"
	}
}

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Excludes
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtClassOrObject

class ForbiddenClassName(config: Config = Config.empty) : SubRule<KtClassOrObject>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val forbiddenNames = Excludes(valueOrDefault(FORBIDDEN_NAME, ""))

	override fun apply(element: KtClassOrObject) {
		val name = element.name ?: ""
		val forbiddenEntries = forbiddenNames.matches(name)

		if (forbiddenEntries.isNotEmpty()) {
			var description = "Class name $name is forbidden as it contains:"
			forbiddenEntries.forEach { description += " $it," }
			description.trimEnd { it.equals(",") }

			report(CodeSmell(issue.copy(description = description), Entity.from(element)))
		}
	}

	companion object {
		const val FORBIDDEN_NAME = "forbiddenName"
	}
}

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtNamedFunction

class FunctionMaxLength(config: Config = Config.empty) : SubRule<KtNamedFunction>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val maximumFunctionNameLength =
			valueOrDefault(MAXIMUM_FUNCTION_NAME_LENGTH, DEFAULT_MAXIMUM_FUNCTION_NAME_LENGTH)

	override fun apply(element: KtNamedFunction) {
		if (element.identifierName().length > maximumFunctionNameLength) {
			report(CodeSmell(
					issue.copy(description = "Function names should be at most $maximumFunctionNameLength characters long."),
					Entity.from(element)))
		}
	}

	companion object {
		const val MAXIMUM_FUNCTION_NAME_LENGTH = "maximumFunctionNameLength"
		private const val DEFAULT_MAXIMUM_FUNCTION_NAME_LENGTH = 30
	}
}

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtNamedFunction

class FunctionMinLength(config: Config = Config.empty) : SubRule<KtNamedFunction>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val minimumFunctionNameLength
			= valueOrDefault(MINIMUM_FUNCTION_NAME_LENGTH, DEFAULT_MINIMUM_FUNCTION_NAME_LENGTH)

	override fun apply(element: KtNamedFunction) {
		if (element.identifierName().length < minimumFunctionNameLength) {
			report(CodeSmell(
					issue.copy(description = "Function names should be at least $minimumFunctionNameLength characters long."),
					Entity.from(element)))
		}
	}

	companion object {
		const val MINIMUM_FUNCTION_NAME_LENGTH = "minimumFunctionNameLength"
		private const val DEFAULT_MINIMUM_FUNCTION_NAME_LENGTH = 3
	}
}

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtNamedFunction

class FunctionNaming(config: Config = Config.empty) : SubRule<KtNamedFunction>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val functionPattern = Regex(valueOrDefault(FUNCTION_PATTERN, "^([a-z$][a-zA-Z$0-9]*)|(`.*`)$"))

	override fun apply(element: KtNamedFunction) {
		if (!element.identifierName().matches(functionPattern)) {
			report(CodeSmell(
					issue.copy(description = "Function names should match the pattern: $functionPattern"),
					Entity.from(element)))
		}
	}

	companion object {
		const val FUNCTION_PATTERN = "functionPattern"
	}
}

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.MultiRule
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.rules.reportFindings
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtVariableDeclaration
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType

/**
 * @author Artur Bosch
 */
class NamingRules(config: Config = Config.empty) : MultiRule() {

	private val variableNamingRule = VariableNaming(config)
	private val variableMinNameLengthRule = VariableMinLength(config)
	private val variableMaxNameLengthRule = VariableMaxLength(config)
	private val constantNamingRule = ConstantNaming(config)
	private val packageNamingRule = PackageNaming(config)
	private val classOrObjectNamingRule = ClassNaming(config)
	private val enumEntryNamingRule = EnumNaming(config)
	private val functionNamingRule = FunctionNaming(config)
	private val functionMaxNameLengthRule = FunctionMaxLength(config)
	private val functionMinNameLengthRule = FunctionMinLength(config)
	private val forbiddenClassNameRule = ForbiddenClassName(config)

	override val rules: List<Rule> = listOf(
			variableNamingRule,
			variableMinNameLengthRule,
			variableMaxNameLengthRule,
			constantNamingRule,
			packageNamingRule,
			classOrObjectNamingRule,
			enumEntryNamingRule,
			functionNamingRule,
			functionMaxNameLengthRule,
			functionMinNameLengthRule,
			forbiddenClassNameRule
	)

	override fun postVisit(root: KtFile) {
		report(rules.flatMap { it.findings })
	}

	override fun visitPackageDirective(directive: KtPackageDirective) {
		super.visitPackageDirective(directive)
		directive.reportFindings(packageNamingRule)
	}

	override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
		if (declaration.nameAsSafeName.isSpecial) {
			return
		}
		declaration.nameIdentifier?.parent?.javaClass?.let {
			when (declaration) {
				is KtVariableDeclaration -> handleVariable(declaration)
				is KtNamedFunction -> handleFunction(declaration)
				is KtEnumEntry -> declaration.reportFindings(enumEntryNamingRule)
				is KtClassOrObject -> handleClassOrObject(declaration)
			}
		}
		super.visitNamedDeclaration(declaration)
	}

	private fun handleClassOrObject(declaration: KtClassOrObject) {
		declaration.reportFindings(classOrObjectNamingRule)
		declaration.reportFindings(forbiddenClassNameRule)
	}

	private fun handleFunction(declaration: KtNamedFunction) {
		declaration.reportFindings(functionNamingRule)
		declaration.reportFindings(functionMaxNameLengthRule)
		declaration.reportFindings(functionMinNameLengthRule)
	}

	private fun handleVariable(declaration: KtVariableDeclaration) {
		declaration.reportFindings(variableMaxNameLengthRule)
		declaration.reportFindings(variableMinNameLengthRule)

		if (declaration.hasConstModifier()) {
			declaration.reportFindings(constantNamingRule)
		} else if (declaration.withinObjectDeclaration() || declaration.isTopLevel()) {
			if (variableNamingRule.doesntMatchPattern(declaration)
					&& constantNamingRule.doesntMatchPattern(declaration)) {
				declaration.reportFindings(variableNamingRule)
			}
		} else {
			declaration.reportFindings(variableNamingRule)
		}
	}

	private fun KtVariableDeclaration.hasConstModifier(): Boolean {
		val modifierList = this.modifierList
		return modifierList != null && modifierList.hasModifier(KtTokens.CONST_KEYWORD)
	}

	private fun KtVariableDeclaration.withinObjectDeclaration(): Boolean {
		return this.getNonStrictParentOfType(KtObjectDeclaration::class.java) != null
	}

	private fun KtVariableDeclaration.isTopLevel(): Boolean = this is KtProperty && this.isTopLevel
}

internal fun KtNamedDeclaration.identifierName() = nameIdentifier?.text ?: SpecialNames.NO_NAME_PROVIDED.asString()

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtPackageDirective

class PackageNaming(config: Config = Config.empty) : SubRule<KtPackageDirective>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val packagePattern = Regex(valueOrDefault(PACKAGE_PATTERN, "^[a-z]+(\\.[a-z][a-z0-9]*)*$"))

	override fun apply(element: KtPackageDirective) {
		val name = element.qualifiedName
		if (name.isNotEmpty() && !name.matches(packagePattern)) {
			report(CodeSmell(
					issue.copy(description = "Package name should match the pattern: $packagePattern"),
					Entity.from(element)))
		}
	}

	companion object {
		const val PACKAGE_PATTERN = "packagePattern"
	}
}

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtVariableDeclaration

class VariableMaxLength(config: Config = Config.empty) : SubRule<KtVariableDeclaration>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val maximumVariableNameLength
			= valueOrDefault(MAXIMUM_VARIABLE_NAME_LENGTH, DEFAULT_MAXIMUM_VARIABLE_NAME_LENGTH)

	override fun apply(element: KtVariableDeclaration) {
		if (element.identifierName().length > maximumVariableNameLength) {
			report(CodeSmell(
					issue.copy(description = "Variable names should be at most $maximumVariableNameLength characters long."),
					Entity.from(element)))
		}
	}

	companion object {
		const val MAXIMUM_VARIABLE_NAME_LENGTH = "maximumVariableNameLength"
		private const val DEFAULT_MAXIMUM_VARIABLE_NAME_LENGTH = 30
	}
}

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtVariableDeclaration

class VariableMinLength(config: Config = Config.empty) : SubRule<KtVariableDeclaration>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val minimumVariableNameLength
			= valueOrDefault(MINIMUM_VARIABLE_NAME_LENGTH, DEFAULT_MINIMUM_VARIABLE_NAME_LENGTH)

	override fun apply(element: KtVariableDeclaration) {
		if (element.identifierName().length < minimumVariableNameLength) {
			report(CodeSmell(
					issue.copy(description = "Variable names should be at least $minimumVariableNameLength characters long."),
					Entity.from(element)))
		}
	}

	companion object {
		const val MINIMUM_VARIABLE_NAME_LENGTH = "minimumVariableNameLength"
		private const val DEFAULT_MINIMUM_VARIABLE_NAME_LENGTH = 3
	}
}

package io.gitlab.arturbosch.detekt.rules.style.naming

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.SubRule
import org.jetbrains.kotlin.psi.KtVariableDeclaration

class VariableNaming(config: Config = Config.empty) : SubRule<KtVariableDeclaration>(config) {
	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			debt = Debt.FIVE_MINS)
	private val variablePattern = Regex(valueOrDefault(VARIABLE_PATTERN, "^(_)?[a-z$][a-zA-Z$0-9]*$"))

	override fun apply(element: KtVariableDeclaration) {
		if (!element.identifierName().matches(variablePattern)) {
			report(CodeSmell(
					issue.copy(description = "Variable names should match the pattern: $variablePattern"),
					Entity.from(element)))
		}
	}

	fun doesntMatchPattern(element: KtVariableDeclaration) = !element.identifierName().matches(variablePattern)

	companion object {
		const val VARIABLE_PATTERN = "variablePattern"
	}
}

package io.gitlab.arturbosch.detekt.rules.style.optional

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.KtTryExpression
import org.jetbrains.kotlin.psi.KtWhenExpression

/**
 * @author Artur Bosch
 */
class ConditionalPathVisitor(private val block: (KtReturnExpression) -> Unit) : DetektVisitor() {

	override fun visitIfExpression(expression: KtIfExpression) {
		if (expression.isLastStatement()) {
			expression.then?.checkIfReturnStatement()
			expression.`else`?.checkIfReturnStatement()
		}
		super.visitIfExpression(expression)
	}

	override fun visitTryExpression(expression: KtTryExpression) {
		if (expression.isLastStatement()) {
			expression.tryBlock.checkIfReturnStatement()
			expression.catchClauses.forEach {
				it.catchBody?.checkIfReturnStatement()
			}
		}
		super.visitTryExpression(expression)
	}

	override fun visitWhenExpression(expression: KtWhenExpression) {
		if (expression.isLastStatement()) {
			expression.entries.forEach {
				it.expression?.checkIfReturnStatement()
			}
		}
		super.visitWhenExpression(expression)
	}

	private fun KtExpression.isLastStatement(): Boolean {
		val parent = parent
		parent is KtBlockExpression
		return when (parent) {
			is KtReturnExpression, is KtProperty -> true
			is KtDeclarationWithBody -> parent.bodyExpression == this
			is KtBlockExpression -> parent.statements.lastOrNull()?.let { it == this } == true
			else -> false
		}
	}

	private fun KtExpression.checkIfReturnStatement() {
		if (this is KtReturnExpression && this.returnedExpression != null) block(this)
		else if (this is KtBlockExpression) {
			val last = this.statements.lastOrNull()
			if (last is KtReturnExpression && last.returnedExpression != null) block(last)
		}
	}

}

package io.gitlab.arturbosch.detekt.rules.style.optional

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtProperty

/**
 * @author Artur Bosch
 */
class OptionalReturnKeyword(config: Config) : Rule(config) {

	override val issue = Issue(javaClass.simpleName,
			Severity.Style,
			"The last expressions inside conditional expressions are always returned. " +
					"This makes the return keyword unnecessary and it can be safely removed.",
			Debt.TEN_MINS)

	private val visitor = ConditionalPathVisitor {
		report(CodeSmell(issue, Entity.from(it)))
	}

	override fun visitDeclaration(dcl: KtDeclaration) {
		if (dcl is KtProperty) {
			dcl.delegateExpressionOrInitializer?.accept(visitor)
		}
		super.visitDeclaration(dcl)
	}
}

package io.gitlab.arturbosch.detekt.rules.style.optional

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtNamedFunction

/**
 * @author Artur Bosch
 */
class OptionalUnit(config: Config = Config.empty) : Rule(config) {

	override val issue = Issue(
			javaClass.simpleName,
			Severity.Style,
			"Return type of 'Unit' is unnecessary and can be safely removed.",
			Debt.FIVE_MINS)

	override fun visitNamedFunction(function: KtNamedFunction) {
		if (function.funKeyword == null) return
		val colon = function.colon
		if (function.hasDeclaredReturnType() && colon != null) {
			val typeReference = function.typeReference
			typeReference?.typeElement?.text?.let {
				if (it == "Unit") {
					report(CodeSmell(issue, Entity.from(typeReference)))
				}
			}
		}
		super.visitNamedFunction(function)
	}
}

package io.gitlab.arturbosch.detekt.sample.extensions.processors

import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.FileProcessListener
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile

/**
 * @author Artur Bosch
 */
class QualifiedNameProcessor : FileProcessListener {

	override fun onProcess(file: KtFile) {
		val packageName = file.packageFqName.asString()
		val nameVisitor = ClassNameVisitor()
		file.accept(nameVisitor)
		val fqNames = nameVisitor.names.mapTo(HashSet()) { "$packageName.$it" }
		file.putUserData(FQ_NAMES_KEY, fqNames)
	}

	override fun onFinish(files: List<KtFile>, result: Detektion) {
		val fqNames = files.mapNotNull { it.getUserData(FQ_NAMES_KEY) }
				.flatMapTo(HashSet()) { it }
		result.addData(FQ_NAMES_KEY, fqNames)
	}

	class ClassNameVisitor : DetektVisitor() {

		val names = mutableSetOf<String>()

		override fun visitClassOrObject(classOrObject: KtClassOrObject) {
			names.add(classOrObject.nameAsSafeName.asString())
		}
	}
}

val FQ_NAMES_KEY: Key<Set<String>> = Key.create<Set<String>>("FQNames")

package io.gitlab.arturbosch.detekt.sample.extensions.reports

import io.gitlab.arturbosch.detekt.api.ConsoleReport
import io.gitlab.arturbosch.detekt.api.Detektion

/**
 * @author Artur Bosch
 */
class QualifiedNamesConsoleReport : ConsoleReport() {

	override fun render(detektion: Detektion): String? {
		return qualifiedNamesReport(detektion)
	}
}

package io.gitlab.arturbosch.detekt.sample.extensions.reports

import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.OutputReport

/**
 * @author Artur Bosch
 */
class QualifiedNamesOutputReport : OutputReport() {

	override var fileName: String = "fqNames"
	override val ending: String = "txt"

	override fun render(detektion: Detektion): String? {
		return qualifiedNamesReport(detektion)
	}
}

package io.gitlab.arturbosch.detekt.sample.extensions.reports

import io.gitlab.arturbosch.detekt.api.Detektion
import org.jetbrains.kotlin.com.intellij.openapi.util.Key

@Suppress("UNCHECKED_CAST", "DEPRECATION")
fun qualifiedNamesReport(detektion: Detektion): String? {
	// referencing the original key 'FQ_NAMES_KEY' does not retrieve the stored values
	// using the deprecated method seems to work for unknown reasons
	val key = Key.findKeyByName("FQNames") as Key<Set<String>>
	val fqNames = detektion.getData(key)
	println("fqNames: " + fqNames)
	if (fqNames == null || fqNames.isEmpty()) return null

	return with(StringBuilder()) {
		fqNames.forEach {
			append(it + "\n")
		}
		toString()
	}
}

package io.gitlab.arturbosch.detekt.sample.extensions.rules

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
import org.jetbrains.kotlin.psi.KtNamedFunction

/**
 * @author Artur Bosch
 */
class TooManyFunctions : Rule() {

	override val issue = Issue(javaClass.simpleName, Severity.CodeSmell, "")

	private var amount: Int = 0

	override fun visitFile(file: PsiFile) {
		super.visitFile(file)
		if (amount > THRESHOLD) {
			report(CodeSmell(issue, Entity.from(file)))
		}
	}

	override fun visitNamedFunction(function: KtNamedFunction) {
		amount++
	}

}

const val THRESHOLD = 10

package io.gitlab.arturbosch.detekt.sample.extensions.rules

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Metric
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
import org.jetbrains.kotlin.psi.KtNamedFunction

/**
 * @author Artur Bosch
 */
class TooManyFunctionsTwo(config: Config) : Rule(config) {

	override val issue = Issue(javaClass.simpleName, Severity.Maintainability,
			"Too many functions can make the maintainability of a file more costly")

	private var amount: Int = 0

	override fun visitFile(file: PsiFile) {
		super.visitFile(file)
		if (amount > THRESHOLD) {
			report(CodeSmell(issue,
					entity = Entity.from(file),
					metrics = listOf(Metric(type = "SIZE", value = amount, threshold = THRESHOLD)),
					references = listOf())
			)
		}
	}

	override fun visitNamedFunction(function: KtNamedFunction) {
		amount++
	}

}