Add nullability check to pkl-formatter (#1526)

This commit is contained in:
Islon Scherer
2026-04-16 17:37:56 +02:00
committed by GitHub
parent eeb0970dc4
commit 03a641354e
5 changed files with 53 additions and 21 deletions

View File

@@ -2,10 +2,28 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:2.9.3=swiftExportClasspathResolvable
com.github.ben-manes.caffeine:caffeine:3.0.5=annotationProcessor,testAnnotationProcessor
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,testAnnotationProcessor
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,testAnnotationProcessor
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,testAnnotationProcessor
com.google.auto:auto-common:1.2.2=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotation:2.48.0=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.28.0=swiftExportClasspathResolvable
io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation
com.google.errorprone:error_prone_annotations:2.48.0=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_check_api:2.48.0=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_core:2.48.0=annotationProcessor,testAnnotationProcessor
com.google.googlejavaformat:google-java-format:1.34.1=annotationProcessor,testAnnotationProcessor
com.google.guava:failureaccess:1.0.3=annotationProcessor,testAnnotationProcessor
com.google.guava:guava:33.5.0-jre=annotationProcessor,testAnnotationProcessor
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,testAnnotationProcessor
com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,testAnnotationProcessor
com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,testAnnotationProcessor
com.uber.nullaway:nullaway:0.13.1=annotationProcessor,testAnnotationProcessor
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,testAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,kotlinInternalAbiValidation,testAnnotationProcessor
io.opentelemetry:opentelemetry-api:1.41.0=swiftExportClasspathResolvable
io.opentelemetry:opentelemetry-context:1.41.0=swiftExportClasspathResolvable
javax.inject:javax.inject:1=annotationProcessor,testAnnotationProcessor
net.bytebuddy:byte-buddy:1.18.3=testCompileClasspath,testRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
org.assertj:assertj-core:3.27.7=testCompileClasspath,testRuntimeClasspath
@@ -14,6 +32,8 @@ org.bouncycastle:bcpkix-jdk18on:1.80=kotlinBouncyCastleConfiguration
org.bouncycastle:bcprov-jdk18on:1.80=kotlinBouncyCastleConfiguration
org.bouncycastle:bcutil-jdk18on:1.80=kotlinBouncyCastleConfiguration
org.checkerframework:checker-qual:3.43.0=swiftExportClasspathResolvable
org.checkerframework:checker-qual:3.53.0=annotationProcessor,testAnnotationProcessor
org.checkerframework:dataflow-nullaway:3.53.0=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlin:abi-tools-api:2.3.20=kotlinInternalAbiValidation
org.jetbrains.kotlin:abi-tools:2.3.20=kotlinInternalAbiValidation
org.jetbrains.kotlin:kotlin-build-tools-api:2.3.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
@@ -46,7 +66,7 @@ org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolv
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable
org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable
org.jetbrains:annotations:13.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testCompileClasspath,testRuntimeClasspath
org.jspecify:jspecify:1.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jspecify:jspecify:1.0.0=annotationProcessor,compileClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:6.0.3=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:6.0.3=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:6.0.3=testCompileClasspath,testRuntimeClasspath
@@ -56,4 +76,5 @@ org.junit.platform:junit-platform-launcher:6.0.3=testRuntimeClasspath
org.junit:junit-bom:6.0.3=testCompileClasspath,testRuntimeClasspath
org.msgpack:msgpack-core:0.9.11=testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
empty=annotationProcessor,apiDependenciesMetadata,compileOnlyDependenciesMetadata,implementationDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions
org.pcollections:pcollections:4.0.1=annotationProcessor,testAnnotationProcessor
empty=apiDependenciesMetadata,compileOnlyDependenciesMetadata,implementationDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,sourcesJar,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions

View File

@@ -16,6 +16,7 @@
plugins {
id("pklAllProjects")
id("pklJavaLibrary")
id("pklJSpecify")
id("pklPublishLibrary")
}

View File

@@ -24,6 +24,7 @@ import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import org.jspecify.annotations.Nullable;
import org.pkl.parser.syntax.Operator;
import org.pkl.parser.syntax.generic.Node;
import org.pkl.parser.syntax.generic.NodeType;
@@ -274,14 +275,14 @@ final class Builder {
gatherFacts(node, flat, lambdaCount, methodCallCount, indexBeforeFirstMethodCall);
BiFunction<Node, Node, FormatNode> leadingSeparator =
BiFunction<Node, Node, @Nullable FormatNode> leadingSeparator =
(prev, next) -> {
if (prev.type == NodeType.OPERATOR) return null;
if (next.type == NodeType.OPERATOR) return line();
return spaceOrLine();
};
BiFunction<Node, Node, FormatNode> trailingSeparator =
BiFunction<Node, Node, @Nullable FormatNode> trailingSeparator =
(prev, next) -> {
if (prev.type == NodeType.OPERATOR) return null;
if (next.type == NodeType.OPERATOR) return lambdaCount[0] > 1 ? forceLine() : line();
@@ -360,7 +361,7 @@ final class Builder {
return new List[] {leading, trailing};
}
private static boolean isMethodCall(Node node) {
private static boolean isMethodCall(@Nullable Node node) {
if (node == null || node.type != NodeType.UNQUALIFIED_ACCESS_EXPR) return false;
for (var child : node.children) {
if (child.type == NodeType.ARGUMENT_LIST) return true;
@@ -574,7 +575,7 @@ final class Builder {
return new Group(newId(), formatGeneric(node.children, spaceOrLine()));
}
private FormatNode formatParameterList(Node node, Integer id) {
private FormatNode formatParameterList(Node node, @Nullable Integer id) {
if (node.children.size() == 2) return new Text("()");
var groupId = id != null ? id : newId();
var nodes =
@@ -630,7 +631,7 @@ final class Builder {
Node node, boolean hasTrailingLambda, boolean twoBy2) {
var children = node.children;
var shouldMultiline = shouldMultilineNodes(node, n -> isTerminal(n, ","));
BiFunction<Node, Node, FormatNode> sep =
BiFunction<Node, Node, @Nullable FormatNode> sep =
(prev, next) -> shouldMultiline ? forceSpaceyLine() : spaceOrLine();
if (twoBy2) {
var pairs = pairArguments(children);
@@ -906,6 +907,8 @@ final class Builder {
var prevNoNewlines = noNewlines;
var elems = cursor.takeUntilBefore(n -> isTerminalSingle(n, ")"));
noNewlines = !isMultilineList(elems);
//noinspection ConstantValue
assert prev != null;
var baseSep = getBaseSeparator(prev, elems.get(0));
if (baseSep != null) result.add(baseSep);
result.addAll(formatGeneric(elems, (FormatNode) null));
@@ -1337,24 +1340,26 @@ final class Builder {
// --- formatGeneric overloads ---
private List<FormatNode> formatGeneric(List<Node> children, FormatNode separator) {
private List<FormatNode> formatGeneric(List<Node> children, @Nullable FormatNode separator) {
return formatGeneric(children, (prev, next) -> separator);
}
private List<FormatNode> formatGeneric(
List<Node> children, BiFunction<Node, Node, FormatNode> separatorFn) {
List<Node> children, BiFunction<Node, Node, @Nullable FormatNode> separatorFn) {
return formatGenericWithGen(children, separatorFn, null);
}
private List<FormatNode> formatGenericWithGen(
List<Node> children, FormatNode separator, BiFunction<Node, Node, FormatNode> generatorFn) {
List<Node> children,
@Nullable FormatNode separator,
BiFunction<Node, @Nullable Node, FormatNode> generatorFn) {
return formatGenericWithGen(children, (prev, next) -> separator, generatorFn);
}
private List<FormatNode> formatGenericWithGen(
List<Node> children,
BiFunction<Node, Node, FormatNode> separatorFn,
BiFunction<Node, Node, FormatNode> generatorFn) {
BiFunction<Node, Node, @Nullable FormatNode> separatorFn,
@Nullable BiFunction<Node, @Nullable Node, FormatNode> generatorFn) {
// skip semicolons
var filtered = new ArrayList<Node>(children.size());
for (var child : children) {
@@ -1423,13 +1428,13 @@ final class Builder {
return base != null ? base : separator;
}
private FormatNode getSeparator(
Node prev, Node next, BiFunction<Node, Node, FormatNode> separatorFn) {
private @Nullable FormatNode getSeparator(
Node prev, Node next, BiFunction<Node, Node, @Nullable FormatNode> separatorFn) {
var base = getBaseSeparator(prev, next);
return base != null ? base : separatorFn.apply(prev, next);
}
private FormatNode getBaseSeparator(Node prev, Node next) {
private @Nullable FormatNode getBaseSeparator(Node prev, Node next) {
if (endsInLineComment(prev)) {
return linesBetween(prev, next) > 1 ? TWO_NEWLINES : mustForceLine();
}
@@ -1637,14 +1642,14 @@ final class Builder {
return new Indent(List.of(nodes));
}
private static Node firstProperChild(Node node) {
private static @Nullable Node firstProperChild(Node node) {
for (var child : node.children) {
if (isProper(child)) return child;
}
return null;
}
private static Node lastProperNode(List<Node> nodes) {
private static @Nullable Node lastProperNode(List<Node> nodes) {
for (var i = nodes.size() - 1; i >= 0; i--) {
if (isProper(nodes.get(i))) return nodes.get(i);
}
@@ -1699,7 +1704,7 @@ final class Builder {
static final class PeekableIterator<T> implements Iterator<T> {
private final Iterator<T> iterator;
private T peeked;
private @Nullable T peeked;
private boolean hasPeeked = false;
PeekableIterator(Iterator<T> iterator) {
@@ -1710,6 +1715,7 @@ final class Builder {
public T next() {
if (hasPeeked) {
hasPeeked = false;
if (peeked == null) throw new NoSuchElementException();
return peeked;
}
return iterator.next();
@@ -1722,7 +1728,7 @@ final class Builder {
T peek() {
if (!hasNext()) throw new NoSuchElementException();
if (hasPeeked) return peeked;
if (hasPeeked && peeked != null) return peeked;
peeked = iterator.next();
hasPeeked = true;
return peeked;

View File

@@ -37,8 +37,8 @@ final class Generator {
node(node, Wrap.DETECT);
}
@SuppressWarnings("StatementWithEmptyBody")
private void node(FormatNode node, Wrap wrap) {
//noinspection StatementWithEmptyBody
if (node instanceof Empty) {
// nothing
} else if (node instanceof Nodes n) {

View File

@@ -0,0 +1,4 @@
@NullMarked
package org.pkl.formatter;
import org.jspecify.annotations.NullMarked;