aboutsummaryrefslogtreecommitdiff
path: root/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt
diff options
context:
space:
mode:
Diffstat (limited to 'ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt')
-rw-r--r--ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt572
1 files changed, 337 insertions, 235 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> {