Improve Formatter API (#1505)

- pass `GrammarVersion` to constructor instead of passing it to each
`format` method
- replace `format(Path): String` with `format(Reader, Appendable)`
- instead of picking which overloads besides `format(String): String`
might be useful, offer a single generalized method that streams input
and output
- add `@Throws(IOException::class)` to ensure that Java callers can
catch this exception
- deprecate old methods
This commit is contained in:
odenix
2026-04-07 14:16:12 -07:00
committed by GitHub
parent 99cbd07518
commit 09435af54f
5 changed files with 68 additions and 34 deletions

View File

@@ -47,7 +47,7 @@ constructor(
private val errWriter: Writer = System.err.writer(), private val errWriter: Writer = System.err.writer(),
) : CliCommand(CliBaseOptions()) { ) : CliCommand(CliBaseOptions()) {
private fun format(contents: String): String { private fun format(contents: String): String {
return Formatter().format(contents, grammarVersion) return Formatter(grammarVersion).format(contents)
} }
private fun writeErrLine(error: String) { private fun writeErrLine(error: String) {

View File

@@ -15,14 +15,24 @@
*/ */
package org.pkl.formatter package org.pkl.formatter
import java.io.IOException
import java.io.Reader
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import kotlin.jvm.Throws
import org.pkl.formatter.ast.ForceLine import org.pkl.formatter.ast.ForceLine
import org.pkl.formatter.ast.Nodes import org.pkl.formatter.ast.Nodes
import org.pkl.parser.GenericParser import org.pkl.parser.GenericParser
/** A formatter for Pkl files that applies canonical formatting rules. */ /**
class Formatter { * A formatter for Pkl files that applies canonical formatting rules.
*
* @param grammarVersion grammar compatibility version
*/
class Formatter
@JvmOverloads
constructor(private val grammarVersion: GrammarVersion = GrammarVersion.latest()) {
/** /**
* Formats a Pkl file from the given file path. * Formats a Pkl file from the given file path.
* *
@@ -32,8 +42,9 @@ class Formatter {
* @throws java.io.IOException if the file cannot be read * @throws java.io.IOException if the file cannot be read
*/ */
@JvmOverloads @JvmOverloads
@Deprecated(message = "use format(path.readText()) instead")
fun format(path: Path, grammarVersion: GrammarVersion = GrammarVersion.latest()): String { fun format(path: Path, grammarVersion: GrammarVersion = GrammarVersion.latest()): String {
return format(Files.readString(path), grammarVersion) return Formatter(grammarVersion).format(Files.readString(path))
} }
/** /**
@@ -43,16 +54,41 @@ class Formatter {
* @param grammarVersion grammar compatibility version * @param grammarVersion grammar compatibility version
* @return the formatted Pkl source code as a string * @return the formatted Pkl source code as a string
*/ */
@JvmOverloads @Deprecated(message = "use Formatter(grammarVersion).format(text) instead")
fun format(text: String, grammarVersion: GrammarVersion = GrammarVersion.latest()): String { fun format(text: String, grammarVersion: GrammarVersion): String {
val parser = GenericParser() return Formatter(grammarVersion).format(text)
val builder = Builder(text, grammarVersion) }
val gen = Generator()
val ast = parser.parseModule(text) /**
val formatAst = builder.format(ast) * Formats the given Pkl source code text.
*
* @param text the Pkl source code to format
* @return the formatted Pkl source code as a string
*/
fun format(text: String): String {
return buildString { format(text, this) }
}
/**
* Formats the given Pkl source code text.
*
* It is the caller's responsibility to close [input], and, if applicable, [output].
*
* @param input the Pkl source code to format
* @param output the formatted Pkl source code
* @throws java.io.IOException if an I/O error occurs during reading or writing
*/
@Throws(IOException::class)
fun format(input: Reader, output: Appendable) {
format(input.readText(), output)
}
private fun format(input: String, output: Appendable) {
val ast = GenericParser().parseModule(input)
val formatAst = Builder(input, grammarVersion).format(ast)
// force a line at the end of the file // force a line at the end of the file
gen.generate(Nodes(listOf(formatAst, ForceLine))) val nodes = Nodes(listOf(formatAst, ForceLine))
return gen.toString() Generator(output).generate(nodes)
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -29,8 +29,7 @@ import org.pkl.formatter.ast.SpaceOrLine
import org.pkl.formatter.ast.Text import org.pkl.formatter.ast.Text
import org.pkl.formatter.ast.Wrap import org.pkl.formatter.ast.Wrap
internal class Generator { internal class Generator(private val buf: Appendable) {
private val buf: StringBuilder = StringBuilder()
private var indent: Int = 0 private var indent: Int = 0
private var size: Int = 0 private var size: Int = 0
private val wrapped: MutableSet<Int> = mutableSetOf() private val wrapped: MutableSet<Int> = mutableSetOf()
@@ -138,10 +137,6 @@ internal class Generator {
return if (beforeLast is Text) beforeLast.text.length else 0 return if (beforeLast is Text) beforeLast.text.length else 0
} }
override fun toString(): String {
return buf.toString()
}
companion object { companion object {
// max line length // max line length
const val MAX = 100 const val MAX = 100

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package org.pkl.formatter
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.isRegularFile import kotlin.io.path.isRegularFile
import kotlin.io.path.readText
import kotlin.reflect.KClass import kotlin.reflect.KClass
import org.pkl.commons.test.InputOutputTestEngine import org.pkl.commons.test.InputOutputTestEngine
import org.pkl.parser.ParserError import org.pkl.parser.ParserError
@@ -58,16 +59,10 @@ abstract class AbstractFormatterSnippetTestsEngine : InputOutputTestEngine() {
class FormatterSnippetTestsEngine : AbstractFormatterSnippetTestsEngine() { class FormatterSnippetTestsEngine : AbstractFormatterSnippetTestsEngine() {
override val testClass: KClass<*> = FormatterSnippetTests::class override val testClass: KClass<*> = FormatterSnippetTests::class
override fun generateOutputFor(inputFile: Path): Pair<Boolean, String> { override fun generateOutputFor(inputFile: Path): Pair<Boolean, String> =
val formatter = Formatter() try {
val (success, output) = true to Formatter().format(inputFile.readText())
try { } catch (_: ParserError) {
val res = formatter.format(inputFile) false to ""
true to res }
} catch (_: ParserError) {
false to ""
}
return success to output
}
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -120,4 +120,12 @@ class FormatterTest {
assertThat(format(src)).isEqualTo("\n") assertThat(format(src)).isEqualTo("\n")
} }
} }
@Test
fun `read from Reader and write to Appendable`() {
val input = " x = 42".reader()
val output = StringBuilder()
Formatter().format(input, output)
assertThat(output.toString()).isEqualTo("x = 42\n")
}
} }