diff options
Diffstat (limited to 'ktlint/src/main/kotlin/com/github/shyiko')
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) { |