Repository navigation
Fail the build on NullAway and ten more Error Prone checks - #201
Merged
Merged
Conversation
Of the 110 annotations 105 hid nothing. 94 named their checks with palantir-baseline's for-rollout: prefix, which plain Error Prone does not take for the check's name: NullAway and the rest reported those lines all along. Others named checks this build does not run (TooManyArguments, ImmutablesStyle), IntelliJ inspections, or javac's unchecked lint, which is off. Without UnicodeEscape's, its one finding shows. The five on Doc's and Level's memoized suppliers stay: Immutable is an error, and the suppliers are mutable caches that the check rightly rejects. They need their own change.
ExecutableTransform's logger, StringWrapper's endLine and the stderr two MainTest tests read after the process had exited were never read. They are gone, and Error Prone now fails the build on the next one.
In PalantirJavaFormatSettings `State` named both IntelliJ's @State annotation and the settings' own State class. The annotation is now written out in full, which leaves the name to the class.
The switch statements in DimensionHelpers.extractDims, CommandLineOptionsParser's two range parsers and SnippetFormatter's snippetWrapper returned from every case. Each is now the switch expression it returns, with yield where a case has statements before its value.
Eight calls in the tests built a message or an expected string by concatenation and handed it to a method that formats one itself: AssertJ's describedAs, hasMessage and isEqualTo(String, Object...) and Guava's checkState. They now pass a format string and its arguments.
The checks that fail the build join excludedPaths in a single block, in alphabetical order. The checks that were only listed as comments for later are gone; the findings still to fix are in the build output.
State also named IntelliJ's @State annotation on the same class, which the previous commit wrote out in full to tell the two apart. The class gets a name of its own and the annotation its import back. The class name is not in .idea/open-java-format.xml: IntelliJ writes the bean's fields into the component element, and PalantirJavaFormatSettingsTest loads files written before.
…fTheRun As a format string the expected comment no longer read like the input it is rewritten from. The concatenation is back, with FormatStringConcatenation suppressed on the test.
…nto abashev/Fix-error-prone-issues
The 21.X.X merge brought in Signature, whose bundled field only went into the serialized signature and carried a suppression. jars() now picks the jars by it: the hash is there exactly when the build declared none of its own, so the step formats with the same jars as before.
jsr305's javax.annotation.Nullable gives way to org.jspecify.annotations.Nullable, and the jsr305 dependency to jspecify 1.0.1, the version Guava already brings. Obs's @CheckReturnValue, the other jsr305 annotation in use, is now Error Prone's. JetBrains' @NotNull goes from the IntelliJ plugin: what is not @nullable is non-null, and NullAway checks it. The plugin does not instrument its code, so the annotations added no runtime checks either. JSpecify's @nullable is a type-use annotation. Where it stood above an @OverRide it now follows it, next to the return type it annotates. CLAUDE.md names the annotation and where it goes on nested and array types.
Outside it NullAway takes Guava's @ParametricNullness for @nullable: PeekingIterator.peek() and next(), Iterables.getLast and getOnlyElement, Iterators.getLast. Every token taken from a PeekingIterator<Token> was reported as possibly null. JSpecify mode reads Guava's type parameters instead, and of NullAway's 102 findings 60 remain. On javac 21 the mode needs -XDaddTypeAnnotationsToSymbol=true to see type-use annotations, @nullable among them, on classes read from jars.
Picnic's check asks for annotations in alphabetical order, and gave 25 suggestions that would move @OverRide and @test below the rest. The annotation that says what a member is stays first, as most of the code has it. JavaCommentsHelperTest's @test is above its @SuppressWarnings again.
Most of the 60 findings were a null the code meant, with no @nullable where it went. Now marked @nullable: - RawTok's and Tok's kind and string value, and a comment's text before it is first read; - the parent of the root exploration node; - the methods maybeGetMethod looks up, and the fields that keep them; - the receiver of an unqualified call, and a dimension list's missing type; - getLineEnding's result for a string without one; - the three scan overrides, which javac calls with null for a missing tree. Where a lookup cannot miss (a token at a position the input has, a range the output built, a level's children), checkNotNull says so and fails as the dereference did. Besides that: - variableFragmentDims no longer takes a missing type: its one caller that may have none maps the Optional instead. - The javadoc lexer's newline after a `}` it moves has the "\n" of every other newline token rather than null. The formatter writes a line break for it and never reads the value. - Newlines.LineIterator returns the line it reads instead of keeping it in a field. - BootstrappingFormatterService runs the formatter without a working directory, rather than throwing, if the java binary has no parent directory. - PalantirJavaFormatSettings.getState() is no longer @nullable: it never returns null. - ParameterizedClass uses JUnit 6's computeIfAbsent, which never returns null, in place of the deprecated getOrComputeIfAbsent. - FormatterIntegrationTest reads the expected output's line separator in the tests that use it. - NullAway takes AssertJ's assertThat(x).isNotNull() in tests for the null check it is.
Its findings are fixed, and the next one now fails the build.
The 36 uses of StandardCharsets.UTF_8 now read UTF_8, as the rest of the code already wrote it, and the ten files no longer import StandardCharsets. The import in a MainTest string is input to the formatter and stays.
- LexicographicalAnnotationAttributeListing would sort the values in an
annotation's arrays: {false, true} for {true, false} in a dozen
@valuesource tests, and a @CsvSource's rows out of the order they are
grouped in.
- StringJoining would turn String.format("%s:%s", lower, higher) into
String.join(":", String.valueOf(lower), String.valueOf(higher)).
- IsInstanceLambdaUsage would turn doc -> doc instanceof Level into
Level.class::isInstance.
Six toString() calls on what string concatenation or Path.of converts anyway are gone: on the diagnostics Main prints, on the paths MainTest expects, and on an environment variable, which is a String already.
An annotation attribute with one value no longer has braces around it (@State's storages), and an array of them no trailing comma (SpotlessExcludesTest's @valuesource, which then fits on one line).
Formatter.formatSourceAndFixImports and JavadocLexer.generateTokens assigned their last value to a variable only to return it; they return it directly.
The settings' tests wrote and read back the native binary settings but never a classpath, the one list in them. The new test round-trips two jars through IntelliJ's serializer.
The ten Collectors.toList() calls are now Stream.toList(), as CLAUDE.md asks of new code, and StringWrapper's Collectors.toMap is toUnmodifiableMap. Nothing changed any of these collections after they were built, and none holds a null, so only their mutability differs: the lists go to command lines, records and assertions, and the map is only read. The previous commit's test round-trips the IntelliJ settings' classpath, the one list that goes through a serializer.
The Java sources that DiagnosticTest and FormatterTest feed the
formatter were strings joined by + or by Joiner.on('\n'). They are text
blocks now, ending in \ where the source had no final newline, and
Joiner is no longer imported.
Twelve members of private nested classes were public or protected, which reaches no further than the file a private class is in. They have no access modifier now: - the constructors of Obs's two nodes, of ReferenceScanner, and of the test's DelegatingFormatter; - the IntelliJ formatting task's constructor and applyReplacements; - SnippetWrapper's three methods and StringWrapper's getReflowReplacements; - TypeNameClassifier's TyParseState.isSingleUnit and next(), with the five overrides of next() in its constants.
abashev
force-pushed
the
abashev/Fix-error-prone-issues
branch
from
November 8, 2026 16:33
db6dfa4 to
fbb6a7c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Error Prone has run on every compile, but nothing it reported failed the build except two JUnit checks, and the warnings scrolled past. This is the first step towards making it fail the build: the suppressions go, nullness moves to JSpecify, and eleven checks become errors once their findings are fixed.
@SuppressWarnings. 105 of the 110 are gone. They hid almost nothing. 94 named their checks with palantir-baseline'sfor-rollout:prefix, which plain Error Prone does not take for a check's name, so NullAway and the rest reported those lines all along. Others named checks this build does not run (TooManyArguments,ImmutablesStyle), IntelliJ inspections, or javac'suncheckedlint, which is off. RemovingUnicodeEscape's brings back its one finding.Immutablestays suppressed. The five suppressions onDoc's andLevel's memoized suppliers remain.Immutableis an error by default, and the check is right:Suppliers.memoizecaches its value, soBreak,TokenandNonBreakingSpace, which are@Immutableand extendDoc, are not immutable. Fixing that is a change of its own.UnusedVariable:ExecutableTransform's logger,StringWrapper'sendLine, and the stderr that twoMainTesttests read after the process had exited.SameNameButDifferent: inPalantirJavaFormatSettings,Statenamed both IntelliJ's@Stateand the settings bean. The bean is nowStoredSettings. Its class name is not stored in.idea/open-java-format.xml, andPalantirJavaFormatSettingsTestloads files written before.RefactorSwitch: four switch statements that returned from every case are now the switch expressions they return.FormatStringConcatenation: seven calls in the tests pass a format string and its arguments to AssertJ'sdescribedAs,hasMessageandisEqualToand to Guava'scheckState, instead of a concatenated string. InJavaCommentsHelperTestthe expected comment stays written out like its input, and the check is suppressed on that test.org.jspecify.annotations.Nullablereplaces jsr305'sjavax.annotation.Nullable, and thejspecifydependency (1.0.1, the version Guava brings) replacesjsr305. JetBrains'@NotNullgoes from the IntelliJ plugin, andObs's jsr305@CheckReturnValueis now Error Prone's. CLAUDE.md says where the type-use annotation goes on nested and array types.@ParametricNullnessfor@Nullable, so every token read from aPeekingIterator<Token>counted as possibly null. That made 49 of its 102 findings; the mode reports 7 others that were real, which leaves 60. On javac 21 the mode needs-XDaddTypeAnnotationsToSymbol=trueto see@Nullableon classes read from jars.@Nullablewhere it went. A lookup that cannot miss now goes throughcheckNotNull, which fails as the dereference did. The commit message lists the few changes beyond annotations, such as the javadoc lexer's one null-valued newline token and JUnit 6'scomputeIfAbsent.RedundantStringConversion: sixtoString()calls on values that concatenation orPath.ofconverts anyway.CanonicalAnnotationSyntax: braces around a single annotation value, and a trailing comma in an annotation array.DirectReturn: two methods that put their last value in a variable only to return it.CollectorMutabilityis an error. The tenCollectors.toList()calls areStream.toList(), as CLAUDE.md asks of new code, andStringWrapper'sCollectors.toMapistoUnmodifiableMap. Nothing changed these collections after they were built, and none holds a null. A new test round-trips the IntelliJ settings' classpath, the one list that goes through a serializer.StringConcatToTextBlockis an error. The Java sources thatDiagnosticTestandFormatterTestfeed the formatter, strings joined by+orJoiner, are text blocks.EffectivelyPrivateis an error. Twelve members of private nested classes lose apublicorprotectedthat reached no further than the file.LexicographicalAnnotationListingwants annotations in alphabetical order. Its 25 suggestions would move@Overrideand@Testbelow the other annotations.LexicographicalAnnotationAttributeListingwould sort annotation arrays,{false, true}in a dozen@ValueSourcetests and a@CsvSource's rows.StringJoiningwould replaceString.format("%s:%s", a, b)withString.joinand twoString.valueOfcalls.IsInstanceLambdaUsagewould replacedoc -> doc instanceof LevelwithLevel.class::isInstance.UTF_8is imported statically, as most of the code already had it: 36 uses ofStandardCharsets.UTF_8.Tests
./gradlew testpasses on JDK 21: 1627 tests, 14 skipped. With an unused local added toCommandLineOptionsParser,:open-java-format:compileJavafails witherror: [UnusedVariable]. WithModifierOrderer.compareTodereferencing its@Nullablefield again, it fails witherror: [NullAway].Not here
30 warnings remain: ConstantNaming 4, and 1 to 3 each for 17 more checks. There are also 49 suggestions: StaticImport 47 and NonStaticImport 2. Those checks,
Immutableincluded, follow one at a time.