Skip to content

Commit

Permalink
Added dry run mode (#212)
Browse files Browse the repository at this point in the history
Summary:
In --dry-run mode, ktfmt will print the file paths of files that need formatting but will not change the file contents. Also added the option to exit the program with code `1` if any changes are needed rather than just if any errors happened.

These changes are in alignment with google-java-format's identical options (see google/google-java-format#106).

Usage:
--dry-run or -n
Prints the paths of the files whose contents would change if the formatter were run normally.

--set-exit-if-changed
Return exit code 1 if there are any formatting changes made (or detected if running in dry mode).

Reviewed By: cgrushko

Differential Revision: D31286652

fbshipit-source-id: c89fb72a1411766b81c28eba66614df78b0fea0a
  • Loading branch information
debrechtabel authored and facebook-github-bot committed Oct 1, 2021
1 parent 64776fa commit b44c58b
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 63 deletions.
62 changes: 39 additions & 23 deletions core/src/main/java/com/facebook/ktfmt/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.facebook.ktfmt

import com.google.common.annotations.VisibleForTesting
import com.google.googlejavaformat.FormattingError
import java.io.BufferedReader
import java.io.File
Expand Down Expand Up @@ -47,7 +46,7 @@ class Main(
}

if (parsedArgs.fileNames.size == 1 && parsedArgs.fileNames[0] == "-") {
val success = formatStdin()
val success = format(null)
return if (success) 0 else 1
}

Expand All @@ -65,36 +64,53 @@ class Main(
}

val success = AtomicBoolean(true)
files.parallelStream().forEach { success.compareAndSet(true, formatFile(it)) }
files.parallelStream().forEach { success.compareAndSet(true, format(it)) }
return if (success.get()) 0 else 1
}

@VisibleForTesting
fun formatStdin(): Boolean {
val code = BufferedReader(InputStreamReader(input)).readText()
/**
* Handles the logic for formatting and flags.
*
* If dry run mode is active, this simply prints the name of the [source] (file path or `<stdin>`)
* to [out]. Otherwise, this will run the appropriate formatting as normal.
*
* @param file The file to format. If null, the code is read from <stdin>.
* @return True if changes were made or no changes were needed, false if there was a failure or if
* changes were made and the --set-exit-if-changed flag is set.
*/
private fun format(file: File?): Boolean {
val fileName = file?.toString() ?: "<stdin>"
try {
out.print(format(parsedArgs.formattingOptions, code))
return true
} catch (e: ParseError) {
handleParseError("<stdin>", e)
}
return false
}
val code = file?.readText() ?: BufferedReader(InputStreamReader(input)).readText()

/** 'formatFile' formats 'file' in place, and return whether it was successful. */
private fun formatFile(file: File): Boolean {
try {
val code = file.readText()
file.writeText(format(parsedArgs.formattingOptions, code))
err.println("Done formatting $file")
return true
val formattedCode = format(parsedArgs.formattingOptions, code)

if (code == formattedCode) {
// The code was already formatted, nothing more to do here
err.println("No changes: $fileName")
return true
}

if (parsedArgs.dryRun) {
out.println(fileName)
} else {
if (file != null) {
file.writeText(formattedCode)
} else {
out.print(formattedCode)
}
err.println("Done formatting $fileName")
}

// If setExitIfChanged is true, then any change should result in an exit code of 1.
return if (parsedArgs.setExitIfChanged) false else true
} catch (e: IOException) {
err.println("Error formatting $file: ${e.message}; skipping.")
err.println("Error formatting $fileName: ${e.message}; skipping.")
} catch (e: ParseError) {
handleParseError(file.toString(), e)
handleParseError(fileName, e)
} catch (e: FormattingError) {
for (diagnostic in e.diagnostics()) {
System.err.println("$file:$diagnostic")
System.err.println("$fileName:$diagnostic")
}
e.printStackTrace(err)
}
Expand Down
20 changes: 18 additions & 2 deletions core/src/main/java/com/facebook/ktfmt/ParsedArgs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,36 @@ package com.facebook.ktfmt
import java.io.PrintStream

/** ParsedArgs holds the arguments passed to ktfmt on the command-line, after parsing. */
data class ParsedArgs(val fileNames: List<String>, val formattingOptions: FormattingOptions)
data class ParsedArgs(
val fileNames: List<String>,
val formattingOptions: FormattingOptions,
/**
* Run the formatter without writing changes to any files. This will print the path of any files
* that would be changed if the formatter is run normally.
*/
val dryRun: Boolean,

/** Return exit code 1 if any formatting changes are detected. */
val setExitIfChanged: Boolean,
)

/** parseOptions parses command-line arguments passed to ktfmt. */
fun parseOptions(err: PrintStream, args: Array<String>): ParsedArgs {
val fileNames = mutableListOf<String>()
var formattingOptions = FormattingOptions()
var dryRun = false
var setExitIfChanged = false

for (arg in args) {
when {
arg == "--dropbox-style" -> formattingOptions = DROPBOX_FORMAT
arg == "--google-style" -> formattingOptions = GOOGLE_FORMAT
arg == "--kotlinlang-style" -> formattingOptions = KOTLINLANG_FORMAT
arg == "--dry-run" || arg == "-n" -> dryRun = true
arg == "--set-exit-if-changed" -> setExitIfChanged = true
arg.startsWith("--") -> err.println("Unexpected option: $arg")
else -> fileNames.add(arg)
}
}
return ParsedArgs(fileNames, formattingOptions)
return ParsedArgs(fileNames, formattingOptions, dryRun, setExitIfChanged)
}
Loading

0 comments on commit b44c58b

Please sign in to comment.