aboutsummaryrefslogtreecommitdiff
path: root/ktlint/src/main/kotlin/com/github/shyiko
diff options
context:
space:
mode:
Diffstat (limited to 'ktlint/src/main/kotlin/com/github/shyiko')
-rw-r--r--ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt572
-rw-r--r--ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/EditorConfig.kt111
-rw-r--r--ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt156
-rw-r--r--ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/MavenDependencyResolver.kt18
4 files changed, 564 insertions, 293 deletions
diff --git a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt
index ce805632..9541cdcb 100644
--- a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt
+++ b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt
@@ -12,6 +12,7 @@ import com.github.shyiko.ktlint.core.RuleSetProvider
import com.github.shyiko.ktlint.internal.EditorConfig
import com.github.shyiko.ktlint.internal.IntellijIDEAIntegration
import com.github.shyiko.ktlint.internal.MavenDependencyResolver
+import com.github.shyiko.ktlint.test.DumpAST
import org.eclipse.aether.RepositoryException
import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.repository.RemoteRepository
@@ -19,19 +20,14 @@ 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.preprocessor.mkdirsOrFail
-import org.kohsuke.args4j.Argument
-import org.kohsuke.args4j.CmdLineException
-import org.kohsuke.args4j.CmdLineParser
-import org.kohsuke.args4j.NamedOptionDef
-import org.kohsuke.args4j.Option
-import org.kohsuke.args4j.OptionHandlerFilter
-import org.kohsuke.args4j.ParserProperties
-import org.kohsuke.args4j.spi.OptionHandler
+import picocli.CommandLine
+import picocli.CommandLine.Command
+import picocli.CommandLine.Option
+import picocli.CommandLine.Parameters
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.PrintStream
-import java.io.PrintWriter
import java.math.BigInteger
import java.net.URLDecoder
import java.nio.file.Path
@@ -39,8 +35,8 @@ 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.ResourceBundle
import java.util.Scanner
import java.util.ServiceLoader
import java.util.concurrent.ArrayBlockingQueue
@@ -50,8 +46,41 @@ 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.system.exitProcess
+@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.gihub.user:repo:master-SNAPSHOT
+
+Flags:""",
+ synopsisHeading = "",
+ customSynopsis = arrayOf(""),
+ sortOptions = false
+)
object Main {
private val DEPRECATED_FLAGS = mapOf(
@@ -64,103 +93,127 @@ object Main {
"--reporter-update" to
"--repository-update"
)
- private val CLI_MAX_LINE_LENGTH_REGEX = Regex("(.{0,120})(?:\\s|$)")
+ @Option(names = arrayOf("--android", "-a"), description = arrayOf("Turn on Android Kotlin Style Guide compatibility"))
+ private var android: Boolean = false
- // todo: this should have been a command, not a flag (consider changing in 1.0.0)
- @Option(name="--format", aliases = arrayOf("-F"), usage = "Fix any deviations from the code style")
- private var format: 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
- @Option(name="--android", aliases = arrayOf("-a"), usage = "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-project"), description = arrayOf("Update Intellij IDEA project settings"))
+ private var applyToProject: Boolean = false
- @Option(name="--reporter",
- usage = "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)")
- private var reporters = ArrayList<String>()
+ @Option(names = arrayOf("--color"), description = arrayOf("Make output colorful"))
+ private var color: Boolean = false
- @Option(name="--ruleset", aliases = arrayOf("-R"),
- usage = "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("--debug"), description = arrayOf("Turn on debug output"))
+ private var debug: Boolean = false
- @Option(name="--repository", aliases = arrayOf("--ruleset-repository", "--reporter-repository"),
- usage = "An additional Maven repository (Maven Central/JCenter/JitPack are active by default) " +
- "(value format: <id>=<url>)")
- private var repositories = ArrayList<String>()
+ // 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(name="--repository-update", aliases = arrayOf("-U", "--ruleset-update", "--reporter-update"),
- usage = "Check remote repositories for updated snapshots")
- private var forceUpdate: 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(name="--limit", usage = "Maximum number of errors to show (default: show all)")
+ @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(name="--relative", usage = "Print files relative to the working directory " +
- "(e.g. dir/file.kt instead of /home/user/project/dir/file.kt)")
+ @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(name="--verbose", aliases = arrayOf("-v"), usage = "Show error codes")
- private var verbose: 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 pottential conflicts"))
+ private var skipClasspathCheck: Boolean = false
- @Option(name="--stdin", usage = "Read file from stdin")
+ @Option(names = arrayOf("--stdin"), description = arrayOf("Read file from stdin"))
private var stdin: Boolean = false
- @Option(name="--version", usage = "Version", help = true)
+ @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(name="--help", aliases = arrayOf("-h"), help = true)
+ @Option(names = arrayOf("--help", "-h"), help = true, hidden = true)
private var help: Boolean = false
- @Option(name="--debug", usage = "Turn on debug output")
- private var debug: 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(name="--apply-to-idea", usage = "Update Intellij IDEA project settings")
- private var apply: Boolean = false
- @Option(name="--install-git-pre-commit-hook", usage = "Install git hook to automatically check files for style violations on commit")
- private var installGitPreCommitHook: Boolean = false
- @Option(name="-y", hidden = true)
+ @Option(names = arrayOf("-y"), hidden = true)
private var forceApply: Boolean = false
- @Argument
+ @Parameters(hidden = true)
private var patterns = ArrayList<String>()
- private fun CmdLineParser.usage(): String =
- """
- An anti-bikeshedding Kotlin linter with built-in formatter (https://github.com/shyiko/ktlint).
-
- Usage:
- ktlint <flags> [patterns]
- java -jar ktlint <flags> [patterns]
+ private val workDir = File(".").canonicalPath
+ private fun File.location() = if (relative) this.toRelativeString(File(workDir)) else this.path
- Examples:
- # check the style of all Kotlin files inside the current dir (recursively)
- # (hidden folders will be skipped)
- ktlint
+ private fun usage() =
+ ByteArrayOutputStream()
+ .also { CommandLine.usage(this, PrintStream(it), CommandLine.Help.Ansi.OFF) }
+ .toString()
+ .replace(" ".repeat(32), " ".repeat(30))
- # check only certain locations (prepend ! to negate the pattern)
- ktlint "src/**/*.kt" "!src/**/*Test.kt"
-
- # auto-correct style violations
- ktlint -F "src/**/*.kt"
-
- # use 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
-
- Flags:
-${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().split("\n")
- .map { line -> (" " + line).replace(CLI_MAX_LINE_LENGTH_REGEX, " $1\n").trimEnd() }
- .joinToString("\n")}
- """.trimIndent()
-
- fun parseCmdLine(args: Array<String>) {
+ 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("=")
@@ -170,43 +223,13 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
}
}
}
- val parser = object : CmdLineParser(this, ParserProperties.defaults()
- .withShowDefaults(false)
- .withUsageWidth(512)
- .withOptionSorter { l, r ->
- l.option.toString().replace("-", "").compareTo(r.option.toString().replace("-", ""))
- }) {
-
- override fun printOption(out: PrintWriter, handler: OptionHandler<*>, len: Int, rb: ResourceBundle?, filter: OptionHandlerFilter?) {
- handler.defaultMetaVariable
- val opt = handler.option as? NamedOptionDef ?: return
- if (opt.hidden() || opt.help()) {
- return
- }
- val maxNameLength = options.map { h ->
- val o = h.option
- (o as? NamedOptionDef)?.let { it.name().length + 1 + (h.defaultMetaVariable ?: "").length } ?: 0
- }.max()!!
- val shorthand = opt.aliases().find { it.startsWith("-") && !it.startsWith("--") }
- val line = (if (shorthand != null) "$shorthand, " else " ") +
- (opt.name() + " " + (handler.defaultMetaVariable ?: "")).padEnd(maxNameLength, ' ') + " " + opt.usage()
- out.println(line)
- }
- }
- try {
- parser.parseArgument(*args)
- } catch (err: CmdLineException) {
- System.err.println("Error: ${err.message}\n\n${parser.usage()}")
- exitProcess(1)
- }
- if (help) { println(parser.usage()); exitProcess(0) }
}
@JvmStatic
fun main(args: Array<String>) {
parseCmdLine(args)
if (version) {
- println(javaClass.`package`.implementationVersion)
+ println(getImplementationVersion())
exitProcess(0)
}
if (installGitPreCommitHook) {
@@ -215,116 +238,59 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
exitProcess(0)
}
}
- if (apply) {
+ if (apply || applyToProject) {
applyToIDEA()
exitProcess(0)
}
- val workDir = File(".").canonicalPath
+ if (printAST) {
+ printAST()
+ exitProcess(0)
+ }
val start = System.currentTimeMillis()
// load 3rd party ruleset(s) (if any)
- val dependencyResolver by lazy { buildDependencyResolver() }
+ val dependencyResolver = lazy(LazyThreadSafetyMode.NONE) { buildDependencyResolver() }
if (!rulesets.isEmpty()) {
loadJARs(dependencyResolver, rulesets)
}
// standard should go first
- val rp = ServiceLoader.load(RuleSetProvider::class.java)
+ val ruleSetProviders = ServiceLoader.load(RuleSetProvider::class.java)
.map { it.get().id to it }
.sortedBy { if (it.first == "standard") "\u0000${it.first}" else it.first }
if (debug) {
- rp.forEach { System.err.println("[DEBUG] Discovered ruleset \"${it.first}\"") }
- }
- data class R(val id: String, val config: Map<String, String>, var output: String?)
- if (reporters.isEmpty()) {
- reporters.add("plain")
+ ruleSetProviders.forEach { System.err.println("[DEBUG] Discovered ruleset \"${it.first}\"") }
}
- val rr = this.reporters.map { reporter ->
- val split = reporter.split(",")
- val (reporterId, rawReporterConfig) = split[0].split("?", limit = 2) + listOf("")
- R(reporterId, mapOf("verbose" to verbose.toString()) + parseQuery(rawReporterConfig),
- split.getOrNull(1)?.let { if (it.startsWith("output=")) it.split("=")[1] else null })
- }.distinct()
- // load reporter
- val reporterLoader = ServiceLoader.load(ReporterProvider::class.java)
- val reporterProviderById = reporterLoader.associate { it.id to it }.let { map ->
- val missingReporters = rr.map { it.id }.distinct().filter { !map.containsKey(it) }
- 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\"") }
- }
- val reporter = Reporter.from(*rr.map { r ->
- val reporterProvider = reporterProviderById[r.id]
- if (reporterProvider == null) {
- System.err.println("Error: reporter \"${r.id}\" wasn't found (available: ${
- reporterProviderById.keys.sorted().joinToString(",")})")
- exitProcess(1)
- }
- if (debug) {
- System.err.println("[DEBUG] Initializing \"${r.id}\" reporter with ${r.config}" +
- (r.output?.let { ", output=$it" } ?: ""))
- }
- val output = if (r.output != null) { File(r.output).parentFile?.mkdirsOrFail(); PrintStream(r.output) } else
- if (stdin) System.err else System.out
- reporterProvider.get(output, r.config).let { reporter ->
- if (r.output != null)
- object : Reporter by reporter {
- override fun afterAll() {
- reporter.afterAll()
- output.close()
- }
- }
- else
- reporter
- }
- }.toTypedArray())
+ val reporter = loadReporter(dependencyResolver)
// load .editorconfig
val userData = (
EditorConfig.of(workDir)
?.also { editorConfig ->
if (debug) {
- System.err.println("[DEBUG] Discovered .editorconfig (${editorConfig.path.parent})")
+ System.err.println("[DEBUG] Discovered .editorconfig (${
+ generateSequence(editorConfig) { it.parent }.map { it.path.parent.toFile().location() }.joinToString()
+ })")
System.err.println("[DEBUG] ${editorConfig.mapKeys { it.key }} loaded from .editorconfig")
}
}
?: emptyMap<String, String>()
) + mapOf("android" to android.toString())
- data class LintErrorWithCorrectionInfo(val err: LintError, val corrected: Boolean)
- fun lintErrorFrom(e: Exception): LintError = 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
- }
val tripped = AtomicBoolean()
+ 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 (relative && fileName != "<text>") File(fileName).toRelativeString(File(workDir)) else fileName
- }")
+ System.err.println("[DEBUG] Checking ${if (fileName != "<text>") File(fileName).location() else fileName}")
}
val result = ArrayList<LintErrorWithCorrectionInfo>()
+ val localUserData = if (fileName != "<text>") userData + ("file_path" to fileName) else userData
if (format) {
val formattedFileContent = try {
- format(fileName, fileContent, rp.map { it.second.get() }, userData) { err, corrected ->
+ format(fileName, fileContent, ruleSetProviders.map { it.second.get() }, localUserData) { err, corrected ->
if (!corrected) {
result.add(LintErrorWithCorrectionInfo(err, corrected))
+ tripped.set(true)
}
}
} catch (e: Exception) {
- result.add(LintErrorWithCorrectionInfo(lintErrorFrom(e), false))
+ result.add(LintErrorWithCorrectionInfo(e.toLintError(), false))
tripped.set(true)
fileContent // making sure `cat file | ktlint --stdint > file` is (relatively) safe
}
@@ -337,19 +303,17 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
}
} else {
try {
- lint(fileName, fileContent, rp.map { it.second.get() }, userData) { err ->
- tripped.set(true)
+ lint(fileName, fileContent, ruleSetProviders.map { it.second.get() }, localUserData) { err ->
result.add(LintErrorWithCorrectionInfo(err, false))
+ tripped.set(true)
}
} catch (e: Exception) {
- result.add(LintErrorWithCorrectionInfo(lintErrorFrom(e), false))
+ result.add(LintErrorWithCorrectionInfo(e.toLintError(), false))
+ tripped.set(true)
}
}
return result
}
- if (limit < 0) {
- limit = Int.MAX_VALUE
- }
val (fileNumber, errorNumber) = Pair(AtomicInteger(), AtomicInteger())
fun report(fileName: String, errList: List<LintErrorWithCorrectionInfo>) {
fileNumber.incrementAndGet()
@@ -365,35 +329,141 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
if (stdin) {
report("<text>", process("<text>", String(System.`in`.readBytes())))
} else {
- val pathIterator = 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))
- }
- pathIterator
- .asSequence()
+ fileSequence()
.takeWhile { errorNumber.get() < limit }
- .map(Path::toFile)
- .map { file ->
- Callable { file to process(file.path, file.readText()) }
- }
- .parallel({ (file, errList) ->
- report(if (relative) file.toRelativeString(File(workDir)) else file.path, errList) })
+ .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)")
+ System.err.println("[DEBUG] ${
+ System.currentTimeMillis() - start
+ }ms / $fileNumber file(s) / $errorNumber error(s)")
}
if (tripped.get()) {
exitProcess(1)
}
}
- fun installGitPreCommitHook() {
+ 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>): 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()
+ }
+ }
+ 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?")
@@ -417,17 +487,16 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
System.err.println(".git/hooks/pre-commit installed")
}
- fun applyToIDEA() {
+ private fun applyToIDEA() {
try {
val workDir = Paths.get(".")
if (!forceApply) {
- val fileList = IntellijIDEAIntegration.apply(workDir, true, android)
+ 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 }
}
@@ -438,7 +507,7 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
exitProcess(1)
}
}
- IntellijIDEAIntegration.apply(workDir, false, android)
+ 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?")
@@ -449,15 +518,15 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
System.err.println("(if you experience any issues please report them at https://github.com/shyiko/ktlint)")
}
- fun hex(input: ByteArray) = BigInteger(MessageDigest.getInstance("SHA-256").digest(input)).toString(16)
+ 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 (~/)
- fun expandTilde(path: String) = path.replaceFirst(Regex("^~"), System.getProperty("user.home"))
+ private fun expandTilde(path: String) = path.replaceFirst(Regex("^~"), System.getProperty("user.home"))
- fun <T> List<T>.head(limit: Int) = if (limit == size) this else this.subList(0, limit)
+ private fun <T> List<T>.head(limit: Int) = if (limit == size) this else this.subList(0, limit)
- fun buildDependencyResolver(): MavenDependencyResolver {
+ private fun buildDependencyResolver(): MavenDependencyResolver {
val mavenLocal = File(File(System.getProperty("user.home"), ".m2"), "repository")
mavenLocal.mkdirsOrFail()
val dependencyResolver = MavenDependencyResolver(
@@ -482,7 +551,7 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
val url = repository.substring(colon + 1)
RemoteRepository.Builder(id, "default", url).build()
},
- forceUpdate
+ forceUpdate == true
)
if (debug) {
dependencyResolver.setTransferEventListener { e ->
@@ -493,14 +562,15 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
return dependencyResolver
}
- fun loadJARs(dependencyResolver: MavenDependencyResolver, artifacts: List<String>) {
+ // 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.resolve(DefaultArtifact(artifact)).map { it.toURI().toURL() }
+ dependencyResolver.value.resolve(DefaultArtifact(artifact)).map { it.toURI().toURL() }
} catch (e: IllegalArgumentException) {
val file = File(expandTilde(artifact))
if (!file.exists()) {
@@ -518,11 +588,25 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
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
})
}
- fun parseQuery(query: String) = query.split("&")
+ 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],
@@ -531,24 +615,42 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
map
}
- 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
+ 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)
+ }
- 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
+ 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)
+ }
- fun java.net.URLClassLoader.addURLs(url: Iterable<java.net.URL>) {
+ 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) }
}
- fun <T>Sequence<Callable<T>>.parallel(cb: (T) -> Unit,
- numberOfThreads: Int = Runtime.getRuntime().availableProcessors()) {
+ private fun <T> Sequence<Callable<T>>.parallel(
+ cb: (T) -> Unit,
+ numberOfThreads: Int = Runtime.getRuntime().availableProcessors()
+ ) {
val q = ArrayBlockingQueue<Future<T>>(numberOfThreads)
val pill = object : Future<T> {
diff --git a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/EditorConfig.kt b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/EditorConfig.kt
index 519f6a59..bce07c5a 100644
--- a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/EditorConfig.kt
+++ b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/EditorConfig.kt
@@ -1,12 +1,13 @@
package com.github.shyiko.ktlint.internal
-import org.ini4j.Wini
import java.io.ByteArrayInputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
+import java.util.Properties
class EditorConfig private constructor (
+ val parent: EditorConfig?,
val path: Path,
private val data: Map<String, String>
) : Map<String, String> by data {
@@ -16,21 +17,113 @@ class EditorConfig private constructor (
fun of(dir: String) =
of(Paths.get(dir))
fun of(dir: Path) =
- locate(dir)?.let { EditorConfig(it, load(it)) }
+ 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))
+ }
private fun locate(dir: Path?): Path? = when (dir) {
null -> null
- else -> Paths.get(dir.toString(), ".editorconfig").let {
+ else -> dir.resolve(".editorconfig").let {
if (Files.exists(it)) it else locate(dir.parent)
}
}
- private fun load(path: Path): Map<String, String> {
- val editorConfig = Wini(ByteArrayInputStream(Files.readAllBytes(path)))
- // right now ktlint requires explicit [*.{kt,kts}] section
- // (this way we can be sure that users want .editorconfig to be recognized by ktlint)
- val section = editorConfig["*.{kt,kts}"]
- return section?.toSortedMap() ?: emptyMap()
+ 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<String>(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
}
}
}
diff --git a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt
index 7c7b23fb..0e3cc9f1 100644
--- a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt
+++ b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt
@@ -20,72 +20,103 @@ import javax.xml.xpath.XPathFactory
object IntellijIDEAIntegration {
+ @Suppress("UNUSED_PARAMETER")
@Throws(IOException::class)
- fun apply(workDir: Path, dryRun: Boolean, android: Boolean = false): Array<Path> {
+ fun apply(workDir: Path, dryRun: Boolean, android: Boolean = false, local: Boolean = false): Array<Path> {
if (!Files.isDirectory(workDir.resolve(".idea"))) {
throw ProjectNotFoundException()
}
- val home = System.getProperty("user.home")
val editorConfig: Map<String, String> = EditorConfig.of(".") ?: emptyMap()
- val continuationIndentSize = editorConfig["continuation_indent_size"]?.toIntOrNull() ?: if (android) 8 else 4
val indentSize = editorConfig["indent_size"]?.toIntOrNull() ?: 4
- 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()
- val updates = (paths.flatMap { dir ->
- sequenceOf(
- Paths.get(dir.toString(), "codestyles", "$codeStyleName.xml") to
- overwriteWithResource("/config/codestyles/ktlint.xml") { resource ->
+ 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("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") to {
- var arr = "<application></application>".toByteArray()
+ 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") to {
+ var arr = "<project version=\"4\"></project>".toByteArray()
try {
- arr = Files.readAllBytes(Paths.get(dir.toString(), "options", "editor.codeinsight.xml"))
+ arr = Files.readAllBytes(Paths.get(workDir.toString(), ".idea", "workspace.xml"))
} catch (e: IOException) {
if (e !is NoSuchFileException) {
throw e
}
}
- enableOptimizeImportsOnTheFly(arr)
+ enableOptimizeImportsOnTheFlyInsideWorkspace(arr)
}
)
- } + 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()
+ } 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") to {
+ var arr = "<application></application>".toByteArray()
+ try {
+ arr = Files.readAllBytes(Paths.get(dir.toString(), "options", "editor.codeinsight.xml"))
+ } catch (e: IOException) {
+ if (e !is NoSuchFileException) {
+ throw e
+ }
+ }
+ enableOptimizeImportsOnTheFly(arr)
+ }
+ )
+ } + 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)
@@ -133,6 +164,39 @@ object IntellijIDEAIntegration {
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"))
diff --git a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/MavenDependencyResolver.kt b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/MavenDependencyResolver.kt
index a01f015f..1760279f 100644
--- a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/MavenDependencyResolver.kt
+++ b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/MavenDependencyResolver.kt
@@ -8,6 +8,7 @@ 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
@@ -21,8 +22,11 @@ import org.eclipse.aether.transport.http.HttpTransporterFactory
import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator
import java.io.File
-class MavenDependencyResolver(baseDir: File, val repositories: Iterable<RemoteRepository>,
- forceUpdate: Boolean) {
+class MavenDependencyResolver(
+ baseDir: File,
+ val repositories: Iterable<RemoteRepository>,
+ forceUpdate: Boolean
+) {
private val repoSystem: RepositorySystem
private val session: RepositorySystemSession
@@ -32,11 +36,19 @@ class MavenDependencyResolver(baseDir: File, val repositories: Iterable<RemoteRe
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
+ session.updatePolicy = if (forceUpdate) {
+ RepositoryPolicy.UPDATE_POLICY_ALWAYS
+ } else {
RepositoryPolicy.UPDATE_POLICY_NEVER
+ }
}
fun setTransferEventListener(listener: (event: TransferEvent) -> Unit) {