mirror of
https://github.com/apple/pkl.git
synced 2026-04-23 08:48:36 +02:00
Implement canonical formatter (#1107)
CLI commands also added: `pkl format check` and `pkl format apply`.
This commit is contained in:
@@ -733,6 +733,33 @@ pkl shell-completion bash
|
|||||||
pkl shell-completion zsh
|
pkl shell-completion zsh
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[command-format-check]]
|
||||||
|
=== `pkl format check`
|
||||||
|
|
||||||
|
*Synopsis*: `pkl format check <file-or-dir-path>`
|
||||||
|
|
||||||
|
This command checks for format violations on the given file or directory and print their names to stdout. +
|
||||||
|
It returns a non-zero status code in case violations are found.
|
||||||
|
|
||||||
|
If the path is a directory, recursively looks for files with a `.pkl` extension, or files named `PklProject`.
|
||||||
|
|
||||||
|
[[command-format-apply]]
|
||||||
|
=== `pkl format apply`
|
||||||
|
|
||||||
|
*Synopsis*: `pkl format apply [<options>] <file-or-dir-path>`
|
||||||
|
|
||||||
|
This command formats the given files overwriting them.
|
||||||
|
|
||||||
|
If the path is a directory, recursively looks for files with a `.pkl` extension, or files named `PklProject`.
|
||||||
|
|
||||||
|
==== Options
|
||||||
|
|
||||||
|
.-s, --silent
|
||||||
|
[%collapsible]
|
||||||
|
====
|
||||||
|
Do not write the name of wrongly formatted files to stdout.
|
||||||
|
====
|
||||||
|
|
||||||
[[common-options]]
|
[[common-options]]
|
||||||
=== Common options
|
=== Common options
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ dependencies {
|
|||||||
implementation(libs.jlineTerminal)
|
implementation(libs.jlineTerminal)
|
||||||
implementation(libs.jlineTerminalJansi)
|
implementation(libs.jlineTerminalJansi)
|
||||||
implementation(projects.pklServer)
|
implementation(projects.pklServer)
|
||||||
|
implementation(projects.pklFormatter)
|
||||||
implementation(libs.clikt)
|
implementation(libs.clikt)
|
||||||
|
|
||||||
testImplementation(projects.pklCommonsTest)
|
testImplementation(projects.pklCommonsTest)
|
||||||
|
|||||||
52
pkl-cli/src/main/kotlin/org/pkl/cli/CliFormatterApply.kt
Normal file
52
pkl-cli/src/main/kotlin/org/pkl/cli/CliFormatterApply.kt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.cli
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.writeText
|
||||||
|
import org.pkl.commons.cli.CliBaseOptions
|
||||||
|
import org.pkl.commons.cli.CliException
|
||||||
|
|
||||||
|
class CliFormatterApply(cliBaseOptions: CliBaseOptions, path: Path, private val silent: Boolean) :
|
||||||
|
CliFormatterCommand(cliBaseOptions, path) {
|
||||||
|
|
||||||
|
override fun doRun() {
|
||||||
|
var status = 0
|
||||||
|
|
||||||
|
for (path in paths()) {
|
||||||
|
val contents = Files.readString(path)
|
||||||
|
val (formatted, stat) = format(path, contents)
|
||||||
|
status = if (status == 0) stat else status
|
||||||
|
if (stat != 0) continue
|
||||||
|
if (!silent && contents != formatted) {
|
||||||
|
consoleWriter.write(path.toAbsolutePath().toString())
|
||||||
|
consoleWriter.flush()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
path.writeText(formatted, Charsets.UTF_8)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
consoleWriter.write("Could not overwrite `$path`: ${e.message}")
|
||||||
|
consoleWriter.flush()
|
||||||
|
status = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (status != 0) {
|
||||||
|
throw CliException("Formatting violations found.", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
pkl-cli/src/main/kotlin/org/pkl/cli/CliFormatterCheck.kt
Normal file
43
pkl-cli/src/main/kotlin/org/pkl/cli/CliFormatterCheck.kt
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.cli
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import org.pkl.commons.cli.CliBaseOptions
|
||||||
|
import org.pkl.commons.cli.CliException
|
||||||
|
|
||||||
|
class CliFormatterCheck(cliBaseOptions: CliBaseOptions, path: Path) :
|
||||||
|
CliFormatterCommand(cliBaseOptions, path) {
|
||||||
|
|
||||||
|
override fun doRun() {
|
||||||
|
var status = 0
|
||||||
|
|
||||||
|
for (path in paths()) {
|
||||||
|
val contents = Files.readString(path)
|
||||||
|
val (formatted, stat) = format(path, contents)
|
||||||
|
status = if (status == 0) stat else status
|
||||||
|
if (contents != formatted) {
|
||||||
|
consoleWriter.write(path.toAbsolutePath().toString())
|
||||||
|
consoleWriter.flush()
|
||||||
|
status = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (status != 0) {
|
||||||
|
throw CliException("Formatting violations found.", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
pkl-cli/src/main/kotlin/org/pkl/cli/CliFormatterCommand.kt
Normal file
53
pkl-cli/src/main/kotlin/org/pkl/cli/CliFormatterCommand.kt
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.cli
|
||||||
|
|
||||||
|
import java.io.Writer
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.ExperimentalPathApi
|
||||||
|
import kotlin.io.path.extension
|
||||||
|
import kotlin.io.path.isDirectory
|
||||||
|
import kotlin.io.path.name
|
||||||
|
import kotlin.io.path.walk
|
||||||
|
import org.pkl.commons.cli.CliBaseOptions
|
||||||
|
import org.pkl.commons.cli.CliCommand
|
||||||
|
import org.pkl.formatter.Formatter
|
||||||
|
import org.pkl.parser.GenericParserError
|
||||||
|
|
||||||
|
abstract class CliFormatterCommand
|
||||||
|
@JvmOverloads
|
||||||
|
constructor(
|
||||||
|
options: CliBaseOptions,
|
||||||
|
protected val path: Path,
|
||||||
|
protected val consoleWriter: Writer = System.out.writer(),
|
||||||
|
) : CliCommand(options) {
|
||||||
|
protected fun format(file: Path, contents: String): Pair<String, Int> {
|
||||||
|
try {
|
||||||
|
return Formatter().format(contents) to 0
|
||||||
|
} catch (pe: GenericParserError) {
|
||||||
|
consoleWriter.write("Could not format `$file`: $pe")
|
||||||
|
consoleWriter.flush()
|
||||||
|
return "" to 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPathApi::class)
|
||||||
|
protected fun paths(): Sequence<Path> {
|
||||||
|
return if (path.isDirectory()) {
|
||||||
|
path.walk().filter { it.extension == "pkl" || it.name == "PklProject" }
|
||||||
|
} else sequenceOf(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.cli.commands
|
||||||
|
|
||||||
|
import com.github.ajalt.clikt.core.Context
|
||||||
|
import com.github.ajalt.clikt.core.NoOpCliktCommand
|
||||||
|
import com.github.ajalt.clikt.core.subcommands
|
||||||
|
import com.github.ajalt.clikt.parameters.arguments.argument
|
||||||
|
import com.github.ajalt.clikt.parameters.options.flag
|
||||||
|
import com.github.ajalt.clikt.parameters.options.option
|
||||||
|
import com.github.ajalt.clikt.parameters.types.path
|
||||||
|
import java.nio.file.Path
|
||||||
|
import org.pkl.cli.CliFormatterApply
|
||||||
|
import org.pkl.cli.CliFormatterCheck
|
||||||
|
import org.pkl.commons.cli.commands.BaseCommand
|
||||||
|
|
||||||
|
class FormatterCommand : NoOpCliktCommand(name = "format") {
|
||||||
|
override fun help(context: Context) = "Run commands related to formatting"
|
||||||
|
|
||||||
|
override fun helpEpilog(context: Context) = "For more information, visit $helpLink"
|
||||||
|
|
||||||
|
init {
|
||||||
|
subcommands(FormatterCheckCommand(), FormatterApplyCommand())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FormatterCheckCommand : BaseCommand(name = "check", helpLink = helpLink) {
|
||||||
|
override val helpString: String =
|
||||||
|
"Check if the given files are properly formatted, printing the file name to stdout in case they are not. Returns non-zero in case of failure."
|
||||||
|
|
||||||
|
val path: Path by
|
||||||
|
argument(name = "path", help = "File or directory to check.")
|
||||||
|
.path(mustExist = true, canBeDir = true)
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
CliFormatterCheck(baseOptions.baseOptions(emptyList()), path).run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FormatterApplyCommand : BaseCommand(name = "apply", helpLink = helpLink) {
|
||||||
|
override val helpString: String =
|
||||||
|
"Overwrite all the files in place with the formatted version. Returns non-zero in case of failure."
|
||||||
|
|
||||||
|
val path: Path by
|
||||||
|
argument(name = "path", help = "File or directory to format.")
|
||||||
|
.path(mustExist = true, canBeDir = true)
|
||||||
|
|
||||||
|
val silent: Boolean by
|
||||||
|
option(
|
||||||
|
names = arrayOf("-s", "--silent"),
|
||||||
|
help = "Do not write the name of the files that failed formatting to stdout.",
|
||||||
|
)
|
||||||
|
.flag()
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
CliFormatterApply(baseOptions.baseOptions(emptyList()), path, silent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ class RootCommand : NoOpCliktCommand(name = "pkl") {
|
|||||||
ProjectCommand(),
|
ProjectCommand(),
|
||||||
DownloadPackageCommand(),
|
DownloadPackageCommand(),
|
||||||
AnalyzeCommand(),
|
AnalyzeCommand(),
|
||||||
|
FormatterCommand(),
|
||||||
CompletionCommand(
|
CompletionCommand(
|
||||||
name = "shell-completion",
|
name = "shell-completion",
|
||||||
help = "Generate a completion script for the given shell",
|
help = "Generate a completion script for the given shell",
|
||||||
|
|||||||
36
pkl-formatter/gradle.lockfile
Normal file
36
pkl-formatter/gradle.lockfile
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
|
net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata
|
||||||
|
org.assertj:assertj-core:3.27.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-build-common:2.0.21=kotlinBuildToolsApiClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-build-tools-api:2.0.21=kotlinBuildToolsApiClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-build-tools-impl:2.0.21=kotlinBuildToolsApiClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-compiler-embeddable:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-compiler-runner:2.0.21=kotlinBuildToolsApiClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-daemon-client:2.0.21=kotlinBuildToolsApiClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.0.21=kotlinKlibCommonizerClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-native-prebuilt:2.0.21=kotlinNativeBundleConfiguration
|
||||||
|
org.jetbrains.kotlin:kotlin-reflect:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-script-runtime:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-scripting-common:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||||
|
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||||
|
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||||
|
org.jetbrains.kotlin:kotlin-scripting-jvm:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.21=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib:2.0.21=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||||
|
org.jetbrains:annotations:13.0=compileClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
|
org.junit.jupiter:junit-jupiter-api:5.13.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.junit.jupiter:junit-jupiter-engine:5.13.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.junit.jupiter:junit-jupiter-params:5.13.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.junit.platform:junit-platform-commons:1.13.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.junit.platform:junit-platform-engine:1.13.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.junit.platform:junit-platform-launcher:1.13.0=testRuntimeClasspath
|
||||||
|
org.junit:junit-bom:5.13.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
|
empty=annotationProcessor,compileOnlyDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions
|
||||||
48
pkl-formatter/pkl-formatter.gradle.kts
Normal file
48
pkl-formatter/pkl-formatter.gradle.kts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
plugins {
|
||||||
|
pklAllProjects
|
||||||
|
pklKotlinLibrary
|
||||||
|
pklPublishLibrary
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(projects.pklParser)
|
||||||
|
|
||||||
|
testImplementation(projects.pklCommonsTest)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
inputs
|
||||||
|
.dir("src/test/files/FormatterSnippetTests/input")
|
||||||
|
.withPropertyName("formatterSnippetTestsInput")
|
||||||
|
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||||
|
inputs
|
||||||
|
.dir("src/test/files/FormatterSnippetTests/output")
|
||||||
|
.withPropertyName("formatterSnippetTestsOutput")
|
||||||
|
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
named<MavenPublication>("library") {
|
||||||
|
pom {
|
||||||
|
url.set("https://github.com/apple/pkl/tree/main/pkl-formatter")
|
||||||
|
description.set("Formatter for Pkl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1258
pkl-formatter/src/main/kotlin/org/pkl/formatter/Builder.kt
Normal file
1258
pkl-formatter/src/main/kotlin/org/pkl/formatter/Builder.kt
Normal file
File diff suppressed because it is too large
Load Diff
53
pkl-formatter/src/main/kotlin/org/pkl/formatter/Formatter.kt
Normal file
53
pkl-formatter/src/main/kotlin/org/pkl/formatter/Formatter.kt
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.formatter
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import org.pkl.formatter.ast.ForceLine
|
||||||
|
import org.pkl.formatter.ast.Nodes
|
||||||
|
import org.pkl.parser.GenericParser
|
||||||
|
|
||||||
|
/** A formatter for Pkl files that applies canonical formatting rules. */
|
||||||
|
class Formatter {
|
||||||
|
/**
|
||||||
|
* Formats a Pkl file from the given file path.
|
||||||
|
*
|
||||||
|
* @param path the path to the Pkl file to format
|
||||||
|
* @return the formatted Pkl source code as a string
|
||||||
|
* @throws java.io.IOException if the file cannot be read
|
||||||
|
*/
|
||||||
|
fun format(path: Path): String {
|
||||||
|
return format(Files.readString(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
val parser = GenericParser()
|
||||||
|
val builder = Builder(text)
|
||||||
|
val gen = Generator()
|
||||||
|
val ast = parser.parseModule(text)
|
||||||
|
val formatAst = builder.format(ast)
|
||||||
|
// force a line at the end of the file
|
||||||
|
gen.generate(Nodes(listOf(formatAst, ForceLine)))
|
||||||
|
return gen.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
149
pkl-formatter/src/main/kotlin/org/pkl/formatter/Generator.kt
Normal file
149
pkl-formatter/src/main/kotlin/org/pkl/formatter/Generator.kt
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.formatter
|
||||||
|
|
||||||
|
import org.pkl.formatter.ast.Empty
|
||||||
|
import org.pkl.formatter.ast.ForceLine
|
||||||
|
import org.pkl.formatter.ast.ForceWrap
|
||||||
|
import org.pkl.formatter.ast.FormatNode
|
||||||
|
import org.pkl.formatter.ast.Group
|
||||||
|
import org.pkl.formatter.ast.IfWrap
|
||||||
|
import org.pkl.formatter.ast.Indent
|
||||||
|
import org.pkl.formatter.ast.Line
|
||||||
|
import org.pkl.formatter.ast.MultilineStringGroup
|
||||||
|
import org.pkl.formatter.ast.Nodes
|
||||||
|
import org.pkl.formatter.ast.Space
|
||||||
|
import org.pkl.formatter.ast.SpaceOrLine
|
||||||
|
import org.pkl.formatter.ast.Text
|
||||||
|
import org.pkl.formatter.ast.Wrap
|
||||||
|
|
||||||
|
internal class Generator {
|
||||||
|
private val buf: StringBuilder = StringBuilder()
|
||||||
|
private var indent: Int = 0
|
||||||
|
private var size: Int = 0
|
||||||
|
private val wrapped: MutableSet<Int> = mutableSetOf()
|
||||||
|
private var shouldAddIndent = false
|
||||||
|
|
||||||
|
fun generate(node: FormatNode) {
|
||||||
|
node(node, Wrap.DETECT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun node(node: FormatNode, wrap: Wrap) {
|
||||||
|
when (node) {
|
||||||
|
is Empty -> {}
|
||||||
|
is Nodes -> node.nodes.forEach { node(it, wrap) }
|
||||||
|
is Group -> {
|
||||||
|
val width = node.nodes.sumOf { it.width(wrapped) }
|
||||||
|
val wrap =
|
||||||
|
if (size + width > MAX) {
|
||||||
|
wrapped += node.id
|
||||||
|
Wrap.ENABLED
|
||||||
|
} else {
|
||||||
|
Wrap.DETECT
|
||||||
|
}
|
||||||
|
node.nodes.forEach { node(it, wrap) }
|
||||||
|
}
|
||||||
|
is ForceWrap -> {
|
||||||
|
wrapped += node.id
|
||||||
|
val wrap = Wrap.ENABLED
|
||||||
|
node.nodes.forEach { node(it, wrap) }
|
||||||
|
}
|
||||||
|
is IfWrap -> {
|
||||||
|
if (wrapped.contains(node.id)) {
|
||||||
|
node(node.ifWrap, Wrap.ENABLED)
|
||||||
|
} else {
|
||||||
|
node(node.ifNotWrap, wrap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Text -> text(node.text)
|
||||||
|
is Line -> {
|
||||||
|
if (wrap.isEnabled()) {
|
||||||
|
newline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ForceLine -> newline()
|
||||||
|
is SpaceOrLine -> {
|
||||||
|
if (wrap.isEnabled()) {
|
||||||
|
newline()
|
||||||
|
} else {
|
||||||
|
text(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Space -> text(" ")
|
||||||
|
is Indent -> {
|
||||||
|
if (wrap.isEnabled() && node.nodes.isNotEmpty()) {
|
||||||
|
size += INDENT.length
|
||||||
|
indent++
|
||||||
|
node.nodes.forEach { node(it, wrap) }
|
||||||
|
indent--
|
||||||
|
} else {
|
||||||
|
node.nodes.forEach { node(it, wrap) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is MultilineStringGroup -> {
|
||||||
|
val indentLength = indent * INDENT.length
|
||||||
|
val offset = (indentLength + 1) - node.endQuoteCol
|
||||||
|
var previousNewline = false
|
||||||
|
for ((i, child) in node.nodes.withIndex()) {
|
||||||
|
when {
|
||||||
|
child is ForceLine -> newline(shouldIndent = false) // don't indent
|
||||||
|
child is Text &&
|
||||||
|
previousNewline &&
|
||||||
|
child.text.isBlank() &&
|
||||||
|
child.text.length == indentLength &&
|
||||||
|
node.nodes[i + 1] is ForceLine -> {}
|
||||||
|
child is Text && previousNewline && offset != 0 -> text(reposition(child.text, offset))
|
||||||
|
else -> node(child, Wrap.DETECT) // always detect wrapping
|
||||||
|
}
|
||||||
|
previousNewline = child is ForceLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun text(value: String) {
|
||||||
|
if (shouldAddIndent) {
|
||||||
|
repeat(times = indent) { buf.append(INDENT) }
|
||||||
|
shouldAddIndent = false
|
||||||
|
}
|
||||||
|
size += value.length
|
||||||
|
buf.append(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newline(shouldIndent: Boolean = true) {
|
||||||
|
size = INDENT.length * indent
|
||||||
|
buf.append('\n')
|
||||||
|
shouldAddIndent = shouldIndent
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reposition(text: String, offset: Int): String {
|
||||||
|
return if (offset > 0) {
|
||||||
|
" ".repeat(offset) + text
|
||||||
|
} else {
|
||||||
|
text.drop(-offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return buf.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// max line length
|
||||||
|
const val MAX = 100
|
||||||
|
private const val INDENT = " "
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.formatter
|
||||||
|
|
||||||
|
internal class NaturalOrderComparator(private val ignoreCase: Boolean = false) :
|
||||||
|
Comparator<String> {
|
||||||
|
|
||||||
|
override fun compare(s1: String, s2: String): Int {
|
||||||
|
var i = 0
|
||||||
|
var j = 0
|
||||||
|
|
||||||
|
while (i < s1.length && j < s2.length) {
|
||||||
|
val c1 = if (ignoreCase) s1[i].lowercaseChar() else s1[i]
|
||||||
|
val c2 = if (ignoreCase) s2[j].lowercaseChar() else s2[j]
|
||||||
|
|
||||||
|
if (c1.isDigit() && c2.isDigit()) {
|
||||||
|
val (num1, nextI) = getNumber(s1, i)
|
||||||
|
val (num2, nextJ) = getNumber(s2, j)
|
||||||
|
|
||||||
|
val numComparison = num1.compareTo(num2)
|
||||||
|
if (numComparison != 0) {
|
||||||
|
return numComparison
|
||||||
|
}
|
||||||
|
i = nextI
|
||||||
|
j = nextJ
|
||||||
|
} else {
|
||||||
|
val charComparison = c1.compareTo(c2)
|
||||||
|
if (charComparison != 0) {
|
||||||
|
return charComparison
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s1.length.compareTo(s2.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNumber(s: String, startIndex: Int): LongAndInt {
|
||||||
|
var i = startIndex
|
||||||
|
val start = i
|
||||||
|
|
||||||
|
while (i < s.length && s[i].isDigit()) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
val numStr = s.substring(start, i)
|
||||||
|
val number = numStr.toLongOrNull() ?: 0L
|
||||||
|
return LongAndInt(number, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// use this instead of Pair to avoid boxing
|
||||||
|
private data class LongAndInt(val l: Long, var i: Int)
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.formatter.ast
|
||||||
|
|
||||||
|
import org.pkl.formatter.Generator
|
||||||
|
|
||||||
|
enum class Wrap {
|
||||||
|
ENABLED,
|
||||||
|
DETECT;
|
||||||
|
|
||||||
|
fun isEnabled(): Boolean = this == ENABLED
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface FormatNode {
|
||||||
|
fun width(wrapped: Set<Int>): Int =
|
||||||
|
when (this) {
|
||||||
|
is Nodes -> nodes.sumOf { it.width(wrapped) }
|
||||||
|
is Group -> nodes.sumOf { it.width(wrapped) }
|
||||||
|
is Indent -> nodes.sumOf { it.width(wrapped) }
|
||||||
|
is ForceWrap -> nodes.sumOf { it.width(wrapped + id) }
|
||||||
|
is IfWrap -> if (id in wrapped) ifWrap.width(wrapped) else ifNotWrap.width(wrapped)
|
||||||
|
is Text -> text.length
|
||||||
|
is SpaceOrLine,
|
||||||
|
is Space -> 1
|
||||||
|
is ForceLine,
|
||||||
|
is MultilineStringGroup -> Generator.MAX
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Text(val text: String) : FormatNode
|
||||||
|
|
||||||
|
object Empty : FormatNode
|
||||||
|
|
||||||
|
object Line : FormatNode
|
||||||
|
|
||||||
|
object ForceLine : FormatNode
|
||||||
|
|
||||||
|
object SpaceOrLine : FormatNode
|
||||||
|
|
||||||
|
object Space : FormatNode
|
||||||
|
|
||||||
|
data class Indent(val nodes: List<FormatNode>) : FormatNode
|
||||||
|
|
||||||
|
data class Nodes(val nodes: List<FormatNode>) : FormatNode
|
||||||
|
|
||||||
|
data class Group(val id: Int, val nodes: List<FormatNode>) : FormatNode
|
||||||
|
|
||||||
|
data class ForceWrap(val id: Int, val nodes: List<FormatNode>) : FormatNode
|
||||||
|
|
||||||
|
data class MultilineStringGroup(val endQuoteCol: Int, val nodes: List<FormatNode>) : FormatNode
|
||||||
|
|
||||||
|
data class IfWrap(val id: Int, val ifWrap: FormatNode, val ifNotWrap: FormatNode) : FormatNode
|
||||||
10
pkl-formatter/src/test/files/FormatterSnippetTests/input/class-bodies.pkl
vendored
Normal file
10
pkl-formatter/src/test/files/FormatterSnippetTests/input/class-bodies.pkl
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
class Foo {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bar
|
||||||
|
{
|
||||||
|
qux = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
class Baz { prop = 0; prop2 = 1 }
|
||||||
16
pkl-formatter/src/test/files/FormatterSnippetTests/input/comma-termination.pkl
vendored
Normal file
16
pkl-formatter/src/test/files/FormatterSnippetTests/input/comma-termination.pkl
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
function fun(a, b, c, d) = a
|
||||||
|
|
||||||
|
noTrailingCommas = fun(1, 2, 3, 4,)
|
||||||
|
|
||||||
|
trailingCommas = fun("loooooooooooooooooooongString", "loooooooooooooooongString", "looooooooooooooooongString", "notTooLong")
|
||||||
|
|
||||||
|
noTrailingCommaObjParams {
|
||||||
|
fooooooooooooooooooooooooooooooooo, baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar, baaaaaaaaaaaaaaaaaaaaaz ->
|
||||||
|
1 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trailingCommaInLambdas = (fooooooooooooooooooooooooooooooooo, baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar, baaaaaaaaaaaaaaaaaaaaaz) -> 1
|
||||||
|
|
||||||
|
trailingCommaInConstraints: String(isSomethingSomethingSomething, isSomethingElse, isSomethingSomethingSomethingElse)
|
||||||
|
|
||||||
|
trailingCommaInTypeParameters: Mapping<SomethingSomethingSomethingSomething, SomethingSomething | SomethingElse>
|
||||||
31
pkl-formatter/src/test/files/FormatterSnippetTests/input/comment-interleaved.pkl
vendored
Normal file
31
pkl-formatter/src/test/files/FormatterSnippetTests/input/comment-interleaved.pkl
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
module comment.interleaved
|
||||||
|
|
||||||
|
local test: Int|String =
|
||||||
|
if (true)
|
||||||
|
1 // It's the same as "100%"
|
||||||
|
else
|
||||||
|
"8%"
|
||||||
|
|
||||||
|
foo: ( // some comment
|
||||||
|
* String(
|
||||||
|
// if dtstart is defined and a datetime, until must also be a datetime if defined
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|Int
|
||||||
|
// trailing comment
|
||||||
|
)?
|
||||||
|
|
||||||
|
foo2: (// some comment
|
||||||
|
Int, // other comment
|
||||||
|
String
|
||||||
|
) ->Int
|
||||||
|
|
||||||
|
bar = ( // some comment
|
||||||
|
10 + 10
|
||||||
|
// another comment
|
||||||
|
)
|
||||||
|
|
||||||
|
bar2 = ( // some comment
|
||||||
|
foo, bar
|
||||||
|
// another comment
|
||||||
|
) -> foo + bar
|
||||||
8
pkl-formatter/src/test/files/FormatterSnippetTests/input/dangling-doc-comment.pkl
vendored
Normal file
8
pkl-formatter/src/test/files/FormatterSnippetTests/input/dangling-doc-comment.pkl
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module dangling
|
||||||
|
|
||||||
|
/// Doc comment.
|
||||||
|
///
|
||||||
|
/// for this field
|
||||||
|
// sepearted by a stray comment
|
||||||
|
/// but continues here.
|
||||||
|
some: String
|
||||||
14
pkl-formatter/src/test/files/FormatterSnippetTests/input/doc-comments.pkl
vendored
Normal file
14
pkl-formatter/src/test/files/FormatterSnippetTests/input/doc-comments.pkl
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
//// line 1
|
||||||
|
///// line 2
|
||||||
|
foo = 1
|
||||||
|
|
||||||
|
/// line 1
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// line 2
|
||||||
|
bar = 1
|
||||||
|
|
||||||
|
///line 1
|
||||||
|
///line 2
|
||||||
|
baz = 1
|
||||||
25
pkl-formatter/src/test/files/FormatterSnippetTests/input/expr-binary.pkl
vendored
Normal file
25
pkl-formatter/src/test/files/FormatterSnippetTests/input/expr-binary.pkl
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
foo {
|
||||||
|
123123123123 + 123123123123 * 123123123123 - 123123123123 / 123123123123 + 123123123123 ** 123123123123
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
|
bar = clazz.superclass == null || clazz.superclass.reflectee == Module || clazz.superclass.reflectee == Typed
|
||||||
|
|
||||||
|
baz =
|
||||||
|
new Listing {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
} |> mixin1 |> mixin2
|
||||||
|
|
||||||
|
qux =
|
||||||
|
(baz) {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
} {
|
||||||
|
3
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
|
minus = superLoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongVariable - 100000
|
||||||
5
pkl-formatter/src/test/files/FormatterSnippetTests/input/expr-chain.pkl
vendored
Normal file
5
pkl-formatter/src/test/files/FormatterSnippetTests/input/expr-chain.pkl
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
res = myList.map((it) -> it.partition).filter((it) -> someList.contains(it))
|
||||||
|
|
||||||
|
res2 = myList.map(lambda1).filter(lambda2)
|
||||||
|
|
||||||
|
res3 = myList.map((it) -> it.partition)
|
||||||
7
pkl-formatter/src/test/files/FormatterSnippetTests/input/expr-if.pkl
vendored
Normal file
7
pkl-formatter/src/test/files/FormatterSnippetTests/input/expr-if.pkl
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
foo =
|
||||||
|
if (someCondition) 10000
|
||||||
|
else
|
||||||
|
if (someOtherCondition) 20000
|
||||||
|
else
|
||||||
|
if (someAnotherCondition) 30000
|
||||||
|
else 4
|
||||||
11
pkl-formatter/src/test/files/FormatterSnippetTests/input/expr-let.pkl
vendored
Normal file
11
pkl-formatter/src/test/files/FormatterSnippetTests/input/expr-let.pkl
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
foo = let (vaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaariable = 10) 1 * 1
|
||||||
|
|
||||||
|
bar = let (someVariable = new Listing {
|
||||||
|
1
|
||||||
|
}) 1 * 1
|
||||||
|
|
||||||
|
baz =
|
||||||
|
let (someVariable = 10000000)
|
||||||
|
let (someOtherVariable = 2000000)
|
||||||
|
let (someAnotherVariable = 3000000) someVariable + someOtherVariable + someAnotherVariable
|
||||||
19
pkl-formatter/src/test/files/FormatterSnippetTests/input/imports.pkl
vendored
Normal file
19
pkl-formatter/src/test/files/FormatterSnippetTests/input/imports.pkl
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// top level comment
|
||||||
|
|
||||||
|
import "@foo/Foo.pkl" as foo
|
||||||
|
import* "**.pkl"
|
||||||
|
import "pkl:math"
|
||||||
|
|
||||||
|
import "package://example.com/myPackage@1.0.0#/Qux.pkl"
|
||||||
|
import* "file:///tmp/*.pkl"
|
||||||
|
|
||||||
|
|
||||||
|
import "https://example.com/baz.pkl"
|
||||||
|
import "module2.pkl"
|
||||||
|
import "pkl:reflect"
|
||||||
|
import "..."
|
||||||
|
import* "@foo/**.pkl"
|
||||||
|
import "@bar/Bar.pkl"
|
||||||
|
import "Module12.pkl"
|
||||||
|
import "module11.pkl"
|
||||||
|
import "module1.pkl"
|
||||||
51
pkl-formatter/src/test/files/FormatterSnippetTests/input/indentation.pkl
vendored
Normal file
51
pkl-formatter/src/test/files/FormatterSnippetTests/input/indentation.pkl
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
foo = new {
|
||||||
|
bar = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
baz: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
bar =
|
||||||
|
new Listing {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
baz =
|
||||||
|
(foo) {
|
||||||
|
2
|
||||||
|
3
|
||||||
|
}
|
||||||
|
|
||||||
|
qux =
|
||||||
|
(x, y) -> new Listing {
|
||||||
|
x
|
||||||
|
y
|
||||||
|
}
|
||||||
|
|
||||||
|
forGen = new Listing {
|
||||||
|
for (someVar in new Listing {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
}) {
|
||||||
|
[someVar] = someVar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
objParams: Listing<Int> =
|
||||||
|
new {
|
||||||
|
default {
|
||||||
|
key -> key + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
objParams2: Listing<Int> = new {
|
||||||
|
default { someVeryLongParameter1, someVeryLongParameter2, someVeryLongParameter3, someVeryLongParameter4 ->
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parenType: (ReaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongType(reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalylongConstraint))
|
||||||
|
|
||||||
|
functionType: (ReaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongType,ReaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongType2) -> String
|
||||||
64
pkl-formatter/src/test/files/FormatterSnippetTests/input/line-breaks.pkl
vendored
Normal file
64
pkl-formatter/src/test/files/FormatterSnippetTests/input/line-breaks.pkl
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
module
|
||||||
|
foo.bar.baz
|
||||||
|
amends
|
||||||
|
"bar.pkl"
|
||||||
|
|
||||||
|
import
|
||||||
|
"@foo/Foo.pkl"
|
||||||
|
as foo
|
||||||
|
|
||||||
|
local
|
||||||
|
open
|
||||||
|
class Bar {}
|
||||||
|
|
||||||
|
const
|
||||||
|
local
|
||||||
|
baz = 10
|
||||||
|
|
||||||
|
local
|
||||||
|
function
|
||||||
|
fun(x) =
|
||||||
|
x
|
||||||
|
|
||||||
|
const local prooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooperty: String = "foo"
|
||||||
|
|
||||||
|
local function function2(parameter1: Parameter1Type, parameter2: Parameter2Type, parameter3: Parameter3Type, parameter4: Parameter4Type): String = ""
|
||||||
|
|
||||||
|
local const function function3(parameter1: String|Int, parameter2: String|Int): Mapping<String|Int, String> =
|
||||||
|
new {}
|
||||||
|
|
||||||
|
prop = function2(loooooooooooooooooogParameter1, loooooooooooooooooogParameter2, loooooooooooooooooogParameter3, loooooooooooooooooogParameter4)
|
||||||
|
|
||||||
|
prop2: String
|
||||||
|
|Int
|
||||||
|
|Boolean
|
||||||
|
|
||||||
|
funcParam = fun(
|
||||||
|
(x, y) -> new Listing {
|
||||||
|
x
|
||||||
|
y
|
||||||
|
})
|
||||||
|
|
||||||
|
funcParam2 = aFun(foo, 10 * 10, anotherVariable, if (true) 100000 else 200000, (param1, param2) -> param1 * param2)
|
||||||
|
|
||||||
|
funcParam3 = aFun(foo, 10 * 10, anotherVariable, 200000, (param1, param2) -> param1 * param2)
|
||||||
|
|
||||||
|
funcParam4 = aFun(foo, 10 * 10, anotherVariable, if (true) 100000 else 200000, new Listing {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
})
|
||||||
|
|
||||||
|
open local class SomeReallyInterestingClassName<in SomeInParameter, out SomeOutParameter> extends AnotherInterestingClassName {
|
||||||
|
foo: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
local function resourceMapping(type): Mapping<String, unknown> =
|
||||||
|
new Mapping { default = (key) -> (type) { metadata { name = key } } }
|
||||||
|
|
||||||
|
local const function biiiiiiiiiiiiiiiiiiiiiiiiigFunction(param1: String, param2: String(!isBlank)): Boolean
|
||||||
|
|
||||||
|
local const function someFunction(param1: String, param2: String(!isBlank)): Boolean
|
||||||
|
|
||||||
|
local function render(currentIndent: String) =
|
||||||
|
"\(currentIndent)@\(identifier.render(currentIndent))" +
|
||||||
|
if (body == null) "" else " " + body.render(currentIndent)
|
||||||
20
pkl-formatter/src/test/files/FormatterSnippetTests/input/line-breaks2.pkl
vendored
Normal file
20
pkl-formatter/src/test/files/FormatterSnippetTests/input/line-breaks2.pkl
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaly.loooooooooooooooooooooooooooooooog.naaaaaaaaaaaaaaaaaaaaaaaaaame
|
||||||
|
extends "reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongModule.pkl"
|
||||||
|
|
||||||
|
import "reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongModule.pkl" as foo
|
||||||
|
|
||||||
|
local open class LoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongName {}
|
||||||
|
|
||||||
|
local open class ReaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongName {}
|
||||||
|
|
||||||
|
const hidden loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooogName = 99
|
||||||
|
|
||||||
|
const hidden reallyLoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooogName = 99
|
||||||
|
|
||||||
|
const local function looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooogName(x: Int, y) = x + y
|
||||||
|
|
||||||
|
typealias Foo = LooooooooooooooooooooooooongTypeName|AnotherLooooooooooooooooooooooooongTypeName|OtherLooooooooooooooooooooooooongTypeName
|
||||||
|
|
||||||
|
bar: Boolean|Mapping<LooooooooooooooooooooooooooooongTypeName, AnotherLooooooooooooooooooooooooooooongTypeName>(loooooooooooooooooooogConstraint)
|
||||||
|
|
||||||
|
hidden foobar: LongType(someVeryyyyyyyloooong, requirements.might.be.even_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooonger)
|
||||||
18
pkl-formatter/src/test/files/FormatterSnippetTests/input/line-width.pkl
vendored
Normal file
18
pkl-formatter/src/test/files/FormatterSnippetTests/input/line-width.pkl
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
module reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaly.loooooooooooooooooooooooooooooooog.naaaaaaaaaaaaaaaaaaaaaaaaaaaaame
|
||||||
|
extends "reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongModule.pkl"
|
||||||
|
|
||||||
|
local reallyLongVariableName = true
|
||||||
|
local anotherReallyLongName = 1
|
||||||
|
local evenLongerVariableName = 2
|
||||||
|
|
||||||
|
/// this property has a reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaly long doc comment
|
||||||
|
property = if (reallyLongVariableName) anotherReallyLongName else evenLongerVariableName + anotherReallyLongName
|
||||||
|
|
||||||
|
longString = "reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaly long string"
|
||||||
|
|
||||||
|
shortProperty = 1
|
||||||
|
|
||||||
|
reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongVariableName = 10
|
||||||
|
|
||||||
|
reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongVariableName2 =
|
||||||
|
if (reallyLongVariableName) anotherReallyLongName else evenLongerVariableName + anotherReallyLongName
|
||||||
4
pkl-formatter/src/test/files/FormatterSnippetTests/input/map-function.pkl
vendored
Normal file
4
pkl-formatter/src/test/files/FormatterSnippetTests/input/map-function.pkl
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
foo = Map(1000, "some random string", 20000, "another random string", 30000, "yet another random string")
|
||||||
|
|
||||||
|
incorrect = Map("This has", 1000000, "an incorrect number", 2000000, "of parameters", 30000000, "passed to Map")
|
||||||
3
pkl-formatter/src/test/files/FormatterSnippetTests/input/modifiers.pkl
vendored
Normal file
3
pkl-formatter/src/test/files/FormatterSnippetTests/input/modifiers.pkl
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
hidden const foo = 1
|
||||||
|
|
||||||
|
open local class Foo {}
|
||||||
13
pkl-formatter/src/test/files/FormatterSnippetTests/input/module-definitions.pkl
vendored
Normal file
13
pkl-formatter/src/test/files/FormatterSnippetTests/input/module-definitions.pkl
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// comment
|
||||||
|
// one
|
||||||
|
|
||||||
|
|
||||||
|
/// doc comment
|
||||||
|
|
||||||
|
open
|
||||||
|
module
|
||||||
|
foo
|
||||||
|
.bar
|
||||||
|
|
||||||
|
amends
|
||||||
|
"baz.pkl"
|
||||||
29
pkl-formatter/src/test/files/FormatterSnippetTests/input/multi-line-strings.pkl
vendored
Normal file
29
pkl-formatter/src/test/files/FormatterSnippetTests/input/multi-line-strings.pkl
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
foo = """
|
||||||
|
asd \(new {
|
||||||
|
bar = 1
|
||||||
|
}) asd
|
||||||
|
"""
|
||||||
|
|
||||||
|
bar = """
|
||||||
|
line 1
|
||||||
|
line 2
|
||||||
|
line3
|
||||||
|
"""
|
||||||
|
|
||||||
|
baz = """
|
||||||
|
\n
|
||||||
|
\(bar)
|
||||||
|
line
|
||||||
|
\u{123}
|
||||||
|
"""
|
||||||
|
|
||||||
|
// remove unneeded spaces
|
||||||
|
qux = """
|
||||||
|
foo
|
||||||
|
|
||||||
|
bar
|
||||||
|
|
||||||
|
baz
|
||||||
|
|
||||||
|
\(foo)
|
||||||
|
"""
|
||||||
22
pkl-formatter/src/test/files/FormatterSnippetTests/input/object-members.pkl
vendored
Normal file
22
pkl-formatter/src/test/files/FormatterSnippetTests/input/object-members.pkl
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
foo: Listing<Int> = new {1 2 3; 4;5 6 7}
|
||||||
|
|
||||||
|
bar: Listing<Int> = new { 1 2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
|
lineIsTooBig: Listing<Int> = new { 999999; 1000000; 1000001; 1000002; 1000003; 1000004; 1000005; 1000006; 1000007; 1000008; 1000009 }
|
||||||
|
|
||||||
|
lineIsTooBig2 { 999999; 1000000; 1000001; 1000002; 1000003; 1000004; 1000005; 1000006; 1000007; 1000008; 1000009; 1000010 }
|
||||||
|
|
||||||
|
baz = new Dynamic {
|
||||||
|
1 2
|
||||||
|
3 4
|
||||||
|
|
||||||
|
["foo"] = 3; bar = 30
|
||||||
|
|
||||||
|
|
||||||
|
baz = true
|
||||||
|
}
|
||||||
|
|
||||||
|
qux = new Dynamic {1 2 3 prop="prop"; [0]=9}
|
||||||
12
pkl-formatter/src/test/files/FormatterSnippetTests/input/prefixes.pkl
vendored
Normal file
12
pkl-formatter/src/test/files/FormatterSnippetTests/input/prefixes.pkl
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
/// Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong doc comment
|
||||||
|
@Annotation { looooooooooooooooooooooooooooooooooooooooooooooooooongVariableName = 10 }
|
||||||
|
typealias Foo = String
|
||||||
|
|
||||||
|
/// Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong doc comment
|
||||||
|
@Annotation { looooooooooooooooooooooooooooooooooooooooooooooooooongVariableName = 10 }
|
||||||
|
foo = "should fit in a single line"
|
||||||
|
|
||||||
|
/// Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong doc comment
|
||||||
|
@Annotation { looooooooooooooooooooooooooooooooooooooooooooooooooongVariableName = 10 }
|
||||||
|
function bar(x): String = "result: \(x)"
|
||||||
23
pkl-formatter/src/test/files/FormatterSnippetTests/input/spaces.pkl
vendored
Normal file
23
pkl-formatter/src/test/files/FormatterSnippetTests/input/spaces.pkl
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import"foo.pkl"
|
||||||
|
|
||||||
|
bar=new Listing < Int > ( !isEmpty ){1;2}//a bar
|
||||||
|
|
||||||
|
typealias Typealias=(String,Int)->Boolean
|
||||||
|
|
||||||
|
///a baz
|
||||||
|
///returns its parameter
|
||||||
|
baz = (x,y,z)->x+y+z
|
||||||
|
|
||||||
|
function fun ( x:Int ? ,b :Boolean )= if(b)/***return x**/x else x+bar [0 ]
|
||||||
|
|
||||||
|
prop = trace (1) + super . foo + module .foo
|
||||||
|
|
||||||
|
prop2 {
|
||||||
|
for(x in List(1)) {
|
||||||
|
when(x == 1){
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
choices: "foo" | * "bar" | String
|
||||||
6
pkl-formatter/src/test/files/FormatterSnippetTests/input/type-aliases.pkl
vendored
Normal file
6
pkl-formatter/src/test/files/FormatterSnippetTests/input/type-aliases.pkl
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
typealias
|
||||||
|
Foo
|
||||||
|
=
|
||||||
|
String
|
||||||
|
|
||||||
|
typealias VeryLoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongTypeAlias = String(!isEmpty)
|
||||||
9
pkl-formatter/src/test/files/FormatterSnippetTests/input/when.pkl
vendored
Normal file
9
pkl-formatter/src/test/files/FormatterSnippetTests/input/when.pkl
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
foo {
|
||||||
|
when (true)
|
||||||
|
{
|
||||||
|
bar = 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bar = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
11
pkl-formatter/src/test/files/FormatterSnippetTests/output/class-bodies.pkl
vendored
Normal file
11
pkl-formatter/src/test/files/FormatterSnippetTests/output/class-bodies.pkl
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class Foo {}
|
||||||
|
|
||||||
|
class Bar {
|
||||||
|
qux = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
class Baz {
|
||||||
|
prop = 0
|
||||||
|
|
||||||
|
prop2 = 1
|
||||||
|
}
|
||||||
35
pkl-formatter/src/test/files/FormatterSnippetTests/output/comma-termination.pkl
vendored
Normal file
35
pkl-formatter/src/test/files/FormatterSnippetTests/output/comma-termination.pkl
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
function fun(a, b, c, d) = a
|
||||||
|
|
||||||
|
noTrailingCommas = fun(1, 2, 3, 4)
|
||||||
|
|
||||||
|
trailingCommas =
|
||||||
|
fun(
|
||||||
|
"loooooooooooooooooooongString",
|
||||||
|
"loooooooooooooooongString",
|
||||||
|
"looooooooooooooooongString",
|
||||||
|
"notTooLong",
|
||||||
|
)
|
||||||
|
|
||||||
|
noTrailingCommaObjParams {
|
||||||
|
fooooooooooooooooooooooooooooooooo,
|
||||||
|
baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar,
|
||||||
|
baaaaaaaaaaaaaaaaaaaaaz ->
|
||||||
|
1 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trailingCommaInLambdas = (
|
||||||
|
fooooooooooooooooooooooooooooooooo,
|
||||||
|
baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar,
|
||||||
|
baaaaaaaaaaaaaaaaaaaaaz,
|
||||||
|
) -> 1
|
||||||
|
|
||||||
|
trailingCommaInConstraints: String(
|
||||||
|
isSomethingSomethingSomething,
|
||||||
|
isSomethingElse,
|
||||||
|
isSomethingSomethingSomethingElse,
|
||||||
|
)
|
||||||
|
|
||||||
|
trailingCommaInTypeParameters: Mapping<
|
||||||
|
SomethingSomethingSomethingSomething,
|
||||||
|
SomethingSomething | SomethingElse,
|
||||||
|
>
|
||||||
34
pkl-formatter/src/test/files/FormatterSnippetTests/output/comment-interleaved.pkl
vendored
Normal file
34
pkl-formatter/src/test/files/FormatterSnippetTests/output/comment-interleaved.pkl
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
module comment.interleaved
|
||||||
|
|
||||||
|
local test: Int | String =
|
||||||
|
if (true)
|
||||||
|
1 // It's the same as "100%"
|
||||||
|
else
|
||||||
|
"8%"
|
||||||
|
|
||||||
|
foo: ( // some comment
|
||||||
|
*String(
|
||||||
|
// if dtstart is defined and a datetime, until must also be a datetime if defined
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
| Int
|
||||||
|
// trailing comment
|
||||||
|
)?
|
||||||
|
|
||||||
|
foo2: ( // some comment
|
||||||
|
Int, // other comment
|
||||||
|
String,
|
||||||
|
) ->
|
||||||
|
Int
|
||||||
|
|
||||||
|
bar =
|
||||||
|
( // some comment
|
||||||
|
10 + 10
|
||||||
|
// another comment
|
||||||
|
)
|
||||||
|
|
||||||
|
bar2 = ( // some comment
|
||||||
|
foo,
|
||||||
|
bar
|
||||||
|
// another comment
|
||||||
|
) -> foo + bar
|
||||||
8
pkl-formatter/src/test/files/FormatterSnippetTests/output/dangling-doc-comment.pkl
vendored
Normal file
8
pkl-formatter/src/test/files/FormatterSnippetTests/output/dangling-doc-comment.pkl
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module dangling
|
||||||
|
|
||||||
|
/// Doc comment.
|
||||||
|
///
|
||||||
|
/// for this field
|
||||||
|
// sepearted by a stray comment
|
||||||
|
/// but continues here.
|
||||||
|
some: String
|
||||||
13
pkl-formatter/src/test/files/FormatterSnippetTests/output/doc-comments.pkl
vendored
Normal file
13
pkl-formatter/src/test/files/FormatterSnippetTests/output/doc-comments.pkl
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/// / line 1
|
||||||
|
/// // line 2
|
||||||
|
foo = 1
|
||||||
|
|
||||||
|
/// line 1
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// line 2
|
||||||
|
bar = 1
|
||||||
|
|
||||||
|
/// line 1
|
||||||
|
/// line 2
|
||||||
|
baz = 1
|
||||||
34
pkl-formatter/src/test/files/FormatterSnippetTests/output/expr-binary.pkl
vendored
Normal file
34
pkl-formatter/src/test/files/FormatterSnippetTests/output/expr-binary.pkl
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
foo {
|
||||||
|
123123123123
|
||||||
|
+ 123123123123 * 123123123123 -
|
||||||
|
123123123123 / 123123123123
|
||||||
|
+ 123123123123 ** 123123123123
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
|
bar =
|
||||||
|
clazz.superclass == null
|
||||||
|
|| clazz.superclass.reflectee == Module
|
||||||
|
|| clazz.superclass.reflectee == Typed
|
||||||
|
|
||||||
|
baz =
|
||||||
|
new Listing {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|> mixin1
|
||||||
|
|> mixin2
|
||||||
|
|
||||||
|
qux = (baz) {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
} {
|
||||||
|
3
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
|
minus =
|
||||||
|
superLoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongVariable -
|
||||||
|
100000
|
||||||
8
pkl-formatter/src/test/files/FormatterSnippetTests/output/expr-chain.pkl
vendored
Normal file
8
pkl-formatter/src/test/files/FormatterSnippetTests/output/expr-chain.pkl
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
res =
|
||||||
|
myList
|
||||||
|
.map((it) -> it.partition)
|
||||||
|
.filter((it) -> someList.contains(it))
|
||||||
|
|
||||||
|
res2 = myList.map(lambda1).filter(lambda2)
|
||||||
|
|
||||||
|
res3 = myList.map((it) -> it.partition)
|
||||||
9
pkl-formatter/src/test/files/FormatterSnippetTests/output/expr-if.pkl
vendored
Normal file
9
pkl-formatter/src/test/files/FormatterSnippetTests/output/expr-if.pkl
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
foo =
|
||||||
|
if (someCondition)
|
||||||
|
10000
|
||||||
|
else if (someOtherCondition)
|
||||||
|
20000
|
||||||
|
else if (someAnotherCondition)
|
||||||
|
30000
|
||||||
|
else
|
||||||
|
4
|
||||||
18
pkl-formatter/src/test/files/FormatterSnippetTests/output/expr-let.pkl
vendored
Normal file
18
pkl-formatter/src/test/files/FormatterSnippetTests/output/expr-let.pkl
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
foo =
|
||||||
|
let (vaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaariable =
|
||||||
|
10
|
||||||
|
)
|
||||||
|
1 * 1
|
||||||
|
|
||||||
|
bar =
|
||||||
|
let (someVariable = new Listing {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
1 * 1
|
||||||
|
|
||||||
|
baz =
|
||||||
|
let (someVariable = 10000000)
|
||||||
|
let (someOtherVariable = 2000000)
|
||||||
|
let (someAnotherVariable = 3000000)
|
||||||
|
someVariable + someOtherVariable + someAnotherVariable
|
||||||
21
pkl-formatter/src/test/files/FormatterSnippetTests/output/imports.pkl
vendored
Normal file
21
pkl-formatter/src/test/files/FormatterSnippetTests/output/imports.pkl
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// top level comment
|
||||||
|
|
||||||
|
import "https://example.com/baz.pkl"
|
||||||
|
import "package://example.com/myPackage@1.0.0#/Qux.pkl"
|
||||||
|
import "pkl:math"
|
||||||
|
import "pkl:reflect"
|
||||||
|
|
||||||
|
import "@bar/Bar.pkl"
|
||||||
|
import "@foo/Foo.pkl" as foo
|
||||||
|
|
||||||
|
import "..."
|
||||||
|
import "module1.pkl"
|
||||||
|
import "module2.pkl"
|
||||||
|
import "module11.pkl"
|
||||||
|
import "Module12.pkl"
|
||||||
|
|
||||||
|
import* "file:///tmp/*.pkl"
|
||||||
|
|
||||||
|
import* "@foo/**.pkl"
|
||||||
|
|
||||||
|
import* "**.pkl"
|
||||||
61
pkl-formatter/src/test/files/FormatterSnippetTests/output/indentation.pkl
vendored
Normal file
61
pkl-formatter/src/test/files/FormatterSnippetTests/output/indentation.pkl
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
foo = new {
|
||||||
|
bar = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
baz: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
bar = new Listing {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
baz = (foo) {
|
||||||
|
2
|
||||||
|
3
|
||||||
|
}
|
||||||
|
|
||||||
|
qux = (x, y) -> new Listing {
|
||||||
|
x
|
||||||
|
y
|
||||||
|
}
|
||||||
|
|
||||||
|
forGen = new Listing {
|
||||||
|
for (
|
||||||
|
someVar in new Listing {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
[someVar] = someVar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
objParams: Listing<Int> = new {
|
||||||
|
default { key ->
|
||||||
|
key + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
objParams2: Listing<Int> = new {
|
||||||
|
default {
|
||||||
|
someVeryLongParameter1,
|
||||||
|
someVeryLongParameter2,
|
||||||
|
someVeryLongParameter3,
|
||||||
|
someVeryLongParameter4 ->
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parenType: (
|
||||||
|
ReaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongType(
|
||||||
|
reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalylongConstraint,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
functionType: (
|
||||||
|
ReaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongType,
|
||||||
|
ReaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongType2,
|
||||||
|
) ->
|
||||||
|
String
|
||||||
75
pkl-formatter/src/test/files/FormatterSnippetTests/output/line-breaks.pkl
vendored
Normal file
75
pkl-formatter/src/test/files/FormatterSnippetTests/output/line-breaks.pkl
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
module foo.bar.baz
|
||||||
|
|
||||||
|
amends "bar.pkl"
|
||||||
|
|
||||||
|
import "@foo/Foo.pkl" as foo
|
||||||
|
|
||||||
|
open local class Bar {}
|
||||||
|
|
||||||
|
local const baz = 10
|
||||||
|
|
||||||
|
local function fun(x) = x
|
||||||
|
|
||||||
|
local const prooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooperty: String =
|
||||||
|
"foo"
|
||||||
|
|
||||||
|
local function function2(
|
||||||
|
parameter1: Parameter1Type,
|
||||||
|
parameter2: Parameter2Type,
|
||||||
|
parameter3: Parameter3Type,
|
||||||
|
parameter4: Parameter4Type,
|
||||||
|
): String = ""
|
||||||
|
|
||||||
|
local const function function3(
|
||||||
|
parameter1: String | Int,
|
||||||
|
parameter2: String | Int,
|
||||||
|
): Mapping<String | Int, String> = new {}
|
||||||
|
|
||||||
|
prop =
|
||||||
|
function2(
|
||||||
|
loooooooooooooooooogParameter1,
|
||||||
|
loooooooooooooooooogParameter2,
|
||||||
|
loooooooooooooooooogParameter3,
|
||||||
|
loooooooooooooooooogParameter4,
|
||||||
|
)
|
||||||
|
|
||||||
|
prop2: String | Int | Boolean
|
||||||
|
|
||||||
|
funcParam =
|
||||||
|
fun((x, y) -> new Listing {
|
||||||
|
x
|
||||||
|
y
|
||||||
|
})
|
||||||
|
|
||||||
|
funcParam2 =
|
||||||
|
aFun(foo, 10 * 10, anotherVariable, if (true) 100000 else 200000, (param1, param2) ->
|
||||||
|
param1 * param2
|
||||||
|
)
|
||||||
|
|
||||||
|
funcParam3 = aFun(foo, 10 * 10, anotherVariable, 200000, (param1, param2) -> param1 * param2)
|
||||||
|
|
||||||
|
funcParam4 =
|
||||||
|
aFun(foo, 10 * 10, anotherVariable, if (true) 100000 else 200000, new Listing {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
})
|
||||||
|
|
||||||
|
open local class SomeReallyInterestingClassName<in SomeInParameter, out SomeOutParameter>
|
||||||
|
extends AnotherInterestingClassName {
|
||||||
|
foo: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
local function resourceMapping(type): Mapping<String, unknown> = new Mapping {
|
||||||
|
default = (key) -> (type) { metadata { name = key } }
|
||||||
|
}
|
||||||
|
|
||||||
|
local const function biiiiiiiiiiiiiiiiiiiiiiiiigFunction(
|
||||||
|
param1: String,
|
||||||
|
param2: String(!isBlank),
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
local const function someFunction(param1: String, param2: String(!isBlank)): Boolean
|
||||||
|
|
||||||
|
local function render(currentIndent: String) =
|
||||||
|
"\(currentIndent)@\(identifier.render(currentIndent))"
|
||||||
|
+ if (body == null) "" else " " + body.render(currentIndent)
|
||||||
42
pkl-formatter/src/test/files/FormatterSnippetTests/output/line-breaks2.pkl
vendored
Normal file
42
pkl-formatter/src/test/files/FormatterSnippetTests/output/line-breaks2.pkl
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
module
|
||||||
|
reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaly
|
||||||
|
.loooooooooooooooooooooooooooooooog
|
||||||
|
.naaaaaaaaaaaaaaaaaaaaaaaaaame
|
||||||
|
|
||||||
|
extends
|
||||||
|
"reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongModule.pkl"
|
||||||
|
|
||||||
|
import
|
||||||
|
"reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongModule.pkl"
|
||||||
|
as foo
|
||||||
|
|
||||||
|
open local class LoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongName {}
|
||||||
|
|
||||||
|
open local class ReaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongName {}
|
||||||
|
|
||||||
|
hidden const loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooogName =
|
||||||
|
99
|
||||||
|
|
||||||
|
hidden const reallyLoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooogName =
|
||||||
|
99
|
||||||
|
|
||||||
|
local const function looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooogName(
|
||||||
|
x: Int,
|
||||||
|
y,
|
||||||
|
) = x + y
|
||||||
|
|
||||||
|
typealias Foo =
|
||||||
|
LooooooooooooooooooooooooongTypeName
|
||||||
|
| AnotherLooooooooooooooooooooooooongTypeName
|
||||||
|
| OtherLooooooooooooooooooooooooongTypeName
|
||||||
|
|
||||||
|
bar: Boolean
|
||||||
|
| Mapping<
|
||||||
|
LooooooooooooooooooooooooooooongTypeName,
|
||||||
|
AnotherLooooooooooooooooooooooooooooongTypeName,
|
||||||
|
>(loooooooooooooooooooogConstraint)
|
||||||
|
|
||||||
|
hidden foobar: LongType(
|
||||||
|
someVeryyyyyyyloooong,
|
||||||
|
requirements.might.be.even_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooonger,
|
||||||
|
)
|
||||||
32
pkl-formatter/src/test/files/FormatterSnippetTests/output/line-width.pkl
vendored
Normal file
32
pkl-formatter/src/test/files/FormatterSnippetTests/output/line-width.pkl
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
module
|
||||||
|
reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaly
|
||||||
|
.loooooooooooooooooooooooooooooooog
|
||||||
|
.naaaaaaaaaaaaaaaaaaaaaaaaaaaaame
|
||||||
|
|
||||||
|
extends
|
||||||
|
"reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongModule.pkl"
|
||||||
|
|
||||||
|
local reallyLongVariableName = true
|
||||||
|
local anotherReallyLongName = 1
|
||||||
|
local evenLongerVariableName = 2
|
||||||
|
|
||||||
|
/// this property has a reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaly long doc comment
|
||||||
|
property =
|
||||||
|
if (reallyLongVariableName)
|
||||||
|
anotherReallyLongName
|
||||||
|
else
|
||||||
|
evenLongerVariableName + anotherReallyLongName
|
||||||
|
|
||||||
|
longString =
|
||||||
|
"reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaly long string"
|
||||||
|
|
||||||
|
shortProperty = 1
|
||||||
|
|
||||||
|
reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongVariableName =
|
||||||
|
10
|
||||||
|
|
||||||
|
reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalyLongVariableName2 =
|
||||||
|
if (reallyLongVariableName)
|
||||||
|
anotherReallyLongName
|
||||||
|
else
|
||||||
|
evenLongerVariableName + anotherReallyLongName
|
||||||
14
pkl-formatter/src/test/files/FormatterSnippetTests/output/map-function.pkl
vendored
Normal file
14
pkl-formatter/src/test/files/FormatterSnippetTests/output/map-function.pkl
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
foo =
|
||||||
|
Map(
|
||||||
|
1000, "some random string",
|
||||||
|
20000, "another random string",
|
||||||
|
30000, "yet another random string",
|
||||||
|
)
|
||||||
|
|
||||||
|
incorrect =
|
||||||
|
Map(
|
||||||
|
"This has", 1000000,
|
||||||
|
"an incorrect number", 2000000,
|
||||||
|
"of parameters", 30000000,
|
||||||
|
"passed to Map",
|
||||||
|
)
|
||||||
3
pkl-formatter/src/test/files/FormatterSnippetTests/output/modifiers.pkl
vendored
Normal file
3
pkl-formatter/src/test/files/FormatterSnippetTests/output/modifiers.pkl
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
hidden const foo = 1
|
||||||
|
|
||||||
|
open local class Foo {}
|
||||||
7
pkl-formatter/src/test/files/FormatterSnippetTests/output/module-definitions.pkl
vendored
Normal file
7
pkl-formatter/src/test/files/FormatterSnippetTests/output/module-definitions.pkl
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// comment
|
||||||
|
// one
|
||||||
|
|
||||||
|
/// doc comment
|
||||||
|
open module foo.bar
|
||||||
|
|
||||||
|
amends "baz.pkl"
|
||||||
33
pkl-formatter/src/test/files/FormatterSnippetTests/output/multi-line-strings.pkl
vendored
Normal file
33
pkl-formatter/src/test/files/FormatterSnippetTests/output/multi-line-strings.pkl
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
foo =
|
||||||
|
"""
|
||||||
|
asd \(new {
|
||||||
|
bar = 1
|
||||||
|
}) asd
|
||||||
|
"""
|
||||||
|
|
||||||
|
bar =
|
||||||
|
"""
|
||||||
|
line 1
|
||||||
|
line 2
|
||||||
|
line3
|
||||||
|
"""
|
||||||
|
|
||||||
|
baz =
|
||||||
|
"""
|
||||||
|
\n
|
||||||
|
\(bar)
|
||||||
|
line
|
||||||
|
\u{123}
|
||||||
|
"""
|
||||||
|
|
||||||
|
// remove unneeded spaces
|
||||||
|
qux =
|
||||||
|
"""
|
||||||
|
foo
|
||||||
|
|
||||||
|
bar
|
||||||
|
|
||||||
|
baz
|
||||||
|
|
||||||
|
\(foo)
|
||||||
|
"""
|
||||||
51
pkl-formatter/src/test/files/FormatterSnippetTests/output/object-members.pkl
vendored
Normal file
51
pkl-formatter/src/test/files/FormatterSnippetTests/output/object-members.pkl
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
foo: Listing<Int> = new { 1; 2; 3; 4; 5; 6; 7 }
|
||||||
|
|
||||||
|
bar: Listing<Int> = new {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
|
lineIsTooBig: Listing<Int> = new {
|
||||||
|
999999
|
||||||
|
1000000
|
||||||
|
1000001
|
||||||
|
1000002
|
||||||
|
1000003
|
||||||
|
1000004
|
||||||
|
1000005
|
||||||
|
1000006
|
||||||
|
1000007
|
||||||
|
1000008
|
||||||
|
1000009
|
||||||
|
}
|
||||||
|
|
||||||
|
lineIsTooBig2 {
|
||||||
|
999999
|
||||||
|
1000000
|
||||||
|
1000001
|
||||||
|
1000002
|
||||||
|
1000003
|
||||||
|
1000004
|
||||||
|
1000005
|
||||||
|
1000006
|
||||||
|
1000007
|
||||||
|
1000008
|
||||||
|
1000009
|
||||||
|
1000010
|
||||||
|
}
|
||||||
|
|
||||||
|
baz = new Dynamic {
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
|
||||||
|
["foo"] = 3
|
||||||
|
bar = 30
|
||||||
|
|
||||||
|
baz = true
|
||||||
|
}
|
||||||
|
|
||||||
|
qux = new Dynamic { 1; 2; 3; prop = "prop"; [0] = 9 }
|
||||||
11
pkl-formatter/src/test/files/FormatterSnippetTests/output/prefixes.pkl
vendored
Normal file
11
pkl-formatter/src/test/files/FormatterSnippetTests/output/prefixes.pkl
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/// Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong doc comment
|
||||||
|
@Annotation { looooooooooooooooooooooooooooooooooooooooooooooooooongVariableName = 10 }
|
||||||
|
typealias Foo = String
|
||||||
|
|
||||||
|
/// Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong doc comment
|
||||||
|
@Annotation { looooooooooooooooooooooooooooooooooooooooooooooooooongVariableName = 10 }
|
||||||
|
foo = "should fit in a single line"
|
||||||
|
|
||||||
|
/// Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong doc comment
|
||||||
|
@Annotation { looooooooooooooooooooooooooooooooooooooooooooooooooongVariableName = 10 }
|
||||||
|
function bar(x): String = "result: \(x)"
|
||||||
23
pkl-formatter/src/test/files/FormatterSnippetTests/output/spaces.pkl
vendored
Normal file
23
pkl-formatter/src/test/files/FormatterSnippetTests/output/spaces.pkl
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import "foo.pkl"
|
||||||
|
|
||||||
|
bar = new Listing<Int>(!isEmpty) { 1; 2 } //a bar
|
||||||
|
|
||||||
|
typealias Typealias = (String, Int) -> Boolean
|
||||||
|
|
||||||
|
/// a baz
|
||||||
|
/// returns its parameter
|
||||||
|
baz = (x, y, z) -> x + y + z
|
||||||
|
|
||||||
|
function fun(x: Int?, b: Boolean) = if (b) /***return x**/ x else x + bar[0]
|
||||||
|
|
||||||
|
prop = trace(1) + super.foo + module.foo
|
||||||
|
|
||||||
|
prop2 {
|
||||||
|
for (x in List(1)) {
|
||||||
|
when (x == 1) {
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
choices: "foo" | *"bar" | String
|
||||||
4
pkl-formatter/src/test/files/FormatterSnippetTests/output/type-aliases.pkl
vendored
Normal file
4
pkl-formatter/src/test/files/FormatterSnippetTests/output/type-aliases.pkl
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
typealias Foo = String
|
||||||
|
|
||||||
|
typealias VeryLoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongTypeAlias =
|
||||||
|
String(!isEmpty)
|
||||||
7
pkl-formatter/src/test/files/FormatterSnippetTests/output/when.pkl
vendored
Normal file
7
pkl-formatter/src/test/files/FormatterSnippetTests/output/when.pkl
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
foo {
|
||||||
|
when (true) {
|
||||||
|
bar = 1
|
||||||
|
} else {
|
||||||
|
bar = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.formatter
|
||||||
|
|
||||||
|
import org.junit.platform.commons.annotation.Testable
|
||||||
|
|
||||||
|
@Testable class FormatterSnippetTests
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.formatter
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.isRegularFile
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import org.pkl.commons.test.InputOutputTestEngine
|
||||||
|
import org.pkl.parser.ParserError
|
||||||
|
|
||||||
|
abstract class AbstractFormatterSnippetTestsEngine : InputOutputTestEngine() {
|
||||||
|
|
||||||
|
private val snippetsDir: Path =
|
||||||
|
rootProjectDir.resolve("pkl-formatter/src/test/files/FormatterSnippetTests")
|
||||||
|
|
||||||
|
private val expectedOutputDir: Path = snippetsDir.resolve("output")
|
||||||
|
|
||||||
|
/** Convenience for development; this selects which snippet test(s) to run. */
|
||||||
|
// language=regexp
|
||||||
|
internal val selection: String = ""
|
||||||
|
|
||||||
|
override val includedTests: List<Regex> = listOf(Regex(".*$selection\\.pkl"))
|
||||||
|
|
||||||
|
override val inputDir: Path = snippetsDir.resolve("input")
|
||||||
|
|
||||||
|
override val isInputFile: (Path) -> Boolean = { it.isRegularFile() }
|
||||||
|
|
||||||
|
override fun expectedOutputFileFor(inputFile: Path): Path {
|
||||||
|
val relativePath = relativize(inputFile, inputDir).toString()
|
||||||
|
return expectedOutputDir.resolve(relativePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun relativize(path: Path, base: Path): Path {
|
||||||
|
if (System.getProperty("os.name").contains("Windows")) {
|
||||||
|
if (path.isAbsolute && base.isAbsolute && (path.root != base.root)) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base.relativize(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FormatterSnippetTestsEngine : AbstractFormatterSnippetTestsEngine() {
|
||||||
|
override val testClass: KClass<*> = FormatterSnippetTests::class
|
||||||
|
|
||||||
|
override fun generateOutputFor(inputFile: Path): Pair<Boolean, String> {
|
||||||
|
val formatter = Formatter()
|
||||||
|
val (success, output) =
|
||||||
|
try {
|
||||||
|
val res = formatter.format(inputFile)
|
||||||
|
true to res
|
||||||
|
} catch (_: ParserError) {
|
||||||
|
false to ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return success to output
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.formatter
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.pkl.parser.GenericParserError
|
||||||
|
|
||||||
|
class FormatterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `multiline string - wrong start quote`() {
|
||||||
|
val ex =
|
||||||
|
assertThrows<GenericParserError> {
|
||||||
|
format(
|
||||||
|
"""
|
||||||
|
foo = ""${'"'}line1
|
||||||
|
line 2
|
||||||
|
""${'"'}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(ex.message).contains("The content of a multi-line string must begin on a new line")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `multiline string - wrong end quote`() {
|
||||||
|
val ex =
|
||||||
|
assertThrows<GenericParserError> {
|
||||||
|
format(
|
||||||
|
"""
|
||||||
|
foo = ""${'"'}
|
||||||
|
line1
|
||||||
|
line 2""${'"'}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(ex.message)
|
||||||
|
.contains("The closing delimiter of a multi-line string must begin on a new line")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `multiline string - wrong indentation`() {
|
||||||
|
val ex =
|
||||||
|
assertThrows<GenericParserError> {
|
||||||
|
format(
|
||||||
|
"""
|
||||||
|
foo = ""${'"'}
|
||||||
|
line1
|
||||||
|
line 2
|
||||||
|
""${'"'}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(ex.message)
|
||||||
|
.contains("Line must match or exceed indentation of the String's last line.")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun format(code: String): String {
|
||||||
|
return Formatter().format(code.trimIndent())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.pkl.formatter.FormatterSnippetTestsEngine
|
||||||
1574
pkl-parser/src/main/java/org/pkl/parser/GenericParser.java
Normal file
1574
pkl-parser/src/main/java/org/pkl/parser/GenericParser.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.parser;
|
||||||
|
|
||||||
|
import org.pkl.parser.syntax.generic.FullSpan;
|
||||||
|
|
||||||
|
public class GenericParserError extends RuntimeException {
|
||||||
|
private final FullSpan span;
|
||||||
|
|
||||||
|
public GenericParserError(String msg, FullSpan span) {
|
||||||
|
super(msg);
|
||||||
|
this.span = span;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FullSpan getSpan() {
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getMessage() + " at " + span;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ package org.pkl.parser;
|
|||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
|
import org.pkl.parser.syntax.generic.FullSpan;
|
||||||
import org.pkl.parser.util.ErrorMessages;
|
import org.pkl.parser.util.ErrorMessages;
|
||||||
|
|
||||||
public class Lexer {
|
public class Lexer {
|
||||||
@@ -25,6 +26,10 @@ public class Lexer {
|
|||||||
private final int size;
|
private final int size;
|
||||||
protected int cursor = 0;
|
protected int cursor = 0;
|
||||||
protected int sCursor = 0;
|
protected int sCursor = 0;
|
||||||
|
private int line = 1;
|
||||||
|
private int sLine = 1;
|
||||||
|
private int col = 1;
|
||||||
|
private int sCol = 1;
|
||||||
private char lookahead;
|
private char lookahead;
|
||||||
private State state = State.DEFAULT;
|
private State state = State.DEFAULT;
|
||||||
private final Deque<InterpolationScope> interpolationStack = new ArrayDeque<>();
|
private final Deque<InterpolationScope> interpolationStack = new ArrayDeque<>();
|
||||||
@@ -50,6 +55,11 @@ public class Lexer {
|
|||||||
return new Span(sCursor, cursor - sCursor);
|
return new Span(sCursor, cursor - sCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The full span of the last lexed token
|
||||||
|
public FullSpan fullSpan() {
|
||||||
|
return new FullSpan(sCursor, cursor - sCursor, sLine, sCol, line, col);
|
||||||
|
}
|
||||||
|
|
||||||
// The text of the last lexed token
|
// The text of the last lexed token
|
||||||
public String text() {
|
public String text() {
|
||||||
return new String(source, sCursor, cursor - sCursor);
|
return new String(source, sCursor, cursor - sCursor);
|
||||||
@@ -65,6 +75,8 @@ public class Lexer {
|
|||||||
|
|
||||||
public Token next() {
|
public Token next() {
|
||||||
sCursor = cursor;
|
sCursor = cursor;
|
||||||
|
sLine = line;
|
||||||
|
sCol = col;
|
||||||
newLinesBetween = 0;
|
newLinesBetween = 0;
|
||||||
return switch (state) {
|
return switch (state) {
|
||||||
case DEFAULT -> nextDefault();
|
case DEFAULT -> nextDefault();
|
||||||
@@ -79,7 +91,9 @@ public class Lexer {
|
|||||||
sCursor = cursor;
|
sCursor = cursor;
|
||||||
if (ch == '\n') {
|
if (ch == '\n') {
|
||||||
newLinesBetween++;
|
newLinesBetween++;
|
||||||
|
sLine = line;
|
||||||
}
|
}
|
||||||
|
sCol = col;
|
||||||
ch = nextChar();
|
ch = nextChar();
|
||||||
}
|
}
|
||||||
return switch (ch) {
|
return switch (ch) {
|
||||||
@@ -678,15 +692,23 @@ public class Lexer {
|
|||||||
} else {
|
} else {
|
||||||
lookahead = source[cursor];
|
lookahead = source[cursor];
|
||||||
}
|
}
|
||||||
|
if (tmp == '\n') {
|
||||||
|
line++;
|
||||||
|
col = 1;
|
||||||
|
} else {
|
||||||
|
col++;
|
||||||
|
}
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void backup() {
|
private void backup() {
|
||||||
lookahead = source[--cursor];
|
lookahead = source[--cursor];
|
||||||
|
col--;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void backup(int amount) {
|
private void backup(int amount) {
|
||||||
cursor -= amount;
|
cursor -= amount;
|
||||||
|
col -= amount;
|
||||||
lookahead = source[cursor];
|
lookahead = source[cursor];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package org.pkl.parser;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import org.pkl.parser.syntax.Annotation;
|
import org.pkl.parser.syntax.Annotation;
|
||||||
@@ -1809,16 +1808,13 @@ public class Parser {
|
|||||||
private FullToken forceNext() {
|
private FullToken forceNext() {
|
||||||
var tk = lexer.next();
|
var tk = lexer.next();
|
||||||
precededBySemicolon = false;
|
precededBySemicolon = false;
|
||||||
while (AFFIXES.contains(tk)) {
|
while (tk.isAffix()) {
|
||||||
precededBySemicolon = precededBySemicolon || tk == Token.SEMICOLON;
|
precededBySemicolon = precededBySemicolon || tk == Token.SEMICOLON;
|
||||||
tk = lexer.next();
|
tk = lexer.next();
|
||||||
}
|
}
|
||||||
return new FullToken(tk, lexer.span(), lexer.newLinesBetween);
|
return new FullToken(tk, lexer.span(), lexer.newLinesBetween);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final EnumSet<Token> AFFIXES =
|
|
||||||
EnumSet.of(Token.LINE_COMMENT, Token.BLOCK_COMMENT, Token.SEMICOLON);
|
|
||||||
|
|
||||||
// Like next, but don't ignore comments
|
// Like next, but don't ignore comments
|
||||||
private FullToken nextComment() {
|
private FullToken nextComment() {
|
||||||
prev = _lookahead;
|
prev = _lookahead;
|
||||||
|
|||||||
@@ -191,6 +191,13 @@ public enum Token {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAffix() {
|
||||||
|
return switch (this) {
|
||||||
|
case LINE_COMMENT, BLOCK_COMMENT, SEMICOLON -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public String text() {
|
public String text() {
|
||||||
if (this == UNDERSCORE) {
|
if (this == UNDERSCORE) {
|
||||||
return "_";
|
return "_";
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ public enum Operator {
|
|||||||
INT_DIV(9, true),
|
INT_DIV(9, true),
|
||||||
MOD(9, true),
|
MOD(9, true),
|
||||||
POW(10, false),
|
POW(10, false),
|
||||||
DOT(11, true),
|
NON_NULL(16, true),
|
||||||
QDOT(11, true);
|
SUBSCRIPT(18, true),
|
||||||
|
DOT(20, true),
|
||||||
|
QDOT(20, true);
|
||||||
|
|
||||||
private final int prec;
|
private final int prec;
|
||||||
private final boolean isLeftAssoc;
|
private final boolean isLeftAssoc;
|
||||||
@@ -53,4 +55,33 @@ public enum Operator {
|
|||||||
public boolean isLeftAssoc() {
|
public boolean isLeftAssoc() {
|
||||||
return isLeftAssoc;
|
return isLeftAssoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Operator byName(String name) {
|
||||||
|
return switch (name) {
|
||||||
|
case "??" -> NULL_COALESCE;
|
||||||
|
case "|>" -> PIPE;
|
||||||
|
case "||" -> OR;
|
||||||
|
case "&&" -> AND;
|
||||||
|
case "==" -> EQ_EQ;
|
||||||
|
case "!=" -> NOT_EQ;
|
||||||
|
case "is" -> IS;
|
||||||
|
case "as" -> AS;
|
||||||
|
case "<" -> LT;
|
||||||
|
case "<=" -> LTE;
|
||||||
|
case ">" -> GT;
|
||||||
|
case ">=" -> GTE;
|
||||||
|
case "+" -> PLUS;
|
||||||
|
case "-" -> MINUS;
|
||||||
|
case "*" -> MULT;
|
||||||
|
case "/" -> DIV;
|
||||||
|
case "~/" -> INT_DIV;
|
||||||
|
case "%" -> MOD;
|
||||||
|
case "**" -> POW;
|
||||||
|
case "!!" -> NON_NULL;
|
||||||
|
case "[" -> SUBSCRIPT;
|
||||||
|
case "." -> DOT;
|
||||||
|
case "?." -> QDOT;
|
||||||
|
default -> throw new RuntimeException("Unknown operator: " + name);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.parser.syntax.generic;
|
||||||
|
|
||||||
|
import org.pkl.parser.Span;
|
||||||
|
|
||||||
|
public record FullSpan(
|
||||||
|
int charIndex, int length, int lineBegin, int colBegin, int lineEnd, int colEnd) {
|
||||||
|
|
||||||
|
public FullSpan endWith(FullSpan end) {
|
||||||
|
return new FullSpan(
|
||||||
|
charIndex,
|
||||||
|
end.charIndex - charIndex + end.length,
|
||||||
|
lineBegin,
|
||||||
|
colBegin,
|
||||||
|
end.lineEnd,
|
||||||
|
end.colEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Span toSpan() {
|
||||||
|
return new Span(charIndex, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean sameLine(FullSpan other) {
|
||||||
|
return lineEnd == other.lineBegin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FullSpan stopSpan() {
|
||||||
|
return new FullSpan(charIndex + length - 1, 1, lineEnd, colEnd, lineEnd, colEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(%d:%d - %d:%d)".formatted(lineBegin, colBegin, lineEnd, colEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.parser.syntax.generic;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.pkl.parser.util.Nullable;
|
||||||
|
|
||||||
|
public class Node {
|
||||||
|
public final List<Node> children;
|
||||||
|
public final FullSpan span;
|
||||||
|
public final NodeType type;
|
||||||
|
private @Nullable String text;
|
||||||
|
|
||||||
|
public Node(NodeType type, FullSpan span) {
|
||||||
|
this(type, span, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node(NodeType type, FullSpan span, List<Node> children) {
|
||||||
|
this.type = type;
|
||||||
|
this.span = span;
|
||||||
|
this.children = Collections.unmodifiableList(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node(NodeType type, List<Node> children) {
|
||||||
|
this.type = type;
|
||||||
|
if (children.isEmpty()) throw new RuntimeException("No children or span given for node");
|
||||||
|
var end = children.get(children.size() - 1).span;
|
||||||
|
this.span = children.get(0).span.endWith(end);
|
||||||
|
this.children = Collections.unmodifiableList(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String text(char[] source) {
|
||||||
|
if (text == null) {
|
||||||
|
text = new String(source, span.charIndex(), span.length());
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the first child of type {@code type} or {@code null}. */
|
||||||
|
public @Nullable Node findChildByType(NodeType type) {
|
||||||
|
for (var child : children) {
|
||||||
|
if (child.type == type) return child;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Node node = (Node) o;
|
||||||
|
return Objects.equals(children, node.children)
|
||||||
|
&& Objects.equals(span, node.span)
|
||||||
|
&& Objects.equals(type, node.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(children, span, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Node{type='" + type + "', span=" + span + ", children=" + children + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.parser.syntax.generic;
|
||||||
|
|
||||||
|
public enum NodeType {
|
||||||
|
TERMINAL,
|
||||||
|
SHEBANG,
|
||||||
|
// affixes,
|
||||||
|
LINE_COMMENT(NodeKind.AFFIX),
|
||||||
|
BLOCK_COMMENT(NodeKind.AFFIX),
|
||||||
|
SEMICOLON(NodeKind.AFFIX),
|
||||||
|
|
||||||
|
MODULE,
|
||||||
|
DOC_COMMENT,
|
||||||
|
DOC_COMMENT_LINE,
|
||||||
|
MODIFIER,
|
||||||
|
MODIFIER_LIST,
|
||||||
|
AMENDS_CLAUSE,
|
||||||
|
EXTENDS_CLAUSE,
|
||||||
|
MODULE_DECLARATION,
|
||||||
|
MODULE_DEFINITION,
|
||||||
|
ANNOTATION,
|
||||||
|
IDENTIFIER,
|
||||||
|
QUALIFIED_IDENTIFIER,
|
||||||
|
IMPORT,
|
||||||
|
IMPORT_ALIAS,
|
||||||
|
IMPORT_LIST,
|
||||||
|
TYPEALIAS,
|
||||||
|
TYPEALIAS_HEADER,
|
||||||
|
TYPEALIAS_BODY,
|
||||||
|
CLASS,
|
||||||
|
CLASS_HEADER,
|
||||||
|
CLASS_HEADER_EXTENDS,
|
||||||
|
CLASS_BODY,
|
||||||
|
CLASS_BODY_ELEMENTS,
|
||||||
|
CLASS_METHOD,
|
||||||
|
CLASS_METHOD_HEADER,
|
||||||
|
CLASS_METHOD_BODY,
|
||||||
|
CLASS_PROPERTY,
|
||||||
|
CLASS_PROPERTY_HEADER,
|
||||||
|
CLASS_PROPERTY_HEADER_BEGIN,
|
||||||
|
CLASS_PROPERTY_BODY,
|
||||||
|
OBJECT_BODY,
|
||||||
|
OBJECT_MEMBER_LIST,
|
||||||
|
PARAMETER,
|
||||||
|
TYPE_ANNOTATION,
|
||||||
|
PARAMETER_LIST,
|
||||||
|
PARAMETER_LIST_ELEMENTS,
|
||||||
|
TYPE_PARAMETER_LIST,
|
||||||
|
TYPE_PARAMETER_LIST_ELEMENTS,
|
||||||
|
ARGUMENT_LIST,
|
||||||
|
ARGUMENT_LIST_ELEMENTS,
|
||||||
|
TYPE_ARGUMENT_LIST,
|
||||||
|
TYPE_ARGUMENT_LIST_ELEMENTS,
|
||||||
|
OBJECT_PARAMETER_LIST,
|
||||||
|
TYPE_PARAMETER,
|
||||||
|
STRING_CONSTANT,
|
||||||
|
OPERATOR,
|
||||||
|
STRING_NEWLINE,
|
||||||
|
STRING_ESCAPE,
|
||||||
|
|
||||||
|
// members
|
||||||
|
OBJECT_ELEMENT,
|
||||||
|
OBJECT_PROPERTY,
|
||||||
|
OBJECT_PROPERTY_HEADER,
|
||||||
|
OBJECT_PROPERTY_HEADER_BEGIN,
|
||||||
|
OBJECT_PROPERTY_BODY,
|
||||||
|
OBJECT_METHOD,
|
||||||
|
MEMBER_PREDICATE,
|
||||||
|
OBJECT_ENTRY,
|
||||||
|
OBJECT_ENTRY_HEADER,
|
||||||
|
OBJECT_SPREAD,
|
||||||
|
WHEN_GENERATOR,
|
||||||
|
WHEN_GENERATOR_HEADER,
|
||||||
|
FOR_GENERATOR,
|
||||||
|
FOR_GENERATOR_HEADER,
|
||||||
|
FOR_GENERATOR_HEADER_DEFINITION,
|
||||||
|
FOR_GENERATOR_HEADER_DEFINITION_HEADER,
|
||||||
|
|
||||||
|
// expressions
|
||||||
|
THIS_EXPR(NodeKind.EXPR),
|
||||||
|
OUTER_EXPR(NodeKind.EXPR),
|
||||||
|
MODULE_EXPR(NodeKind.EXPR),
|
||||||
|
NULL_EXPR(NodeKind.EXPR),
|
||||||
|
THROW_EXPR(NodeKind.EXPR),
|
||||||
|
TRACE_EXPR(NodeKind.EXPR),
|
||||||
|
IMPORT_EXPR(NodeKind.EXPR),
|
||||||
|
READ_EXPR(NodeKind.EXPR),
|
||||||
|
NEW_EXPR(NodeKind.EXPR),
|
||||||
|
NEW_HEADER,
|
||||||
|
UNARY_MINUS_EXPR(NodeKind.EXPR),
|
||||||
|
LOGICAL_NOT_EXPR(NodeKind.EXPR),
|
||||||
|
FUNCTION_LITERAL_EXPR(NodeKind.EXPR),
|
||||||
|
FUNCTION_LITERAL_BODY,
|
||||||
|
PARENTHESIZED_EXPR(NodeKind.EXPR),
|
||||||
|
PARENTHESIZED_EXPR_ELEMENTS,
|
||||||
|
SUPER_SUBSCRIPT_EXPR(NodeKind.EXPR),
|
||||||
|
SUPER_ACCESS_EXPR(NodeKind.EXPR),
|
||||||
|
SUBSCRIPT_EXPR(NodeKind.EXPR),
|
||||||
|
IF_EXPR(NodeKind.EXPR),
|
||||||
|
IF_HEADER,
|
||||||
|
IF_CONDITION,
|
||||||
|
IF_CONDITION_EXPR,
|
||||||
|
IF_THEN_EXPR,
|
||||||
|
IF_ELSE_EXPR,
|
||||||
|
LET_EXPR(NodeKind.EXPR),
|
||||||
|
LET_PARAMETER_DEFINITION,
|
||||||
|
LET_PARAMETER,
|
||||||
|
BOOL_LITERAL_EXPR(NodeKind.EXPR),
|
||||||
|
INT_LITERAL_EXPR(NodeKind.EXPR),
|
||||||
|
FLOAT_LITERAL_EXPR(NodeKind.EXPR),
|
||||||
|
SINGLE_LINE_STRING_LITERAL_EXPR(NodeKind.EXPR),
|
||||||
|
MULTI_LINE_STRING_LITERAL_EXPR(NodeKind.EXPR),
|
||||||
|
UNQUALIFIED_ACCESS_EXPR(NodeKind.EXPR),
|
||||||
|
NON_NULL_EXPR(NodeKind.EXPR),
|
||||||
|
AMENDS_EXPR(NodeKind.EXPR),
|
||||||
|
BINARY_OP_EXPR(NodeKind.EXPR),
|
||||||
|
|
||||||
|
// types
|
||||||
|
UNKNOWN_TYPE(NodeKind.TYPE),
|
||||||
|
NOTHING_TYPE(NodeKind.TYPE),
|
||||||
|
MODULE_TYPE(NodeKind.TYPE),
|
||||||
|
UNION_TYPE(NodeKind.TYPE),
|
||||||
|
FUNCTION_TYPE(NodeKind.TYPE),
|
||||||
|
FUNCTION_TYPE_PARAMETERS,
|
||||||
|
PARENTHESIZED_TYPE(NodeKind.TYPE),
|
||||||
|
PARENTHESIZED_TYPE_ELEMENTS,
|
||||||
|
DECLARED_TYPE(NodeKind.TYPE),
|
||||||
|
NULLABLE_TYPE(NodeKind.TYPE),
|
||||||
|
STRING_CONSTANT_TYPE(NodeKind.TYPE),
|
||||||
|
CONSTRAINED_TYPE(NodeKind.TYPE),
|
||||||
|
CONSTRAINED_TYPE_CONSTRAINT,
|
||||||
|
CONSTRAINED_TYPE_ELEMENTS;
|
||||||
|
|
||||||
|
private final NodeKind kind;
|
||||||
|
|
||||||
|
NodeType() {
|
||||||
|
this.kind = NodeKind.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeType(NodeKind kind) {
|
||||||
|
this.kind = kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAffix() {
|
||||||
|
return kind == NodeKind.AFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpression() {
|
||||||
|
return kind == NodeKind.EXPR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isType() {
|
||||||
|
return kind == NodeKind.TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum NodeKind {
|
||||||
|
TYPE,
|
||||||
|
EXPR,
|
||||||
|
AFFIX,
|
||||||
|
NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@NonnullByDefault
|
||||||
|
package org.pkl.parser.syntax.generic;
|
||||||
|
|
||||||
|
import org.pkl.parser.util.NonnullByDefault;
|
||||||
261
pkl-parser/src/test/kotlin/org/pkl/parser/GenericSexpRenderer.kt
Normal file
261
pkl-parser/src/test/kotlin/org/pkl/parser/GenericSexpRenderer.kt
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.parser
|
||||||
|
|
||||||
|
import java.util.EnumSet
|
||||||
|
import org.pkl.parser.syntax.generic.Node
|
||||||
|
import org.pkl.parser.syntax.generic.NodeType
|
||||||
|
|
||||||
|
class GenericSexpRenderer(code: String) {
|
||||||
|
private var tab = ""
|
||||||
|
private var buf = StringBuilder()
|
||||||
|
private val source = code.toCharArray()
|
||||||
|
|
||||||
|
fun render(node: Node): String {
|
||||||
|
innerRender(node)
|
||||||
|
return buf.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun innerRender(node: Node) {
|
||||||
|
if (node.type == NodeType.UNION_TYPE) {
|
||||||
|
renderUnionType(node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node.type == NodeType.BINARY_OP_EXPR && binopName(node).endsWith("ualifiedAccessExpr")) {
|
||||||
|
renderQualifiedAccess(node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
doRender(name(node), collectChildren(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doRender(name: String, children: List<Node>) {
|
||||||
|
buf.append(tab)
|
||||||
|
buf.append("(")
|
||||||
|
buf.append(name)
|
||||||
|
val oldTab = increaseTab()
|
||||||
|
for (child in children) {
|
||||||
|
buf.append('\n')
|
||||||
|
innerRender(child)
|
||||||
|
}
|
||||||
|
tab = oldTab
|
||||||
|
buf.append(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderUnionType(node: Node) {
|
||||||
|
buf.append(tab)
|
||||||
|
buf.append("(")
|
||||||
|
buf.append(name(node))
|
||||||
|
val oldTab = increaseTab()
|
||||||
|
var previousTerminal: Node? = null
|
||||||
|
for (child in node.children) {
|
||||||
|
if (child.type == NodeType.TERMINAL) previousTerminal = child
|
||||||
|
if (child.type in IGNORED_CHILDREN) continue
|
||||||
|
buf.append('\n')
|
||||||
|
if (previousTerminal != null && previousTerminal.text(source) == "*") {
|
||||||
|
previousTerminal = null
|
||||||
|
renderDefaultUnionType(child)
|
||||||
|
} else {
|
||||||
|
innerRender(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tab = oldTab
|
||||||
|
buf.append(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderQualifiedAccess(node: Node) {
|
||||||
|
var children = node.children
|
||||||
|
if (children.last().type == NodeType.UNQUALIFIED_ACCESS_EXPR) {
|
||||||
|
children = children.dropLast(1) + collectChildren(children.last())
|
||||||
|
}
|
||||||
|
val toRender = mutableListOf<Node>()
|
||||||
|
for (child in children) {
|
||||||
|
if (child.type in IGNORED_CHILDREN || child.type == NodeType.OPERATOR) continue
|
||||||
|
toRender += child
|
||||||
|
}
|
||||||
|
doRender(name(node), toRender)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderDefaultUnionType(node: Node) {
|
||||||
|
buf.append(tab)
|
||||||
|
buf.append("(defaultUnionType\n")
|
||||||
|
val oldTab = increaseTab()
|
||||||
|
innerRender(node)
|
||||||
|
tab = oldTab
|
||||||
|
buf.append(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectChildren(node: Node): List<Node> =
|
||||||
|
when (node.type) {
|
||||||
|
NodeType.MULTI_LINE_STRING_LITERAL_EXPR ->
|
||||||
|
node.children.filter { it.type !in IGNORED_CHILDREN && !it.type.isStringData() }
|
||||||
|
NodeType.SINGLE_LINE_STRING_LITERAL_EXPR -> {
|
||||||
|
val children = node.children.filter { it.type !in IGNORED_CHILDREN }
|
||||||
|
val res = mutableListOf<Node>()
|
||||||
|
var prev: Node? = null
|
||||||
|
for (child in children) {
|
||||||
|
val inARow = child.type.isStringData() && (prev != null && prev.type.isStringData())
|
||||||
|
if (!inARow) {
|
||||||
|
res += child
|
||||||
|
}
|
||||||
|
prev = child
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
NodeType.DOC_COMMENT -> listOf()
|
||||||
|
else -> {
|
||||||
|
val nodes = mutableListOf<Node>()
|
||||||
|
for (child in node.children) {
|
||||||
|
if (child.type in IGNORED_CHILDREN) continue
|
||||||
|
if (child.type in UNPACK_CHILDREN) {
|
||||||
|
nodes += collectChildren(child)
|
||||||
|
} else {
|
||||||
|
nodes += child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NodeType.isStringData(): Boolean =
|
||||||
|
this == NodeType.STRING_CONSTANT || this == NodeType.STRING_ESCAPE
|
||||||
|
|
||||||
|
private fun name(node: Node): String =
|
||||||
|
when (node.type) {
|
||||||
|
NodeType.MODULE_DECLARATION -> "moduleHeader"
|
||||||
|
NodeType.IMPORT -> importName(node, isExpr = false)
|
||||||
|
NodeType.IMPORT_EXPR -> importName(node, isExpr = true)
|
||||||
|
NodeType.BINARY_OP_EXPR -> binopName(node)
|
||||||
|
NodeType.CLASS -> "clazz"
|
||||||
|
NodeType.EXTENDS_CLAUSE,
|
||||||
|
NodeType.AMENDS_CLAUSE -> "extendsOrAmendsClause"
|
||||||
|
NodeType.TYPEALIAS -> "typeAlias"
|
||||||
|
NodeType.STRING_ESCAPE -> "stringConstant"
|
||||||
|
NodeType.READ_EXPR -> {
|
||||||
|
val terminal = node.children.find { it.type == NodeType.TERMINAL }!!.text(source)
|
||||||
|
when (terminal) {
|
||||||
|
"read*" -> "readGlobExpr"
|
||||||
|
"read?" -> "readNullExpr"
|
||||||
|
else -> "readExpr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val names = node.type.name.split('_').map { it.lowercase() }
|
||||||
|
if (names.size > 1) {
|
||||||
|
val capitalized = names.drop(1).map { n -> n.replaceFirstChar { it.titlecase() } }
|
||||||
|
(listOf(names[0]) + capitalized).joinToString("")
|
||||||
|
} else names[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importName(node: Node, isExpr: Boolean): String {
|
||||||
|
val terminal = node.children.find { it.type == NodeType.TERMINAL }!!.text(source)
|
||||||
|
val suffix = if (isExpr) "Expr" else "Clause"
|
||||||
|
return if (terminal == "import*") "importGlob$suffix" else "import$suffix"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun binopName(node: Node): String {
|
||||||
|
val op = node.children.find { it.type == NodeType.OPERATOR }!!.text(source)
|
||||||
|
return when (op) {
|
||||||
|
"**" -> "exponentiationExpr"
|
||||||
|
"*",
|
||||||
|
"/",
|
||||||
|
"~/",
|
||||||
|
"%" -> "multiplicativeExpr"
|
||||||
|
"+",
|
||||||
|
"-" -> "additiveExpr"
|
||||||
|
">",
|
||||||
|
">=",
|
||||||
|
"<",
|
||||||
|
"<=" -> "comparisonExpr"
|
||||||
|
"is" -> "typeCheckExpr"
|
||||||
|
"as" -> "typeCastExpr"
|
||||||
|
"==",
|
||||||
|
"!=" -> "equalityExpr"
|
||||||
|
"&&" -> "logicalAndExpr"
|
||||||
|
"||" -> "logicalOrExpr"
|
||||||
|
"|>" -> "pipeExpr"
|
||||||
|
"??" -> "nullCoalesceExpr"
|
||||||
|
"." -> "qualifiedAccessExpr"
|
||||||
|
"?." -> "nullableQualifiedAccessExpr"
|
||||||
|
else -> throw RuntimeException("Unknown operator: $op")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun increaseTab(): String {
|
||||||
|
val old = tab
|
||||||
|
tab += " "
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val IGNORED_CHILDREN =
|
||||||
|
EnumSet.of(
|
||||||
|
NodeType.LINE_COMMENT,
|
||||||
|
NodeType.BLOCK_COMMENT,
|
||||||
|
NodeType.SHEBANG,
|
||||||
|
NodeType.SEMICOLON,
|
||||||
|
NodeType.TERMINAL,
|
||||||
|
NodeType.OPERATOR,
|
||||||
|
NodeType.STRING_NEWLINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val UNPACK_CHILDREN =
|
||||||
|
EnumSet.of(
|
||||||
|
NodeType.MODULE_DEFINITION,
|
||||||
|
NodeType.IMPORT_LIST,
|
||||||
|
NodeType.IMPORT_ALIAS,
|
||||||
|
NodeType.TYPEALIAS_HEADER,
|
||||||
|
NodeType.TYPEALIAS_BODY,
|
||||||
|
NodeType.CLASS_PROPERTY_HEADER,
|
||||||
|
NodeType.CLASS_PROPERTY_HEADER_BEGIN,
|
||||||
|
NodeType.CLASS_PROPERTY_BODY,
|
||||||
|
NodeType.CLASS_METHOD_HEADER,
|
||||||
|
NodeType.CLASS_METHOD_BODY,
|
||||||
|
NodeType.CLASS_HEADER,
|
||||||
|
NodeType.CLASS_HEADER_EXTENDS,
|
||||||
|
NodeType.CLASS_BODY_ELEMENTS,
|
||||||
|
NodeType.MODIFIER_LIST,
|
||||||
|
NodeType.NEW_HEADER,
|
||||||
|
NodeType.OBJECT_MEMBER_LIST,
|
||||||
|
NodeType.OBJECT_ENTRY_HEADER,
|
||||||
|
NodeType.OBJECT_PROPERTY_HEADER,
|
||||||
|
NodeType.OBJECT_PROPERTY_HEADER_BEGIN,
|
||||||
|
NodeType.OBJECT_PROPERTY_BODY,
|
||||||
|
NodeType.OBJECT_PARAMETER_LIST,
|
||||||
|
NodeType.FOR_GENERATOR_HEADER,
|
||||||
|
NodeType.FOR_GENERATOR_HEADER_DEFINITION,
|
||||||
|
NodeType.FOR_GENERATOR_HEADER_DEFINITION_HEADER,
|
||||||
|
NodeType.WHEN_GENERATOR_HEADER,
|
||||||
|
NodeType.IF_HEADER,
|
||||||
|
NodeType.IF_CONDITION,
|
||||||
|
NodeType.IF_CONDITION_EXPR,
|
||||||
|
NodeType.IF_THEN_EXPR,
|
||||||
|
NodeType.IF_ELSE_EXPR,
|
||||||
|
NodeType.FUNCTION_LITERAL_BODY,
|
||||||
|
NodeType.ARGUMENT_LIST_ELEMENTS,
|
||||||
|
NodeType.PARAMETER_LIST_ELEMENTS,
|
||||||
|
NodeType.CONSTRAINED_TYPE_CONSTRAINT,
|
||||||
|
NodeType.CONSTRAINED_TYPE_ELEMENTS,
|
||||||
|
NodeType.TYPE_PARAMETER_LIST_ELEMENTS,
|
||||||
|
NodeType.TYPE_ARGUMENT_LIST_ELEMENTS,
|
||||||
|
NodeType.LET_PARAMETER_DEFINITION,
|
||||||
|
NodeType.LET_PARAMETER,
|
||||||
|
NodeType.PARENTHESIZED_EXPR_ELEMENTS,
|
||||||
|
NodeType.PARENTHESIZED_TYPE_ELEMENTS,
|
||||||
|
NodeType.FUNCTION_TYPE_PARAMETERS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.parser
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
import kotlin.io.path.extension
|
||||||
|
import kotlin.io.path.pathString
|
||||||
|
import kotlin.io.path.readText
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.assertj.core.api.SoftAssertions
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.parallel.Execution
|
||||||
|
import org.junit.jupiter.api.parallel.ExecutionMode
|
||||||
|
import org.pkl.commons.walk
|
||||||
|
|
||||||
|
@Execution(ExecutionMode.CONCURRENT)
|
||||||
|
class ParserComparisonTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compareSnippetTests() {
|
||||||
|
SoftAssertions.assertSoftly { softly ->
|
||||||
|
getSnippets()
|
||||||
|
.parallelStream()
|
||||||
|
.map { Pair(it.pathString, it.readText()) }
|
||||||
|
.forEach { (path, snippet) ->
|
||||||
|
try {
|
||||||
|
compare(snippet, path, softly)
|
||||||
|
} catch (e: GenericParserError) {
|
||||||
|
softly.fail("path: $path. Message: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSnippets(): List<Path> {
|
||||||
|
return Path("../pkl-core/src/test/files/LanguageSnippetTests/input")
|
||||||
|
.walk()
|
||||||
|
.filter { path ->
|
||||||
|
val pathStr = path.toString().replace("\\", "/")
|
||||||
|
path.extension == "pkl" &&
|
||||||
|
!exceptions.any { pathStr.endsWith(it) } &&
|
||||||
|
!regexExceptions.any { it.matches(pathStr) }
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compare(code: String, path: String? = null, softly: SoftAssertions? = null) {
|
||||||
|
val (sexp, genSexp) = renderBoth(code)
|
||||||
|
when {
|
||||||
|
(path != null && softly != null) ->
|
||||||
|
softly.assertThat(genSexp).`as`("path: $path").isEqualTo(sexp)
|
||||||
|
else -> assertThat(genSexp).`as`("path: $path").isEqualTo(sexp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun renderBoth(code: String): Pair<String, String> =
|
||||||
|
Pair(renderCode(code), renderGenericCode(code))
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun renderCode(code: String): String {
|
||||||
|
val parser = Parser()
|
||||||
|
val mod = parser.parseModule(code)
|
||||||
|
val renderer = SexpRenderer()
|
||||||
|
return renderer.render(mod)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderGenericCode(code: String): String {
|
||||||
|
val parser = GenericParser()
|
||||||
|
val mod = parser.parseModule(code)
|
||||||
|
val renderer = GenericSexpRenderer(code)
|
||||||
|
return renderer.render(mod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tests that are not syntactically valid Pkl
|
||||||
|
private val exceptions =
|
||||||
|
setOf(
|
||||||
|
"stringError1.pkl",
|
||||||
|
"annotationIsNotExpression2.pkl",
|
||||||
|
"amendsRequiresParens.pkl",
|
||||||
|
"errors/parser18.pkl",
|
||||||
|
"errors/nested1.pkl",
|
||||||
|
"errors/invalidCharacterEscape.pkl",
|
||||||
|
"errors/invalidUnicodeEscape.pkl",
|
||||||
|
"errors/unterminatedUnicodeEscape.pkl",
|
||||||
|
"errors/keywordNotAllowedHere1.pkl",
|
||||||
|
"errors/keywordNotAllowedHere2.pkl",
|
||||||
|
"errors/keywordNotAllowedHere3.pkl",
|
||||||
|
"errors/keywordNotAllowedHere4.pkl",
|
||||||
|
"errors/moduleWithHighMinPklVersionAndParseErrors.pkl",
|
||||||
|
"errors/underscore.pkl",
|
||||||
|
"errors/shebang.pkl",
|
||||||
|
"notAUnionDefault.pkl",
|
||||||
|
"multipleDefaults.pkl",
|
||||||
|
"modules/invalidModule1.pkl",
|
||||||
|
)
|
||||||
|
|
||||||
|
private val regexExceptions =
|
||||||
|
setOf(
|
||||||
|
Regex(".*/errors/delimiters/.*"),
|
||||||
|
Regex(".*/errors/parser\\d+\\.pkl"),
|
||||||
|
Regex(".*/parser/.*"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,13 +82,22 @@ class SexpRenderer {
|
|||||||
}
|
}
|
||||||
if (decl.extendsOrAmendsDecl !== null) {
|
if (decl.extendsOrAmendsDecl !== null) {
|
||||||
buf.append('\n')
|
buf.append('\n')
|
||||||
buf.append(tab)
|
renderExtendsOrAmendsClause(decl.extendsOrAmendsDecl!!)
|
||||||
buf.append("(extendsOrAmendsClause)")
|
|
||||||
}
|
}
|
||||||
tab = oldTab
|
tab = oldTab
|
||||||
buf.append(')')
|
buf.append(')')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun renderExtendsOrAmendsClause(clause: ExtendsOrAmendsClause) {
|
||||||
|
buf.append(tab)
|
||||||
|
buf.append("(extendsOrAmendsClause")
|
||||||
|
val oldTab = increaseTab()
|
||||||
|
buf.append('\n')
|
||||||
|
renderStringConstant(clause.url)
|
||||||
|
tab = oldTab
|
||||||
|
buf.append(')')
|
||||||
|
}
|
||||||
|
|
||||||
fun renderImport(imp: ImportClause) {
|
fun renderImport(imp: ImportClause) {
|
||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
if (imp.isGlob) {
|
if (imp.isGlob) {
|
||||||
@@ -97,6 +106,8 @@ class SexpRenderer {
|
|||||||
buf.append("(importClause")
|
buf.append("(importClause")
|
||||||
}
|
}
|
||||||
val oldTab = increaseTab()
|
val oldTab = increaseTab()
|
||||||
|
buf.append('\n')
|
||||||
|
renderStringConstant(imp.importStr)
|
||||||
if (imp.alias !== null) {
|
if (imp.alias !== null) {
|
||||||
buf.append('\n')
|
buf.append('\n')
|
||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
@@ -178,6 +189,7 @@ class SexpRenderer {
|
|||||||
buf.append("(identifier)")
|
buf.append("(identifier)")
|
||||||
val tparList = `typealias`.typeParameterList
|
val tparList = `typealias`.typeParameterList
|
||||||
if (tparList !== null) {
|
if (tparList !== null) {
|
||||||
|
buf.append('\n')
|
||||||
renderTypeParameterList(tparList)
|
renderTypeParameterList(tparList)
|
||||||
}
|
}
|
||||||
buf.append('\n')
|
buf.append('\n')
|
||||||
@@ -244,6 +256,7 @@ class SexpRenderer {
|
|||||||
buf.append("(identifier)")
|
buf.append("(identifier)")
|
||||||
val tparList = classMethod.typeParameterList
|
val tparList = classMethod.typeParameterList
|
||||||
if (tparList !== null) {
|
if (tparList !== null) {
|
||||||
|
buf.append('\n')
|
||||||
renderTypeParameterList(tparList)
|
renderTypeParameterList(tparList)
|
||||||
}
|
}
|
||||||
buf.append('\n')
|
buf.append('\n')
|
||||||
@@ -385,11 +398,7 @@ class SexpRenderer {
|
|||||||
is MultiLineStringLiteralExpr -> renderMultiLineStringLiteral(expr)
|
is MultiLineStringLiteralExpr -> renderMultiLineStringLiteral(expr)
|
||||||
is ThrowExpr -> renderThrowExpr(expr)
|
is ThrowExpr -> renderThrowExpr(expr)
|
||||||
is TraceExpr -> renderTraceExpr(expr)
|
is TraceExpr -> renderTraceExpr(expr)
|
||||||
is ImportExpr -> {
|
is ImportExpr -> renderImportExpr(expr)
|
||||||
buf.append(tab)
|
|
||||||
val name = if (expr.isGlob) "(importGlobExpr)" else "(importExpr)"
|
|
||||||
buf.append(name)
|
|
||||||
}
|
|
||||||
is ReadExpr -> renderReadExpr(expr)
|
is ReadExpr -> renderReadExpr(expr)
|
||||||
is UnqualifiedAccessExpr -> renderUnqualifiedAccessExpr(expr)
|
is UnqualifiedAccessExpr -> renderUnqualifiedAccessExpr(expr)
|
||||||
is QualifiedAccessExpr -> renderQualifiedAccessExpr(expr)
|
is QualifiedAccessExpr -> renderQualifiedAccessExpr(expr)
|
||||||
@@ -399,7 +408,7 @@ class SexpRenderer {
|
|||||||
is IfExpr -> renderIfExpr(expr)
|
is IfExpr -> renderIfExpr(expr)
|
||||||
is LetExpr -> renderLetExpr(expr)
|
is LetExpr -> renderLetExpr(expr)
|
||||||
is FunctionLiteralExpr -> renderFunctionLiteralExpr(expr)
|
is FunctionLiteralExpr -> renderFunctionLiteralExpr(expr)
|
||||||
is ParenthesizedExpr -> renderParenthesisedExpr(expr)
|
is ParenthesizedExpr -> renderParenthesizedExpr(expr)
|
||||||
is NewExpr -> renderNewExpr(expr)
|
is NewExpr -> renderNewExpr(expr)
|
||||||
is AmendsExpr -> renderAmendsExpr(expr)
|
is AmendsExpr -> renderAmendsExpr(expr)
|
||||||
is NonNullExpr -> renderNonNullExpr(expr)
|
is NonNullExpr -> renderNonNullExpr(expr)
|
||||||
@@ -421,7 +430,7 @@ class SexpRenderer {
|
|||||||
renderExpr(part.expr)
|
renderExpr(part.expr)
|
||||||
} else {
|
} else {
|
||||||
buf.append('\n').append(tab)
|
buf.append('\n').append(tab)
|
||||||
buf.append("(stringConstantExpr)")
|
buf.append("(stringConstant)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf.append(')')
|
buf.append(')')
|
||||||
@@ -480,6 +489,17 @@ class SexpRenderer {
|
|||||||
tab = oldTab
|
tab = oldTab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun renderImportExpr(expr: ImportExpr) {
|
||||||
|
buf.append(tab)
|
||||||
|
val name = if (expr.isGlob) "(importGlobExpr" else "(importExpr"
|
||||||
|
buf.append(name)
|
||||||
|
val oldTab = increaseTab()
|
||||||
|
buf.append('\n')
|
||||||
|
renderStringConstant(expr.importStr)
|
||||||
|
buf.append(')')
|
||||||
|
tab = oldTab
|
||||||
|
}
|
||||||
|
|
||||||
fun renderUnqualifiedAccessExpr(expr: UnqualifiedAccessExpr) {
|
fun renderUnqualifiedAccessExpr(expr: UnqualifiedAccessExpr) {
|
||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
buf.append("(unqualifiedAccessExpr")
|
buf.append("(unqualifiedAccessExpr")
|
||||||
@@ -517,6 +537,7 @@ class SexpRenderer {
|
|||||||
buf.append("(superAccessExpr")
|
buf.append("(superAccessExpr")
|
||||||
val oldTab = increaseTab()
|
val oldTab = increaseTab()
|
||||||
buf.append('\n')
|
buf.append('\n')
|
||||||
|
buf.append(tab)
|
||||||
buf.append("(identifier)")
|
buf.append("(identifier)")
|
||||||
if (expr.argumentList !== null) {
|
if (expr.argumentList !== null) {
|
||||||
buf.append('\n')
|
buf.append('\n')
|
||||||
@@ -588,7 +609,7 @@ class SexpRenderer {
|
|||||||
tab = oldTab
|
tab = oldTab
|
||||||
}
|
}
|
||||||
|
|
||||||
fun renderParenthesisedExpr(expr: ParenthesizedExpr) {
|
fun renderParenthesizedExpr(expr: ParenthesizedExpr) {
|
||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
buf.append("(parenthesizedExpr")
|
buf.append("(parenthesizedExpr")
|
||||||
val oldTab = increaseTab()
|
val oldTab = increaseTab()
|
||||||
@@ -713,6 +734,11 @@ class SexpRenderer {
|
|||||||
tab = oldTab
|
tab = oldTab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun renderStringConstant(str: StringConstant) {
|
||||||
|
buf.append(tab)
|
||||||
|
buf.append("(stringConstant)")
|
||||||
|
}
|
||||||
|
|
||||||
fun renderTypeAnnotation(typeAnnotation: TypeAnnotation) {
|
fun renderTypeAnnotation(typeAnnotation: TypeAnnotation) {
|
||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
buf.append("(typeAnnotation")
|
buf.append("(typeAnnotation")
|
||||||
@@ -737,10 +763,7 @@ class SexpRenderer {
|
|||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
buf.append("(moduleType)")
|
buf.append("(moduleType)")
|
||||||
}
|
}
|
||||||
is StringConstantType -> {
|
is StringConstantType -> renderStringConstantType(type)
|
||||||
buf.append(tab)
|
|
||||||
buf.append("(stringConstantType)")
|
|
||||||
}
|
|
||||||
is DeclaredType -> renderDeclaredType(type)
|
is DeclaredType -> renderDeclaredType(type)
|
||||||
is ParenthesizedType -> renderParenthesizedType(type)
|
is ParenthesizedType -> renderParenthesizedType(type)
|
||||||
is NullableType -> renderNullableType(type)
|
is NullableType -> renderNullableType(type)
|
||||||
@@ -750,6 +773,16 @@ class SexpRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun renderStringConstantType(type: StringConstantType) {
|
||||||
|
buf.append(tab)
|
||||||
|
buf.append("(stringConstantType")
|
||||||
|
val oldTab = increaseTab()
|
||||||
|
buf.append('\n')
|
||||||
|
renderStringConstant(type.str)
|
||||||
|
buf.append(')')
|
||||||
|
tab = oldTab
|
||||||
|
}
|
||||||
|
|
||||||
fun renderDeclaredType(type: DeclaredType) {
|
fun renderDeclaredType(type: DeclaredType) {
|
||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
buf.append("(declaredType")
|
buf.append("(declaredType")
|
||||||
@@ -778,7 +811,7 @@ class SexpRenderer {
|
|||||||
|
|
||||||
fun renderParenthesizedType(type: ParenthesizedType) {
|
fun renderParenthesizedType(type: ParenthesizedType) {
|
||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
buf.append("(parenthesisedType")
|
buf.append("(parenthesizedType")
|
||||||
val oldTab = increaseTab()
|
val oldTab = increaseTab()
|
||||||
buf.append('\n')
|
buf.append('\n')
|
||||||
renderType(type.type)
|
renderType(type.type)
|
||||||
@@ -903,12 +936,11 @@ class SexpRenderer {
|
|||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
buf.append("(objectMethod")
|
buf.append("(objectMethod")
|
||||||
val oldTab = increaseTab()
|
val oldTab = increaseTab()
|
||||||
buf.append('\n')
|
|
||||||
for (mod in method.modifiers) {
|
for (mod in method.modifiers) {
|
||||||
buf.append('\n')
|
buf.append('\n')
|
||||||
renderModifier(mod)
|
renderModifier(mod)
|
||||||
}
|
}
|
||||||
buf.append('\n')
|
buf.append('\n').append(tab)
|
||||||
buf.append("(identifier)")
|
buf.append("(identifier)")
|
||||||
val tparList = method.typeParameterList
|
val tparList = method.typeParameterList
|
||||||
if (tparList !== null) {
|
if (tparList !== null) {
|
||||||
@@ -1012,19 +1044,20 @@ class SexpRenderer {
|
|||||||
|
|
||||||
fun renderTypeParameterList(typeParameterList: TypeParameterList) {
|
fun renderTypeParameterList(typeParameterList: TypeParameterList) {
|
||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
buf.append("(TypeParameterList\n")
|
buf.append("(typeParameterList")
|
||||||
val oldTab = increaseTab()
|
val oldTab = increaseTab()
|
||||||
for (tpar in typeParameterList.parameters) {
|
for (tpar in typeParameterList.parameters) {
|
||||||
buf.append('\n')
|
buf.append('\n')
|
||||||
renderTypeParameter(tpar)
|
renderTypeParameter(tpar)
|
||||||
}
|
}
|
||||||
|
buf.append(')')
|
||||||
tab = oldTab
|
tab = oldTab
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun renderTypeParameter(ignored: TypeParameter?) {
|
fun renderTypeParameter(ignored: TypeParameter?) {
|
||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
buf.append("(TypeParameter\n")
|
buf.append("(typeParameter\n")
|
||||||
val oldTab = increaseTab()
|
val oldTab = increaseTab()
|
||||||
buf.append(tab)
|
buf.append(tab)
|
||||||
buf.append("(identifier))")
|
buf.append("(identifier))")
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ include("pkl-doc")
|
|||||||
|
|
||||||
include("pkl-executor")
|
include("pkl-executor")
|
||||||
|
|
||||||
|
include("pkl-formatter")
|
||||||
|
|
||||||
include("pkl-gradle")
|
include("pkl-gradle")
|
||||||
|
|
||||||
include("pkl-parser")
|
include("pkl-parser")
|
||||||
|
|||||||
Reference in New Issue
Block a user