Comma on newline preceded by // comment will move code into comment

diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/SpacingAroundCommaRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/SpacingAroundCommaRule.kt
index 5a947817..17485ea7 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/SpacingAroundCommaRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/SpacingAroundCommaRule.kt
@@ -2,10 +2,14 @@ package com.pinterest.ktlint.ruleset.standard

 import com.pinterest.ktlint.core.Rule
 import com.pinterest.ktlint.core.ast.isPartOfString
+import com.pinterest.ktlint.core.ast.isWhiteSpaceWithNewline
 import com.pinterest.ktlint.core.ast.nextLeaf
+import com.pinterest.ktlint.core.ast.nextSibling
+import com.pinterest.ktlint.core.ast.prevCodeLeaf
 import com.pinterest.ktlint.core.ast.prevLeaf
 import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.psi.PsiComment
 import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
 import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement

@@ -21,7 +25,20 @@ class SpacingAroundCommaRule : Rule("comma-spacing") {
             if (prevLeaf is PsiWhiteSpace) {
                 emit(prevLeaf.startOffset, "Unexpected spacing before \\"${node.text}\\"", true)
                 if (autoCorrect) {
-                    prevLeaf.node.treeParent.removeChild(prevLeaf.node)
+                    val isPrecededByComment = prevLeaf.prevLeaf { it !is PsiWhiteSpace } is PsiComment
+                    if (isPrecededByComment && prevLeaf.isWhiteSpaceWithNewline()) {
+                        // If comma is on new line and preceded by a comment, it should be moved before this comment
+                        // https://github.com/pinterest/ktlint/issues/367
+                        val previousStatement = node.prevCodeLeaf()!!
+                        previousStatement.treeParent.addChild(node.clone(), previousStatement.nextSibling { true })
+                        val nextLeaf = node.nextLeaf()
+                        if (nextLeaf is PsiWhiteSpace) {
+                            nextLeaf.treeParent.removeChild(nextLeaf)
+                        }
+                        node.treeParent.removeChild(node)
+                    } else {
+                        prevLeaf.treeParent.removeChild(prevLeaf)
+                    }
                 }
             }
             if (node.nextLeaf() !is PsiWhiteSpace) {
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/SpacingAroundCommaRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/SpacingAroundCommaRuleTest.kt
index 4a8db107..ee148da6 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/SpacingAroundCommaRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/SpacingAroundCommaRuleTest.kt
@@ -47,6 +47,8 @@ class SpacingAroundCommaRuleTest {
     fun testFormat() {
         assertThat(SpacingAroundCommaRule().format("fun main() { x(1,3); x(1, 3) }"))
             .isEqualTo("fun main() { x(1, 3); x(1, 3) }")
+        assertThat(SpacingAroundCommaRule().format("fun main() { x(1 /* comment */ , 3); x(1 /* comment */, 3) }"))
+            .isEqualTo("fun main() { x(1 /* comment */, 3); x(1 /* comment */, 3) }")
         assertThat(
             SpacingAroundCommaRule().format(
                 """
@@ -69,5 +71,28 @@ class SpacingAroundCommaRuleTest {
             ) = Unit
             """.trimIndent()
         )
+        assertThat(
+            SpacingAroundCommaRule().format(
+                """
+                fun fn(
+                    arg1: Int ,
+                    arg2: Int // some comment
+                    , arg3: Int
+                    // other comment
+                    , arg4: Int
+                ) = Unit
+                """.trimIndent()
+            )
+        ).isEqualTo(
+            """
+            fun fn(
+                arg1: Int,
+                arg2: Int, // some comment
+                arg3: Int,
+                // other comment
+                arg4: Int
+            ) = Unit
+            """.trimIndent()
+        )
     }
 }

Comma on newline preceded by // comment will move code into comment
I got a when phrase. The case is

before
```java
when (type) {
            IHomeLivingConstant.LIVING_TYPE_GUESS_YOU_LIKE //a
                , IHomeLivingConstant.Live_MODULE_CONTENT_CODE // b
                , IHomeLivingConstant.LIVE_MODULE_AUTO_PLAY  //c
-> {
    // do something
}
```

after formatting, I turn out
```java
when (type) {
            IHomeLivingConstant.LIVING_TYPE_GUESS_YOU_LIKE //a, IHomeLivingConstant.Live_MODULE_CONTENT_CODE // b, IHomeLivingConstant.LIVE_MODULE_AUTO_PLAY  //c
-> {
    //do something
}

```

This lead to bugs cause it will not invoke compile error, but is wrong logically.  And it is hard to find the bug in the hundreds of formatted files.

package yourpkgname

import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider

class CustomRuleSetProvider : RuleSetProvider {

    override fun get(): RuleSet = RuleSet("custom", NoVarRule())
}

package yourpkgname

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.VAR_KEYWORD
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

class NoVarRule : Rule("no-var") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.elementType == VAR_KEYWORD) {
            emit(node.startOffset, "Unexpected var, use val instead", false)
        }
    }
}

package com.pinterest.ktlint

import com.github.shyiko.klob.Glob
import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.LintError
import com.pinterest.ktlint.core.ParseException
import com.pinterest.ktlint.core.Reporter
import com.pinterest.ktlint.core.ReporterProvider
import com.pinterest.ktlint.core.RuleExecutionException
import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider
import com.pinterest.ktlint.internal.EditorConfig
import com.pinterest.ktlint.internal.IntellijIDEAIntegration
import com.pinterest.ktlint.internal.MavenDependencyResolver
import com.pinterest.ktlint.test.DumpAST
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.io.PrintStream
import java.math.BigInteger
import java.net.URLDecoder
import java.nio.file.Path
import java.nio.file.Paths
import java.security.MessageDigest
import java.util.ArrayList
import java.util.Arrays
import java.util.LinkedHashMap
import java.util.NoSuchElementException
import java.util.Scanner
import java.util.ServiceLoader
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.Callable
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.jar.Manifest
import kotlin.concurrent.thread
import kotlin.system.exitProcess
import org.eclipse.aether.RepositoryException
import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.repository.RepositoryPolicy
import org.eclipse.aether.repository.RepositoryPolicy.CHECKSUM_POLICY_IGNORE
import org.eclipse.aether.repository.RepositoryPolicy.UPDATE_POLICY_NEVER
import org.jetbrains.kotlin.backend.common.onlyIf
import picocli.CommandLine
import picocli.CommandLine.Command
import picocli.CommandLine.Option
import picocli.CommandLine.Parameters

@Command(
    headerHeading = """An anti-bikeshedding Kotlin linter with built-in formatter
(https://github.com/shyiko/ktlint).

Usage:
  ktlint <flags> [patterns]
  java -jar ktlint <flags> [patterns]

Examples:
  # check the style of all Kotlin files inside the current dir (recursively)
  # (hidden folders will be skipped)
  ktlint

  # check only certain locations (prepend ! to negate the pattern)
  ktlint "src/**/*.kt" "!src/**/*Test.kt"

  # auto-correct style violations
  ktlint -F "src/**/*.kt"

  # custom reporter
  ktlint --reporter=plain?group_by_file
  # multiple reporters can be specified like this
  ktlint --reporter=plain \
    --reporter=checkstyle,output=ktlint-checkstyle-report.xml
  # 3rd-party reporter
  ktlint --reporter=html,artifact=com.github.user:repo:master-SNAPSHOT

Flags:""",
    synopsisHeading = "",
    customSynopsis = arrayOf(""),
    sortOptions = false
)
object Main {

    private val DEPRECATED_FLAGS = mapOf(
        "--ruleset-repository" to
            "--repository",
        "--reporter-repository" to
            "--repository",
        "--ruleset-update" to
            "--repository-update",
        "--reporter-update" to
            "--repository-update"
    )
    @Option(names = arrayOf("--android", "-a"), description = arrayOf("Turn on Android Kotlin Style Guide compatibility"))
    private var android: Boolean = false

    // todo: make it a command in 1.0.0 (it's too late now as we might interfere with valid "lint" patterns)
    @Option(names = arrayOf("--apply-to-idea"), description = arrayOf("Update Intellij IDEA settings (global)"))
    private var apply: Boolean = false

    // todo: make it a command in 1.0.0 (it's too late now as we might interfere with valid "lint" patterns)
    @Option(names = arrayOf("--apply-to-idea-project"), description = arrayOf("Update Intellij IDEA project settings"))
    private var applyToProject: Boolean = false

    @Option(names = arrayOf("--color"), description = arrayOf("Make output colorful"))
    private var color: Boolean = false

    @Option(names = arrayOf("--debug"), description = arrayOf("Turn on debug output"))
    private var debug: Boolean = false

    // todo: this should have been a command, not a flag (consider changing in 1.0.0)
    @Option(names = arrayOf("--format", "-F"), description = arrayOf("Fix any deviations from the code style"))
    private var format: Boolean = false

    @Option(
        names = arrayOf("--install-git-pre-commit-hook"),
        description = arrayOf(
            "Install git hook to automatically check files for style violations on commit"
        )
    )
    private var installGitPreCommitHook: Boolean = false

    @Option(
        names = arrayOf("--install-git-pre-push-hook"),
        description = arrayOf(
            "Install git hook to automatically check files for style violations before push"
        )
    )
    private var installGitPrePushHook: Boolean = false

    @Option(
        names = arrayOf("--limit"),
        description = arrayOf(
            "Maximum number of errors to show (default: show all)"
        )
    )
    private var limit: Int = -1
        get() = if (field < 0) Int.MAX_VALUE else field

    @Option(
        names = arrayOf("--print-ast"),
        description = arrayOf(
            "Print AST (useful when writing/debugging rules)"
        )
    )
    private var printAST: Boolean = false

    @Option(
        names = arrayOf("--relative"),
        description = arrayOf(
            "Print files relative to the working directory " +
                "(e.g. dir/file.kt instead of /home/user/project/dir/file.kt)"
        )
    )
    private var relative: Boolean = false

    @Option(
        names = arrayOf("--reporter"),
        description = arrayOf(
            "A reporter to use (built-in: plain (default), plain?group_by_file, json, checkstyle). " +
                "To use a third-party reporter specify either a path to a JAR file on the filesystem or a" +
                "<groupId>:<artifactId>:<version> triple pointing to a remote artifact (in which case ktlint will first " +
                "check local cache (~/.m2/repository) and then, if not found, attempt downloading it from " +
                "Maven Central/JCenter/JitPack/user-provided repository)\n" +
                "e.g. \"html,artifact=com.github.username:ktlint-reporter-html:master-SNAPSHOT\""
        )
    )
    private var reporters = ArrayList<String>()

    @Option(
        names = arrayOf("--repository"),
        description = arrayOf(
            "An additional Maven repository (Maven Central/JCenter/JitPack are active by default) " +
                "(value format: <id>=<url>)"
        )
    )
    private var repositories = ArrayList<String>()
    @Option(names = arrayOf("--ruleset-repository", "--reporter-repository"), hidden = true)
    private var repositoriesDeprecated = ArrayList<String>()

    @Option(
        names = arrayOf("--repository-update", "-U"),
        description = arrayOf(
            "Check remote repositories for updated snapshots"
        )
    )
    private var forceUpdate: Boolean? = null
    @Option(names = arrayOf("--ruleset-update", "--reporter-update"), hidden = true)
    private var forceUpdateDeprecated: Boolean? = null

    @Option(
        names = arrayOf("--ruleset", "-R"),
        description = arrayOf(
            "A path to a JAR file containing additional ruleset(s) or a " +
                "<groupId>:<artifactId>:<version> triple pointing to a remote artifact (in which case ktlint will first " +
                "check local cache (~/.m2/repository) and then, if not found, attempt downloading it from " +
                "Maven Central/JCenter/JitPack/user-provided repository)"
        )
    )
    private var rulesets = ArrayList<String>()

    @Option(names = arrayOf("--skip-classpath-check"), description = arrayOf("Do not check classpath for potential conflicts"))
    private var skipClasspathCheck: Boolean = false

    @Option(names = arrayOf("--stdin"), description = arrayOf("Read file from stdin"))
    private var stdin: Boolean = false

    @Option(names = arrayOf("--verbose", "-v"), description = arrayOf("Show error codes"))
    private var verbose: Boolean = false

    @Option(names = arrayOf("--version"), description = arrayOf("Print version information"))
    private var version: Boolean = false

    @Option(names = arrayOf("--help", "-h"), help = true, hidden = true)
    private var help: Boolean = false

    @Option(names = arrayOf("-y"), hidden = true)
    private var forceApply: Boolean = false

    @Option(names = arrayOf("--editorconfig"), description = arrayOf("Path to .editorconfig"))
    private var editorConfigPath: String? = null

    @Option(
        names = arrayOf("--experimental"),
        description = arrayOf(
            "Enabled experimental rules (ktlint-ruleset-experimental)"
        )
    )
    private var experimental: Boolean = false

    @Parameters(hidden = true)
    private var patterns = ArrayList<String>()

    private val workDir = File(".").canonicalPath
    private fun File.location() = if (relative) this.toRelativeString(File(workDir)) else this.path

    private fun usage() =
        ByteArrayOutputStream()
            .also { CommandLine.usage(this, PrintStream(it), CommandLine.Help.Ansi.OFF) }
            .toString()
            .replace(" ".repeat(32), " ".repeat(30))

    private fun parseCmdLine(args: Array<String>) {
        try {
            CommandLine.populateCommand(this, *args)
            repositories.addAll(repositoriesDeprecated)
            if (forceUpdateDeprecated != null && forceUpdate == null) {
                forceUpdate = forceUpdateDeprecated
            }
        } catch (e: Exception) {
            System.err.println("Error: ${e.message}\n\n${usage()}")
            exitProcess(1)
        }
        if (help) {
            println(usage())
            exitProcess(0)
        }
        args.forEach { arg ->
            if (arg.startsWith("--") && arg.contains("=")) {
                val flag = arg.substringBefore("=")
                val alt = DEPRECATED_FLAGS[flag]
                if (alt != null) {
                    System.err.println("$flag flag is deprecated and will be removed in 1.0.0 (use $alt instead)")
                }
            }
        }
    }

    @JvmStatic
    fun main(args: Array<String>) {
        parseCmdLine(args)
        if (version) {
            println(getImplementationVersion())
            exitProcess(0)
        }
        if (installGitPreCommitHook) {
            installGitPreCommitHook()
            if (!apply) {
                exitProcess(0)
            }
        }
        if (installGitPrePushHook) {
            installGitPrePushHook()
            if (!apply) {
                exitProcess(0)
            }
        }
        if (apply || applyToProject) {
            applyToIDEA()
            exitProcess(0)
        }
        if (printAST) {
            printAST()
            exitProcess(0)
        }
        val start = System.currentTimeMillis()
        // load 3rd party ruleset(s) (if any)
        val dependencyResolver = lazy(LazyThreadSafetyMode.NONE) { buildDependencyResolver() }
        if (!rulesets.isEmpty()) {
            loadJARs(dependencyResolver, rulesets)
        }
        // standard should go first
        val ruleSetProviders =
            ServiceLoader.load(RuleSetProvider::class.java)
                .map { it.get().id to it }
                .filter { (id) -> experimental || id != "experimental" }
                .sortedBy { if (it.first == "standard") "\u0000${it.first}" else it.first }
        if (debug) {
            ruleSetProviders.forEach { System.err.println("[DEBUG] Discovered ruleset \"${it.first}\"") }
        }
        val tripped = AtomicBoolean()
        val reporter = loadReporter(dependencyResolver) { tripped.get() }
        val resolveUserData = userDataResolver()
        data class LintErrorWithCorrectionInfo(val err: LintError, val corrected: Boolean)
        fun process(fileName: String, fileContent: String): List<LintErrorWithCorrectionInfo> {
            if (debug) {
                System.err.println("[DEBUG] Checking ${if (fileName != "<text>") File(fileName).location() else fileName}")
            }
            val result = ArrayList<LintErrorWithCorrectionInfo>()
            val userData = resolveUserData(fileName)
            if (format) {
                val formattedFileContent = try {
                    format(fileName, fileContent, ruleSetProviders.map { it.second.get() }, userData) { err, corrected ->
                        if (!corrected) {
                            result.add(LintErrorWithCorrectionInfo(err, corrected))
                            tripped.set(true)
                        }
                    }
                } catch (e: Exception) {
                    result.add(LintErrorWithCorrectionInfo(e.toLintError(), false))
                    tripped.set(true)
                    fileContent // making sure `cat file | ktlint --stdint > file` is (relatively) safe
                }
                if (stdin) {
                    print(formattedFileContent)
                } else {
                    if (fileContent !== formattedFileContent) {
                        File(fileName).writeText(formattedFileContent, charset("UTF-8"))
                    }
                }
            } else {
                try {
                    lint(fileName, fileContent, ruleSetProviders.map { it.second.get() }, userData) { err ->
                        result.add(LintErrorWithCorrectionInfo(err, false))
                        tripped.set(true)
                    }
                } catch (e: Exception) {
                    result.add(LintErrorWithCorrectionInfo(e.toLintError(), false))
                    tripped.set(true)
                }
            }
            return result
        }
        val (fileNumber, errorNumber) = Pair(AtomicInteger(), AtomicInteger())
        fun report(fileName: String, errList: List<LintErrorWithCorrectionInfo>) {
            fileNumber.incrementAndGet()
            val errListLimit = minOf(errList.size, maxOf(limit - errorNumber.get(), 0))
            errorNumber.addAndGet(errListLimit)
            reporter.before(fileName)
            errList.head(errListLimit).forEach { (err, corrected) ->
                reporter.onLintError(
                    fileName,
                    if (!err.canBeAutoCorrected) err.copy(detail = err.detail + " (cannot be auto-corrected)") else err,
                    corrected
                )
            }
            reporter.after(fileName)
        }
        reporter.beforeAll()
        if (stdin) {
            report("<text>", process("<text>", String(System.`in`.readBytes())))
        } else {
            fileSequence()
                .takeWhile { errorNumber.get() < limit }
                .map { file -> Callable { file to process(file.path, file.readText()) } }
                .parallel({ (file, errList) -> report(file.location(), errList) })
        }
        reporter.afterAll()
        if (debug) {
            System.err.println(
                "[DEBUG] ${
                System.currentTimeMillis() - start
                }ms / $fileNumber file(s) / $errorNumber error(s)"
            )
        }
        if (tripped.get()) {
            exitProcess(1)
        }
    }

    private fun userDataResolver(): (String) -> Map<String, String> {
        val cliUserData = mapOf("android" to android.toString())
        if (editorConfigPath != null) {
            val userData = (
                EditorConfig.of(File(editorConfigPath).canonicalPath)
                    ?.onlyIf({ debug }) { printEditorConfigChain(it) }
                    ?: emptyMap<String, String>()
                ) + cliUserData
            return fun (fileName: String) = userData + ("file_path" to fileName)
        }
        val workdirUserData = lazy {
            (
                EditorConfig.of(workDir)
                    ?.onlyIf({ debug }) { printEditorConfigChain(it) }
                    ?: emptyMap<String, String>()
                ) + cliUserData
        }
        val editorConfig = EditorConfig.cached()
        val editorConfigSet = ConcurrentHashMap<Path, Boolean>()
        return fun (fileName: String): Map<String, String> {
            if (fileName == "<text>") {
                return workdirUserData.value
            }
            return (
                editorConfig.of(Paths.get(fileName).parent)
                    ?.onlyIf({ debug }) {
                        printEditorConfigChain(it) {
                            editorConfigSet.put(it.path, true) != true
                        }
                    }
                    ?: emptyMap<String, String>()
                ) + cliUserData + ("file_path" to fileName)
        }
    }

    private fun printEditorConfigChain(ec: EditorConfig, predicate: (EditorConfig) -> Boolean = { true }) {
        for (lec in generateSequence(ec) { it.parent }.takeWhile(predicate)) {
            System.err.println(
                "[DEBUG] Discovered .editorconfig (${lec.path.parent.toFile().location()})" +
                    " {${lec.entries.joinToString(", ")}}"
            )
        }
    }

    private fun getImplementationVersion() =
        javaClass.`package`.implementationVersion
            // JDK 9 regression workaround (https://bugs.openjdk.java.net/browse/JDK-8190987, fixed in JDK 10)
            // (note that version reported by the fallback might not be null if META-INF/MANIFEST.MF is
            // loaded from another JAR on the classpath (e.g. if META-INF/MANIFEST.MF wasn't created as part of the build))
            ?: javaClass.getResourceAsStream("/META-INF/MANIFEST.MF")
                ?.let { stream ->
                    Manifest(stream).mainAttributes.getValue("Implementation-Version")
                }

    private fun loadReporter(dependencyResolver: Lazy<MavenDependencyResolver>, tripped: () -> Boolean): Reporter {
        data class ReporterTemplate(val id: String, val artifact: String?, val config: Map<String, String>, var output: String?)
        val tpls = (if (reporters.isEmpty()) listOf("plain") else reporters)
            .map { reporter ->
                val split = reporter.split(",")
                val (reporterId, rawReporterConfig) = split[0].split("?", limit = 2) + listOf("")
                ReporterTemplate(
                    reporterId,
                    split.lastOrNull { it.startsWith("artifact=") }?.let { it.split("=")[1] },
                    mapOf("verbose" to verbose.toString(), "color" to color.toString()) + parseQuery(rawReporterConfig),
                    split.lastOrNull { it.startsWith("output=") }?.let { it.split("=")[1] }
                )
            }
            .distinct()
        val reporterLoader = ServiceLoader.load(ReporterProvider::class.java)
        val reporterProviderById = reporterLoader.associate { it.id to it }.let { map ->
            val missingReporters = tpls.filter { !map.containsKey(it.id) }.mapNotNull { it.artifact }.distinct()
            if (!missingReporters.isEmpty()) {
                loadJARs(dependencyResolver, missingReporters)
                reporterLoader.reload()
                reporterLoader.associate { it.id to it }
            } else map
        }
        if (debug) {
            reporterProviderById.forEach { (id) -> System.err.println("[DEBUG] Discovered reporter \"$id\"") }
        }
        fun ReporterTemplate.toReporter(): Reporter {
            val reporterProvider = reporterProviderById[id]
            if (reporterProvider == null) {
                System.err.println(
                    "Error: reporter \"$id\" wasn't found (available: ${
                    reporterProviderById.keys.sorted().joinToString(",")
                    })"
                )
                exitProcess(1)
            }
            if (debug) {
                System.err.println(
                    "[DEBUG] Initializing \"$id\" reporter with $config" +
                        (output?.let { ", output=$it" } ?: "")
                )
            }
            val stream = if (output != null) {
                File(output).parentFile?.mkdirsOrFail(); PrintStream(output, "UTF-8")
            } else if (stdin) System.err else System.out
            return reporterProvider.get(stream, config)
                .let { reporter ->
                    if (output != null) {
                        object : Reporter by reporter {
                            override fun afterAll() {
                                reporter.afterAll()
                                stream.close()
                                if (tripped()) {
                                    System.err.println("\"$id\" report written to ${File(output).absoluteFile.location()}")
                                }
                            }
                        }
                    } else {
                        reporter
                    }
                }
        }
        return Reporter.from(*tpls.map { it.toReporter() }.toTypedArray())
    }

    private fun Exception.toLintError(): LintError = this.let { e ->
        when (e) {
            is ParseException ->
                LintError(
                    e.line, e.col, "",
                    "Not a valid Kotlin file (${e.message?.toLowerCase()})"
                )
            is RuleExecutionException -> {
                if (debug) {
                    System.err.println("[DEBUG] Internal Error (${e.ruleId})")
                    e.printStackTrace(System.err)
                }
                LintError(
                    e.line, e.col, "",
                    "Internal Error (${e.ruleId}). " +
                        "Please create a ticket at https://github.com/shyiko/ktlint/issue " +
                        "(if possible, provide the source code that triggered an error)"
                )
            }
            else -> throw e
        }
    }

    private fun printAST() {
        fun process(fileName: String, fileContent: String) {
            if (debug) {
                System.err.println("[DEBUG] Analyzing ${if (fileName != "<text>") File(fileName).location() else fileName}")
            }
            try {
                lint(fileName, fileContent, listOf(RuleSet("debug", DumpAST(System.out, color))), emptyMap()) {}
            } catch (e: Exception) {
                if (e is ParseException) {
                    throw ParseException(e.line, e.col, "Not a valid Kotlin file (${e.message?.toLowerCase()})")
                }
                throw e
            }
        }
        if (stdin) {
            process("<text>", String(System.`in`.readBytes()))
        } else {
            for (file in fileSequence()) {
                process(file.path, file.readText())
            }
        }
    }

    private fun fileSequence() =
        when {
            patterns.isEmpty() ->
                Glob.from("**/*.kt", "**/*.kts")
                    .iterate(Paths.get(workDir), Glob.IterationOption.SKIP_HIDDEN)
            else ->
                Glob.from(*patterns.map { expandTilde(it) }.toTypedArray())
                    .iterate(Paths.get(workDir))
        }
            .asSequence()
            .map(Path::toFile)

    private fun installGitPreCommitHook() {
        if (!File(".git").isDirectory) {
            System.err.println(
                ".git directory not found. " +
                    "Are you sure you are inside project root directory?"
            )
            exitProcess(1)
        }
        val hooksDir = File(".git", "hooks")
        hooksDir.mkdirsOrFail()
        val preCommitHookFile = File(hooksDir, "pre-commit")
        val expectedPreCommitHook =
            ClassLoader.getSystemClassLoader()
                .getResourceAsStream("ktlint-git-pre-commit-hook${if (android) "-android" else ""}.sh").readBytes()
        // backup existing hook (if any)
        val actualPreCommitHook = try { preCommitHookFile.readBytes() } catch (e: FileNotFoundException) { null }
        if (actualPreCommitHook != null && !actualPreCommitHook.isEmpty() && !Arrays.equals(actualPreCommitHook, expectedPreCommitHook)) {
            val backupFile = File(hooksDir, "pre-commit.ktlint-backup." + hex(actualPreCommitHook))
            System.err.println(".git/hooks/pre-commit -> $backupFile")
            preCommitHookFile.copyTo(backupFile, overwrite = true)
        }
        // > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
        preCommitHookFile.writeBytes(expectedPreCommitHook)
        preCommitHookFile.setExecutable(true)
        System.err.println(".git/hooks/pre-commit installed")
    }

    private fun installGitPrePushHook() {
        if (!File(".git").isDirectory) {
            System.err.println(
                ".git directory not found. " +
                    "Are you sure you are inside project root directory?"
            )
            exitProcess(1)
        }
        val hooksDir = File(".git", "hooks")
        hooksDir.mkdirsOrFail()
        val prePushHookFile = File(hooksDir, "pre-push")
        val expectedPrePushHook =
            ClassLoader.getSystemClassLoader()
                .getResourceAsStream("ktlint-git-pre-push-hook${if (android) "-android" else ""}.sh").readBytes()
        // backup existing hook (if any)
        val actualPrePushHook = try { prePushHookFile.readBytes() } catch (e: FileNotFoundException) { null }
        if (actualPrePushHook != null && !actualPrePushHook.isEmpty() && !Arrays.equals(actualPrePushHook, expectedPrePushHook)) {
            val backupFile = File(hooksDir, "pre-push.ktlint-backup." + hex(actualPrePushHook))
            System.err.println(".git/hooks/pre-push -> $backupFile")
            prePushHookFile.copyTo(backupFile, overwrite = true)
        }
        // > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
        prePushHookFile.writeBytes(expectedPrePushHook)
        prePushHookFile.setExecutable(true)
        System.err.println(".git/hooks/pre-push installed")
    }

    private fun applyToIDEA() {
        try {
            val workDir = Paths.get(".")
            if (!forceApply) {
                val fileList = IntellijIDEAIntegration.apply(workDir, true, android, applyToProject)
                System.err.println(
                    "The following files are going to be updated:\n\n\t" +
                        fileList.joinToString("\n\t") +
                        "\n\nDo you wish to proceed? [y/n]\n" +
                        "(in future, use -y flag if you wish to skip confirmation)"
                )
                val scanner = Scanner(System.`in`)
                val res =
                    generateSequence {
                        try { scanner.next() } catch (e: NoSuchElementException) { null }
                    }
                        .filter { line -> !line.trim().isEmpty() }
                        .first()
                if (!"y".equals(res, ignoreCase = true)) {
                    System.err.println("(update canceled)")
                    exitProcess(1)
                }
            }
            IntellijIDEAIntegration.apply(workDir, false, android, applyToProject)
        } catch (e: IntellijIDEAIntegration.ProjectNotFoundException) {
            System.err.println(
                ".idea directory not found. " +
                    "Are you sure you are inside project root directory?"
            )
            exitProcess(1)
        }
        System.err.println("(updated)")
        System.err.println("\nPlease restart your IDE")
        System.err.println("(if you experience any issues please report them at https://github.com/shyiko/ktlint)")
    }

    private fun hex(input: ByteArray) = BigInteger(MessageDigest.getInstance("SHA-256").digest(input)).toString(16)

    // a complete solution would be to implement https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html
    // this implementation takes care only of the most commonly used case (~/)
    private fun expandTilde(path: String) = path.replaceFirst(Regex("^~"), System.getProperty("user.home"))

    private fun <T> List<T>.head(limit: Int) = if (limit == size) this else this.subList(0, limit)

    private fun buildDependencyResolver(): MavenDependencyResolver {
        val mavenLocal = File(File(System.getProperty("user.home"), ".m2"), "repository")
        mavenLocal.mkdirsOrFail()
        val dependencyResolver = MavenDependencyResolver(
            mavenLocal,
            listOf(
                RemoteRepository.Builder(
                    "central", "default", "https://repo1.maven.org/maven2/"
                ).setSnapshotPolicy(
                    RepositoryPolicy(
                        false, UPDATE_POLICY_NEVER,
                        CHECKSUM_POLICY_IGNORE
                    )
                ).build(),
                RemoteRepository.Builder(
                    "bintray", "default", "https://jcenter.bintray.com"
                ).setSnapshotPolicy(
                    RepositoryPolicy(
                        false, UPDATE_POLICY_NEVER,
                        CHECKSUM_POLICY_IGNORE
                    )
                ).build(),
                RemoteRepository.Builder(
                    "jitpack", "default", "https://jitpack.io"
                ).build()
            ) + repositories.map { repository ->
                val colon = repository.indexOf("=").apply {
                    if (this == -1) {
                        throw RuntimeException(
                            "$repository is not a valid repository entry " +
                                "(make sure it's provided as <id>=<url>"
                        )
                    }
                }
                val id = repository.substring(0, colon)
                val url = repository.substring(colon + 1)
                RemoteRepository.Builder(id, "default", url).build()
            },
            forceUpdate == true
        )
        if (debug) {
            dependencyResolver.setTransferEventListener { e ->
                System.err.println(
                    "[DEBUG] Transfer ${e.type.toString().toLowerCase()} ${e.resource.repositoryUrl}" +
                        e.resource.resourceName + (e.exception?.let { " (${it.message})" } ?: "")
                )
            }
        }
        return dependencyResolver
    }

    // fixme: isn't going to work on JDK 9
    private fun loadJARs(dependencyResolver: Lazy<MavenDependencyResolver>, artifacts: List<String>) {
        (ClassLoader.getSystemClassLoader() as java.net.URLClassLoader)
            .addURLs(
                artifacts.flatMap { artifact ->
                    if (debug) {
                        System.err.println("[DEBUG] Resolving $artifact")
                    }
                    val result = try {
                        dependencyResolver.value.resolve(DefaultArtifact(artifact)).map { it.toURI().toURL() }
                    } catch (e: IllegalArgumentException) {
                        val file = File(expandTilde(artifact))
                        if (!file.exists()) {
                            System.err.println("Error: $artifact does not exist")
                            exitProcess(1)
                        }
                        listOf(file.toURI().toURL())
                    } catch (e: RepositoryException) {
                        if (debug) {
                            e.printStackTrace()
                        }
                        System.err.println("Error: $artifact wasn't found")
                        exitProcess(1)
                    }
                    if (debug) {
                        result.forEach { url -> System.err.println("[DEBUG] Loading $url") }
                    }
                    if (!skipClasspathCheck) {
                        if (result.any { it.toString().substringAfterLast("/").startsWith("ktlint-core-") }) {
                            System.err.println(
                                "\"$artifact\" appears to have a runtime/compile dependency on \"ktlint-core\".\n" +
                                    "Please inform the author that \"com.github.shyiko:ktlint*\" should be marked " +
                                    "compileOnly (Gradle) / provided (Maven).\n" +
                                    "(to suppress this warning use --skip-classpath-check)"
                            )
                        }
                        if (result.any { it.toString().substringAfterLast("/").startsWith("kotlin-stdlib-") }) {
                            System.err.println(
                                "\"$artifact\" appears to have a runtime/compile dependency on \"kotlin-stdlib\".\n" +
                                    "Please inform the author that \"org.jetbrains.kotlin:kotlin-stdlib*\" should be marked " +
                                    "compileOnly (Gradle) / provided (Maven).\n" +
                                    "(to suppress this warning use --skip-classpath-check)"
                            )
                        }
                    }
                    result
                }
            )
    }

    private fun parseQuery(query: String) =
        query.split("&")
            .fold(LinkedHashMap<String, String>()) { map, s ->
                if (!s.isEmpty()) {
                    s.split("=", limit = 2).let { e ->
                        map.put(
                            e[0],
                            URLDecoder.decode(e.getOrElse(1) { "true" }, "UTF-8")
                        )
                    }
                }
                map
            }

    private fun lint(
        fileName: String,
        text: String,
        ruleSets: Iterable<RuleSet>,
        userData: Map<String, String>,
        cb: (e: LintError) -> Unit
    ) =
        if (fileName.endsWith(".kt", ignoreCase = true)) {
            KtLint.lint(text, ruleSets, userData, cb)
        } else {
            KtLint.lintScript(text, ruleSets, userData, cb)
        }

    private fun format(
        fileName: String,
        text: String,
        ruleSets: Iterable<RuleSet>,
        userData: Map<String, String>,
        cb: (e: LintError, corrected: Boolean) -> Unit
    ): String =
        if (fileName.endsWith(".kt", ignoreCase = true)) {
            KtLint.format(text, ruleSets, userData, cb)
        } else {
            KtLint.formatScript(text, ruleSets, userData, cb)
        }

    private fun java.net.URLClassLoader.addURLs(url: Iterable<java.net.URL>) {
        val method = java.net.URLClassLoader::class.java.getDeclaredMethod("addURL", java.net.URL::class.java)
        method.isAccessible = true
        url.forEach { method.invoke(this, it) }
    }

    private fun File.mkdirsOrFail() {
        if (!mkdirs() && !isDirectory) {
            throw IOException("Unable to create \"${this}\" directory")
        }
    }

    /**
     * Executes "Callable"s in parallel (lazily).
     * The results are gathered one-by-one (by `cb(<callable result>)`) in the order of corresponding "Callable"s
     * in the "Sequence" (think `seq.toList().map { executorService.submit(it) }.forEach { cb(it.get()) }` but without
     * buffering an entire sequence).
     *
     * Once kotlinx-coroutines are out of "experimental" stage everything below can be replaced with
     * ```
     * suspend fun <T> Sequence<Callable<T>>.parallel(...) {
     *     val ctx = newFixedThreadPoolContext(numberOfThreads, "Sequence<Callable<T>>.parallel")
     *     ctx.use {
     *         val channel = produce(ctx, numberOfThreads) {
     *             for (task in this@parallel) {
     *                 send(async(ctx) { task.call() })
     *             }
     *         }
     *         for (res in channel) {
     *             cb(res.await())
     *         }
     *     }
     * }
     * ```
     */
    private fun <T> Sequence<Callable<T>>.parallel(
        cb: (T) -> Unit,
        numberOfThreads: Int = Runtime.getRuntime().availableProcessors()
    ) {
        val pill = object : Future<T> {

            override fun isDone(): Boolean { throw UnsupportedOperationException() }
            override fun get(timeout: Long, unit: TimeUnit): T { throw UnsupportedOperationException() }
            override fun get(): T { throw UnsupportedOperationException() }
            override fun cancel(mayInterruptIfRunning: Boolean): Boolean { throw UnsupportedOperationException() }
            override fun isCancelled(): Boolean { throw UnsupportedOperationException() }
        }
        val q = ArrayBlockingQueue<Future<T>>(numberOfThreads)
        val producer = thread(start = true) {
            val executorService = Executors.newCachedThreadPool()
            try {
                for (task in this) {
                    q.put(executorService.submit(task))
                }
                q.put(pill)
            } catch (e: InterruptedException) {
                // we've been asked to stop consuming sequence
            } finally {
                executorService.shutdown()
            }
        }
        try {
            while (true) {
                val result = q.take()
                if (result != pill) cb(result.get()) else break
            }
        } finally {
            producer.interrupt() // in case q.take()/result.get() throws
            producer.join()
        }
    }
}

package com.pinterest.ktlint.core

/**
 * @see [EditorConfig](http://editorconfig.org/)
 */
interface EditorConfig {

    enum class IntentStyle { SPACE, TAB }

    val indentStyle: IntentStyle
    val indentSize: Int
    val tabWidth: Int
    val maxLineLength: Int
    val insertFinalNewline: Boolean?

    fun get(key: String): String?

    companion object {
        fun fromMap(map: Map<String, String>): EditorConfig {
            val indentStyle = when {
                map["indent_style"]?.toLowerCase() == "tab" -> IntentStyle.TAB
                else -> IntentStyle.SPACE
            }
            val indentSize = map["indent_size"].let { v ->
                if (v?.toLowerCase() == "unset") -1 else v?.toIntOrNull() ?: 4
            }
            val tabWidth = map["indent_size"]?.toIntOrNull()
            val maxLineLength = map["max_line_length"]?.toIntOrNull() ?: -1
            val insertFinalNewline = map["insert_final_newline"]?.toBoolean()
            return object : EditorConfig {
                override val indentStyle = indentStyle
                override val indentSize = indentSize
                override val tabWidth = tabWidth ?: indentSize
                override val maxLineLength = maxLineLength
                override val insertFinalNewline = insertFinalNewline
                override fun get(key: String): String? = map[key]
            }
        }
    }
}

package com.pinterest.ktlint.core

import com.pinterest.ktlint.core.ast.prevLeaf
import java.util.ArrayList
import java.util.HashSet
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.mock.MockProject
import org.jetbrains.kotlin.com.intellij.openapi.Disposable
import org.jetbrains.kotlin.com.intellij.openapi.diagnostic.DefaultLogger
import org.jetbrains.kotlin.com.intellij.openapi.diagnostic.Logger as DiagnosticLogger
import org.jetbrains.kotlin.com.intellij.openapi.extensions.ExtensionPoint
import org.jetbrains.kotlin.com.intellij.openapi.extensions.Extensions.getArea
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
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.PsiComment
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.PsiErrorElement
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.TreeCopyHandler
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.KtFile
import sun.reflect.ReflectionFactory

object KtLint {

    val EDITOR_CONFIG_USER_DATA_KEY = Key<EditorConfig>("EDITOR_CONFIG")
    val ANDROID_USER_DATA_KEY = Key<Boolean>("ANDROID")
    val FILE_PATH_USER_DATA_KEY = Key<String>("FILE_PATH")

    private val psiFileFactory: PsiFileFactory
    private val nullSuppression = { _: Int, _: String, _: Boolean -> false }

    init {
        // do not print anything to the stderr when lexer is unable to match input
        class LoggerFactory : DiagnosticLogger.Factory {
            override fun getLoggerInstance(p: String): DiagnosticLogger = object : DefaultLogger(null) {
                override fun warn(message: String?, t: Throwable?) {}
                override fun error(message: String?, vararg details: String?) {}
            }
        }
        DiagnosticLogger.setFactory(LoggerFactory::class.java)
        val compilerConfiguration = CompilerConfiguration()
        compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
        val project = KotlinCoreEnvironment.createForProduction(
            Disposable {},
            compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES
        ).project
        // everything below (up to PsiFileFactory.getInstance(...)) is to get AST mutations (`ktlint -F ...`) working
        // otherwise it's not needed
        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(*arrayOfNulls<Class<*>>(0))
                    )
                    return constructor.newInstance(*emptyArray()) as T
                }
                return null
            }
        }
        val extensionPoint = "org.jetbrains.kotlin.com.intellij.treeCopyHandler"
        val extensionClassName = TreeCopyHandler::class.java.name!!
        for (area in arrayOf(getArea(project), getArea(null))) {
            if (!area.hasExtensionPoint(extensionPoint)) {
                area.registerExtensionPoint(extensionPoint, extensionClassName, ExtensionPoint.Kind.INTERFACE)
            }
        }
        project as MockProject
        project.registerService(PomModel::class.java, pomModel)
        psiFileFactory = PsiFileFactory.getInstance(project)
    }

    /**
     * Check source for lint errors.
     *
     * @param text source
     * @param ruleSets a collection of "RuleSet"s used to validate source
     * @param cb callback that is going to be executed for every lint error
     *
     * @throws ParseException if text is not a valid Kotlin code
     * @throws RuleExecutionException in case of internal failure caused by a bug in rule implementation
     */
    fun lint(text: String, ruleSets: Iterable<RuleSet>, cb: (e: LintError) -> Unit) {
        lint(text, ruleSets, emptyMap(), cb, script = false)
    }

    fun lint(text: String, ruleSets: Iterable<RuleSet>, userData: Map<String, String>, cb: (e: LintError) -> Unit) {
        lint(text, ruleSets, userData, cb, script = false)
    }

    /**
     * Check source for lint errors.
     *
     * @param text script source
     * @param ruleSets a collection of "RuleSet"s used to validate source
     * @param cb callback that is going to be executed for every lint error
     *
     * @throws ParseException if text is not a valid Kotlin code
     * @throws RuleExecutionException in case of internal failure caused by a bug in rule implementation
     */
    fun lintScript(text: String, ruleSets: Iterable<RuleSet>, cb: (e: LintError) -> Unit) {
        lint(text, ruleSets, emptyMap(), cb, script = true)
    }

    fun lintScript(
        text: String,
        ruleSets: Iterable<RuleSet>,
        userData: Map<String, String>,
        cb: (e: LintError) -> Unit
    ) {
        lint(text, ruleSets, userData, cb, script = true)
    }

    private fun lint(
        text: String,
        ruleSets: Iterable<RuleSet>,
        userData: Map<String, String>,
        cb: (e: LintError) -> Unit,
        script: Boolean
    ) {
        val normalizedText = text.replace("\r\n", "\n").replace("\r", "\n")
        val positionByOffset = calculateLineColByOffset(normalizedText)
        val fileName = if (script) "file.kts" else "file.kt"
        val psiFile = psiFileFactory.createFileFromText(fileName, KotlinLanguage.INSTANCE, normalizedText) as KtFile
        val errorElement = psiFile.findErrorElement()
        if (errorElement != null) {
            val (line, col) = positionByOffset(errorElement.textOffset)
            throw ParseException(line, col, errorElement.errorDescription)
        }
        val rootNode = psiFile.node
        injectUserData(rootNode, userData)
        val isSuppressed = calculateSuppressedRegions(rootNode)
        val errors = mutableListOf<LintError>()
        visitor(rootNode, ruleSets).invoke { node, rule, fqRuleId ->
            // fixme: enforcing suppression based on node.startOffset is wrong
            // (not just because not all nodes are leaves but because rules are free to emit (and fix!) errors at any position)
            if (!isSuppressed(node.startOffset, fqRuleId, node === rootNode)) {
                try {
                    rule.visit(node, false) { offset, errorMessage, canBeAutoCorrected ->
                        // https://github.com/shyiko/ktlint/issues/158#issuecomment-462728189
                        if (node.startOffset != offset && isSuppressed(offset, fqRuleId, node === rootNode)) {
                            return@visit
                        }
                        val (line, col) = positionByOffset(offset)
                        errors.add(LintError(line, col, fqRuleId, errorMessage, canBeAutoCorrected))
                    }
                } catch (e: Exception) {
                    val (line, col) = positionByOffset(node.startOffset)
                    throw RuleExecutionException(line, col, fqRuleId, e)
                }
            }
        }
        errors
            .sortedWith(Comparator { l, r -> if (l.line != r.line) l.line - r.line else l.col - r.col })
            .forEach(cb)
    }

    private fun injectUserData(node: ASTNode, userData: Map<String, String>) {
        val android = userData["android"]?.toBoolean() ?: false
        val editorConfigMap =
            if (android &&
                userData["max_line_length"].let { it?.toLowerCase() != "off" && it?.toIntOrNull() == null }
            ) {
                userData + mapOf("max_line_length" to "100")
            } else {
                userData
            }
        node.putUserData(FILE_PATH_USER_DATA_KEY, userData["file_path"])
        node.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(editorConfigMap - "android" - "file_path"))
        node.putUserData(ANDROID_USER_DATA_KEY, android)
    }

    private fun visitor(
        rootNode: ASTNode,
        ruleSets: Iterable<RuleSet>,
        concurrent: Boolean = true,
        filter: (fqRuleId: String) -> Boolean = { true }
    ): ((node: ASTNode, rule: Rule, fqRuleId: String) -> Unit) -> Unit {
        val fqrsRestrictedToRoot = mutableListOf<Pair<String, Rule>>()
        val fqrs = mutableListOf<Pair<String, Rule>>()
        val fqrsExpectedToBeExecutedLastOnRoot = mutableListOf<Pair<String, Rule>>()
        val fqrsExpectedToBeExecutedLast = mutableListOf<Pair<String, Rule>>()
        for (ruleSet in ruleSets) {
            val prefix = if (ruleSet.id === "standard") "" else "${ruleSet.id}:"
            for (rule in ruleSet) {
                val fqRuleId = "$prefix${rule.id}"
                if (!filter(fqRuleId)) {
                    continue
                }
                val fqr = fqRuleId to rule
                when {
                    rule is Rule.Modifier.Last -> fqrsExpectedToBeExecutedLast.add(fqr)
                    rule is Rule.Modifier.RestrictToRootLast -> fqrsExpectedToBeExecutedLastOnRoot.add(fqr)
                    rule is Rule.Modifier.RestrictToRoot -> fqrsRestrictedToRoot.add(fqr)
                    else -> fqrs.add(fqr)
                }
            }
        }
        return { visit ->
            for ((fqRuleId, rule) in fqrsRestrictedToRoot) {
                visit(rootNode, rule, fqRuleId)
            }
            if (concurrent) {
                rootNode.visit { node ->
                    for ((fqRuleId, rule) in fqrs) {
                        visit(node, rule, fqRuleId)
                    }
                }
            } else {
                for ((fqRuleId, rule) in fqrs) {
                    rootNode.visit { node ->
                        visit(node, rule, fqRuleId)
                    }
                }
            }
            for ((fqRuleId, rule) in fqrsExpectedToBeExecutedLastOnRoot) {
                visit(rootNode, rule, fqRuleId)
            }
            if (!fqrsExpectedToBeExecutedLast.isEmpty()) {
                if (concurrent) {
                    rootNode.visit { node ->
                        for ((fqRuleId, rule) in fqrsExpectedToBeExecutedLast) {
                            visit(node, rule, fqRuleId)
                        }
                    }
                } else {
                    for ((fqRuleId, rule) in fqrsExpectedToBeExecutedLast) {
                        rootNode.visit { node ->
                            visit(node, rule, fqRuleId)
                        }
                    }
                }
            }
        }
    }

    private fun calculateLineColByOffset(text: String): (offset: Int) -> Pair<Int, Int> {
        var i = -1
        val e = text.length
        val arr = ArrayList<Int>()
        do {
            arr.add(i + 1)
            i = text.indexOf('\n', i + 1)
        } while (i != -1)
        arr.add(e + if (arr.last() == e) 1 else 0)
        val segmentTree = SegmentTree(arr.toTypedArray())
        return { offset ->
            val line = segmentTree.indexOf(offset)
            if (line != -1) {
                val col = offset - segmentTree.get(line).left
                line + 1 to col + 1
            } else {
                1 to 1
            }
        }
    }

    private fun calculateSuppressedRegions(rootNode: ASTNode) =
        SuppressionHint.collect(rootNode).let { listOfHints ->
            if (listOfHints.isEmpty()) nullSuppression else { offset, ruleId, root ->
                if (root) {
                    val h = listOfHints[0]
                    h.range.endInclusive == 0 && (h.disabledRules.isEmpty() || h.disabledRules.contains(ruleId))
                } else {
                    listOfHints.any { (range, disabledRules) ->
                        (disabledRules.isEmpty() || disabledRules.contains(ruleId)) && range.contains(offset)
                    }
                }
            }
        }

    /**
     * Fix style violations.
     *
     * @param text source
     * @param ruleSets a collection of "RuleSet"s used to validate source
     * @param cb callback that is going to be executed for every lint error
     *
     * @throws ParseException if text is not a valid Kotlin code
     * @throws RuleExecutionException in case of internal failure caused by a bug in rule implementation
     */
    fun format(text: String, ruleSets: Iterable<RuleSet>, cb: (e: LintError, corrected: Boolean) -> Unit): String =
        format(text, ruleSets, emptyMap<String, String>(), cb, script = false)

    fun format(
        text: String,
        ruleSets: Iterable<RuleSet>,
        userData: Map<String, String>,
        cb: (e: LintError, corrected: Boolean) -> Unit
    ): String = format(text, ruleSets, userData, cb, script = false)

    /**
     * Fix style violations.
     *
     * @param text script source
     * @param ruleSets a collection of "RuleSet"s used to validate source
     * @param cb callback that is going to be executed for every lint error
     *
     * @throws ParseException if text is not a valid Kotlin code
     * @throws RuleExecutionException in case of internal failure caused by a bug in rule implementation
     */
    fun formatScript(
        text: String,
        ruleSets: Iterable<RuleSet>,
        cb: (e: LintError, corrected: Boolean) -> Unit
    ): String =
        format(text, ruleSets, emptyMap(), cb, script = true)

    fun formatScript(
        text: String,
        ruleSets: Iterable<RuleSet>,
        userData: Map<String, String>,
        cb: (e: LintError, corrected: Boolean) -> Unit
    ): String = format(text, ruleSets, userData, cb, script = true)

    private fun format(
        text: String,
        ruleSets: Iterable<RuleSet>,
        userData: Map<String, String>,
        cb: (e: LintError, corrected: Boolean) -> Unit,
        script: Boolean
    ): String {
        val normalizedText = text.replace("\r\n", "\n").replace("\r", "\n")
        val positionByOffset = calculateLineColByOffset(normalizedText)
        val fileName = if (script) "file.kts" else "file.kt"
        val psiFile = psiFileFactory.createFileFromText(fileName, KotlinLanguage.INSTANCE, normalizedText) as KtFile
        val errorElement = psiFile.findErrorElement()
        if (errorElement != null) {
            val (line, col) = positionByOffset(errorElement.textOffset)
            throw ParseException(line, col, errorElement.errorDescription)
        }
        val rootNode = psiFile.node
        injectUserData(rootNode, userData)
        var isSuppressed = calculateSuppressedRegions(rootNode)
        var tripped = false
        var mutated = false
        visitor(rootNode, ruleSets, concurrent = false)
            .invoke { node, rule, fqRuleId ->
                // fixme: enforcing suppression based on node.startOffset is wrong
                // (not just because not all nodes are leaves but because rules are free to emit (and fix!) errors at any position)
                if (!isSuppressed(node.startOffset, fqRuleId, node === rootNode)) {
                    try {
                        rule.visit(node, true) { _, _, canBeAutoCorrected ->
                            tripped = true
                            if (canBeAutoCorrected) {
                                mutated = true
                                if (isSuppressed !== nullSuppression) {
                                    isSuppressed = calculateSuppressedRegions(rootNode)
                                }
                            }
                        }
                    } catch (e: Exception) {
                        // line/col cannot be reliably mapped as exception might originate from a node not present
                        // in the original AST
                        throw RuleExecutionException(0, 0, fqRuleId, e)
                    }
                }
            }
        if (tripped) {
            val errors = mutableListOf<Pair<LintError, Boolean>>()
            visitor(rootNode, ruleSets).invoke { node, rule, fqRuleId ->
                // fixme: enforcing suppression based on node.startOffset is wrong
                // (not just because not all nodes are leaves but because rules are free to emit (and fix!) errors at any position)
                if (!isSuppressed(node.startOffset, fqRuleId, node === rootNode)) {
                    try {
                        rule.visit(node, false) { offset, errorMessage, canBeAutoCorrected ->
                            // https://github.com/shyiko/ktlint/issues/158#issuecomment-462728189
                            if (node.startOffset != offset && isSuppressed(offset, fqRuleId, node === rootNode)) {
                                return@visit
                            }
                            val (line, col) = positionByOffset(offset)
                            errors.add(Pair(LintError(line, col, fqRuleId, errorMessage, canBeAutoCorrected), false))
                        }
                    } catch (e: Exception) {
                        val (line, col) = positionByOffset(node.startOffset)
                        throw RuleExecutionException(line, col, fqRuleId, e)
                    }
                }
            }
            errors
                .sortedWith(Comparator { (l), (r) -> if (l.line != r.line) l.line - r.line else l.col - r.col })
                .forEach { (e, corrected) -> cb(e, corrected) }
        }
        return if (mutated) rootNode.text.replace("\n", determineLineSeparator(text, userData)) else text
    }

    private fun determineLineSeparator(fileContent: String, userData: Map<String, String>): String {
        val eol = userData["end_of_line"]?.trim()?.toLowerCase()
        return when {
            eol == "native" -> System.lineSeparator()
            eol == "crlf" || eol != "lf" && fileContent.lastIndexOf('\r') != -1 -> "\r\n"
            else -> "\n"
        }
    }

    /**
     * @param range zero-based range of lines where lint errors should be suppressed
     * @param disabledRules empty set means "all"
     */
    private data class SuppressionHint(val range: IntRange, val disabledRules: Set<String> = emptySet()) {

        companion object {

            fun collect(rootNode: ASTNode): List<SuppressionHint> {
                val result = ArrayList<SuppressionHint>()
                val open = ArrayList<SuppressionHint>()
                rootNode.visit { node ->
                    if (node is PsiComment) {
                        val text = node.getText()
                        if (text.startsWith("//")) {
                            val commentText = text.removePrefix("//").trim()
                            parseHintArgs(commentText, "ktlint-disable")?.let { args ->
                                val lineStart = (
                                    node.prevLeaf { it is PsiWhiteSpace && it.textContains('\n') } as
                                        PsiWhiteSpace?
                                    )?.let { it.node.startOffset + it.text.lastIndexOf('\n') + 1 } ?: 0
                                result.add(SuppressionHint(IntRange(lineStart, node.startOffset), HashSet(args)))
                            }
                        } else {
                            val commentText = text.removePrefix("/*").removeSuffix("*/").trim()
                            parseHintArgs(commentText, "ktlint-disable")?.apply {
                                open.add(SuppressionHint(IntRange(node.startOffset, node.startOffset), HashSet(this)))
                            }
                                ?: parseHintArgs(commentText, "ktlint-enable")?.apply {
                                    // match open hint
                                    val disabledRules = HashSet(this)
                                    val openHintIndex = open.indexOfLast { it.disabledRules == disabledRules }
                                    if (openHintIndex != -1) {
                                        val openingHint = open.removeAt(openHintIndex)
                                        result.add(
                                            SuppressionHint(
                                                IntRange(openingHint.range.start, node.startOffset),
                                                disabledRules
                                            )
                                        )
                                    }
                                }
                        }
                    }
                }
                result.addAll(
                    open.map {
                        SuppressionHint(IntRange(it.range.first, rootNode.textLength), it.disabledRules)
                    }
                )
                return result
            }

            private fun parseHintArgs(commentText: String, key: String): List<String>? {
                if (commentText.startsWith(key)) {
                    val parsedComment = splitCommentBySpace(commentText)
                    // assert exact match
                    if (parsedComment[0] == key) {
                        return parsedComment.tail()
                    }
                }
                return null
            }

            private fun splitCommentBySpace(comment: String) =
                comment.replace(Regex("\\s"), " ").replace(" {2,}", " ").split(" ")

            private fun <T> List<T>.tail() = this.subList(1, this.size)
        }
    }

    private class SegmentTree {

        private val segments: List<Segment>

        constructor(sortedArray: Array<Int>) {
            require(sortedArray.size > 1) { "At least two data points are required" }
            sortedArray.reduce { r, v -> require(r <= v) { "Data points are not sorted (ASC)" }; v }
            segments = sortedArray.take(sortedArray.size - 1)
                .mapIndexed { i: Int, v: Int -> Segment(v, sortedArray[i + 1] - 1) }
        }

        fun get(i: Int): Segment = segments[i]
        fun indexOf(v: Int): Int = binarySearch(v, 0, this.segments.size - 1)

        private fun binarySearch(v: Int, l: Int, r: Int): Int = when {
            l > r -> -1
            else -> {
                val i = l + (r - l) / 2
                val s = segments[i]
                if (v < s.left) binarySearch(v, l, i - 1) else (if (s.right < v) binarySearch(v, i + 1, r) else i)
            }
        }
    }

    private data class Segment(val left: Int, val right: Int)

    private fun PsiElement.findErrorElement(): PsiErrorElement? {
        if (this is PsiErrorElement) {
            return this
        }
        this.children.forEach { child ->
            val errorElement = child.findErrorElement()
            if (errorElement != null) {
                return errorElement
            }
        }
        return null
    }

    private fun ASTNode.visit(cb: (node: ASTNode) -> Unit) {
        cb(this)
        this.getChildren(null).forEach { it.visit(cb) }
    }
}

package com.pinterest.ktlint.core

/**
 * Lint error.
 *
 * @param line line number (one-based)
 * @param col column number (one-based)
 * @param ruleId rule id (prepended with "<ruleSetId>:" in case of non-standard ruleset)
 * @param detail error message
 */
data class LintError(val line: Int, val col: Int, val ruleId: String, val detail: String) {

    // fixme:
    // not included in equals/hashCode for backward-compatibility with ktlint < 0.25.0
    // subject to change in 1.0.0
    var canBeAutoCorrected: Boolean = false
        private set(value) {
            field = value
        }

    constructor(
        line: Int,
        col: Int,
        ruleId: String,
        detail: String,
        canBeAutoCorrected: Boolean
    ) : this(line, col, ruleId, detail) {
        this.canBeAutoCorrected = canBeAutoCorrected
    }
}

package com.pinterest.ktlint.core

/**
 * @param line line number (one-based)
 * @param col column number (one-based)
 * @param message message
 */
class ParseException(val line: Int, val col: Int, message: String) : RuntimeException(message)

package com.pinterest.ktlint.core

/**
 * Implementation must be thread-safe. In particular, [onLintError] might be called in parallel for
 * different files (but not for the same file) ([before], [onLintError] and [after] are guarantied to be called
 * on the same thread).
 * @see ReporterProvider
 */
interface Reporter {

    /**
     * Called once, before the processing begins
     * (regardless of whether any files matching the pattern are (going to) found).
     * It's guarantied to be called before any of the other [Reporter]s methods.
     */
    fun beforeAll() {}
    /**
     * Called when file (matching the pattern) is found but before it's parsed.
     */
    fun before(file: String) {}
    fun onLintError(file: String, err: LintError, corrected: Boolean)
    /**
     * Called after ktlint is done with the file.
     */
    fun after(file: String) {}
    /**
     * Called once, after all the files (if any) have been processed.
     * It's guarantied to be called after all other [Reporter]s methods.
     */
    fun afterAll() {}

    companion object {
        fun from(vararg reporters: Reporter): Reporter {
            return object : Reporter {
                override fun beforeAll() { reporters.forEach(Reporter::beforeAll) }
                override fun before(file: String) { reporters.forEach { it.before(file) } }
                override fun onLintError(file: String, err: LintError, corrected: Boolean) =
                    reporters.forEach { it.onLintError(file, err, corrected) }
                override fun after(file: String) { reporters.forEach { it.after(file) } }
                override fun afterAll() { reporters.forEach(Reporter::afterAll) }
            }
        }
    }
}

package com.pinterest.ktlint.core

import java.io.PrintStream

/**
 * `ktlint` uses [ServiceLoader](http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html) to
 * discover all available [ReporterProvider]s on the classpath and so each [ReporterProvider] must be registered using
 * `META-INF/services/com.github.shyiko.ktlint.core.ReporterProvider`
 * (see `ktlint-reporter-plain/src/main/resources` for an example).
 */
interface ReporterProvider {
    val id: String
    fun get(out: PrintStream, opt: Map<String, String>): Reporter
}

package com.pinterest.ktlint.core

import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.lang.FileASTNode

/**
 * A rule contract.
 *
 * Implementation **doesn't** have to be thread-safe or stateless
 * (provided [RuleSetProvider] creates a new instance on each `get()` call).
 *
 * @param id must be unique within the ruleset
 *
 * @see RuleSet
 */
abstract class Rule(val id: String) {

    init {
        require(id.matches(Regex("[a-z]+([-][a-z]+)*"))) { "id must match [a-z]+([-][a-z]+)*" }
    }

    /**
     * This method is going to be executed for each node in AST (in DFS fashion).
     *
     * @param node AST node
     * @param autoCorrect indicates whether rule should attempt auto-correction
     * @param emit a way for rule to notify about a violation (lint error)
     */
    abstract fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    )

    object Modifier {
        /**
         * Any rule implementing this interface will be given root ([FileASTNode]) node only
         * (in other words, [visit] will be called on [FileASTNode] but not on [FileASTNode] children).
         */
        interface RestrictToRoot
        /**
         * Marker interface to indicate that rule must be executed after all other rules (order among multiple
         * [RestrictToRootLast] rules is not defined and should be assumed to be random).
         *
         * Note that [RestrictToRootLast] implements [RestrictToRoot].
         */
        interface RestrictToRootLast : RestrictToRoot
        interface Last
    }
}

package com.pinterest.ktlint.core

/**
 * @param line line number (one-based)
 * @param col column number (one-based)
 * @param ruleId rule id (prepended with "<ruleSetId>:" in case of non-standard ruleset)
 */
class RuleExecutionException(val line: Int, val col: Int, val ruleId: String, cause: Throwable) :
    RuntimeException(cause)

package com.pinterest.ktlint.core

/**
 * A group of [Rule]s discoverable through [RuleSetProvider].
 * @see RuleSetProvider
 */
class RuleSet(val id: String, vararg val rules: Rule) : Iterable<Rule> {

    init {
        require(id.matches(Regex("[a-z]+([-][a-z]+)*"))) { "id must match [a-z]+([-][a-z]+)*" }
        require(!rules.isEmpty()) { "At least one rule must be provided" }
    }

    override fun iterator(): Iterator<Rule> = rules.iterator()
}

package com.pinterest.ktlint.core

/**
 * `ktlint` uses [ServiceLoader](http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html) to
 * discover all available `RuleSetProvider`s on the classpath and so each `RuleSetProvider` must be registered using
 * `META-INF/services/com.github.shyiko.ktlint.core.RuleSetProvider`
 * (see `ktlint-ruleset-standard/src/main/resources` for an example).
 */
interface RuleSetProvider {

    /**
     * This method is going to be called once for each file (which means if any of the rules have state or
     * are not thread-safe - a new RuleSet must be created).
     */
    fun get(): RuleSet
}

package com.pinterest.ktlint.internal

import java.io.ByteArrayInputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.Properties
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap

class EditorConfig private constructor (
    val parent: EditorConfig?,
    val path: Path,
    private val data: Map<String, String>
) : Map<String, String> by data {

    companion object : EditorConfigLookup {

        override fun of(dir: String) = of(Paths.get(dir))
        override fun of(dir: Path) =
            generateSequence(locate(dir)) { seed -> locate(seed.parent.parent) } // seed.parent == .editorconfig dir
                .map { it to lazy { load(it) } }
                .let { seq ->
                    // stop when .editorconfig with "root = true" is found, go deeper otherwise
                    var prev: Pair<Path, Lazy<Map<String, Map<String, String>>>>? = null
                    seq.takeWhile { pair ->
                        (prev?.second?.value?.get("")?.get("root")?.toBoolean()?.not() ?: true).also { prev = pair }
                    }
                }
                .toList()
                .asReversed()
                .fold(null as EditorConfig?) { parent, (path, data) ->
                    EditorConfig(
                        parent, path,
                        (
                            parent?.data
                                ?: emptyMap()
                            ) + flatten(data.value)
                    )
                }

        fun cached(): EditorConfigLookup = object : EditorConfigLookup {

            // todo: concurrent radix tree can potentially save a lot of memory here
            private val cache = ConcurrentHashMap<Path, CompletableFuture<EditorConfig?>>()

            override fun of(dir: String) = of(Paths.get(dir))
            override fun of(dir: Path): EditorConfig? {
                val cachedEditorConfig = cache[dir]
                return when {
                    cachedEditorConfig != null -> cachedEditorConfig.get()
                    else -> {
                        val future = CompletableFuture<EditorConfig?>()
                        val cachedFuture = cache.putIfAbsent(dir, future)
                        if (cachedFuture == null) {
                            val editorConfigPath = dir.resolve(".editorconfig")
                            val parent = if (dir.parent != null) of(dir.parent) else null
                            try {
                                val editorConfig = if (Files.exists(editorConfigPath)) {
                                    EditorConfig(
                                        parent, editorConfigPath,
                                        (
                                            parent?.data
                                                ?: emptyMap()
                                            ) + flatten(load(editorConfigPath))
                                    )
                                } else {
                                    parent
                                }
                                future.complete(editorConfig)
                                editorConfig
                            } catch (e: Exception) {
                                future.completeExceptionally(e)
                                cache.remove(dir)
                                throw e
                            }
                        } else {
                            cachedFuture.get()
                        }
                    }
                }
            }
        }

        private fun locate(dir: Path?): Path? = when (dir) {
            null -> null
            else -> dir.resolve(".editorconfig").let {
                if (Files.exists(it)) it else locate(dir.parent)
            }
        }

        private fun flatten(data: LinkedHashMap<String, Map<String, String>>): Map<String, String> {
            val map = mutableMapOf<String, String>()
            val patternsToSearchFor = arrayOf("*", "*.kt", "*.kts")
            for ((sectionName, section) in data) {
                if (sectionName == "") {
                    continue
                }
                val patterns = try {
                    parseSection(sectionName.substring(1, sectionName.length - 1))
                } catch (e: Exception) {
                    throw RuntimeException(
                        "ktlint failed to parse .editorconfig section \"$sectionName\"" +
                            " (please report at https://github.com/shyiko/ktlint)",
                        e
                    )
                }
                if (patternsToSearchFor.any { patterns.contains(it) }) {
                    map.putAll(section.toMap())
                }
            }
            return map.toSortedMap()
        }

        private fun load(path: Path) =
            linkedMapOf<String, Map<String, String>>().also { map ->
                object : Properties() {

                    private var section: MutableMap<String, String>? = null

                    override fun put(key: Any, value: Any): Any? {
                        val sectionName = (key as String).trim()
                        if (sectionName.startsWith('[') && sectionName.endsWith(']') && value == "") {
                            section = mutableMapOf<String, String>().also { map.put(sectionName, it) }
                        } else {
                            val section =
                                section
                                    ?: mutableMapOf<String, String>().also { section = it; map.put("", it) }
                            section[key] = value.toString()
                        }
                        return null
                    }
                }.load(ByteArrayInputStream(Files.readAllBytes(path)))
            }

        internal fun parseSection(sectionName: String): List<String> {
            val result = mutableListOf<String>()
            fun List<List<String>>.collect0(i: Int = 0, str: Array<String?>, acc: MutableList<String>) {
                if (i == str.size) {
                    acc.add(str.joinToString(""))
                    return
                }
                for (k in 0 until this[i].size) {
                    str[i] = this[i][k]
                    collect0(i + 1, str, acc)
                }
            }
            // [["*.kt"], ["", "s"], ["~"]] -> [*.kt~, *.kts~]
            fun List<List<String>>.collect(): List<String> =
                mutableListOf<String>().also { this.collect0(0, arrayOfNulls(this.size), it) }
            val chunks: MutableList<MutableList<String>> = mutableListOf()
            chunks.add(mutableListOf())
            var l = 0
            var r = 0
            var partOfBraceExpansion = false
            for (c in sectionName) {
                when (c) {
                    ',' -> {
                        chunks.last().add(sectionName.substring(l, r))
                        l = r + 1
                        if (!partOfBraceExpansion) {
                            result += chunks.collect()
                            chunks.clear()
                            chunks.add(mutableListOf())
                        }
                    }
                    '{', '}' -> {
                        if (partOfBraceExpansion == (c == '}')) {
                            chunks.last().add(sectionName.substring(l, r))
                            l = r + 1
                            chunks.add(mutableListOf())
                            partOfBraceExpansion = !partOfBraceExpansion
                        }
                    }
                }
                r++
            }
            chunks.last().add(sectionName.substring(l, r))
            result += chunks.collect()
            return result
        }
    }
}

interface EditorConfigLookup {
    fun of(dir: String): EditorConfig?
    fun of(dir: Path): EditorConfig?
}

package com.pinterest.ktlint.internal

import com.github.shyiko.klob.Glob
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.nio.charset.Charset
import java.nio.file.Files
import java.nio.file.NoSuchFileException
import java.nio.file.Path
import java.nio.file.Paths
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory
import org.w3c.dom.Element

object IntellijIDEAIntegration {

    @Suppress("UNUSED_PARAMETER")
    @Throws(IOException::class)
    fun apply(workDir: Path, dryRun: Boolean, android: Boolean = false, local: Boolean = false): Array<Path> {
        if (!Files.isDirectory(workDir.resolve(".idea"))) {
            throw ProjectNotFoundException()
        }
        val editorConfig: Map<String, String> = EditorConfig.of(".") ?: emptyMap()
        val indentSize = editorConfig["indent_size"]?.toIntOrNull() ?: 4
        val continuationIndentSize = editorConfig["continuation_indent_size"]?.toIntOrNull() ?: 4
        val updates = if (local) {
            listOf(
                Paths.get(workDir.toString(), ".idea", "codeStyles", "codeStyleConfig.xml") to
                    overwriteWithResource("/project-config/.idea/codeStyles/codeStyleConfig.xml"),
                Paths.get(workDir.toString(), ".idea", "codeStyles", "Project.xml") to
                    overwriteWithResource("/project-config/.idea/codeStyles/Project.xml") { resource ->
                        resource
                            .replace(
                                "option name=\"INDENT_SIZE\" value=\"4\"",
                                "option name=\"INDENT_SIZE\" value=\"$indentSize\""
                            )
                            .replace(
                                "option name=\"CONTINUATION_INDENT_SIZE\" value=\"8\"",
                                "option name=\"CONTINUATION_INDENT_SIZE\" value=\"$continuationIndentSize\""
                            )
                    },
                Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "profiles_settings.xml") to
                    overwriteWithResource("/project-config/.idea/inspectionProfiles/profiles_settings.xml"),
                Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "ktlint.xml") to
                    overwriteWithResource("/project-config/.idea/inspectionProfiles/ktlint.xml"),
                Paths.get(workDir.toString(), ".idea", "workspace.xml").let { src ->
                    src to {
                        var arr = "<project version=\"4\"></project>".toByteArray()
                        try {
                            arr = Files.readAllBytes(Paths.get(workDir.toString(), ".idea", "workspace.xml"))
                        } catch (e: IOException) {
                            if (e !is NoSuchFileException) {
                                throw e
                            }
                        }
                        try {
                            enableOptimizeImportsOnTheFly(arr)
                        } catch (e: Exception) {
                            throw IOException("Failed to enable \"Optimize imports on the fly\" ($src)", e)
                        }
                    }
                }
            )
        } else {
            val home = System.getProperty("user.home")
            val codeStyleName = "ktlint${
            if (continuationIndentSize == 4) "" else "-cis$continuationIndentSize"
            }${
            if (indentSize == 4) "" else "-is$indentSize"
            }"
            val paths =
                // macOS
                Glob.from("IntelliJIdea*", "IdeaIC*", "AndroidStudio*")
                    .iterate(
                        Paths.get(home, "Library", "Preferences"),
                        Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY
                    ).asSequence() +
                    // linux/windows
                    Glob.from(".IntelliJIdea*/config", ".IdeaIC*/config", ".AndroidStudio*/config")
                        .iterate(
                            Paths.get(home),
                            Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY
                        ).asSequence()
            (
                paths.flatMap { dir ->
                    sequenceOf(
                        Paths.get(dir.toString(), "codestyles", "$codeStyleName.xml") to
                            overwriteWithResource("/config/codestyles/ktlint.xml") { resource ->
                                resource
                                    .replace(
                                        "code_scheme name=\"ktlint\"",
                                        "code_scheme name=\"$codeStyleName\""
                                    )
                                    .replace(
                                        "option name=\"INDENT_SIZE\" value=\"4\"",
                                        "option name=\"INDENT_SIZE\" value=\"$indentSize\""
                                    )
                                    .replace(
                                        "option name=\"CONTINUATION_INDENT_SIZE\" value=\"8\"",
                                        "option name=\"CONTINUATION_INDENT_SIZE\" value=\"$continuationIndentSize\""
                                    )
                            },
                        Paths.get(dir.toString(), "options", "code.style.schemes.xml") to
                            overwriteWithResource("/config/options/code.style.schemes.xml") { content ->
                                content
                                    .replace(
                                        "option name=\"CURRENT_SCHEME_NAME\" value=\"ktlint\"",
                                        "option name=\"CURRENT_SCHEME_NAME\" value=\"$codeStyleName\""
                                    )
                            },
                        Paths.get(dir.toString(), "inspection", "ktlint.xml") to
                            overwriteWithResource("/config/inspection/ktlint.xml"),
                        Paths.get(dir.toString(), "options", "editor.codeinsight.xml").let { src ->
                            src to {
                                var arr = "<application></application>".toByteArray()
                                try {
                                    arr = Files.readAllBytes(src)
                                } catch (e: IOException) {
                                    if (e !is NoSuchFileException) {
                                        throw e
                                    }
                                }
                                try {
                                    enableOptimizeImportsOnTheFlyInsideWorkspace(arr)
                                } catch (e: Exception) {
                                    throw IOException("Failed to enable \"Optimize imports on the fly\" ($src)", e)
                                }
                            }
                        }
                    )
                } + sequenceOf(
                    Paths.get(workDir.toString(), ".idea", "codeStyleSettings.xml") to
                        overwriteWithResource("/config/.idea/codeStyleSettings.xml") { content ->
                            content.replace(
                                "option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"ktlint\"",
                                "option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"$codeStyleName\""
                            )
                        },
                    Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "profiles_settings.xml") to
                        overwriteWithResource("/config/.idea/inspectionProfiles/profiles_settings.xml")
                )
                ).toList()
        }
        if (!dryRun) {
            updates.forEach { (path, contentSupplier) ->
                Files.createDirectories(path.parent)
                Files.write(path, contentSupplier())
            }
        }
        return updates.map { (path) -> path }.toTypedArray()
    }

    private fun overwriteWithResource(resource: String, transformer: ((String) -> String) = { it }): () -> ByteArray = {
        transformer(getResourceText(resource)).toByteArray(charset("UTF-8"))
    }

    private fun enableOptimizeImportsOnTheFly(arr: ByteArray): ByteArray {
        /*
        <application>
          <component name="CodeInsightSettings">
            <option name="OPTIMIZE_IMPORTS_ON_THE_FLY" value="true" />
            ...
          </component>
          ...
        </application>
        */
        val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(ByteArrayInputStream(arr))
        val xpath = XPathFactory.newInstance().newXPath()
        var cis = xpath.evaluate(
            "//component[@name='CodeInsightSettings']",
            doc, XPathConstants.NODE
        ) as Element?
        if (cis == null) {
            cis = doc.createElement("component")
            cis.setAttribute("name", "CodeInsightSettings")
            cis = doc.documentElement.appendChild(cis) as Element
        }
        var oiotf = xpath.evaluate(
            "//option[@name='OPTIMIZE_IMPORTS_ON_THE_FLY']",
            cis, XPathConstants.NODE
        ) as Element?
        if (oiotf == null) {
            oiotf = doc.createElement("option")
            oiotf.setAttribute("name", "OPTIMIZE_IMPORTS_ON_THE_FLY")
            oiotf = cis.appendChild(oiotf) as Element
        }
        oiotf.setAttribute("value", "true")
        val transformer = TransformerFactory.newInstance().newTransformer()
        val out = ByteArrayOutputStream()
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
        transformer.transform(DOMSource(doc), StreamResult(out))
        return out.toByteArray()
    }

    private fun enableOptimizeImportsOnTheFlyInsideWorkspace(arr: ByteArray): ByteArray {
        /*
        <project>
          <component name="CodeInsightWorkspaceSettings">
            <option name="optimizeImportsOnTheFly" value="false" />
            ...
          </component>
          ...
        </project>
        */
        val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(ByteArrayInputStream(arr))
        val xpath = XPathFactory.newInstance().newXPath()
        var cis = xpath.evaluate(
            "//component[@name='CodeInsightWorkspaceSettings']",
            doc, XPathConstants.NODE
        ) as Element?
        if (cis == null) {
            cis = doc.createElement("component")
            cis.setAttribute("name", "CodeInsightWorkspaceSettings")
            cis = doc.documentElement.appendChild(cis) as Element
        }
        var oiotf = xpath.evaluate(
            "//option[@name='optimizeImportsOnTheFly']",
            cis, XPathConstants.NODE
        ) as Element?
        if (oiotf == null) {
            oiotf = doc.createElement("option")
            oiotf.setAttribute("name", "optimizeImportsOnTheFly")
            oiotf = cis.appendChild(oiotf) as Element
        }
        oiotf.setAttribute("value", "true")
        val transformer = TransformerFactory.newInstance().newTransformer()
        val out = ByteArrayOutputStream()
        transformer.transform(DOMSource(doc), StreamResult(out))
        return out.toByteArray()
    }

    private fun getResourceText(name: String) =
        this::class.java.getResourceAsStream(name).readBytes().toString(Charset.forName("UTF-8"))

    class ProjectNotFoundException : RuntimeException()
}

package com.pinterest.ktlint.internal

import java.io.File
import org.apache.maven.repository.internal.MavenRepositorySystemUtils
import org.eclipse.aether.DefaultRepositorySystemSession
import org.eclipse.aether.RepositorySystem
import org.eclipse.aether.RepositorySystemSession
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.collection.CollectRequest
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
import org.eclipse.aether.graph.Dependency
import org.eclipse.aether.impl.DefaultServiceLocator
import org.eclipse.aether.repository.LocalRepository
import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.repository.RepositoryPolicy
import org.eclipse.aether.resolution.DependencyRequest
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
import org.eclipse.aether.spi.connector.transport.TransporterFactory
import org.eclipse.aether.transfer.TransferEvent
import org.eclipse.aether.transfer.TransferListener
import org.eclipse.aether.transport.file.FileTransporterFactory
import org.eclipse.aether.transport.http.HttpTransporterFactory
import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator

class MavenDependencyResolver(
    baseDir: File,
    val repositories: Iterable<RemoteRepository>,
    forceUpdate: Boolean
) {

    private val repoSystem: RepositorySystem
    private val session: RepositorySystemSession

    init {
        val locator = MavenRepositorySystemUtils.newServiceLocator()
        locator.addService(RepositoryConnectorFactory::class.java, BasicRepositoryConnectorFactory::class.java)
        locator.addService(TransporterFactory::class.java, FileTransporterFactory::class.java)
        locator.addService(TransporterFactory::class.java, HttpTransporterFactory::class.java)
        locator.setErrorHandler(object : DefaultServiceLocator.ErrorHandler() {
            override fun serviceCreationFailed(type: Class<*>?, impl: Class<*>?, ex: Throwable) {
                throw ex
            }
        })
        repoSystem = locator.getService(RepositorySystem::class.java)
        session = MavenRepositorySystemUtils.newSession()
        session.localRepositoryManager = repoSystem.newLocalRepositoryManager(session, LocalRepository(baseDir))
        session.updatePolicy = if (forceUpdate) {
            RepositoryPolicy.UPDATE_POLICY_ALWAYS
        } else {
            RepositoryPolicy.UPDATE_POLICY_NEVER
        }
    }

    fun setTransferEventListener(listener: (event: TransferEvent) -> Unit) {
        (session as DefaultRepositorySystemSession).transferListener = object : TransferListener {

            override fun transferProgressed(event: TransferEvent) = listener(event)
            override fun transferStarted(event: TransferEvent) = listener(event)
            override fun transferInitiated(event: TransferEvent) = listener(event)
            override fun transferSucceeded(event: TransferEvent) = listener(event)
            override fun transferCorrupted(event: TransferEvent) = listener(event)
            override fun transferFailed(event: TransferEvent) = listener(event)
        }
    }

    fun resolve(vararg artifacts: Artifact): Collection<File> {
        val collectRequest = CollectRequest()
        artifacts.forEach {
            collectRequest.addDependency(Dependency(it, "compile"))
        }
        repositories.forEach {
            collectRequest.addRepository(it)
        }
        val node = repoSystem.collectDependencies(session, collectRequest).root
        repoSystem.resolveDependencies(session, DependencyRequest().apply { root = node })
        return PreorderNodeListGenerator().apply { node.accept(this) }.files
    }
}

package com.pinterest.ktlint.core.ast

import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
import org.jetbrains.kotlin.kdoc.parser.KDocElementTypes
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes

@Suppress("unused")
object ElementType {
    val FILE: IElementType = KtStubElementTypes.FILE

    // KtNodeTypes
    val CLASS: IElementType = KtNodeTypes.CLASS
    val FUN: IElementType = KtNodeTypes.FUN
    val PROPERTY: IElementType = KtNodeTypes.PROPERTY
    val DESTRUCTURING_DECLARATION: IElementType = KtNodeTypes.DESTRUCTURING_DECLARATION
    val DESTRUCTURING_DECLARATION_ENTRY: IElementType = KtNodeTypes.DESTRUCTURING_DECLARATION_ENTRY
    val OBJECT_DECLARATION: IElementType = KtNodeTypes.OBJECT_DECLARATION
    val TYPEALIAS: IElementType = KtNodeTypes.TYPEALIAS
    val ENUM_ENTRY: IElementType = KtNodeTypes.ENUM_ENTRY
    val CLASS_INITIALIZER: IElementType = KtNodeTypes.CLASS_INITIALIZER
    val SCRIPT_INITIALIZER: IElementType = KtNodeTypes.SCRIPT_INITIALIZER
    val SECONDARY_CONSTRUCTOR: IElementType = KtNodeTypes.SECONDARY_CONSTRUCTOR
    val PRIMARY_CONSTRUCTOR: IElementType = KtNodeTypes.PRIMARY_CONSTRUCTOR
    val TYPE_PARAMETER_LIST: IElementType = KtNodeTypes.TYPE_PARAMETER_LIST
    val TYPE_PARAMETER: IElementType = KtNodeTypes.TYPE_PARAMETER
    val SUPER_TYPE_LIST: IElementType = KtNodeTypes.SUPER_TYPE_LIST
    val DELEGATED_SUPER_TYPE_ENTRY: IElementType = KtNodeTypes.DELEGATED_SUPER_TYPE_ENTRY
    val SUPER_TYPE_CALL_ENTRY: IElementType = KtNodeTypes.SUPER_TYPE_CALL_ENTRY
    val SUPER_TYPE_ENTRY: IElementType = KtNodeTypes.SUPER_TYPE_ENTRY
    val PROPERTY_DELEGATE: IElementType = KtNodeTypes.PROPERTY_DELEGATE
    val CONSTRUCTOR_CALLEE: IElementType = KtNodeTypes.CONSTRUCTOR_CALLEE
    val VALUE_PARAMETER_LIST: IElementType = KtNodeTypes.VALUE_PARAMETER_LIST
    val VALUE_PARAMETER: IElementType = KtNodeTypes.VALUE_PARAMETER
    val CLASS_BODY: IElementType = KtNodeTypes.CLASS_BODY
    val IMPORT_LIST: IElementType = KtNodeTypes.IMPORT_LIST
    val FILE_ANNOTATION_LIST: IElementType = KtNodeTypes.FILE_ANNOTATION_LIST
    val IMPORT_DIRECTIVE: IElementType = KtNodeTypes.IMPORT_DIRECTIVE
    val IMPORT_ALIAS: IElementType = KtNodeTypes.IMPORT_ALIAS
    val MODIFIER_LIST: IElementType = KtNodeTypes.MODIFIER_LIST
    val ANNOTATION: IElementType = KtNodeTypes.ANNOTATION
    val ANNOTATION_ENTRY: IElementType = KtNodeTypes.ANNOTATION_ENTRY
    val ANNOTATION_TARGET: IElementType = KtNodeTypes.ANNOTATION_TARGET
    val TYPE_ARGUMENT_LIST: IElementType = KtNodeTypes.TYPE_ARGUMENT_LIST
    val VALUE_ARGUMENT_LIST: IElementType = KtNodeTypes.VALUE_ARGUMENT_LIST
    val VALUE_ARGUMENT: IElementType = KtNodeTypes.VALUE_ARGUMENT
    val LAMBDA_ARGUMENT: IElementType = KtNodeTypes.LAMBDA_ARGUMENT
    val VALUE_ARGUMENT_NAME: IElementType = KtNodeTypes.VALUE_ARGUMENT_NAME
    val TYPE_REFERENCE: IElementType = KtNodeTypes.TYPE_REFERENCE
    val USER_TYPE: IElementType = KtNodeTypes.USER_TYPE
    val DYNAMIC_TYPE: IElementType = KtNodeTypes.DYNAMIC_TYPE
    val FUNCTION_TYPE: IElementType = KtNodeTypes.FUNCTION_TYPE
    val FUNCTION_TYPE_RECEIVER: IElementType = KtNodeTypes.FUNCTION_TYPE_RECEIVER
    val NULLABLE_TYPE: IElementType = KtNodeTypes.NULLABLE_TYPE
    val TYPE_PROJECTION: IElementType = KtNodeTypes.TYPE_PROJECTION
    val PROPERTY_ACCESSOR: IElementType = KtNodeTypes.PROPERTY_ACCESSOR
    val INITIALIZER_LIST: IElementType = KtNodeTypes.INITIALIZER_LIST
    val TYPE_CONSTRAINT_LIST: IElementType = KtNodeTypes.TYPE_CONSTRAINT_LIST
    val TYPE_CONSTRAINT: IElementType = KtNodeTypes.TYPE_CONSTRAINT
    val CONSTRUCTOR_DELEGATION_CALL: IElementType = KtNodeTypes.CONSTRUCTOR_DELEGATION_CALL
    val CONSTRUCTOR_DELEGATION_REFERENCE: IElementType = KtNodeTypes.CONSTRUCTOR_DELEGATION_REFERENCE
    val NULL: IElementType = KtNodeTypes.NULL
    val BOOLEAN_CONSTANT: IElementType = KtNodeTypes.BOOLEAN_CONSTANT
    val FLOAT_CONSTANT: IElementType = KtNodeTypes.FLOAT_CONSTANT
    val CHARACTER_CONSTANT: IElementType = KtNodeTypes.CHARACTER_CONSTANT
    val INTEGER_CONSTANT: IElementType = KtNodeTypes.INTEGER_CONSTANT
    val STRING_TEMPLATE: IElementType = KtNodeTypes.STRING_TEMPLATE
    val LONG_STRING_TEMPLATE_ENTRY: IElementType = KtNodeTypes.LONG_STRING_TEMPLATE_ENTRY
    val SHORT_STRING_TEMPLATE_ENTRY: IElementType = KtNodeTypes.SHORT_STRING_TEMPLATE_ENTRY
    val LITERAL_STRING_TEMPLATE_ENTRY: IElementType = KtNodeTypes.LITERAL_STRING_TEMPLATE_ENTRY
    val ESCAPE_STRING_TEMPLATE_ENTRY: IElementType = KtNodeTypes.ESCAPE_STRING_TEMPLATE_ENTRY
    val PARENTHESIZED: IElementType = KtNodeTypes.PARENTHESIZED
    val RETURN: IElementType = KtNodeTypes.RETURN
    val THROW: IElementType = KtNodeTypes.THROW
    val CONTINUE: IElementType = KtNodeTypes.CONTINUE
    val BREAK: IElementType = KtNodeTypes.BREAK
    val IF: IElementType = KtNodeTypes.IF
    val CONDITION: IElementType = KtNodeTypes.CONDITION
    val THEN: IElementType = KtNodeTypes.THEN
    val ELSE: IElementType = KtNodeTypes.ELSE
    val TRY: IElementType = KtNodeTypes.TRY
    val CATCH: IElementType = KtNodeTypes.CATCH
    val FINALLY: IElementType = KtNodeTypes.FINALLY
    val FOR: IElementType = KtNodeTypes.FOR
    val WHILE: IElementType = KtNodeTypes.WHILE
    val DO_WHILE: IElementType = KtNodeTypes.DO_WHILE
    val LOOP_RANGE: IElementType = KtNodeTypes.LOOP_RANGE
    val BODY: IElementType = KtNodeTypes.BODY
    val BLOCK: IElementType = KtNodeTypes.BLOCK
    val LAMBDA_EXPRESSION: IElementType = KtNodeTypes.LAMBDA_EXPRESSION
    val FUNCTION_LITERAL: IElementType = KtNodeTypes.FUNCTION_LITERAL
    val ANNOTATED_EXPRESSION: IElementType = KtNodeTypes.ANNOTATED_EXPRESSION
    val REFERENCE_EXPRESSION: IElementType = KtNodeTypes.REFERENCE_EXPRESSION
    val ENUM_ENTRY_SUPERCLASS_REFERENCE_EXPRESSION: IElementType = KtStubElementTypes.ENUM_ENTRY_SUPERCLASS_REFERENCE_EXPRESSION
    val OPERATION_REFERENCE: IElementType = KtNodeTypes.OPERATION_REFERENCE
    val LABEL: IElementType = KtNodeTypes.LABEL
    val LABEL_QUALIFIER: IElementType = KtNodeTypes.LABEL_QUALIFIER
    val THIS_EXPRESSION: IElementType = KtNodeTypes.THIS_EXPRESSION
    val SUPER_EXPRESSION: IElementType = KtNodeTypes.SUPER_EXPRESSION
    val BINARY_EXPRESSION: IElementType = KtNodeTypes.BINARY_EXPRESSION
    val BINARY_WITH_TYPE: IElementType = KtNodeTypes.BINARY_WITH_TYPE
    val IS_EXPRESSION: IElementType = KtNodeTypes.IS_EXPRESSION
    val PREFIX_EXPRESSION: IElementType = KtNodeTypes.PREFIX_EXPRESSION
    val POSTFIX_EXPRESSION: IElementType = KtNodeTypes.POSTFIX_EXPRESSION
    val LABELED_EXPRESSION: IElementType = KtNodeTypes.LABELED_EXPRESSION
    val CALL_EXPRESSION: IElementType = KtNodeTypes.CALL_EXPRESSION
    val ARRAY_ACCESS_EXPRESSION: IElementType = KtNodeTypes.ARRAY_ACCESS_EXPRESSION
    val INDICES: IElementType = KtNodeTypes.INDICES
    val DOT_QUALIFIED_EXPRESSION: IElementType = KtStubElementTypes.DOT_QUALIFIED_EXPRESSION
    val CALLABLE_REFERENCE_EXPRESSION: IElementType = KtNodeTypes.CALLABLE_REFERENCE_EXPRESSION
    val CLASS_LITERAL_EXPRESSION: IElementType = KtNodeTypes.CLASS_LITERAL_EXPRESSION
    val SAFE_ACCESS_EXPRESSION: IElementType = KtNodeTypes.SAFE_ACCESS_EXPRESSION
    val OBJECT_LITERAL: IElementType = KtNodeTypes.OBJECT_LITERAL
    val WHEN: IElementType = KtNodeTypes.WHEN
    val WHEN_ENTRY: IElementType = KtNodeTypes.WHEN_ENTRY
    val WHEN_CONDITION_IN_RANGE: IElementType = KtNodeTypes.WHEN_CONDITION_IN_RANGE
    val WHEN_CONDITION_IS_PATTERN: IElementType = KtNodeTypes.WHEN_CONDITION_IS_PATTERN
    val WHEN_CONDITION_WITH_EXPRESSION: IElementType = KtNodeTypes.WHEN_CONDITION_EXPRESSION
    val COLLECTION_LITERAL_EXPRESSION: IElementType = KtNodeTypes.COLLECTION_LITERAL_EXPRESSION
    val PACKAGE_DIRECTIVE: IElementType = KtNodeTypes.PACKAGE_DIRECTIVE
    val SCRIPT: IElementType = KtNodeTypes.SCRIPT
    val TYPE_CODE_FRAGMENT: IElementType = KtNodeTypes.TYPE_CODE_FRAGMENT
    val EXPRESSION_CODE_FRAGMENT: IElementType = KtNodeTypes.EXPRESSION_CODE_FRAGMENT
    val BLOCK_CODE_FRAGMENT: IElementType = KtNodeTypes.BLOCK_CODE_FRAGMENT

    // KtTokens
    val EOF: IElementType = KtTokens.EOF
    val RESERVED: IElementType = KtTokens.RESERVED
    val BLOCK_COMMENT: IElementType = KtTokens.BLOCK_COMMENT
    val EOL_COMMENT: IElementType = KtTokens.EOL_COMMENT
    val SHEBANG_COMMENT: IElementType = KtTokens.SHEBANG_COMMENT
    val WHITE_SPACE: IElementType = KtTokens.WHITE_SPACE
    val INTEGER_LITERAL: IElementType = KtTokens.INTEGER_LITERAL
    val FLOAT_LITERAL: IElementType = KtTokens.FLOAT_LITERAL // FLOAT_CONSTANT
    val CHARACTER_LITERAL: IElementType = KtTokens.CHARACTER_LITERAL
    val CLOSING_QUOTE: IElementType = KtTokens.CLOSING_QUOTE
    val OPEN_QUOTE: IElementType = KtTokens.OPEN_QUOTE
    val REGULAR_STRING_PART: IElementType = KtTokens.REGULAR_STRING_PART
    val ESCAPE_SEQUENCE: IElementType = KtTokens.ESCAPE_SEQUENCE
    val SHORT_TEMPLATE_ENTRY_START: IElementType = KtTokens.SHORT_TEMPLATE_ENTRY_START
    val LONG_TEMPLATE_ENTRY_START: IElementType = KtTokens.LONG_TEMPLATE_ENTRY_START
    val LONG_TEMPLATE_ENTRY_END: IElementType = KtTokens.LONG_TEMPLATE_ENTRY_END
    val DANGLING_NEWLINE: IElementType = KtTokens.DANGLING_NEWLINE
    val PACKAGE_KEYWORD: IElementType = KtTokens.PACKAGE_KEYWORD
    val AS_KEYWORD: IElementType = KtTokens.AS_KEYWORD
    val TYPEALIAS_KEYWORD: IElementType = KtTokens.TYPE_ALIAS_KEYWORD
    val CLASS_KEYWORD: IElementType = KtTokens.CLASS_KEYWORD
    val THIS_KEYWORD: IElementType = KtTokens.THIS_KEYWORD
    val SUPER_KEYWORD: IElementType = KtTokens.SUPER_KEYWORD
    val VAL_KEYWORD: IElementType = KtTokens.VAL_KEYWORD
    val VAR_KEYWORD: IElementType = KtTokens.VAR_KEYWORD
    val FUN_KEYWORD: IElementType = KtTokens.FUN_KEYWORD
    val FOR_KEYWORD: IElementType = KtTokens.FOR_KEYWORD
    val NULL_KEYWORD: IElementType = KtTokens.NULL_KEYWORD
    val TRUE_KEYWORD: IElementType = KtTokens.TRUE_KEYWORD
    val FALSE_KEYWORD: IElementType = KtTokens.FALSE_KEYWORD
    val IS_KEYWORD: IElementType = KtTokens.IS_KEYWORD
    val IN_KEYWORD: IElementType = KtTokens.IN_KEYWORD
    val THROW_KEYWORD: IElementType = KtTokens.THROW_KEYWORD
    val RETURN_KEYWORD: IElementType = KtTokens.RETURN_KEYWORD
    val BREAK_KEYWORD: IElementType = KtTokens.BREAK_KEYWORD
    val CONTINUE_KEYWORD: IElementType = KtTokens.CONTINUE_KEYWORD
    val OBJECT_KEYWORD: IElementType = KtTokens.OBJECT_KEYWORD
    val IF_KEYWORD: IElementType = KtTokens.IF_KEYWORD
    val TRY_KEYWORD: IElementType = KtTokens.TRY_KEYWORD
    val ELSE_KEYWORD: IElementType = KtTokens.ELSE_KEYWORD
    val WHILE_KEYWORD: IElementType = KtTokens.WHILE_KEYWORD
    val DO_KEYWORD: IElementType = KtTokens.DO_KEYWORD
    val WHEN_KEYWORD: IElementType = KtTokens.WHEN_KEYWORD
    val INTERFACE_KEYWORD: IElementType = KtTokens.INTERFACE_KEYWORD
    val TYPEOF_KEYWORD: IElementType = KtTokens.TYPEOF_KEYWORD
    val AS_SAFE: IElementType = KtTokens.AS_SAFE
    val IDENTIFIER: IElementType = KtTokens.IDENTIFIER
    val FIELD_IDENTIFIER: IElementType = KtTokens.FIELD_IDENTIFIER
    val LBRACKET: IElementType = KtTokens.LBRACKET
    val RBRACKET: IElementType = KtTokens.RBRACKET
    val LBRACE: IElementType = KtTokens.LBRACE
    val RBRACE: IElementType = KtTokens.RBRACE
    val LPAR: IElementType = KtTokens.LPAR
    val RPAR: IElementType = KtTokens.RPAR
    val DOT: IElementType = KtTokens.DOT
    val PLUSPLUS: IElementType = KtTokens.PLUSPLUS
    val MINUSMINUS: IElementType = KtTokens.MINUSMINUS
    val MUL: IElementType = KtTokens.MUL
    val PLUS: IElementType = KtTokens.PLUS
    val MINUS: IElementType = KtTokens.MINUS
    val EXCL: IElementType = KtTokens.EXCL
    val DIV: IElementType = KtTokens.DIV
    val PERC: IElementType = KtTokens.PERC
    val LT: IElementType = KtTokens.LT
    val GT: IElementType = KtTokens.GT
    val LTEQ: IElementType = KtTokens.LTEQ
    val GTEQ: IElementType = KtTokens.GTEQ
    val EQEQEQ: IElementType = KtTokens.EQEQEQ
    val ARROW: IElementType = KtTokens.ARROW
    val DOUBLE_ARROW: IElementType = KtTokens.DOUBLE_ARROW
    val EXCLEQEQEQ: IElementType = KtTokens.EXCLEQEQEQ
    val EQEQ: IElementType = KtTokens.EQEQ
    val EXCLEQ: IElementType = KtTokens.EXCLEQ
    val EXCLEXCL: IElementType = KtTokens.EXCLEXCL
    val ANDAND: IElementType = KtTokens.ANDAND
    val OROR: IElementType = KtTokens.OROR
    val SAFE_ACCESS: IElementType = KtTokens.SAFE_ACCESS
    val ELVIS: IElementType = KtTokens.ELVIS
    val QUEST: IElementType = KtTokens.QUEST
    val COLONCOLON: IElementType = KtTokens.COLONCOLON
    val COLON: IElementType = KtTokens.COLON
    val SEMICOLON: IElementType = KtTokens.SEMICOLON
    val DOUBLE_SEMICOLON: IElementType = KtTokens.DOUBLE_SEMICOLON
    val RANGE: IElementType = KtTokens.RANGE
    val EQ: IElementType = KtTokens.EQ
    val MULTEQ: IElementType = KtTokens.MULTEQ
    val DIVEQ: IElementType = KtTokens.DIVEQ
    val PERCEQ: IElementType = KtTokens.PERCEQ
    val PLUSEQ: IElementType = KtTokens.PLUSEQ
    val MINUSEQ: IElementType = KtTokens.MINUSEQ
    val NOT_IN: IElementType = KtTokens.NOT_IN
    val NOT_IS: IElementType = KtTokens.NOT_IS
    val HASH: IElementType = KtTokens.HASH
    val AT: IElementType = KtTokens.AT
    val COMMA: IElementType = KtTokens.COMMA
    val EOL_OR_SEMICOLON: IElementType = KtTokens.EOL_OR_SEMICOLON
    val FILE_KEYWORD: IElementType = KtTokens.FILE_KEYWORD
    val FIELD_KEYWORD: IElementType = KtTokens.FIELD_KEYWORD
    val PROPERTY_KEYWORD: IElementType = KtTokens.PROPERTY_KEYWORD
    val RECEIVER_KEYWORD: IElementType = KtTokens.RECEIVER_KEYWORD
    val PARAM_KEYWORD: IElementType = KtTokens.PARAM_KEYWORD
    val SETPARAM_KEYWORD: IElementType = KtTokens.SETPARAM_KEYWORD
    val DELEGATE_KEYWORD: IElementType = KtTokens.DELEGATE_KEYWORD
    val IMPORT_KEYWORD: IElementType = KtTokens.IMPORT_KEYWORD
    val WHERE_KEYWORD: IElementType = KtTokens.WHERE_KEYWORD
    val BY_KEYWORD: IElementType = KtTokens.BY_KEYWORD
    val GET_KEYWORD: IElementType = KtTokens.GET_KEYWORD
    val SET_KEYWORD: IElementType = KtTokens.SET_KEYWORD
    val CONSTRUCTOR_KEYWORD: IElementType = KtTokens.CONSTRUCTOR_KEYWORD
    val INIT_KEYWORD: IElementType = KtTokens.INIT_KEYWORD
    val ABSTRACT_KEYWORD: IElementType = KtTokens.ABSTRACT_KEYWORD
    val ENUM_KEYWORD: IElementType = KtTokens.ENUM_KEYWORD
    val OPEN_KEYWORD: IElementType = KtTokens.OPEN_KEYWORD
    val INNER_KEYWORD: IElementType = KtTokens.INNER_KEYWORD
    val OVERRIDE_KEYWORD: IElementType = KtTokens.OVERRIDE_KEYWORD
    val PRIVATE_KEYWORD: IElementType = KtTokens.PRIVATE_KEYWORD
    val PUBLIC_KEYWORD: IElementType = KtTokens.PUBLIC_KEYWORD
    val INTERNAL_KEYWORD: IElementType = KtTokens.INTERNAL_KEYWORD
    val PROTECTED_KEYWORD: IElementType = KtTokens.PROTECTED_KEYWORD
    val CATCH_KEYWORD: IElementType = KtTokens.CATCH_KEYWORD
    val OUT_KEYWORD: IElementType = KtTokens.OUT_KEYWORD
    val VARARG_KEYWORD: IElementType = KtTokens.VARARG_KEYWORD
    val REIFIED_KEYWORD: IElementType = KtTokens.REIFIED_KEYWORD
    val DYNAMIC_KEYWORD: IElementType = KtTokens.DYNAMIC_KEYWORD
    val COMPANION_KEYWORD: IElementType = KtTokens.COMPANION_KEYWORD
    val SEALED_KEYWORD: IElementType = KtTokens.SEALED_KEYWORD
    val DEFAULT_VISIBILITY_KEYWORD: IElementType = PUBLIC_KEYWORD
    val FINALLY_KEYWORD: IElementType = KtTokens.FINALLY_KEYWORD
    val FINAL_KEYWORD: IElementType = KtTokens.FINAL_KEYWORD
    val LATEINIT_KEYWORD: IElementType = KtTokens.LATEINIT_KEYWORD
    val DATA_KEYWORD: IElementType = KtTokens.DATA_KEYWORD
    val INLINE_KEYWORD: IElementType = KtTokens.INLINE_KEYWORD
    val NOINLINE_KEYWORD: IElementType = KtTokens.NOINLINE_KEYWORD
    val TAILREC_KEYWORD: IElementType = KtTokens.TAILREC_KEYWORD
    val EXTERNAL_KEYWORD: IElementType = KtTokens.EXTERNAL_KEYWORD
    val ANNOTATION_KEYWORD: IElementType = KtTokens.ANNOTATION_KEYWORD
    val CROSSINLINE_KEYWORD: IElementType = KtTokens.CROSSINLINE_KEYWORD
    val OPERATOR_KEYWORD: IElementType = KtTokens.OPERATOR_KEYWORD
    val INFIX_KEYWORD: IElementType = KtTokens.INFIX_KEYWORD
    val CONST_KEYWORD: IElementType = KtTokens.CONST_KEYWORD
    val SUSPEND_KEYWORD: IElementType = KtTokens.SUSPEND_KEYWORD
    val HEADER_KEYWORD: IElementType = KtTokens.HEADER_KEYWORD
    val IMPL_KEYWORD: IElementType = KtTokens.IMPL_KEYWORD
    val EXPECT_KEYWORD: IElementType = KtTokens.EXPECT_KEYWORD
    val ACTUAL_KEYWORD: IElementType = KtTokens.ACTUAL_KEYWORD

    // KDocTokens
    val KDOC: IElementType = KDocTokens.KDOC
    val KDOC_START: IElementType = KDocTokens.START
    val KDOC_END: IElementType = KDocTokens.END
    val KDOC_LEADING_ASTERISK: IElementType = KDocTokens.LEADING_ASTERISK
    val KDOC_TEXT: IElementType = KDocTokens.TEXT
    val KDOC_CODE_BLOCK_TEXT: IElementType = KDocTokens.CODE_BLOCK_TEXT
    val KDOC_TAG_NAME: IElementType = KDocTokens.TAG_NAME
    val KDOC_MARKDOWN_LINK: IElementType = KDocTokens.MARKDOWN_LINK
    val KDOC_MARKDOWN_ESCAPED_CHAR: IElementType = KDocTokens.MARKDOWN_ESCAPED_CHAR
    val KDOC_MARKDOWN_INLINE_LINK: IElementType = KDocTokens.MARKDOWN_INLINE_LINK
    val KDOC_SECTION: IElementType = KDocElementTypes.KDOC_SECTION
    val KDOC_TAG: IElementType = KDocElementTypes.KDOC_TAG
    val KDOC_NAME: IElementType = KDocElementTypes.KDOC_NAME
}

package com.pinterest.ktlint.core.ast

import com.pinterest.ktlint.core.ast.ElementType.STRING_TEMPLATE
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import kotlin.reflect.KClass
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType

fun ASTNode.nextLeaf(includeEmpty: Boolean = false, skipSubtree: Boolean = false): ASTNode? {
    var n = if (skipSubtree) this.lastChildLeafOrSelf().nextLeafAny() else this.nextLeafAny()
    if (!includeEmpty) {
        while (n != null && n.textLength == 0) {
            n = n.nextLeafAny()
        }
    }
    return n
}

fun ASTNode.nextLeaf(p: (ASTNode) -> Boolean): ASTNode? {
    var n = this.nextLeafAny()
    while (n != null && !p(n)) {
        n = n.nextLeafAny()
    }
    return n
}

private fun ASTNode.nextLeafAny(): ASTNode? {
    var n = this
    if (n.firstChildNode != null) {
        do {
            n = n.firstChildNode
        } while (n.firstChildNode != null)
        return n
    }
    return n.nextLeafStrict()
}

private fun ASTNode.nextLeafStrict(): ASTNode? {
    val nextSibling: ASTNode? = treeNext
    if (nextSibling != null) {
        return nextSibling.firstChildLeafOrSelf()
    }
    return treeParent?.nextLeafStrict()
}

fun ASTNode.firstChildLeafOrSelf(): ASTNode {
    var n = this
    if (n.firstChildNode != null) {
        do {
            n = n.firstChildNode
        } while (n.firstChildNode != null)
        return n
    }
    return n
}

fun ASTNode.prevLeaf(includeEmpty: Boolean = false): ASTNode? {
    var n = this.prevLeafAny()
    if (!includeEmpty) {
        while (n != null && n.textLength == 0) {
            n = n.prevLeafAny()
        }
    }
    return n
}

fun ASTNode.prevLeaf(p: (ASTNode) -> Boolean): ASTNode? {
    var n = this.prevLeafAny()
    while (n != null && !p(n)) {
        n = n.prevLeafAny()
    }
    return n
}

private fun ASTNode.prevLeafAny(): ASTNode? {
    val prevSibling = treePrev
    if (prevSibling != null) {
        return treePrev.lastChildLeafOrSelf()
    }
    return treeParent?.prevLeafAny()
}

fun ASTNode.lastChildLeafOrSelf(): ASTNode {
    var n = this
    if (n.lastChildNode != null) {
        do {
            n = n.lastChildNode
        } while (n.lastChildNode != null)
        return n
    }
    return n
}

fun ASTNode.prevCodeLeaf(includeEmpty: Boolean = false): ASTNode? {
    var n = prevLeaf(includeEmpty)
    while (n != null && (n.elementType == WHITE_SPACE || n.isPartOfComment())) {
        n = n.prevLeaf(includeEmpty)
    }
    return n
}

fun ASTNode.nextCodeLeaf(includeEmpty: Boolean = false, skipSubtree: Boolean = false): ASTNode? {
    var n = nextLeaf(includeEmpty, skipSubtree)
    while (n != null && (n.elementType == WHITE_SPACE || n.isPartOfComment())) {
        n = n.nextLeaf(includeEmpty, skipSubtree)
    }
    return n
}

fun ASTNode.prevCodeSibling(): ASTNode? =
    prevSibling { it.elementType != WHITE_SPACE && !it.isPartOfComment() }

inline fun ASTNode.prevSibling(p: (ASTNode) -> Boolean): ASTNode? {
    var n = this.treePrev
    while (n != null) {
        if (p(n)) {
            return n
        }
        n = n.treePrev
    }
    return null
}

fun ASTNode.nextCodeSibling(): ASTNode? =
    nextSibling { it.elementType != WHITE_SPACE && !it.isPartOfComment() }

inline fun ASTNode.nextSibling(p: (ASTNode) -> Boolean): ASTNode? {
    var n = this.treeNext
    while (n != null) {
        if (p(n)) {
            return n
        }
        n = n.treeNext
    }
    return null
}

/**
 * @param elementType [ElementType].*
 */
fun ASTNode.parent(elementType: IElementType, strict: Boolean = true): ASTNode? {
    var n: ASTNode? = if (strict) this.treeParent else this
    while (n != null) {
        if (n.elementType == elementType) {
            return n
        }
        n = n.treeParent
    }
    return null
}

fun ASTNode.parent(p: (ASTNode) -> Boolean, strict: Boolean = true): ASTNode? {
    var n: ASTNode? = if (strict) this.treeParent else this
    while (n != null) {
        if (p(n)) {
            return n
        }
        n = n.treeParent
    }
    return null
}

/**
 * @param elementType [ElementType].*
 */
fun ASTNode.isPartOf(elementType: IElementType) =
    parent(elementType, strict = false) != null

fun ASTNode.isPartOf(klass: KClass<out PsiElement>): Boolean {
    var n: ASTNode? = this
    while (n != null) {
        if (klass.java.isInstance(n.psi)) {
            return true
        }
        n = n.treeParent
    }
    return false
}

fun ASTNode.isPartOfString() =
    parent(STRING_TEMPLATE, strict = false) != null

fun ASTNode?.isWhiteSpaceWithNewline() =
    this != null && elementType == WHITE_SPACE && textContains('\n')
fun ASTNode?.isWhiteSpaceWithoutNewline() =
    this != null && elementType == WHITE_SPACE && !textContains('\n')

fun ASTNode.isRoot() = elementType == ElementType.FILE
fun ASTNode.isLeaf() = firstChildNode == null

fun ASTNode.isPartOfComment() = parent({ it.psi is PsiComment }, strict = false) != null

fun ASTNode.children() =
    generateSequence(firstChildNode) { node -> node.treeNext }

fun LeafElement.upsertWhitespaceBeforeMe(text: String): LeafElement {
    val s = treePrev
    return if (s != null && s.elementType == WHITE_SPACE) {
        (s.psi as LeafElement).rawReplaceWithText(text)
    } else {
        PsiWhiteSpaceImpl(text).also { w ->
            (psi as LeafElement).rawInsertBeforeMe(w)
        }
    }
}
fun LeafElement.upsertWhitespaceAfterMe(text: String): LeafElement {
    val s = treeNext
    return if (s != null && s.elementType == WHITE_SPACE) {
        (s.psi as LeafElement).rawReplaceWithText(text)
    } else {
        PsiWhiteSpaceImpl(text).also { w ->
            (psi as LeafElement).rawInsertAfterMe(w)
        }
    }
}

fun ASTNode.visit(enter: (node: ASTNode) -> Unit) {
    enter(this)
    this.getChildren(null).forEach { it.visit(enter) }
}

fun ASTNode.visit(enter: (node: ASTNode) -> Unit, exit: (node: ASTNode) -> Unit) {
    enter(this)
    this.getChildren(null).forEach { it.visit(enter, exit) }
    exit(this)
}

package com.pinterest.ktlint.reporter.checkstyle

import com.pinterest.ktlint.core.LintError
import com.pinterest.ktlint.core.Reporter
import java.io.PrintStream
import java.util.ArrayList
import java.util.concurrent.ConcurrentHashMap

class CheckStyleReporter(val out: PrintStream) : Reporter {

    private val acc = ConcurrentHashMap<String, MutableList<LintError>>()

    override fun onLintError(file: String, err: LintError, corrected: Boolean) {
        if (!corrected) {
            acc.getOrPut(file) { ArrayList<LintError>() }.add(err)
        }
    }

    override fun afterAll() {
        out.println("""<?xml version="1.0" encoding="utf-8"?>""")
        out.println("""<checkstyle version="8.0">""")
        for ((file, errList) in acc.entries.sortedBy { it.key }) {
            out.println("""	<file name="${file.escapeXMLAttrValue()}">""")
            for ((line, col, ruleId, detail) in errList) {
                out.println(
                    """		<error line="$line" column="$col" severity="error" message="${
                    detail.escapeXMLAttrValue()
                    }" source="$ruleId" />"""
                )
            }
            out.println("""	</file>""")
        }
        out.println("""</checkstyle>""")
    }

    private fun String.escapeXMLAttrValue() =
        this.replace("&", "&").replace("\"", """).replace("'", "'")
            .replace("<", "<").replace(">", ">")
}

package com.pinterest.ktlint.reporter.checkstyle

import com.pinterest.ktlint.core.Reporter
import com.pinterest.ktlint.core.ReporterProvider
import java.io.PrintStream

class CheckStyleReporterProvider : ReporterProvider {
    override val id: String = "checkstyle"
    override fun get(out: PrintStream, opt: Map<String, String>): Reporter = CheckStyleReporter(out)
}

package com.pinterest.ktlint.reporter.json

import com.pinterest.ktlint.core.LintError
import com.pinterest.ktlint.core.Reporter
import java.io.PrintStream
import java.util.ArrayList
import java.util.concurrent.ConcurrentHashMap

class JsonReporter(val out: PrintStream) : Reporter {

    private val acc = ConcurrentHashMap<String, MutableList<LintError>>()

    override fun onLintError(file: String, err: LintError, corrected: Boolean) {
        if (!corrected) {
            acc.getOrPut(file) { ArrayList() }.add(err)
        }
    }

    override fun afterAll() {
        out.println("[")
        val indexLast = acc.size - 1
        for ((index, entry) in acc.entries.sortedBy { it.key }.withIndex()) {
            val (file, errList) = entry
            out.println("""	{""")
            out.println("""		"file": "${file.escapeJsonValue()}",""")
            out.println("""		"errors": [""")
            val errIndexLast = errList.size - 1
            for ((errIndex, err) in errList.withIndex()) {
                val (line, col, ruleId, detail) = err
                out.println("""			{""")
                out.println("""				"line": $line,""")
                out.println("""				"column": $col,""")
                out.println("""				"message": "${detail.escapeJsonValue()}",""")
                out.println("""				"rule": "$ruleId"""")
                out.println("""			}${if (errIndex != errIndexLast) "," else ""}""")
            }
            out.println("""		]""")
            out.println("""	}${if (index != indexLast) "," else ""}""")
        }
        out.println("]")
    }

    private fun String.escapeJsonValue() =
        this
            .replace("\\", "\\\\")
            .replace("\"", "\\\"")
            .replace("\b", "\\b")
            .replace("\n", "\\n")
            .replace("\r", "\\r")
            .replace("\t", "\\t")
}

package com.pinterest.ktlint.reporter.json

import com.pinterest.ktlint.core.Reporter
import com.pinterest.ktlint.core.ReporterProvider
import java.io.PrintStream

class JsonReporterProvider : ReporterProvider {
    override val id: String = "json"
    override fun get(out: PrintStream, opt: Map<String, String>): Reporter = JsonReporter(out)
}

package com.pinterest.ktlint.reporter.plain

import com.pinterest.ktlint.core.LintError
import com.pinterest.ktlint.core.Reporter
import com.pinterest.ktlint.reporter.plain.internal.Color
import com.pinterest.ktlint.reporter.plain.internal.color
import java.io.File
import java.io.PrintStream
import java.util.ArrayList
import java.util.concurrent.ConcurrentHashMap

class PlainReporter(
    val out: PrintStream,
    val verbose: Boolean = false,
    val groupByFile: Boolean = false,
    val color: Boolean = false,
    val pad: Boolean = false
) : Reporter {

    private val acc = ConcurrentHashMap<String, MutableList<LintError>>()

    override fun onLintError(file: String, err: LintError, corrected: Boolean) {
        if (!corrected) {
            if (groupByFile) {
                acc.getOrPut(file) { ArrayList<LintError>() }.add(err)
            } else {
                out.println(
                    "${colorFileName(file)}${":".gray()}${err.line}${
                    ":${"${err.col}:".let { if (pad) String.format("%-4s", it) else it}}".gray()
                    } ${err.detail}${if (verbose) " (${err.ruleId})".gray() else ""}"
                )
            }
        }
    }

    override fun after(file: String) {
        if (groupByFile) {
            val errList = acc[file] ?: return
            out.println(colorFileName(file))
            for ((line, col, ruleId, detail) in errList) {
                out.println(
                    "  $line${
                    ":${if (pad) String.format("%-3s", col) else "$col"}".gray()
                    } $detail${if (verbose) " ($ruleId)".gray() else ""}"
                )
            }
        }
    }

    private fun colorFileName(fileName: String): String {
        val name = fileName.substringAfterLast(File.separator)
        return fileName.substring(0, fileName.length - name.length).gray() + name
    }

    private fun String.gray() =
        if (color) this.color(Color.DARK_GRAY) else this
}

package com.pinterest.ktlint.reporter.plain

import com.pinterest.ktlint.core.Reporter
import com.pinterest.ktlint.core.ReporterProvider
import java.io.PrintStream

class PlainReporterProvider : ReporterProvider {

    override val id: String = "plain"

    override fun get(out: PrintStream, opt: Map<String, String>): Reporter =
        PlainReporter(
            out,
            verbose = opt["verbose"]?.emptyOrTrue() ?: false,
            groupByFile = opt["group_by_file"]?.emptyOrTrue() ?: false,
            color = opt["color"]?.emptyOrTrue() ?: false,
            pad = opt["pad"]?.emptyOrTrue() ?: false
        )

    private fun String.emptyOrTrue() = this == "" || this == "true"
}

package com.pinterest.ktlint.ruleset.experimental

import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider

class ExperimentalRuleSetProvider : RuleSetProvider {

    override fun get(): RuleSet = RuleSet(
        "experimental",
        ImportOrderingRule(),
        IndentationRule()
    )
}

package com.pinterest.ktlint.ruleset.experimental

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.IMPORT_DIRECTIVE
import com.pinterest.ktlint.core.ast.ElementType.IMPORT_LIST
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.PsiWhiteSpaceImpl

/**
 * Alphabetical with capital letters before lower case letters (e.g. Z before a).
 * No blank lines between major groups (android, com, junit, net, org, java, javax).
 * Single group regardless of import type.
 */
class ImportOrderingRule : Rule("import-ordering") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.elementType == IMPORT_LIST) {
            val children = node.getChildren(null)
            if (children.isNotEmpty()) {
                val imports = children.filter { it.elementType == IMPORT_DIRECTIVE }
                val sortedImports = imports.sortedBy { it.text }
                if (imports != sortedImports || hasTooMuchWhitespace(children)) {
                    emit(node.startOffset, "Imports must be ordered in lexicographic order without any empty lines in-between", true)
                    if (autoCorrect) {
                        node.removeRange(node.firstChildNode, node.lastChildNode.treeNext)
                        sortedImports.forEachIndexed { i, astNode ->
                            if (i > 0) {
                                node.addChild(PsiWhiteSpaceImpl("\n"), null)
                            }
                            node.addChild(astNode, null)
                        }
                    }
                }
            }
        }
    }

    private fun hasTooMuchWhitespace(nodes: Array<ASTNode>): Boolean {
        return nodes.any { it is PsiWhiteSpace && (it as PsiWhiteSpace).text != "\n" }
    }
}

package com.pinterest.ktlint.ruleset.experimental

import com.pinterest.ktlint.core.EditorConfig
import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION
import com.pinterest.ktlint.core.ast.ElementType.ARROW
import com.pinterest.ktlint.core.ast.ElementType.BINARY_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.BINARY_WITH_TYPE
import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.BODY
import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.CLOSING_QUOTE
import com.pinterest.ktlint.core.ast.ElementType.COLON
import com.pinterest.ktlint.core.ast.ElementType.COMMA
import com.pinterest.ktlint.core.ast.ElementType.CONDITION
import com.pinterest.ktlint.core.ast.ElementType.DOT
import com.pinterest.ktlint.core.ast.ElementType.DOT_QUALIFIED_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.ELSE
import com.pinterest.ktlint.core.ast.ElementType.ELVIS
import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.EQ
import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_LITERAL
import com.pinterest.ktlint.core.ast.ElementType.GT
import com.pinterest.ktlint.core.ast.ElementType.KDOC
import com.pinterest.ktlint.core.ast.ElementType.KDOC_START
import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.LBRACE
import com.pinterest.ktlint.core.ast.ElementType.LBRACKET
import com.pinterest.ktlint.core.ast.ElementType.LITERAL_STRING_TEMPLATE_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.LONG_STRING_TEMPLATE_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.LPAR
import com.pinterest.ktlint.core.ast.ElementType.LT
import com.pinterest.ktlint.core.ast.ElementType.OBJECT_LITERAL
import com.pinterest.ktlint.core.ast.ElementType.OPEN_QUOTE
import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE
import com.pinterest.ktlint.core.ast.ElementType.PARENTHESIZED
import com.pinterest.ktlint.core.ast.ElementType.PROPERTY_ACCESSOR
import com.pinterest.ktlint.core.ast.ElementType.RBRACE
import com.pinterest.ktlint.core.ast.ElementType.RBRACKET
import com.pinterest.ktlint.core.ast.ElementType.REGULAR_STRING_PART
import com.pinterest.ktlint.core.ast.ElementType.RPAR
import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS
import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.STRING_TEMPLATE
import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_CALL_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST
import com.pinterest.ktlint.core.ast.ElementType.THEN
import com.pinterest.ktlint.core.ast.ElementType.TYPE_ARGUMENT_LIST
import com.pinterest.ktlint.core.ast.ElementType.TYPE_CONSTRAINT_LIST
import com.pinterest.ktlint.core.ast.ElementType.TYPE_PARAMETER_LIST
import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT
import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST
import com.pinterest.ktlint.core.ast.ElementType.WHEN_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.WHERE_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.children
import com.pinterest.ktlint.core.ast.isPartOf
import com.pinterest.ktlint.core.ast.isPartOfComment
import com.pinterest.ktlint.core.ast.isWhiteSpaceWithNewline
import com.pinterest.ktlint.core.ast.isWhiteSpaceWithoutNewline
import com.pinterest.ktlint.core.ast.nextCodeLeaf
import com.pinterest.ktlint.core.ast.nextCodeSibling
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.nextSibling
import com.pinterest.ktlint.core.ast.parent
import com.pinterest.ktlint.core.ast.prevCodeLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.prevSibling
import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe
import com.pinterest.ktlint.core.ast.visit
import java.lang.StringBuilder
import java.util.Deque
import java.util.LinkedList
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.KtSuperTypeList

/**
 * ktlint's rule that checks & corrects indentation.
 *
 * To keep things simple, we walk the AST twice:
 * - 1st pass - insert missing newlines (e.g. between parentheses of a multi-line function call)
 * - 2st pass - correct indentation
 *
 * Current limitations:
 * - indent_style=tab not supported.
 * - "all or nothing" (currently, rule can only be disabled for an entire file)
 */
class IndentationRule : Rule("indent"), Rule.Modifier.RestrictToRootLast {

    private companion object {
        // run `KTLINT_DEBUG=experimental/indent ktlint ...` to enable debug output
        private val debugMode =
            (System.getenv("KTLINT_DEBUG") ?: "").split(",").contains("experimental/indent")

        private inline fun debug(msg: () -> String) {
            if (debugMode) {
                System.err.println("[DEBUG] indent: ${msg()}")
            }
        }

        private val lTokenSet = TokenSet.create(LPAR, LBRACE, LBRACKET, LT)
        private val rTokenSet = TokenSet.create(RPAR, RBRACE, RBRACKET, GT)
        private val matchingRToken =
            lTokenSet.types.zip(rTokenSet.types).toMap()
    }

    private var line = 1
    private var expectedIndent = 0 // TODO: merge into IndentContext

    private fun reset() {
        line = 1
        expectedIndent = 0
    }

    private inline fun debug(msg: () -> String) {
        IndentationRule.debug { "$line: " + msg() }
    }

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
        if (editorConfig.indentStyle == EditorConfig.IntentStyle.TAB || editorConfig.indentSize <= 1) {
            return
        }
        reset()
        IndentationRule.debug { "phase: rearrangement (auto correction ${if (autoCorrect) "on" else "off"})" }
        // step 1: insert newlines (if/where needed)
        var emitted = false
        rearrange(node, autoCorrect) { offset, errorMessage, canBeAutoCorrected ->
            emitted = true
            emit(offset, errorMessage, canBeAutoCorrected)
        }
        if (emitted && autoCorrect) {
            IndentationRule.debug {
                "indenting:\n" +
                    node.text.split("\n").mapIndexed { i, v -> "\t${i + 1}: $v" }.joinToString("\n")
            }
        }
        reset()
        IndentationRule.debug { "phase: indentation" }
        // step 2: correct indentation
        indent(node, autoCorrect, emit, editorConfig)
    }

    private fun rearrange(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        node.visit { n ->
            when (n.elementType) {
                LPAR, LBRACE, LBRACKET -> rearrangeBlock(n, autoCorrect, emit) // TODO: LT
                SUPER_TYPE_LIST -> rearrangeSuperTypeList(n, autoCorrect, emit)
                VALUE_PARAMETER_LIST, VALUE_ARGUMENT_LIST -> rearrangeValueList(n, autoCorrect, emit)
                EQ -> rearrangeEq(n, autoCorrect, emit)
                ARROW -> rearrangeArrow(n, autoCorrect, emit)
                WHITE_SPACE -> line += n.text.count { it == '\n' }
            }
        }
    }

    private fun rearrangeBlock(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        val rElementType = matchingRToken[node.elementType]
        var newlineInBetween = false
        var parameterListInBetween = false
        var numberOfArgs = 0
        var firstArg: ASTNode? = null
        // matching ), ] or }
        val r = node.nextSibling {
            newlineInBetween = newlineInBetween || it.textContains('\n')
            parameterListInBetween = parameterListInBetween || it.elementType == VALUE_PARAMETER_LIST
            if (it.elementType == VALUE_ARGUMENT) {
                numberOfArgs++
                firstArg = it
            }
            it.elementType == rElementType
        }!!
        if (
            !newlineInBetween ||
            // keep { p ->
            // }
            (node.elementType == LBRACE && parameterListInBetween) ||
            // keep ({
            // }) and (object : C {
            // })
            (
                numberOfArgs == 1 &&
                    firstArg?.firstChildNode?.elementType
                    ?.let { it == OBJECT_LITERAL || it == LAMBDA_EXPRESSION } == true
                )
        ) {
            return
        }
        if (!node.nextCodeLeaf()?.prevLeaf().isWhiteSpaceWithNewline() &&
            // IDEA quirk:
            // if (true &&
            //     true
            // ) {
            // }
            // instead of
            // if (
            //     true &&
            //     true
            // ) {
            // }
            node.treeNext?.elementType != CONDITION
        ) {
            requireNewlineAfterLeaf(node, autoCorrect, emit)
        }
        if (!r.prevLeaf().isWhiteSpaceWithNewline()) {
            requireNewlineBeforeLeaf(r, autoCorrect, emit)
        }
    }

    private fun rearrangeSuperTypeList(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        val entries = (node.psi as KtSuperTypeList).entries
        if (
            node.textContains('\n') &&
            entries.size > 1 &&
            // e.g.
            //
            // class A : B, C,
            //     D
            // or
            // class A : B, C({
            // }), D
            //
            // but not
            //
            // class A : B, C, D({
            // })
            !(
                entries.head().all { it.elementType == SUPER_TYPE_ENTRY } &&
                    entries.last().elementType == SUPER_TYPE_CALL_ENTRY
                )
        ) {
            // put space after :
            if (!node.prevLeaf().isWhiteSpaceWithNewline()) {
                val colon = node.prevCodeLeaf()!!
                if (
                    !colon.prevLeaf().isWhiteSpaceWithNewline() &&
                    colon.prevCodeLeaf().let { it?.elementType != RPAR || !it.prevLeaf().isWhiteSpaceWithNewline() }
                ) {
                    requireNewlineAfterLeaf(colon, autoCorrect, emit)
                }
            }
            // put entries on separate lines
            // TODO: group emit()s below with the one above into one (similar to ParameterListWrappingRule)
            for (c in node.children()) {
                if (c.elementType == COMMA && !c.treeNext.isWhiteSpaceWithNewline()) {
                    requireNewlineAfterLeaf(c, autoCorrect, emit)
                }
            }
        }
    }

    private fun rearrangeValueList(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        for (c in node.children()) {
            if (
                (c.elementType == VALUE_PARAMETER || c.elementType == VALUE_ARGUMENT || c.elementType == ANNOTATION) &&
                c.textContains('\n')
            ) {
                // rearrange
                //
                // a, b, value(
                // ), c, d
                //
                // to
                //
                // a, b,
                // value(
                // ),
                // c, d

                // insert \n in front of multi-line value
                val prevSibling = c.prevSibling { it.elementType != WHITE_SPACE }
                if (
                    prevSibling?.elementType == COMMA &&
                    !prevSibling.treeNext.isWhiteSpaceWithNewline()
                ) {
                    requireNewlineAfterLeaf(prevSibling, autoCorrect, emit)
                }
                // insert \n after multi-line value
                val nextSibling = c.nextSibling { it.elementType != WHITE_SPACE }
                if (
                    nextSibling?.elementType == COMMA &&
                    !nextSibling.treeNext.isWhiteSpaceWithNewline()
                ) {
                    requireNewlineAfterLeaf(nextSibling, autoCorrect, emit)
                }
            }
        }
    }

    private fun rearrangeEq(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (
            !node.nextCodeSibling()?.elementType.let {
                it == DOT_QUALIFIED_EXPRESSION ||
                    it == SAFE_ACCESS_EXPRESSION ||
                    it == BINARY_EXPRESSION ||
                    it == BINARY_WITH_TYPE
            } ||
            !node.nextSubstringContains('\n') ||
            (
                mustBeFollowedByNewline(node) &&
                    // force """ to be on a separate line
                    !node.nextCodeLeaf().let { it?.elementType == OPEN_QUOTE && it.text == "\"\"\"" }
                )
        ) {
            return
        }
        val nextCodeLeaf = node.nextCodeLeaf()!!
        // val v = (...
        if (nextCodeLeaf.elementType in lTokenSet) {
            return
        }
        if (!nextCodeLeaf.prevLeaf().isWhiteSpaceWithNewline()) {
            requireNewlineAfterLeaf(node, autoCorrect, emit)
        }
    }

    private fun ASTNode.nextSubstringContains(c: Char): Boolean {
        var n = this.treeNext
        while (n != null) {
            if (n.textContains(c)) {
                return true
            }
            n = n.treeNext
        }
        return false
    }

    private fun mustBeFollowedByNewline(node: ASTNode): Boolean {
        // find EOL token (last token before \n)
        // if token is in lTokenSet
        //     find matching rToken
        //     return true if there is no newline after the rToken
        // return false
        val p = node.treeParent
        val nextCodeSibling = node.nextCodeSibling() // e.g. BINARY_EXPRESSION
        var lToken = nextCodeSibling?.nextLeaf { it.isWhiteSpaceWithNewline() }?.prevCodeLeaf()
        if (lToken != null && lToken.elementType !in lTokenSet) {
            // special cases:
            // x = y.f({ z ->
            // })
            // x = y.f(0, 1,
            // 2, 3)
            lToken = lToken.prevLeaf { it.elementType in lTokenSet || it == node }
        }
        if (lToken != null && lToken.elementType in lTokenSet) {
            val rElementType = matchingRToken[lToken.elementType]
            val rToken = lToken.nextSibling { it.elementType == rElementType }
            val lf = rToken?.nextLeaf { it.isWhiteSpaceWithNewline() }
            return lf?.parent({ it == p }) == null
        }
        return false
    }

    private fun rearrangeArrow(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        val p = node.treeParent
        if (
            // check
            // `{ p -> ... }`
            // and
            // `when { m -> ... }`
            // only
            p.elementType.let { it != FUNCTION_LITERAL && it != WHEN_ENTRY } ||
            // ... and only if expression after -> spans multiple lines
            !p.textContains('\n') ||
            // permit
            // when {
            //     m -> 0 + d({
            //     })
            // }
            (p.elementType == WHEN_ENTRY && mustBeFollowedByNewline(node)) ||
            // permit
            // when (this) {
            //     in 0x1F600..0x1F64F, // Emoticons
            //     0x200D // Zero-width Joiner
            //     -> true
            // }
            (p.elementType == WHEN_ENTRY && node.prevLeaf()?.textContains('\n') == true)
        ) {
            return
        }
        if (!node.nextCodeLeaf()?.prevLeaf().isWhiteSpaceWithNewline()) {
            requireNewlineAfterLeaf(node, autoCorrect, emit)
        }
        val r = node.nextSibling { it.elementType == RBRACE } ?: return
        if (!r.prevLeaf().isWhiteSpaceWithNewline()) {
            requireNewlineBeforeLeaf(r, autoCorrect, emit)
        }
    }

    private fun requireNewlineBeforeLeaf(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        emit(
            node.startOffset - 1,
            """Missing newline before "${node.text}"""",
            true
        )
        debug { (if (!autoCorrect) "would have " else "") + "inserted newline before ${node.text}" }
        if (autoCorrect) {
            (node.psi as LeafPsiElement).upsertWhitespaceBeforeMe("\n ")
        }
    }

    private fun requireNewlineAfterLeaf(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        emit(
            node.startOffset + 1,
            """Missing newline after "${node.text}"""",
            true
        )
        debug { (if (!autoCorrect) "would have " else "") + "inserted newline after ${node.text}" }
        if (autoCorrect) {
            (node.psi as LeafPsiElement).upsertWhitespaceAfterMe("\n ")
        }
    }

    private class IndentContext {
        private val exitAdj = mutableMapOf<ASTNode, Int>()
        val ignored = mutableSetOf<ASTNode>()
        val blockOpeningLineStack: Deque<Int> = LinkedList<Int>()
        var localAdj: Int = 0
        fun exitAdjBy(node: ASTNode, change: Int) {
            exitAdj.compute(node) { _, v -> (v ?: 0) + change }
        }
        fun clearExitAdj(node: ASTNode): Int? =
            exitAdj.remove(node)
    }

    private fun indent(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
        editorConfig: EditorConfig
    ) {
        val firstNotEmptyLeaf = node.nextLeaf()
        if (firstNotEmptyLeaf?.let { it.elementType == WHITE_SPACE && !it.textContains('\n') } == true) {
            visitWhiteSpace(firstNotEmptyLeaf, autoCorrect, emit, editorConfig)
        }
        val ctx = IndentContext()
        node.visit(
            { n ->
                when (n.elementType) {
                    LPAR, LBRACE, LBRACKET -> {
                        // ({[ should increase expectedIndent by 1
                        val prevBlockLine = ctx.blockOpeningLineStack.peek() ?: -1
                        if (prevBlockLine != line) {
                            expectedIndent++
                            debug { "++${n.text} -> $expectedIndent" }
                        }
                        ctx.blockOpeningLineStack.push(line)
                    }
                    RPAR, RBRACE, RBRACKET -> {
                        // ]}) should decrease expectedIndent by 1
                        val blockLine = ctx.blockOpeningLineStack.pop()
                        val prevBlockLine = ctx.blockOpeningLineStack.peek() ?: -1
                        if (prevBlockLine != blockLine) {
                            expectedIndent--
                            debug { "--${n.text} -> $expectedIndent" }
                        }
                    }
                    LT ->
                        // <T>
                        if (n.treeParent.elementType.let { it == TYPE_PARAMETER_LIST || it == TYPE_ARGUMENT_LIST }) {
                            expectedIndent++
                            debug { "++${n.text} -> $expectedIndent" }
                        }
                    GT ->
                        // <T>
                        if (n.treeParent.elementType.let { it == TYPE_PARAMETER_LIST || it == TYPE_ARGUMENT_LIST }) {
                            expectedIndent--
                            debug { "--${n.text} -> $expectedIndent" }
                        }
                    SUPER_TYPE_LIST ->
                        // class A :
                        //     SUPER_TYPE_LIST
                        adjustExpectedIndentInsideSuperTypeList(n)
                    SUPER_TYPE_CALL_ENTRY -> {
                        // IDEA quirk:
                        //
                        // class A : B({
                        //     f() {}
                        // }),
                        //     C({
                        //         f() {}
                        //     })
                        //
                        // instead of expected
                        //
                        // class A : B({
                        //         f() {}
                        //     }),
                        //     C({
                        //         f() {}
                        //     })
                        adjustExpectedIndentInsideSuperTypeCall(n, ctx)
                    }
                    STRING_TEMPLATE ->
                        indentStringTemplate(n, autoCorrect, emit, editorConfig)
                    DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION, BINARY_EXPRESSION, BINARY_WITH_TYPE -> {
                        val prevBlockLine = ctx.blockOpeningLineStack.peek() ?: -1
                        if (prevBlockLine == line) {
                            ctx.ignored.add(n)
                        }
                    }
                    WHITE_SPACE ->
                        if (n.textContains('\n')) {
                            if (
                                !n.isPartOfComment() &&
                                !n.isPartOfTypeConstraint() // FIXME IndentationRuleTest.testLintWhereClause not checked
                            ) {
                                val p = n.treeParent
                                val nextSibling = n.treeNext
                                val prevLeaf = n.prevLeaf { !it.isPartOfComment() && !it.isWhiteSpaceWithoutNewline() }
                                when {
                                    p.elementType.let {
                                        it == DOT_QUALIFIED_EXPRESSION || it == SAFE_ACCESS_EXPRESSION
                                    } ->
                                        // value
                                        //     .x()
                                        //     .y
                                        adjustExpectedIndentInsideQualifiedExpression(n, ctx)
                                    p.elementType.let {
                                        it == BINARY_EXPRESSION || it == BINARY_WITH_TYPE
                                    } ->
                                        // value
                                        //     + x()
                                        //     + y
                                        adjustExpectedIndentInsideBinaryExpression(n, ctx)
                                    nextSibling?.elementType.let {
                                        it == THEN || it == ELSE || it == BODY
                                    } ->
                                        // if (...)
                                        //     THEN
                                        // else
                                        //     ELSE
                                        // while (...)
                                        //     BODY
                                        adjustExpectedIndentInFrontOfControlBlock(n, ctx)
                                    nextSibling?.elementType == PROPERTY_ACCESSOR ->
                                        // val f: Type =
                                        //     PROPERTY_ACCESSOR get() = ...
                                        //     PROPERTY_ACCESSOR set() = ...
                                        adjustExpectedIndentInFrontOfPropertyAccessor(n, ctx)
                                    nextSibling?.elementType == SUPER_TYPE_LIST ->
                                        // class C :
                                        //     SUPER_TYPE_LIST
                                        adjustExpectedIndentInFrontOfSuperTypeList(n, ctx)
                                    prevLeaf?.elementType == EQ ->
                                        // v =
                                        //     value
                                        adjustExpectedIndentAfterEq(n, ctx)
                                    prevLeaf?.elementType == ARROW ->
                                        // when {
                                        //    v ->
                                        //        value
                                        // }
                                        adjustExpectedIndentAfterArrow(n, ctx)
                                }
                                visitWhiteSpace(n, autoCorrect, emit, editorConfig)
                                if (ctx.localAdj != 0) {
                                    expectedIndent += ctx.localAdj
                                    ctx.localAdj = 0
                                }
                            } else if (n.isPartOf(KDOC)) {
                                visitWhiteSpace(n, autoCorrect, emit, editorConfig)
                            }
                            line += n.text.count { it == '\n' }
                        }
                    EOL_COMMENT ->
                        if (debugMode && n.text == "// ktlint-debug-print-expected-indent") {
                            debug { "expected indent: $expectedIndent" }
                        }
                }
            },
            { n ->
                when (n.elementType) {
                    SUPER_TYPE_LIST ->
                        adjustExpectedIndentAfterSuperTypeList(n)
                    DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION, BINARY_EXPRESSION, BINARY_WITH_TYPE ->
                        ctx.ignored.remove(n)
                }
                val adj = ctx.clearExitAdj(n)
                if (adj != null) {
                    expectedIndent += adj
                    debug { "adjusted ${n.elementType} by $adj -> $expectedIndent" }
                }
            }
        )
    }

    private fun adjustExpectedIndentInsideQualifiedExpression(n: ASTNode, ctx: IndentContext) {
        val p = n.treeParent
        val nextSibling = n.treeNext
        if (!ctx.ignored.contains(p) && nextSibling != null) {
            expectedIndent++
            debug { "++inside(${p.elementType}) -> $expectedIndent" }
            val siblingType = nextSibling.elementType
            val e = if (
                siblingType == DOT ||
                siblingType == SAFE_ACCESS ||
                siblingType == ELVIS
            ) {
                nextSibling.treeNext
            } else {
                nextSibling
            }
            ctx.exitAdjBy(e, -1)
        }
    }

    private fun adjustExpectedIndentInsideBinaryExpression(n: ASTNode, ctx: IndentContext) {
        if (!n.isPartOfCondition()) {
            val p = n.treeParent
            if (!ctx.ignored.contains(p)) {
                expectedIndent++
                debug { "++inside(${p.elementType}) -> $expectedIndent" }
                val rOperand = n.nextSibling { sibling ->
                    sibling.elementType != OPERATION_REFERENCE &&
                        sibling.elementType != WHITE_SPACE
                }!!
                ctx.exitAdjBy(rOperand, -1)
            }
            val nextSibling = n.treeNext
            if (
                nextSibling?.elementType.let {
                    it == BINARY_EXPRESSION || it == BINARY_WITH_TYPE
                }
            ) {
                ctx.localAdj = -1
                debug { "--inside(${nextSibling.elementType}) -> $expectedIndent" }
                ctx.exitAdjBy(p, 1)
            }
        }
    }

    private fun adjustExpectedIndentInFrontOfControlBlock(n: ASTNode, ctx: IndentContext) {
        val nextSibling = n.treeNext
        if (nextSibling.nextCodeLeaf()?.elementType !in lTokenSet) {
            expectedIndent++
            debug { "++in_front(${nextSibling.elementType}) -> $expectedIndent" }
            ctx.exitAdjBy(nextSibling, -1)
        }
    }

    private fun adjustExpectedIndentInFrontOfPropertyAccessor(n: ASTNode, ctx: IndentContext) {
        expectedIndent++
        debug { "++in_front(${n.treeNext.elementType}) -> $expectedIndent" }
        ctx.exitAdjBy(n.treeNext, -1)
    }

    private fun adjustExpectedIndentInFrontOfSuperTypeList(n: ASTNode, ctx: IndentContext) {
        expectedIndent++
        debug { "++in_front(${n.treeNext.elementType}) -> $expectedIndent" }
        ctx.localAdj = -1
    }

    private fun adjustExpectedIndentInsideSuperTypeList(n: ASTNode) {
        expectedIndent++
        debug { "++inside(${n.elementType}) -> $expectedIndent" }
    }

    private fun adjustExpectedIndentAfterSuperTypeList(n: ASTNode) {
        expectedIndent--
        debug { "--after(${n.elementType}) -> $expectedIndent" }
    }

    private fun adjustExpectedIndentInsideSuperTypeCall(n: ASTNode, ctx: IndentContext) {
        if (
            // n.treePrev == null &&
            // n.treeParent.elementType == SUPER_TYPE_LIST &&
            n.prevLeaf()?.textContains('\n') == false
        ) {
            expectedIndent--
            debug { "--inside(${n.elementType}) -> $expectedIndent" }
            ctx.exitAdjBy(n, 1)
        }
    }

    private fun adjustExpectedIndentAfterEq(n: ASTNode, ctx: IndentContext) {
        expectedIndent++
        debug { "++after(EQ) -> $expectedIndent" }
        ctx.exitAdjBy(n.treeParent, -1)
    }

    private fun adjustExpectedIndentAfterArrow(n: ASTNode, ctx: IndentContext) {
        val prevBlockLine = ctx.blockOpeningLineStack.peek() ?: -1
        if (prevBlockLine != line) {
            expectedIndent++
            debug { "++after(ARROW) -> $expectedIndent" }
            ctx.exitAdjBy(n.treeParent, -1)
        }
    }

    private fun indentStringTemplate(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
        editorConfig: EditorConfig
    ) {
        val psi = node.psi as KtStringTemplateExpression
        if (psi.isMultiLine() && psi.isFollowedByTrimIndent()) {
            val children = node.children()
            val prefixLength =
                children
                    .fold(StringBuilder()) { sb, child ->
                        when (child.elementType) {
                            LITERAL_STRING_TEMPLATE_ENTRY -> {
                                val text = child.text
                                // bail if indentation contains Tab
                                for (c in text) {
                                    if (c == '\t') {
                                        return // bail
                                    }
                                    if (!c.isWhitespace()) {
                                        break
                                    }
                                }
                                sb.append(text)
                            }
                            LONG_STRING_TEMPLATE_ENTRY -> sb.append("${'$'}{}")
                            else -> sb
                        }
                    }
                    .split('\n')
                    .filter(String::isNotBlank)
                    .map { it.indentLength() }
                    .min() ?: 0
            val expectedPrefixLength = expectedIndent * editorConfig.indentSize
            if (prefixLength != expectedPrefixLength) {
                for (child in children) {
                    if (child.isPrecededByLFStringTemplateEntry()) {
                        when (child.elementType) {
                            LITERAL_STRING_TEMPLATE_ENTRY -> {
                                val v = child.text
                                if (v != "\n") {
                                    val indentLength = v.indentLength()
                                    val expectedIndentLength = indentLength - prefixLength + expectedPrefixLength
                                    if (indentLength != expectedIndentLength) {
                                        reindentStringTemplateEntry(
                                            child,
                                            autoCorrect,
                                            emit,
                                            indentLength,
                                            expectedIndentLength
                                        )
                                    }
                                }
                            }
                            LONG_STRING_TEMPLATE_ENTRY -> {
                                if (expectedPrefixLength != 0) {
                                    preindentStringTemplateEntry(
                                        child.firstChildNode,
                                        autoCorrect,
                                        emit,
                                        expectedPrefixLength
                                    )
                                }
                            }
                        }
                    }
                }
            }
            val closingQuote = children.find { it.elementType == CLOSING_QUOTE }!!
            if (closingQuote.treePrev.text == "\n") {
                // rewriting
                // (
                //     """
                // """.trimIndent()
                // )
                // to
                // (
                //     """
                //     """.trimIndent()
                // )
                if (expectedPrefixLength != 0) {
                    preindentStringTemplateEntry(closingQuote, autoCorrect, emit, expectedPrefixLength)
                }
            } else if (!closingQuote.treePrev.text.isBlank()) {
                // rewriting
                // """
                //     text
                // _""".trimIndent()
                // to
                // """
                //     text
                // _
                // """.trimIndent()
                emit(
                    closingQuote.startOffset,
                    "Missing newline before \"\"\"",
                    true
                )
                if (autoCorrect) {
                    closingQuote as LeafPsiElement
                    closingQuote.rawInsertBeforeMe(LeafPsiElement(REGULAR_STRING_PART, "\n"))
                    closingQuote.rawInsertBeforeMe(
                        LeafPsiElement(REGULAR_STRING_PART, " ".repeat(expectedPrefixLength))
                    )
                }
                debug {
                    (if (!autoCorrect) "would have " else "") +
                        "inserted newline before (closing) \"\"\""
                }
            } else { // preceded by blank LITERAL_STRING_TEMPLATE_ENTRY
                val child = closingQuote.treePrev
                val indentLength = child.text.length
                if (indentLength != expectedPrefixLength) {
                    reindentStringTemplateEntry(child, autoCorrect, emit, indentLength, expectedPrefixLength)
                }
            }
        }
    }

    private fun preindentStringTemplateEntry(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
        expectedIndentLength: Int
    ) {
        emit(
            node.startOffset,
            "Unexpected indentation (0) (should be $expectedIndentLength)",
            true
        )
        if (autoCorrect) {
            (node as LeafPsiElement).rawInsertBeforeMe(
                LeafPsiElement(REGULAR_STRING_PART, " ".repeat(expectedIndentLength))
            )
        }
        debug {
            (if (!autoCorrect) "would have " else "") +
                "changed indentation before ${node.text} to $expectedIndentLength (from 0)"
        }
    }

    private fun reindentStringTemplateEntry(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
        indentLength: Int,
        expectedIndentLength: Int
    ) {
        emit(
            node.startOffset,
            "Unexpected indentation ($indentLength) (should be $expectedIndentLength)",
            true
        )
        if (autoCorrect) {
            (node.firstChildNode as LeafPsiElement).rawReplaceWithText(
                " ".repeat(expectedIndentLength) + node.text.substring(indentLength)
            )
        }
        debug {
            (if (!autoCorrect) "would have " else "") +
                "changed indentation to $expectedIndentLength (from $indentLength)"
        }
    }

    private fun ASTNode.isPrecededByLFStringTemplateEntry() =
        treePrev?.let { it.elementType == LITERAL_STRING_TEMPLATE_ENTRY && it.text == "\n" } == true

    private fun KtStringTemplateExpression.isMultiLine(): Boolean {
        for (child in node.children()) {
            if (child.elementType == LITERAL_STRING_TEMPLATE_ENTRY) {
                val v = child.text
                if (v == "\n") {
                    return true
                }
            }
        }
        return false
    }

    private fun KtStringTemplateExpression.isFollowedByTrimIndent() =
        this.node.nextSibling { it.elementType != DOT }
            .let { it?.elementType == CALL_EXPRESSION && it.text == "trimIndent()" }

    private fun String.indentLength() =
        indexOfFirst { !it.isWhitespace() }.let { if (it == -1) length else it }

    private fun visitWhiteSpace(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
        editorConfig: EditorConfig
    ) {
        val text = node.text
        val nodeIndent = text.substringAfterLast("\n")
        val nextLeaf = node.nextLeaf()
        val nextLeafElementType = nextLeaf?.elementType
        if (nextLeafElementType == OPEN_QUOTE && nextLeaf.text == "\"\"\"" && nodeIndent.isEmpty()) {
            return // raw strings ("""") are allowed at column 0
        }
        val comment = nextLeaf?.parent({ it.psi is PsiComment }, strict = false)
        if (comment != null) {
            if (nodeIndent.isEmpty()) {
                return // comments are allowed at column 0
            }
            if (comment.textContains('\n') && comment.elementType == BLOCK_COMMENT) {
                // FIXME: while we cannot assume any kind of layout inside a block comment,
                // `/*` and `*/` can still be indented
                return
            }
        }
        // adjusting expectedIndent based on what is in front
        val adjustedExpectedIndent = expectedIndent + when {
            // IDEA quirk:
            // val v = (
            //     true
            //     )
            // instead of expected
            // val v = (
            //     true
            // )
            nextLeafElementType == RPAR &&
                node.treeParent?.elementType == PARENTHESIZED ->
                0
            // IDEA quirk:
            // class A : T<
            //     K,
            //     V
            //     > {
            // }
            // instead of expected
            // class A : T<
            //     K,
            //     V
            // > {
            // }
            nextLeafElementType == COLON ->
                1
            nextLeafElementType == GT &&
                node.treeParent?.elementType.let { it == TYPE_PARAMETER_LIST || it == TYPE_ARGUMENT_LIST } ->
                0
            nextLeafElementType in rTokenSet -> -1
            else -> 0
        }
        // indentation with all \t replaced
        val normalizedNodeIndent =
            if (nodeIndent.contains('\t')) {
                emit(
                    node.startOffset + text.length - nodeIndent.length,
                    "Unexpected Tab character(s)",
                    true
                )
                nodeIndent.replace("\t", " ".repeat(editorConfig.tabWidth))
            } else {
                nodeIndent
            }
        val expectedIndentLength =
            adjustedExpectedIndent * editorConfig.indentSize +
                // +1 space before * in `/**\n *\n */`
                if (comment?.elementType == KDOC && nextLeafElementType != KDOC_START) 1 else 0
        if (normalizedNodeIndent.length != expectedIndentLength) {
            emit(
                node.startOffset + text.length - nodeIndent.length,
                "Unexpected indentation (${normalizedNodeIndent.length}) (should be $expectedIndentLength)",
                true
            )
            debug {
                (if (!autoCorrect) "would have " else "") +
                    "changed indentation to $expectedIndentLength (from ${normalizedNodeIndent.length})"
            }
            if (autoCorrect) {
                (node as LeafPsiElement).rawReplaceWithText(
                    text.substringBeforeLast("\n") + "\n" +
                        " ".repeat(expectedIndentLength)
                )
            }
        }
    }

    // e.g.
    // if (condition), while (condition), for (condition), ...
    private fun ASTNode.isPartOfCondition(): Boolean {
        var n = this
        while (n.treeParent?.elementType.let { it == BINARY_EXPRESSION || it == BINARY_WITH_TYPE }) {
            n = n.treeParent
        }
        return n.treeParent?.elementType == CONDITION
    }

    // e.g.
    // where T1 : SubType...,
    //       T2 : SubType...
    private fun ASTNode.isPartOfTypeConstraint() =
        isPartOf(TYPE_CONSTRAINT_LIST) || nextLeaf()?.elementType == WHERE_KEYWORD
}

package com.pinterest.ktlint.ruleset.experimental

internal fun <T> List<T>.head() = this.subList(0, this.size - 1)
internal fun <T> List<T>.tail() = this.subList(1, this.size)

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST
import com.pinterest.ktlint.core.ast.children
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.KtAnnotationEntry

class AnnotationRule : Rule("annotation") {

    companion object {
        const val multipleAnnotationsOnSameLineAsAnnotatedConstructErrorMessage =
            "Multiple annotations should not be placed on the same line as the annotated construct"
        const val annotationsWithParametersAreNotOnSeparateLinesErrorMessage =
            "Annotations with parameters should all be placed on separate lines prior to the annotated construct"
    }

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        val root =
            node.children().firstOrNull { it.elementType == MODIFIER_LIST }
                ?: return

        val annotations =
            root.children()
                .mapNotNull { it.psi as? KtAnnotationEntry }
                .toList()
        check(!annotations.isEmpty()) { "Annotations list should not be empty" }

        // Join the nodes that immediately follow the annotations (whitespace), then add the final whitespace
        // if it's not a child of root. This happens when a new line separates the annotations from the annotated
        // construct. In the following example, there are no whitespace children of root, but root's next sibling is the
        // new line whitespace.
        //
        //      @JvmField
        //      val s: Any
        //
        val whiteSpaces = (annotations.asSequence().map { it.nextSibling } + root.treeNext)
            .filterIsInstance<PsiWhiteSpace>()
            .take(annotations.size)
            .toList()

        val multipleAnnotationsOnSameLineAsAnnotatedConstruct =
            annotations.size > 1 && !whiteSpaces.last().textContains('\n')
        val annotationsWithParametersAreNotOnSeparateLines =
            annotations.any { it.valueArgumentList != null } &&
                !whiteSpaces.all { it.textContains('\n') }

        if (multipleAnnotationsOnSameLineAsAnnotatedConstruct) {
            emit(
                annotations.first().node.startOffset,
                multipleAnnotationsOnSameLineAsAnnotatedConstructErrorMessage,
                true
            )
        }
        if (annotationsWithParametersAreNotOnSeparateLines) {
            emit(
                annotations.first().node.startOffset,
                annotationsWithParametersAreNotOnSeparateLinesErrorMessage,
                true
            )
        }

        if (autoCorrect) {
            val nodeBeforeAnnotations = root.treeParent.treePrev as? PsiWhiteSpace
            // If there is no whitespace before the annotation, the annotation is the first
            // text in the file
            val newLineWithIndent = nodeBeforeAnnotations?.text ?: "\n"

            if (annotationsWithParametersAreNotOnSeparateLines) {
                whiteSpaces.forEach {
                    (it as LeafPsiElement).rawReplaceWithText(newLineWithIndent)
                }
            } else if (multipleAnnotationsOnSameLineAsAnnotatedConstruct) {
                (whiteSpaces.last() as LeafPsiElement).rawReplaceWithText(newLineWithIndent)
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.ANDAND
import com.pinterest.ktlint.core.ast.ElementType.COMMA
import com.pinterest.ktlint.core.ast.ElementType.DIV
import com.pinterest.ktlint.core.ast.ElementType.DOT
import com.pinterest.ktlint.core.ast.ElementType.ELSE_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.ELVIS
import com.pinterest.ktlint.core.ast.ElementType.LBRACE
import com.pinterest.ktlint.core.ast.ElementType.LPAR
import com.pinterest.ktlint.core.ast.ElementType.MINUS
import com.pinterest.ktlint.core.ast.ElementType.MUL
import com.pinterest.ktlint.core.ast.ElementType.OROR
import com.pinterest.ktlint.core.ast.ElementType.PERC
import com.pinterest.ktlint.core.ast.ElementType.PLUS
import com.pinterest.ktlint.core.ast.ElementType.PREFIX_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS
import com.pinterest.ktlint.core.ast.ElementType.WHEN_CONDITION_WITH_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.isPartOfComment
import com.pinterest.ktlint.core.ast.nextCodeLeaf
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevCodeLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
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.LeafElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.lexer.KtTokens

class ChainWrappingRule : Rule("chain-wrapping") {

    private val sameLineTokens = TokenSet.create(MUL, DIV, PERC, ANDAND, OROR)
    private val prefixTokens = TokenSet.create(PLUS, MINUS)
    private val nextLineTokens = TokenSet.create(DOT, SAFE_ACCESS, ELVIS)
    private val noSpaceAroundTokens = TokenSet.create(DOT, SAFE_ACCESS)

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        /*
           org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement (DOT) | "."
           org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl (WHITE_SPACE) | "\n        "
           org.jetbrains.kotlin.psi.KtCallExpression (CALL_EXPRESSION)
         */
        val elementType = node.elementType
        if (nextLineTokens.contains(elementType)) {
            if (node.isPartOfComment()) {
                return
            }
            val nextLeaf = node.nextCodeLeaf()?.prevLeaf()
            if (nextLeaf?.elementType == WHITE_SPACE &&
                nextLeaf.textContains('\n')
            ) {
                emit(node.startOffset, "Line must not end with \"${node.text}\"", true)
                if (autoCorrect) {
                    // rewriting
                    // <prevLeaf><node="."><nextLeaf="\n"> to
                    // <prevLeaf><delete space if any><nextLeaf="\n"><node="."><space if needed>
                    // (or)
                    // <prevLeaf><node="."><spaceBeforeComment><comment><nextLeaf="\n"> to
                    // <prevLeaf><delete space if any><spaceBeforeComment><comment><nextLeaf="\n"><node="."><space if needed>
                    val prevLeaf = node.prevLeaf()
                    if (prevLeaf is PsiWhiteSpace) {
                        prevLeaf.node.treeParent.removeChild(prevLeaf.node)
                    }
                    if (!noSpaceAroundTokens.contains(elementType)) {
                        (nextLeaf as LeafElement).upsertWhitespaceAfterMe(" ")
                    }
                    node.treeParent.removeChild(node)
                    (nextLeaf as LeafElement).rawInsertAfterMe(node as LeafElement)
                }
            }
        } else if (sameLineTokens.contains(elementType) || prefixTokens.contains(elementType)) {
            if (node.isPartOfComment()) {
                return
            }
            val prevLeaf = node.prevLeaf()
            if (
                prevLeaf?.elementType == WHITE_SPACE &&
                prevLeaf.textContains('\n') &&
                // fn(*typedArray<...>()) case
                (elementType != MUL || !prevLeaf.isPartOfSpread()) &&
                // unary +/-
                (!prefixTokens.contains(elementType) || !node.isInPrefixPosition()) &&
                // LeafPsiElement->KtOperationReferenceExpression->KtPrefixExpression->KtWhenConditionWithExpression
                !node.isPartOfWhenCondition()
            ) {
                emit(node.startOffset, "Line must not begin with \"${node.text}\"", true)
                if (autoCorrect) {
                    // rewriting
                    // <insertionPoint><prevLeaf="\n"><node="&&"><nextLeaf=" "> to
                    // <insertionPoint><prevLeaf=" "><node="&&"><nextLeaf="\n"><delete node="&&"><delete nextLeaf=" ">
                    // (or)
                    // <insertionPoint><spaceBeforeComment><comment><prevLeaf="\n"><node="&&"><nextLeaf=" "> to
                    // <insertionPoint><space if needed><node="&&"><spaceBeforeComment><comment><prevLeaf="\n"><delete node="&&"><delete nextLeaf=" ">
                    val nextLeaf = node.nextLeaf()
                    if (nextLeaf is PsiWhiteSpace) {
                        nextLeaf.node.treeParent.removeChild(nextLeaf.node)
                    }
                    val insertionPoint = prevLeaf.prevCodeLeaf() as LeafPsiElement
                    node.treeParent.removeChild(node)
                    insertionPoint.rawInsertAfterMe(node as LeafPsiElement)
                    if (!noSpaceAroundTokens.contains(elementType)) {
                        insertionPoint.upsertWhitespaceAfterMe(" ")
                    }
                }
            }
        }
    }

    private fun ASTNode.isPartOfSpread() =
        prevCodeLeaf()?.let { leaf ->
            val type = leaf.elementType
            type == LPAR ||
                type == COMMA ||
                type == LBRACE ||
                type == ELSE_KEYWORD ||
                KtTokens.OPERATIONS.contains(type)
        } == true

    private fun ASTNode.isInPrefixPosition() =
        treeParent?.treeParent?.elementType == PREFIX_EXPRESSION

    private fun ASTNode.isPartOfWhenCondition() =
        treeParent?.treeParent?.treeParent?.elementType == WHEN_CONDITION_WITH_EXPRESSION
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement

class CommentSpacingRule : Rule("comment-spacing") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is PsiComment && node is LeafPsiElement && node.getText().startsWith("//")) {
            val prevLeaf = node.prevLeaf()
            if (prevLeaf !is PsiWhiteSpace && prevLeaf is LeafPsiElement) {
                emit(node.startOffset, "Missing space before //", true)
                if (autoCorrect) {
                    node.upsertWhitespaceBeforeMe(" ")
                }
            }
            val text = node.getText()
            if (text.length != 2 &&
                !text.startsWith("// ") &&
                !text.startsWith("//noinspection") &&
                !text.startsWith("//region") &&
                !text.startsWith("//endregion") &&
                !text.startsWith("//language=")
            ) {
                emit(node.startOffset, "Missing space after //", true)
                if (autoCorrect) {
                    node.rawReplaceWithText("// " + text.removePrefix("//"))
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.CLASS
import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.FILE_ANNOTATION_LIST
import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
import com.pinterest.ktlint.core.ast.ElementType.IMPORT_LIST
import com.pinterest.ktlint.core.ast.ElementType.KDOC
import com.pinterest.ktlint.core.ast.ElementType.OBJECT_DECLARATION
import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
import com.pinterest.ktlint.core.ast.ElementType.SHEBANG_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.TYPEALIAS
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.prevCodeSibling
import java.nio.file.Paths
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType

/**
 * If there is only one top level class/object/typealias in a given file, then its name should match the file's name.
 */
class FilenameRule : Rule("filename"), Rule.Modifier.RestrictToRoot {

    private val ignoreSet = setOf<IElementType>(
        FILE_ANNOTATION_LIST,
        PACKAGE_DIRECTIVE,
        IMPORT_LIST,
        WHITE_SPACE,
        EOL_COMMENT,
        BLOCK_COMMENT,
        KDOC,
        SHEBANG_COMMENT
    )

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        val filePath = node.getUserData(KtLint.FILE_PATH_USER_DATA_KEY)
        if (filePath?.endsWith(".kt") != true) {
            // ignore all non ".kt" files (including ".kts")
            return
        }
        var type: String? = null
        var className: String? = null
        for (el in node.getChildren(null)) {
            if (el.elementType == CLASS ||
                el.elementType == OBJECT_DECLARATION ||
                el.elementType == TYPEALIAS
            ) {
                if (className != null) {
                    // more than one class/object/typealias present
                    return
                }
                val id = el.findChildByType(IDENTIFIER)
                type = id?.prevCodeSibling()?.text
                className = id?.text
            } else if (!ignoreSet.contains(el.elementType)) {
                // https://github.com/android/android-ktx/blob/51005889235123f41492eaaecde3c623473dfe95/src/main/java/androidx/core/graphics/Path.kt case
                return
            }
        }
        if (className != null) {
            val unescapedClassName = className.replace("`", "")
            val name = Paths.get(filePath).fileName.toString().substringBefore(".")
            if (name != "package" && name != unescapedClassName) {
                emit(0, "$type $className should be declared in a file named $unescapedClassName.kt", false)
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.isRoot
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.PsiWhiteSpaceImpl

class FinalNewlineRule : Rule("final-newline"), Rule.Modifier.RestrictToRoot {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.isRoot()) {
            val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
            val insertFinalNewline = editorConfig.insertFinalNewline ?: return
            val lastNode = lastChildNodeOf(node)
            if (insertFinalNewline) {
                if (lastNode !is PsiWhiteSpace || !lastNode.textContains('\n')) {
                    emit(0, "File must end with a newline (\\n)", true)
                    if (autoCorrect) {
                        node.addChild(PsiWhiteSpaceImpl("\n"), null)
                    }
                }
            } else {
                if (lastNode is PsiWhiteSpace && lastNode.textContains('\n')) {
                    emit(lastNode.startOffset, "Redundant newline (\\n) at the end of file", true)
                    if (autoCorrect) {
                        lastNode.node.treeParent.removeChild(lastNode.node)
                    }
                }
            }
        }
    }

    private tailrec fun lastChildNodeOf(node: ASTNode): ASTNode? =
        if (node.lastChildNode == null) node else lastChildNodeOf(node.lastChildNode)
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.CONSTRUCTOR_DELEGATION_CALL
import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST
import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE
import com.pinterest.ktlint.core.ast.isPartOf
import com.pinterest.ktlint.core.ast.isRoot
import com.pinterest.ktlint.core.ast.nextLeaf
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.psi.KtParameterList
import org.jetbrains.kotlin.psi.KtTypeConstraintList

class IndentationRule : Rule("indent") {

    private var indentSize = -1

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.isRoot()) {
            val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
            indentSize = editorConfig.indentSize
            return
        }
        if (indentSize <= 1) {
            return
        }
        if (node is PsiWhiteSpace && !node.isPartOf(PsiComment::class)) {
            val lines = node.getText().split("\n")
            if (lines.size > 1 && !node.isPartOf(KtTypeConstraintList::class)) {
                var offset = node.startOffset + lines.first().length + 1
                val previousIndentSize = node.previousIndentSize()
                lines.tail().forEach { indent ->
                    if (indent.isNotEmpty() && (indent.length - previousIndentSize) % indentSize != 0) {
                        if (!node.isPartOf(KtParameterList::class)) { // parameter list wrapping enforced by ParameterListWrappingRule
                            emit(
                                offset,
                                "Unexpected indentation (${indent.length}) (it should be ${previousIndentSize + indentSize})",
                                false
                            )
                        }
                    }
                    offset += indent.length + 1
                }
            }
            if (node.textContains('\t')) {
                val text = node.getText()
                emit(node.startOffset + text.indexOf('\t'), "Unexpected Tab character(s)", true)
                if (autoCorrect) {
                    (node as LeafPsiElement).rawReplaceWithText(text.replace("\t", " ".repeat(indentSize)))
                }
            }
        }
    }

    // todo: calculating indent based on the previous line value is wrong (see IndentationRule.testLint)
    private fun ASTNode.previousIndentSize(): Int {
        var node: ASTNode? = this.treeParent
        while (node != null) {
            val nextNode = node.treeNext?.elementType
            if (node is PsiWhiteSpace &&
                nextNode != TYPE_REFERENCE &&
                nextNode != SUPER_TYPE_LIST &&
                nextNode != CONSTRUCTOR_DELEGATION_CALL &&
                node.textContains('\n') &&
                node.nextLeaf()?.isPartOf(PsiComment::class) != true
            ) {
                val text = node.getText()
                return text.length - text.lastIndexOf('\n') - 1
            }
            node = node.treePrev ?: node.treeParent
        }
        return 0
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType
import com.pinterest.ktlint.core.ast.isPartOf
import com.pinterest.ktlint.core.ast.isRoot
import com.pinterest.ktlint.core.ast.parent
import com.pinterest.ktlint.core.ast.prevCodeSibling
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtPackageDirective

class MaxLineLengthRule : Rule("max-line-length"), Rule.Modifier.Last {

    private var maxLineLength: Int = -1
    private var rangeTree = RangeTree()

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.isRoot()) {
            val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
            maxLineLength = editorConfig.maxLineLength
            if (maxLineLength <= 0) {
                return
            }
            val errorOffset = arrayListOf<Int>()
            val text = node.text
            val lines = text.split("\n")
            var offset = 0
            for (line in lines) {
                if (line.length > maxLineLength) {
                    val el = node.psi.findElementAt(offset + line.length - 1)!!.node
                    if (!el.isPartOf(KDoc::class) && !el.isPartOfRawMultiLineString()) {
                        if (!el.isPartOf(PsiComment::class)) {
                            if (!el.isPartOf(KtPackageDirective::class) && !el.isPartOf(KtImportDirective::class)) {
                                // fixme:
                                // normally we would emit here but due to API limitations we need to hold off until
                                // node spanning the same offset is 'visit'ed
                                // (for ktlint-disable directive to have effect (when applied))
                                // this will be rectified in the upcoming release(s)
                                errorOffset.add(offset)
                            }
                        } else {
                            // if comment is the only thing on the line - fine, otherwise emit an error
                            val prevLeaf = el.prevCodeSibling()
                            if (prevLeaf != null && prevLeaf.startOffset >= offset) {
                                // fixme:
                                // normally we would emit here but due to API limitations we need to hold off until
                                // node spanning the same offset is 'visit'ed
                                // (for ktlint-disable directive to have effect (when applied))
                                // this will be rectified in the upcoming release(s)
                                errorOffset.add(offset)
                            }
                        }
                    }
                }
                offset += line.length + 1
            }
            rangeTree = RangeTree(errorOffset)
        } else if (!rangeTree.isEmpty() && node.psi is LeafPsiElement) {
            rangeTree
                .query(node.startOffset, node.startOffset + node.textLength)
                .forEach { offset ->
                    emit(offset, "Exceeded max line length ($maxLineLength)", false)
                }
        }
    }

    fun ASTNode.isPartOfRawMultiLineString() =
        parent(ElementType.STRING_TEMPLATE, strict = false)
            ?.let { it.firstChildNode.text == "\"\"\"" && it.textContains('\n') } == true
}

class RangeTree(seq: List<Int> = emptyList()) {

    private var emptyArrayView = ArrayView(0, 0)
    private var arr: IntArray = seq.toIntArray()

    init {
        if (arr.isNotEmpty()) {
            arr.reduce { p, n -> require(p <= n) { "Input must be sorted" }; n }
        }
    }

    // runtime: O(log(n)+k), where k is number of matching points
    // space: O(1)
    fun query(vmin: Int, vmax: Int): RangeTree.ArrayView {
        var r = arr.size - 1
        if (r == -1 || vmax < arr[0] || arr[r] < vmin) {
            return emptyArrayView
        }
        // binary search for min(arr[l] >= vmin)
        var l = 0
        while (l < r) {
            val m = (r + l) / 2
            if (vmax < arr[m]) {
                r = m - 1
            } else if (arr[m] < vmin) {
                l = m + 1
            } else {
                // arr[l] ?<=? vmin <= arr[m] <= vmax ?<=? arr[r]
                if (vmin <= arr[l]) break else l++ // optimization
                r = m
            }
        }
        if (l > r || arr[l] < vmin) {
            return emptyArrayView
        }
        // find max(k) such as arr[k] < vmax
        var k = l
        while (k < arr.size) {
            if (arr[k] >= vmax) {
                break
            }
            k++
        }
        return ArrayView(l, k)
    }

    fun isEmpty() = arr.isEmpty()

    inner class ArrayView(private var l: Int, private val r: Int) {

        val size: Int = r - l

        fun get(i: Int): Int {
            if (i < 0 || i >= size) {
                throw IndexOutOfBoundsException()
            }
            return arr[l + i]
        }

        inline fun forEach(cb: (v: Int) -> Unit) {
            var i = 0
            while (i < size) {
                cb(get(i++))
            }
        }

        override fun toString(): String {
            if (l == r) {
                return "[]"
            }
            val sb = StringBuilder("[")
            var i = l
            while (i < r) {
                sb.append(arr[i]).append(", ")
                i++
            }
            sb.replace(sb.length - 2, sb.length, "")
            sb.append("]")
            return sb.toString()
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.ABSTRACT_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.ACTUAL_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.COMPANION_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.CONST_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.DATA_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.ENUM_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.EXPECT_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.EXTERNAL_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.FINAL_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.INFIX_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.INLINE_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.INNER_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.INTERNAL_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.LATEINIT_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.OPEN_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.OPERATOR_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.OVERRIDE_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.PRIVATE_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.PROTECTED_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.PUBLIC_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.SEALED_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.SUSPEND_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.TAILREC_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.VARARG_KEYWORD
import java.util.Arrays
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtDeclarationModifierList

class ModifierOrderRule : Rule("modifier-order") {

    // subset of ElementType.MODIFIER_KEYWORDS_ARRAY (+ annotations entries)
    private val order = arrayOf(
        ANNOTATION_ENTRY,
        PUBLIC_KEYWORD, PROTECTED_KEYWORD, PRIVATE_KEYWORD, INTERNAL_KEYWORD,
        EXPECT_KEYWORD, ACTUAL_KEYWORD,
        FINAL_KEYWORD, OPEN_KEYWORD, ABSTRACT_KEYWORD, SEALED_KEYWORD, CONST_KEYWORD,
        EXTERNAL_KEYWORD,
        OVERRIDE_KEYWORD,
        LATEINIT_KEYWORD,
        TAILREC_KEYWORD,
        VARARG_KEYWORD,
        SUSPEND_KEYWORD,
        INNER_KEYWORD,
        ENUM_KEYWORD, ANNOTATION_KEYWORD,
        COMPANION_KEYWORD,
        INLINE_KEYWORD,
        INFIX_KEYWORD,
        OPERATOR_KEYWORD,
        DATA_KEYWORD
        // NOINLINE_KEYWORD, CROSSINLINE_KEYWORD, OUT_KEYWORD, IN_KEYWORD, REIFIED_KEYWORD
        // HEADER_KEYWORD, IMPL_KEYWORD
    )
    private val tokenSet = TokenSet.create(*order)

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.psi is KtDeclarationModifierList) {
            val modifierArr = node.getChildren(tokenSet)
            val sorted = modifierArr.copyOf().apply { sortWith(compareBy { order.indexOf(it.elementType) }) }
            if (!Arrays.equals(modifierArr, sorted)) {
                // Since annotations can be fairly lengthy and/or span multiple lines we are
                // squashing them into a single placeholder text to guarantee a single line output
                emit(
                    node.startOffset,
                    "Incorrect modifier order (should be \"${
                    squashAnnotations(sorted).joinToString(" ")
                    }\")",
                    true
                )
                if (autoCorrect) {
                    modifierArr.forEachIndexed { i, n ->
                        node.replaceChild(n, sorted[i].clone() as ASTNode)
                    }
                }
            }
        }
    }

    private fun squashAnnotations(sorted: Array<ASTNode>): List<String> {
        val nonAnnotationModifiers = sorted.filter { it.psi !is KtAnnotationEntry }
        return if (nonAnnotationModifiers.size != sorted.size) {
            listOf("@Annotation...") + nonAnnotationModifiers.map { it.text }
        } else {
            nonAnnotationModifiers.map { it.text }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.ELSE
import com.pinterest.ktlint.core.ast.ElementType.LBRACE
import com.pinterest.ktlint.core.ast.ElementType.RBRACE
import com.pinterest.ktlint.core.ast.ElementType.THEN
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.TreeElement

/**
 * TODO: if, for, when branch, do, while
 * @author yokotaso <yokotaso.t@gmail.com>
 */
class MultiLineIfElseRule : Rule("multiline-if-else") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.elementType == THEN || node.elementType == ELSE) {
            if (!node.treePrev.textContains('\n')) { // if (...) <statement>
                return
            }
            if (node.firstChildNode?.firstChildNode?.elementType != LBRACE) {
                emit(node.firstChildNode.startOffset, "Missing { ... }", true)
                if (autoCorrect) {
                    (node.firstChildNode.firstChildNode as TreeElement).rawInsertBeforeMe(LeafPsiElement(RBRACE, "{"))
                    (node.lastChildNode.lastChildNode as TreeElement).rawInsertAfterMe(LeafPsiElement(LBRACE, "}"))
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.RBRACE
import com.pinterest.ktlint.core.ast.nextLeaf
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

class NoBlankLineBeforeRbraceRule : Rule("no-blank-line-before-rbrace") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is PsiWhiteSpace &&
            node.textContains('\n') &&
            node.nextLeaf()?.elementType == RBRACE
        ) {
            val split = node.getText().split("\n")
            if (split.size > 2) {
                emit(
                    node.startOffset + split[0].length + split[1].length + 1,
                    "Unexpected blank line(s) before \"}\"", true
                )
                if (autoCorrect) {
                    (node as LeafPsiElement).rawReplaceWithText("${split.first()}\n${split.last()}")
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.nextLeaf
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

class NoConsecutiveBlankLinesRule : Rule("no-consecutive-blank-lines") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is PsiWhiteSpace) {
            val text = node.getText()
            val lfcount = text.count { it == '\n' }
            if (lfcount < 2) {
                return
            }
            val eof = node.nextLeaf() == null
            if (lfcount > 2 || eof) {
                val split = text.split("\n")
                emit(node.startOffset + split[0].length + split[1].length + 2, "Needless blank line(s)", true)
                if (autoCorrect) {
                    (node as LeafPsiElement)
                        .rawReplaceWithText("${split.first()}\n${if (eof) "" else "\n"}${split.last()}")
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY
import com.pinterest.ktlint.core.ast.ElementType.LBRACE
import com.pinterest.ktlint.core.ast.ElementType.RBRACE
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.isPartOf
import com.pinterest.ktlint.core.ast.nextLeaf
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.KtObjectLiteralExpression

class NoEmptyClassBodyRule : Rule("no-empty-class-body") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.elementType == CLASS_BODY &&
            node.firstChildNode?.let { n ->
                n.elementType == LBRACE &&
                    n.nextLeaf { it.elementType != WHITE_SPACE }?.elementType == RBRACE
            } == true &&
            !node.isPartOf(KtObjectLiteralExpression::class)
        ) {
            emit(node.startOffset, "Unnecessary block (\"{}\")", true)
            if (autoCorrect) {
                val prevNode = node.treePrev
                if (prevNode.elementType == WHITE_SPACE) {
                    // remove space between declaration and block
                    prevNode.treeParent.removeChild(prevNode)
                }
                // remove block
                node.treeParent.removeChild(node)
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.BLOCK
import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
import com.pinterest.ktlint.core.ast.parent
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

class NoItParamInMultilineLambdaRule : Rule("no-it-in-multiline-lambda") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        // fixme: we are not accounting for "it" variable that can be defined in the same scope
        if (node.elementType == IDENTIFIER && node.text == "it") {
            val block = node.parent(BLOCK)
            if (block != null && block.textContains('\n')) {
                emit(node.startOffset, "Multiline lambda must explicitly name \"it\" parameter", false)
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.ELSE_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.IF_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.LBRACE
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
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

class NoLineBreakAfterElseRule : Rule("no-line-break-after-else") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is PsiWhiteSpace &&
            node.textContains('\n')
        ) {
            if (node.prevLeaf()?.elementType == ELSE_KEYWORD &&
                node.nextLeaf()?.elementType.let { it == IF_KEYWORD || it == LBRACE }
            ) {
                emit(node.startOffset + 1, "Unexpected line break after \"else\"", true)
                if (autoCorrect) {
                    (node as LeafPsiElement).rawReplaceWithText(" ")
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.EQ
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

class NoLineBreakBeforeAssignmentRule : Rule("no-line-break-before-assignment") {

    override fun visit(node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
        if (node.elementType == EQ) {
            val prevElement = node.treePrev?.psi
            if (prevElement is PsiWhiteSpace && prevElement.text.contains("\n")) {
                emit(node.startOffset, "Line break before assignment is not allowed", true)
                if (autoCorrect) {
                    (node.treeNext?.psi as LeafPsiElement).rawReplaceWithText(prevElement.text)
                    (prevElement as LeafPsiElement).rawReplaceWithText(" ")
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
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

class NoMultipleSpacesRule : Rule("no-multi-spaces") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is PsiWhiteSpace && !node.textContains('\n') && node.getTextLength() > 1) {
            emit(node.startOffset + 1, "Unnecessary space(s)", true)
            if (autoCorrect) {
                (node as LeafPsiElement).rawReplaceWithText(" ")
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.KDOC_TEXT
import com.pinterest.ktlint.core.ast.ElementType.OBJECT_KEYWORD
import com.pinterest.ktlint.core.ast.isPartOf
import com.pinterest.ktlint.core.ast.isPartOfString
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevCodeLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
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.KtEnumEntry

class NoSemicolonsRule : Rule("no-semi") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.elementType == KDOC_TEXT) {
            return
        }
        if (node is LeafPsiElement && node.textMatches(";") && !node.isPartOfString() &&
            !node.isPartOf(KtEnumEntry::class)
        ) {
            val nextLeaf = node.nextLeaf()
            if (doesNotRequirePreSemi(nextLeaf)) {
                if (node.prevCodeLeaf()?.elementType == OBJECT_KEYWORD) {
                    // https://github.com/shyiko/ktlint/issues/281
                    return
                }
                emit(node.startOffset, "Unnecessary semicolon", true)
                if (autoCorrect) {
                    node.treeParent.removeChild(node)
                }
            } else if (nextLeaf !is PsiWhiteSpace) {
                val prevLeaf = node.prevLeaf()
                if (prevLeaf is PsiWhiteSpace && prevLeaf.textContains('\n')) { // \n;{
                    return
                }
                // todo: move to a separate rule
                emit(node.startOffset + 1, "Missing spacing after \";\"", true)
                if (autoCorrect) {
                    node.upsertWhitespaceAfterMe(" ")
                }
            }
        }
    }

    private fun doesNotRequirePreSemi(nextLeaf: ASTNode?): Boolean {
        if (nextLeaf is PsiWhiteSpace) {
            val nextNextLeaf = nextLeaf.nextLeaf()
            return (
                nextNextLeaf == null || // \s+ and then eof
                    nextLeaf.textContains('\n') && nextNextLeaf.text != "{"
                )
        }
        return nextLeaf == null /* eof */
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.nextLeaf
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

class NoTrailingSpacesRule : Rule("no-trailing-spaces") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is PsiWhiteSpace) {
            val lines = node.getText().split("\n")
            if (lines.size > 1) {
                val violated = checkForTrailingSpaces(lines.head(), node.startOffset, emit)
                if (violated && autoCorrect) {
                    (node as LeafPsiElement).rawReplaceWithText("\n".repeat(lines.size - 1) + lines.last())
                }
            } else if (node.nextLeaf() == null /* eof */) {
                val violated = checkForTrailingSpaces(lines, node.startOffset, emit)
                if (violated && autoCorrect) {
                    (node as LeafPsiElement).rawReplaceWithText("\n".repeat(lines.size - 1))
                }
            }
        }
    }

    private fun checkForTrailingSpaces(
        lines: List<String>,
        offset: Int,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ): Boolean {
        var violated = false
        var violationOffset = offset
        lines.forEach { line ->
            if (!line.isEmpty()) {
                emit(violationOffset, "Trailing space(s)", true)
                violated = true
            }
            violationOffset += line.length + 1
        }
        return violated
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.FUN
import com.pinterest.ktlint.core.ast.ElementType.LBRACE
import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST
import com.pinterest.ktlint.core.ast.nextCodeLeaf
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

class NoUnitReturnRule : Rule("no-unit-return") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.elementType == TYPE_REFERENCE &&
            node.treeParent.elementType == FUN &&
            node.text == "Unit" &&
            node.nextCodeLeaf(skipSubtree = true)?.elementType == LBRACE
        ) {
            emit(node.startOffset, "Unnecessary \"Unit\" return type", true)
            if (autoCorrect) {
                var prevNode = node
                while (prevNode.treePrev.elementType != VALUE_PARAMETER_LIST) {
                    prevNode = prevNode.treePrev
                }
                node.treeParent.removeRange(prevNode, node.treeNext)
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.IMPORT_DIRECTIVE
import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE
import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
import com.pinterest.ktlint.core.ast.isPartOf
import com.pinterest.ktlint.core.ast.isRoot
import com.pinterest.ktlint.core.ast.visit
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtPackageDirective

class NoUnusedImportsRule : Rule("no-unused-imports") {

    private val componentNRegex = Regex("^component\\d+$")

    private val operatorSet = setOf(
        // unary
        "unaryPlus", "unaryMinus", "not",
        // inc/dec
        "inc", "dec",
        // arithmetic
        "plus", "minus", "times", "div", "rem", "mod", "rangeTo",
        // in
        "contains",
        // indexed access
        "get", "set",
        // invoke
        "invoke",
        // augmented assignments
        "plusAssign", "minusAssign", "timesAssign", "divAssign", "modAssign",
        // (in)equality
        "equals",
        // comparison
        "compareTo",
        // iteration (https://github.com/shyiko/ktlint/issues/40)
        "iterator",
        // by (https://github.com/shyiko/ktlint/issues/54)
        "getValue", "setValue"
    )
    private val ref = mutableSetOf<String>()
    private var packageName = ""

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.isRoot()) {
            ref.clear() // rule can potentially be executed more than once (when formatting)
            ref.add("*")
            node.visit { vnode ->
                val psi = vnode.psi
                val type = vnode.elementType
                if (type == KDocTokens.MARKDOWN_LINK && psi is KDocLink) {
                    val linkText = psi.getLinkText().replace("`", "")
                    ref.add(linkText.split('.').first())
                } else if ((type == REFERENCE_EXPRESSION || type == OPERATION_REFERENCE) &&
                    !vnode.isPartOf(IMPORT_DIRECTIVE)
                ) {
                    ref.add(vnode.text.trim('`'))
                }
            }
        } else if (node.elementType == PACKAGE_DIRECTIVE) {
            val packageDirective = node.psi as KtPackageDirective
            packageName = packageDirective.qualifiedName
        } else if (node.elementType == IMPORT_DIRECTIVE) {
            val importDirective = node.psi as KtImportDirective
            val name = importDirective.importPath?.importedName?.asString()
            val importPath = importDirective.importPath?.pathStr!!
            if (importDirective.aliasName == null &&
                (packageName.isEmpty() || importPath.startsWith("$packageName.")) &&
                importPath.substring(packageName.length + 1).indexOf('.') == -1
            ) {
                emit(node.startOffset, "Unnecessary import", true)
                if (autoCorrect) {
                    importDirective.delete()
                }
            } else if (name != null && !ref.contains(name) && !operatorSet.contains(name) && !name.isComponentN()) {
                emit(node.startOffset, "Unused import", true)
                if (autoCorrect) {
                    importDirective.delete()
                }
            }
        }
    }

    private fun String.isComponentN() = componentNRegex.matches(this)
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.IMPORT_DIRECTIVE
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.KtImportDirective

class NoWildcardImportsRule : Rule("no-wildcard-imports") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.elementType == IMPORT_DIRECTIVE) {
            val importDirective = node.psi as KtImportDirective
            val path = importDirective.importPath?.pathStr
            if (path != null && !path.startsWith("kotlinx.android.synthetic") && path.contains('*')) {
                emit(node.startOffset, "Wildcard import", false)
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.KtPackageDirective

/**
 * @see [Kotlin Style Guide](https://kotlinlang.org/docs/reference/coding-conventions.html#naming-rules)
 * @see [Android Style Guide](https://android.github.io/kotlin-guides/style.html#package-names)
 * @author yokotaso <yokotaso.t@gmail.com>
 */
class PackageNameRule : Rule("package-name") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.elementType == PACKAGE_DIRECTIVE) {
            val qualifiedName = (node.psi as KtPackageDirective).qualifiedName
            if (qualifiedName.isEmpty()) {
                return
            }
            // https://github.com/shyiko/ktlint/issues/280
/*
            val filePath = node.psi.containingFile.node.getUserData(KtLint.FILE_PATH_USER_DATA_KEY) ?: return
            val fileDir = filePath.substringBeforeLast(File.separatorChar)
                .replace('.', File.separatorChar) // kotlinc a/b.c/d/E.kt produces a/b/c/d/E.class
            if (!fileDir.endsWith(File.separatorChar + qualifiedName.replace('.', File.separatorChar))) {
                emit(node.startOffset, "Package directive doesn't match file location", false)
            }
*/
            if (qualifiedName.contains('_')) {
                emit(node.startOffset, "Package name must not contain underscore", false)
                // "package name must be in lowercase" is violated by too many to projects in the wild to forbid
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_LITERAL
import com.pinterest.ktlint.core.ast.ElementType.LPAR
import com.pinterest.ktlint.core.ast.ElementType.RPAR
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.children
import com.pinterest.ktlint.core.ast.isRoot
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.visit
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.LeafElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl

class ParameterListWrappingRule : Rule("parameter-list-wrapping") {

    private var indentSize = -1
    private var maxLineLength = -1

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.isRoot()) {
            val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
            indentSize = editorConfig.indentSize
            maxLineLength = editorConfig.maxLineLength
            return
        }
        if (indentSize <= 0) {
            return
        }
        if (node.elementType == VALUE_PARAMETER_LIST &&
            // skip when there are no parameters
            node.firstChildNode?.treeNext?.elementType != RPAR &&
            // skip lambda parameters
            node.treeParent?.elementType != FUNCTION_LITERAL
        ) {
            // each parameter should be on a separate line if
            // - at least one of the parameters is
            // - maxLineLength exceeded (and separating parameters with \n would actually help)
            // in addition, "(" and ")" must be on separates line if any of the parameters are (otherwise on the same)
            val putParametersOnSeparateLines =
                node.textContains('\n') ||
                    // max_line_length exceeded
                    maxLineLength > -1 && (node.column - 1 + node.textLength) > maxLineLength
            if (putParametersOnSeparateLines) {
                // aiming for
                // ... LPAR
                // <line indent + indentSize> VALUE_PARAMETER...
                // <line indent> RPAR
                val indent = "\n" + node.lineIndent()
                val paramIndent = indent + " ".repeat(indentSize) // single indent as recommended by Jetbrains/Google
                nextChild@ for (child in node.children()) {
                    when (child.elementType) {
                        LPAR -> {
                            val prevLeaf = child.prevLeaf()
                            if (prevLeaf is PsiWhiteSpace && prevLeaf.textContains('\n')) {
                                emit(child.startOffset, errorMessage(child), true)
                                if (autoCorrect) {
                                    prevLeaf.delete()
                                }
                            }
                        }
                        VALUE_PARAMETER,
                        RPAR -> {
                            var paramInnerIndentAdjustment = 0
                            val prevLeaf = child.prevLeaf()
                            val intendedIndent = if (child.elementType == VALUE_PARAMETER) {
                                paramIndent
                            } else {
                                indent
                            }
                            if (prevLeaf is PsiWhiteSpace) {
                                val spacing = prevLeaf.getText()
                                val cut = spacing.lastIndexOf("\n")
                                if (cut > -1) {
                                    val childIndent = spacing.substring(cut)
                                    if (childIndent == intendedIndent) {
                                        continue@nextChild
                                    }
                                    emit(
                                        child.startOffset,
                                        "Unexpected indentation" +
                                            " (expected ${intendedIndent.length - 1}, actual ${childIndent.length - 1})",
                                        true
                                    )
                                } else {
                                    emit(child.startOffset, errorMessage(child), true)
                                }
                                if (autoCorrect) {
                                    val adjustedIndent = (if (cut > -1) spacing.substring(0, cut) else "") + intendedIndent
                                    paramInnerIndentAdjustment = adjustedIndent.length - prevLeaf.getTextLength()
                                    (prevLeaf as LeafPsiElement).rawReplaceWithText(adjustedIndent)
                                }
                            } else {
                                emit(child.startOffset, errorMessage(child), true)
                                if (autoCorrect) {
                                    paramInnerIndentAdjustment = intendedIndent.length - child.column
                                    node.addChild(PsiWhiteSpaceImpl(intendedIndent), child)
                                }
                            }
                            if (paramInnerIndentAdjustment != 0 &&
                                child.elementType == VALUE_PARAMETER
                            ) {
                                child.visit { n ->
                                    if (n.elementType == WHITE_SPACE && n.textContains('\n')) {
                                        val split = n.text.split("\n")
                                        (n as LeafElement).rawReplaceWithText(
                                            split.joinToString("\n") {
                                                if (paramInnerIndentAdjustment > 0) {
                                                    it + " ".repeat(paramInnerIndentAdjustment)
                                                } else {
                                                    it.substring(0, Math.max(it.length + paramInnerIndentAdjustment, 0))
                                                }
                                            }
                                        )
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private val ASTNode.column: Int
        get() {
            var leaf = this.prevLeaf()
            var offsetToTheLeft = 0
            while (leaf != null) {
                if (leaf.elementType == WHITE_SPACE && leaf.textContains('\n')) {
                    offsetToTheLeft += leaf.textLength - 1 - leaf.text.lastIndexOf('\n')
                    break
                }
                offsetToTheLeft += leaf.textLength
                leaf = leaf.prevLeaf()
            }
            return offsetToTheLeft + 1
        }

    private fun errorMessage(node: ASTNode) =
        when (node.elementType) {
            LPAR -> """Unnecessary newline before "(""""
            VALUE_PARAMETER ->
                "Parameter should be on a separate line (unless all parameters can fit a single line)"
            RPAR -> """Missing newline before ")""""
            else -> throw UnsupportedOperationException()
        }

    private fun ASTNode.lineIndent(): String {
        var leaf = this.prevLeaf()
        while (leaf != null) {
            if (leaf.elementType == WHITE_SPACE && leaf.textContains('\n')) {
                return leaf.text.substring(leaf.text.lastIndexOf('\n') + 1)
            }
            leaf = leaf.prevLeaf()
        }
        return ""
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION
import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION_ENTRY
import com.pinterest.ktlint.core.ast.isPartOf
import com.pinterest.ktlint.core.ast.isPartOfComment
import com.pinterest.ktlint.core.ast.isPartOfString
import com.pinterest.ktlint.core.ast.isWhiteSpaceWithNewline
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe
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.KtClassOrObject
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtTypeConstraint
import org.jetbrains.kotlin.psi.KtTypeParameterList

class SpacingAroundColonRule : Rule("colon-spacing") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is LeafPsiElement && node.textMatches(":") && !node.isPartOfString() && !node.isPartOfComment()) {
            if (node.isPartOf(ANNOTATION) || node.isPartOf(ANNOTATION_ENTRY)) {
                // todo: enforce "no spacing"
                return
            }
            val removeSpacingBefore =
                node.parent !is KtClassOrObject &&
                    node.parent !is KtConstructor<*> && // constructor : this/super
                    node.parent !is KtTypeConstraint && // where T : S
                    node.parent?.parent !is KtTypeParameterList
            val prevLeaf = node.prevLeaf()
            if (prevLeaf != null &&
                prevLeaf.isWhiteSpaceWithNewline() &&
                // FIXME: relocate : so that it would be in front of comment
                // (see SpacingAroundColonRuleTest.testFormatEOF & ChainWrappingRule)
                prevLeaf.prevLeaf()?.isPartOfComment() != true
            ) {
                emit(prevLeaf.startOffset, "Unexpected newline before \":\"", true)
                val text = prevLeaf.text
                if (autoCorrect) {
                    if (removeSpacingBefore) {
                        prevLeaf.treeParent.removeChild(prevLeaf)
                    } else {
                        (prevLeaf as LeafPsiElement).rawReplaceWithText(" ")
                    }
                    node.upsertWhitespaceAfterMe(text)
                }
            }
            if (node.prevSibling is PsiWhiteSpace && removeSpacingBefore) {
                emit(node.startOffset, "Unexpected spacing before \":\"", true)
                if (autoCorrect) {
                    node.prevSibling.node.treeParent.removeChild(node.prevSibling.node)
                }
            }
            val missingSpacingBefore =
                node.prevSibling !is PsiWhiteSpace &&
                    (
                        node.parent is KtClassOrObject || node.parent is KtConstructor<*> ||
                            node.parent is KtTypeConstraint || node.parent.parent is KtTypeParameterList
                        )
            val missingSpacingAfter = node.nextSibling !is PsiWhiteSpace
            when {
                missingSpacingBefore && missingSpacingAfter -> {
                    emit(node.startOffset, "Missing spacing around \":\"", true)
                    if (autoCorrect) {
                        node.upsertWhitespaceBeforeMe(" ")
                        node.upsertWhitespaceAfterMe(" ")
                    }
                }
                missingSpacingBefore -> {
                    emit(node.startOffset, "Missing spacing before \":\"", true)
                    if (autoCorrect) {
                        node.upsertWhitespaceBeforeMe(" ")
                    }
                }
                missingSpacingAfter -> {
                    emit(node.startOffset + 1, "Missing spacing after \":\"", true)
                    if (autoCorrect) {
                        node.upsertWhitespaceAfterMe(" ")
                    }
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.isPartOfString
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
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

class SpacingAroundCommaRule : Rule("comma-spacing") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is LeafPsiElement && node.textMatches(",") && !node.isPartOfString()) {
            val prevLeaf = node.prevLeaf()
            if (prevLeaf is PsiWhiteSpace) {
                emit(prevLeaf.startOffset, "Unexpected spacing before \"${node.text}\"", true)
                if (autoCorrect) {
                    prevLeaf.node.treeParent.removeChild(prevLeaf.node)
                }
            }
            if (node.nextLeaf() !is PsiWhiteSpace) {
                emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true)
                if (autoCorrect) {
                    node.upsertWhitespaceAfterMe(" ")
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.AT
import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY
import com.pinterest.ktlint.core.ast.ElementType.COLONCOLON
import com.pinterest.ktlint.core.ast.ElementType.COMMA
import com.pinterest.ktlint.core.ast.ElementType.DOT
import com.pinterest.ktlint.core.ast.ElementType.EXCLEXCL
import com.pinterest.ktlint.core.ast.ElementType.FUN
import com.pinterest.ktlint.core.ast.ElementType.LBRACE
import com.pinterest.ktlint.core.ast.ElementType.LBRACKET
import com.pinterest.ktlint.core.ast.ElementType.LPAR
import com.pinterest.ktlint.core.ast.ElementType.RBRACE
import com.pinterest.ktlint.core.ast.ElementType.RPAR
import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS
import com.pinterest.ktlint.core.ast.ElementType.SEMICOLON
import com.pinterest.ktlint.core.ast.isPartOfString
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe
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.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtLambdaExpression

class SpacingAroundCurlyRule : Rule("curly-spacing") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is LeafPsiElement && !node.isPartOfString()) {
            val prevLeaf = node.prevLeaf()
            val nextLeaf = node.nextLeaf()
            val spacingBefore: Boolean
            val spacingAfter: Boolean
            if (node.textMatches("{")) {
                spacingBefore =
                    prevLeaf is PsiWhiteSpace ||
                    prevLeaf?.elementType == AT ||
                    (
                        prevLeaf?.elementType == LPAR &&
                            (node.parent is KtLambdaExpression || node.parent.parent is KtLambdaExpression)
                        )
                spacingAfter = nextLeaf is PsiWhiteSpace || nextLeaf?.elementType == RBRACE
                if (prevLeaf is PsiWhiteSpace &&
                    !prevLeaf.textContains('\n') &&
                    prevLeaf.prevLeaf()?.let {
                        it.elementType == LPAR || it.elementType == AT
                    } == true
                ) {
                    emit(node.startOffset, "Unexpected space before \"${node.text}\"", true)
                    if (autoCorrect) {
                        prevLeaf.node.treeParent.removeChild(prevLeaf.node)
                    }
                }
                if (prevLeaf is PsiWhiteSpace &&
                    prevLeaf.textContains('\n') &&
                    (
                        prevLeaf.prevLeaf()?.let {
                            it.elementType == RPAR || KtTokens.KEYWORDS.contains(it.elementType)
                        } == true ||
                            node.treeParent.elementType == CLASS_BODY ||
                            prevLeaf.treeParent.elementType == FUN
                        )
                ) {
                    emit(node.startOffset, "Unexpected newline before \"${node.text}\"", true)
                    if (autoCorrect) {
                        (prevLeaf as LeafPsiElement).rawReplaceWithText(" ")
                    }
                }
            } else if (node.textMatches("}")) {
                spacingBefore = prevLeaf is PsiWhiteSpace || prevLeaf?.elementType == LBRACE
                spacingAfter = nextLeaf == null || nextLeaf is PsiWhiteSpace || shouldNotToBeSeparatedBySpace(nextLeaf)
                if (nextLeaf is PsiWhiteSpace && !nextLeaf.textContains('\n') &&
                    shouldNotToBeSeparatedBySpace(nextLeaf.nextLeaf())
                ) {
                    emit(node.startOffset, "Unexpected space after \"${node.text}\"", true)
                    if (autoCorrect) {
                        nextLeaf.node.treeParent.removeChild(nextLeaf.node)
                    }
                }
            } else {
                return
            }
            when {
                !spacingBefore && !spacingAfter -> {
                    emit(node.startOffset, "Missing spacing around \"${node.text}\"", true)
                    if (autoCorrect) {
                        node.upsertWhitespaceBeforeMe(" ")
                        node.upsertWhitespaceAfterMe(" ")
                    }
                }
                !spacingBefore -> {
                    emit(node.startOffset, "Missing spacing before \"${node.text}\"", true)
                    if (autoCorrect) {
                        node.upsertWhitespaceBeforeMe(" ")
                    }
                }
                !spacingAfter -> {
                    emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true)
                    if (autoCorrect) {
                        node.upsertWhitespaceAfterMe(" ")
                    }
                }
            }
        }
    }

    fun shouldNotToBeSeparatedBySpace(leaf: ASTNode?): Boolean {
        val nextElementType = leaf?.elementType
        return (
            nextElementType == DOT ||
                nextElementType == COMMA ||
                nextElementType == RPAR ||
                nextElementType == SEMICOLON ||
                nextElementType == SAFE_ACCESS ||
                nextElementType == EXCLEXCL ||
                nextElementType == LBRACKET ||
                nextElementType == LPAR ||
                nextElementType == COLONCOLON
            )
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.isPartOfComment
import com.pinterest.ktlint.core.ast.isPartOfString
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
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

class SpacingAroundDotRule : Rule("dot-spacing") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is LeafPsiElement && node.textMatches(".") && !node.isPartOfString() && !node.isPartOfComment()) {
            val prevLeaf = node.prevLeaf()
            if (prevLeaf is PsiWhiteSpace && !prevLeaf.textContains('\n')) {
                emit(prevLeaf.startOffset, "Unexpected spacing before \"${node.text}\"", true)
                if (autoCorrect) {
                    prevLeaf.node.treeParent.removeChild(prevLeaf.node)
                }
            }
            val nextLeaf = node.nextLeaf()
            if (nextLeaf is PsiWhiteSpace) {
                emit(nextLeaf.startOffset, "Unexpected spacing after \"${node.text}\"", true)
                if (autoCorrect) {
                    nextLeaf.node.treeParent.removeChild(nextLeaf.node)
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.CATCH_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.DO_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.ELSE_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.FINALLY_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.FOR_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.GET_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.IF_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.RBRACE
import com.pinterest.ktlint.core.ast.ElementType.SET_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.TRY_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.WHEN_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.WHILE_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
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.LeafElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet.create
import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.kotlin.psi.KtWhenEntry

class SpacingAroundKeywordRule : Rule("keyword-spacing") {

    private val noLFBeforeSet = create(ELSE_KEYWORD, CATCH_KEYWORD, FINALLY_KEYWORD)
    private val tokenSet = create(
        FOR_KEYWORD, IF_KEYWORD, ELSE_KEYWORD, WHILE_KEYWORD, DO_KEYWORD,
        TRY_KEYWORD, CATCH_KEYWORD, FINALLY_KEYWORD, WHEN_KEYWORD
    )

    private val keywordsWithoutSpaces = create(GET_KEYWORD, SET_KEYWORD)

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is LeafPsiElement) {
            if (tokenSet.contains(node.elementType) && node.nextLeaf() !is PsiWhiteSpace) {
                emit(node.startOffset + node.text.length, "Missing spacing after \"${node.text}\"", true)
                if (autoCorrect) {
                    node.upsertWhitespaceAfterMe(" ")
                }
            } else if (keywordsWithoutSpaces.contains(node.elementType) && node.nextLeaf() is PsiWhiteSpace) {
                val parent = node.parent
                val nextLeaf = node.nextLeaf()
                if (parent is KtPropertyAccessor && parent.hasBody() && nextLeaf != null) {
                    emit(node.startOffset, "Unexpected spacing after \"${node.text}\"", true)
                    if (autoCorrect) {
                        nextLeaf.treeParent.removeChild(nextLeaf)
                    }
                }
            }
            if (noLFBeforeSet.contains(node.elementType)) {
                val prevLeaf = node.prevLeaf()
                if (
                    prevLeaf?.elementType == WHITE_SPACE &&
                    prevLeaf.textContains('\n') &&
                    (node.elementType != ELSE_KEYWORD || node.parent !is KtWhenEntry)
                ) {
                    val presumablyCurly = prevLeaf.prevLeaf()
                    if (presumablyCurly != null &&
                        presumablyCurly.elementType == RBRACE &&
                        (
                            node.elementType != ELSE_KEYWORD ||
                                // `if (...) v.let { } else` case
                                presumablyCurly.treeParent?.treeParent?.treeParent == node.treeParent
                            )
                    ) {
                        emit(node.startOffset, "Unexpected newline before \"${node.text}\"", true)
                        if (autoCorrect) {
                            (prevLeaf as LeafElement).rawReplaceWithText(" ")
                        }
                    }
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.ANDAND
import com.pinterest.ktlint.core.ast.ElementType.ARROW
import com.pinterest.ktlint.core.ast.ElementType.DIV
import com.pinterest.ktlint.core.ast.ElementType.DIVEQ
import com.pinterest.ktlint.core.ast.ElementType.ELVIS
import com.pinterest.ktlint.core.ast.ElementType.EQ
import com.pinterest.ktlint.core.ast.ElementType.EQEQ
import com.pinterest.ktlint.core.ast.ElementType.EQEQEQ
import com.pinterest.ktlint.core.ast.ElementType.EXCLEQ
import com.pinterest.ktlint.core.ast.ElementType.EXCLEQEQEQ
import com.pinterest.ktlint.core.ast.ElementType.FUN
import com.pinterest.ktlint.core.ast.ElementType.GT
import com.pinterest.ktlint.core.ast.ElementType.GTEQ
import com.pinterest.ktlint.core.ast.ElementType.LT
import com.pinterest.ktlint.core.ast.ElementType.LTEQ
import com.pinterest.ktlint.core.ast.ElementType.MINUS
import com.pinterest.ktlint.core.ast.ElementType.MINUSEQ
import com.pinterest.ktlint.core.ast.ElementType.MUL
import com.pinterest.ktlint.core.ast.ElementType.MULTEQ
import com.pinterest.ktlint.core.ast.ElementType.OROR
import com.pinterest.ktlint.core.ast.ElementType.PERC
import com.pinterest.ktlint.core.ast.ElementType.PERCEQ
import com.pinterest.ktlint.core.ast.ElementType.PLUS
import com.pinterest.ktlint.core.ast.ElementType.PLUSEQ
import com.pinterest.ktlint.core.ast.ElementType.TYPE_PARAMETER_LIST
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.isPartOf
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.parent
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe
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.LeafElement
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtPrefixExpression
import org.jetbrains.kotlin.psi.KtSuperExpression
import org.jetbrains.kotlin.psi.KtTypeArgumentList
import org.jetbrains.kotlin.psi.KtValueArgument

class SpacingAroundOperatorsRule : Rule("op-spacing") {

    private val tokenSet = TokenSet.create(
        MUL, PLUS, MINUS, DIV, PERC, LT, GT, LTEQ, GTEQ, EQEQEQ, EXCLEQEQEQ, EQEQ,
        EXCLEQ, ANDAND, OROR, ELVIS, EQ, MULTEQ, DIVEQ, PERCEQ, PLUSEQ, MINUSEQ, ARROW
    )

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (tokenSet.contains(node.elementType) &&
            node is LeafElement &&
            !node.isPartOf(KtPrefixExpression::class) && // not unary
            !node.isPartOf(KtTypeArgumentList::class) && // C<T>
            !(node.elementType == MUL && node.isPartOf(KtValueArgument::class)) && // fn(*array)
            !node.isPartOf(KtImportDirective::class) && // import *
            !node.isPartOf(KtSuperExpression::class) // super<T>
        ) {
            if (node.elementType == LT || node.elementType == GT) {
                val list = node.parent(TYPE_PARAMETER_LIST)
                if (list != null) {
                    if (node.elementType == LT) {
                        // ensure no space after < in <T>
                        val nextLeaf = node.nextLeaf()
                        if (nextLeaf?.elementType == WHITE_SPACE && !nextLeaf.textContains('\n')) {
                            emit(node.startOffset + 1, "Unexpected spacing after \"${node.text}\"", true)
                            if (autoCorrect) {
                                nextLeaf.treeParent.removeChild(nextLeaf)
                            }
                        }
                    } else {
                        // ensure no space before > in <T>
                        val prevLeaf = node.prevLeaf()
                        if (prevLeaf?.elementType == WHITE_SPACE && !prevLeaf.textContains('\n')) {
                            emit(prevLeaf.startOffset, "Unexpected spacing before \"${node.text}\"", true)
                            if (autoCorrect) {
                                prevLeaf.treeParent.removeChild(prevLeaf)
                            }
                        }
                    }
                    if (node.parent(TYPE_PARAMETER_LIST)?.treeParent?.elementType != FUN) {
                        return // FIXME: only `fun <T>fn(): T {}` is checked
                    }
                }
            }
            val spacingBefore = node.prevLeaf() is PsiWhiteSpace || node.elementType == GT
            val spacingAfter = node.nextLeaf() is PsiWhiteSpace || node.elementType == LT
            when {
                !spacingBefore && !spacingAfter -> {
                    emit(node.startOffset, "Missing spacing around \"${node.text}\"", true)
                    if (autoCorrect) {
                        node.upsertWhitespaceBeforeMe(" ")
                        node.upsertWhitespaceAfterMe(" ")
                    }
                }
                !spacingBefore -> {
                    emit(node.startOffset, "Missing spacing before \"${node.text}\"", true)
                    if (autoCorrect) {
                        node.upsertWhitespaceBeforeMe(" ")
                    }
                }
                !spacingAfter -> {
                    emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true)
                    if (autoCorrect) {
                        node.upsertWhitespaceAfterMe(" ")
                    }
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
import com.pinterest.ktlint.core.ast.ElementType.LPAR
import com.pinterest.ktlint.core.ast.ElementType.RPAR
import com.pinterest.ktlint.core.ast.ElementType.SUPER_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace

/**
 * Ensures there are no extra spaces around parentheses.
 *
 * See https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
 */
class SpacingAroundParensRule : Rule("paren-spacing") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.elementType == LPAR || node.elementType == RPAR) {
            val prevLeaf = node.prevLeaf()
            val nextLeaf = node.nextLeaf()
            val spacingBefore = if (node.elementType == LPAR) {
                prevLeaf is PsiWhiteSpace && !prevLeaf.textContains('\n') &&
                    (
                        prevLeaf.prevLeaf()?.elementType == IDENTIFIER ||
                            // Super keyword needs special-casing
                            prevLeaf.prevLeaf()?.elementType == SUPER_KEYWORD
                        ) && (
                    node.treeParent?.elementType == VALUE_PARAMETER_LIST ||
                        node.treeParent?.elementType == VALUE_ARGUMENT_LIST
                    )
            } else {
                prevLeaf is PsiWhiteSpace && !prevLeaf.textContains('\n') &&
                    prevLeaf.prevLeaf()?.elementType != LPAR
            }
            val spacingAfter = if (node.elementType == LPAR) {
                nextLeaf is PsiWhiteSpace && (
                    !nextLeaf.textContains('\n') ||
                        nextLeaf.nextLeaf()?.elementType == RPAR
                    )
            } else {
                nextLeaf is PsiWhiteSpace && !nextLeaf.textContains('\n') &&
                    nextLeaf.nextLeaf()?.elementType == RPAR
            }
            when {
                spacingBefore && spacingAfter -> {
                    emit(node.startOffset, "Unexpected spacing around \"${node.text}\"", true)
                    if (autoCorrect) {
                        prevLeaf!!.treeParent.removeChild(prevLeaf)
                        nextLeaf!!.treeParent.removeChild(nextLeaf)
                    }
                }
                spacingBefore -> {
                    emit(prevLeaf!!.startOffset, "Unexpected spacing before \"${node.text}\"", true)
                    if (autoCorrect) {
                        prevLeaf.treeParent.removeChild(prevLeaf)
                    }
                }
                spacingAfter -> {
                    emit(node.startOffset + 1, "Unexpected spacing after \"${node.text}\"", true)
                    if (autoCorrect) {
                        nextLeaf!!.treeParent.removeChild(nextLeaf)
                    }
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.RANGE
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace

class SpacingAroundRangeOperatorRule : Rule("range-spacing") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node.elementType == RANGE) {
            val prevLeaf = node.prevLeaf()
            val nextLeaf = node.nextLeaf()
            when {
                prevLeaf is PsiWhiteSpace && nextLeaf is PsiWhiteSpace -> {
                    emit(node.startOffset, "Unexpected spacing around \"..\"", true)
                    if (autoCorrect) {
                        prevLeaf.node.treeParent.removeChild(prevLeaf.node)
                        nextLeaf.node.treeParent.removeChild(nextLeaf.node)
                    }
                }
                prevLeaf is PsiWhiteSpace -> {
                    emit(prevLeaf.node.startOffset, "Unexpected spacing before \"..\"", true)
                    if (autoCorrect) {
                        prevLeaf.node.treeParent.removeChild(prevLeaf.node)
                    }
                }
                nextLeaf is PsiWhiteSpace -> {
                    emit(nextLeaf.node.startOffset, "Unexpected spacing after \"..\"", true)
                    if (autoCorrect) {
                        nextLeaf.node.treeParent.removeChild(nextLeaf.node)
                    }
                }
            }
        }
    }
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider

class StandardRuleSetProvider : RuleSetProvider {

    override fun get(): RuleSet = RuleSet(
        "standard",
        // disabled ("./mvnw clean verify" fails with "Internal Error")
        // AnnotationRule(),
        ChainWrappingRule(),
        CommentSpacingRule(),
        FilenameRule(),
        FinalNewlineRule(),
        // disabled until there is a way to suppress rules globally (https://git.io/fhxnm)
        // PackageNameRule(),
        // disabled until auto-correct is working properly
        // (e.g. try formatting "if (true)\n    return { _ ->\n        _\n}")
        // MultiLineIfElseRule(),
        IndentationRule(),
        MaxLineLengthRule(),
        ModifierOrderRule(),
        NoBlankLineBeforeRbraceRule(),
        NoConsecutiveBlankLinesRule(),
        NoEmptyClassBodyRule(),
        // disabled until it's clear what to do in case of `import _.it`
        // NoItParamInMultilineLambdaRule(),
        NoLineBreakAfterElseRule(),
        NoLineBreakBeforeAssignmentRule(),
        NoMultipleSpacesRule(),
        NoSemicolonsRule(),
        NoTrailingSpacesRule(),
        NoUnitReturnRule(),
        NoUnusedImportsRule(),
        NoWildcardImportsRule(),
        ParameterListWrappingRule(),
        SpacingAroundColonRule(),
        SpacingAroundCommaRule(),
        SpacingAroundCurlyRule(),
        SpacingAroundDotRule(),
        SpacingAroundKeywordRule(),
        SpacingAroundOperatorsRule(),
        SpacingAroundParensRule(),
        SpacingAroundRangeOperatorRule(),
        StringTemplateRule()
    )
}

package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.CLOSING_QUOTE
import com.pinterest.ktlint.core.ast.ElementType.DOT
import com.pinterest.ktlint.core.ast.ElementType.DOT_QUALIFIED_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.LITERAL_STRING_TEMPLATE_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.LONG_STRING_TEMPLATE_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.SUPER_EXPRESSION
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement

class StringTemplateRule : Rule("string-template") {

    override fun visit(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        val elementType = node.elementType
        // code below is commented out because (setting aside potentially dangerous replaceChild part)
        // `val v: String = "$elementType"` would be rewritten to `val v: String = elementType.toString()` and it's not
        // immediately clear which is better
/*
        if (elementType === SHORT_STRING_TEMPLATE_ENTRY || elementType == LONG_STRING_TEMPLATE_ENTRY) {
            if (node.treePrev.elementType == OPEN_QUOTE && node.treeNext.elementType == CLOSING_QUOTE) {
                emit(node.treePrev.startOffset, "Redundant string template", true)
                val entryStart = node.psi.firstChild
                node.treeParent.treeParent.replaceChild(node.treeParent, entryStart.nextSibling.node)
            }
*/
        if (elementType == LONG_STRING_TEMPLATE_ENTRY) {
            val entryStart = node.firstChildNode
            val dotQualifiedExpression = entryStart.treeNext
            if (dotQualifiedExpression?.elementType == DOT_QUALIFIED_EXPRESSION) {
                val callExpression = dotQualifiedExpression.lastChildNode
                val dot = callExpression.treePrev
                if (dot?.elementType == DOT &&
                    callExpression.text == "toString()" &&
                    dotQualifiedExpression.firstChildNode?.elementType != SUPER_EXPRESSION
                ) {
                    emit(dot.startOffset, "Redundant \"toString()\" call in string template", true)
                    if (autoCorrect) {
                        node.removeChild(dot)
                        node.removeChild(callExpression)
                    }
                }
            }
            if (node.text.startsWith("${'$'}{") &&
                node.text.let { it.substring(2, it.length - 1) }.all { it.isPartOfIdentifier() } &&
                node.treeNext.let { nextSibling ->
                    nextSibling.elementType == CLOSING_QUOTE ||
                        (
                            nextSibling.elementType == LITERAL_STRING_TEMPLATE_ENTRY &&
                                !nextSibling.text[0].isPartOfIdentifier()
                            )
                }
            ) {
                emit(node.treePrev.startOffset + 2, "Redundant curly braces", true)
                if (autoCorrect) {
                    // fixme: a proper way would be to downcast to SHORT_STRING_TEMPLATE_ENTRY
                    (node.firstChildNode as LeafPsiElement).rawReplaceWithText("$") // entry start
                    (node.lastChildNode as LeafPsiElement).rawReplaceWithText("") // entry end
                }
            }
        }
    }

    private fun Char.isPartOfIdentifier() = this == '_' || this.isLetterOrDigit()
}

package com.pinterest.ktlint.ruleset.standard

internal fun <T> List<T>.head() = this.subList(0, this.size - 1)
internal fun <T> List<T>.tail() = this.subList(1, this.size)

package com.pinterest.ktlint.reporter.plain.internal

/**
 * Stripped down version of https://github.com/ziggy42/kolor (ziggy42/kolor#6).
 */
fun String.color(foreground: Color) = "\u001B[${foreground.code}m$this\u001B[0m"

enum class Color(val code: Int) {
    BLACK(30),
    RED(31), GREEN(32), YELLOW(33), BLUE(34), MAGENTA(35), CYAN(36),
    LIGHT_GRAY(37), DARK_GRAY(90),
    LIGHT_RED(91), LIGHT_GREEN(92), LIGHT_YELLOW(93), LIGHT_BLUE(94), LIGHT_MAGENTA(95), LIGHT_CYAN(96),
    WHITE(97)
}