diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79a260eab..716abcc07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,9 @@ jobs: - os: ubuntu-latest java: 20-ea experimental: true + - os: ubuntu-latest + java: 21-ea + experimental: true runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} steps: diff --git a/README.md b/README.md index 2d53f8ae5..4fe0e2126 100644 --- a/README.md +++ b/README.md @@ -44,14 +44,25 @@ presented when you first open a project offering to do this for you.) To enable it by default in new projects, use `File→Other Settings→Default Settings...`. -When enabled, it will replace the normal `Reformat Code` action, which can be -triggered from the `Code` menu or with the Ctrl-Alt-L (by default) keyboard -shortcut. +When enabled, it will replace the normal `Reformat Code` and `Optimize Imports` +actions. -The import ordering is not handled by this plugin, unfortunately. To fix the -import order, download the -[IntelliJ Java Google Style file](https://raw.githubusercontent.com/google/styleguide/gh-pages/intellij-java-google-style.xml) -and import it into File→Settings→Editor→Code Style. +#### IntelliJ JRE Config + +The google-java-format plugin uses some internal classes that aren't available +without extra configuration. To use the plugin, go to `Help→Edit Custom VM +Options...` and paste in these lines: + +``` +--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +``` + +Once you've done that, restart the IDE. ### Eclipse @@ -94,11 +105,12 @@ following JVM flags are required when running on JDK 16 and newer, due to [JEP 396: Strongly Encapsulate JDK Internals by Default](https://openjdk.java.net/jeps/396): ``` ---add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED ---add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED ---add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED ---add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED ---add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED ``` #### Maven diff --git a/core/pom.xml b/core/pom.xml index 10565c66a..dcbd25f1c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -22,7 +22,7 @@ com.google.googlejavaformat google-java-format-parent - HEAD-SNAPSHOT + 1.17.0 google-java-format @@ -102,13 +102,12 @@ https://docs.oracle.com/en/java/javase/11/docs/api + --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED,com.google.googlejavaformat + --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED,com.google.googlejavaformat --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED,com.google.googlejavaformat - --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED,com.google.googlejavaformat --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED,com.google.googlejavaformat --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED,com.google.googlejavaformat --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED,com.google.googlejavaformat - --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED,com.google.googlejavaformat - --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED,com.google.googlejavaformat diff --git a/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java b/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java index db431c040..a45e83b9e 100644 --- a/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java +++ b/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java @@ -159,7 +159,7 @@ public BlankLineWanted merge(BlankLineWanted other) { int depth = 0; /** Add an {@link Op}, and record open/close ops for later validation of unclosed levels. */ - private void add(Op op) { + public final void add(Op op) { if (op instanceof OpenOp) { depth++; } else if (op instanceof CloseOp) { diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java b/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java index c43a91ad1..656b65c83 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java @@ -111,7 +111,7 @@ public void append(String text, Range range) { * there's a blank line here and it's a comment. */ BlankLineWanted wanted = blankLines.getOrDefault(lastK, BlankLineWanted.NO); - if (isComment(text) ? sawNewlines : wanted.wanted().orElse(sawNewlines)) { + if ((sawNewlines && isComment(text)) || wanted.wanted().orElse(sawNewlines)) { ++newlinesPending; } } diff --git a/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java b/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java index ecb30eed6..a0fc2f54a 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java +++ b/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java @@ -49,7 +49,6 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; -import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; @@ -293,9 +292,7 @@ private static RangeMap buildReplacements( } private static String getSimpleName(JCImport importTree) { - return importTree.getQualifiedIdentifier() instanceof JCIdent - ? ((JCIdent) importTree.getQualifiedIdentifier()).getName().toString() - : ((JCFieldAccess) importTree.getQualifiedIdentifier()).getIdentifier().toString(); + return getQualifiedIdentifier(importTree).getIdentifier().toString(); } private static boolean isUnused( @@ -304,18 +301,15 @@ private static boolean isUnused( Multimap> usedInJavadoc, JCImport importTree, String simpleName) { - String qualifier = - ((JCFieldAccess) importTree.getQualifiedIdentifier()).getExpression().toString(); + JCFieldAccess qualifiedIdentifier = getQualifiedIdentifier(importTree); + String qualifier = qualifiedIdentifier.getExpression().toString(); if (qualifier.equals("java.lang")) { return true; } if (unit.getPackageName() != null && unit.getPackageName().toString().equals(qualifier)) { return true; } - if (importTree.getQualifiedIdentifier() instanceof JCFieldAccess - && ((JCFieldAccess) importTree.getQualifiedIdentifier()) - .getIdentifier() - .contentEquals("*")) { + if (qualifiedIdentifier.getIdentifier().contentEquals("*")) { return false; } @@ -328,6 +322,15 @@ private static boolean isUnused( return true; } + private static JCFieldAccess getQualifiedIdentifier(JCImport importTree) { + // Use reflection because the return type is JCTree in some versions and JCFieldAccess in others + try { + return (JCFieldAccess) JCImport.class.getMethod("getQualifiedIdentifier").invoke(importTree); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + /** Applies the replacements to the given source, and re-format any edited javadoc. */ private static String applyReplacements(String source, RangeMap replacements) { // save non-empty fixed ranges for reformatting after fixes are applied diff --git a/core/src/test/java/com/google/googlejavaformat/java/MainTest.java b/core/src/test/java/com/google/googlejavaformat/java/MainTest.java index ad91bfa26..42e12d860 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/MainTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/MainTest.java @@ -54,13 +54,12 @@ public class MainTest { private static final ImmutableList ADD_EXPORTS = ImmutableList.of( + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"); + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"); @Test public void testUsageOutput() { diff --git a/core/src/test/java/com/google/googlejavaformat/java/PartialFormattingTest.java b/core/src/test/java/com/google/googlejavaformat/java/PartialFormattingTest.java index b1142b3b2..1750049d7 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/PartialFormattingTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/PartialFormattingTest.java @@ -679,6 +679,7 @@ public void noTokensOnLine() throws Exception { String input = lines( " package com.google.googlejavaformat.java;", + "", "/*", " * Copyright 2015 Google Inc.", " *", diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27246427.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27246427.output index b1e33e904..3d591f8f6 100644 --- a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27246427.output +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27246427.output @@ -6,6 +6,7 @@ enum TrailingComment { /** a */ Object a; + /** b */ Object b; } diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28788559.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28788559.input deleted file mode 100644 index a38d10b73..000000000 --- a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28788559.input +++ /dev/null @@ -1,7 +0,0 @@ -import a.A;; -import b.B; - -class Test { - A a; - B b; -} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28788559.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28788559.output deleted file mode 100644 index 8a1db808f..000000000 --- a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28788559.output +++ /dev/null @@ -1,8 +0,0 @@ -import a.A; -; -import b.B; - -class Test { - A a; - B b; -} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/CL240367479.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/CL240367479.input deleted file mode 100644 index be76390d7..000000000 --- a/core/src/test/resources/com/google/googlejavaformat/java/testdata/CL240367479.input +++ /dev/null @@ -1,6 +0,0 @@ -package foo;; - -import com.google.second.Foo; -import com.google.first.Bar; - -public class Blim {} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/CL240367479.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/CL240367479.output deleted file mode 100644 index 025d2370e..000000000 --- a/core/src/test/resources/com/google/googlejavaformat/java/testdata/CL240367479.output +++ /dev/null @@ -1,6 +0,0 @@ -package foo; -; -import com.google.second.Foo; -import com.google.first.Bar; - -public class Blim {} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Fields.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Fields.input index 9dc342663..d012d17a3 100644 --- a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Fields.input +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Fields.input @@ -1,5 +1,12 @@ class Fields { + int a = 1; + int b = 1; + + int c = 1; + /** Javadoc */ + int d = 1; + int x = 1; int y = 1; diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Fields.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Fields.output index 376e8b620..81ebd2ac4 100644 --- a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Fields.output +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Fields.output @@ -1,5 +1,13 @@ class Fields { + int a = 1; + int b = 1; + + int c = 1; + + /** Javadoc */ + int d = 1; + int x = 1; int y = 1; diff --git a/idea_plugin/build.gradle b/idea_plugin/build.gradle deleted file mode 100644 index 294d77e73..000000000 --- a/idea_plugin/build.gradle +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2017 Google Inc. 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 - * - * http://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 { - id "org.jetbrains.intellij" version "1.4.0" -} - -repositories { - mavenCentral() -} - -ext { - googleJavaFormatVersion = "1.15.0" -} - -apply plugin: "org.jetbrains.intellij" -apply plugin: "java" - -sourceCompatibility = JavaVersion.VERSION_11 -targetCompatibility = JavaVersion.VERSION_11 - -intellij { - pluginName = "google-java-format" - plugins = ["java"] - version = "221.3427-EAP-CANDIDATE-SNAPSHOT" -} - -patchPluginXml { - pluginDescription = "Formats source code using the google-java-format tool. This version of " + - "the plugin uses version ${googleJavaFormatVersion} of the tool." - version.set("${googleJavaFormatVersion}.0") - sinceBuild = "203" - untilBuild = "" -} - -publishPlugin { - token = project.ext.properties.jetbrainsPluginRepoToken -} - -sourceSets { - main { - java.srcDir "src" - resources.srcDir "resources" - } -} - -dependencies { - implementation "com.google.googlejavaformat:google-java-format:${googleJavaFormatVersion}" -} diff --git a/idea_plugin/build.gradle.kts b/idea_plugin/build.gradle.kts new file mode 100644 index 000000000..474c24d79 --- /dev/null +++ b/idea_plugin/build.gradle.kts @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Google Inc. 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 + * + * http://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 { id("org.jetbrains.intellij") version "1.13.3" } + +apply(plugin = "org.jetbrains.intellij") + +apply(plugin = "java") + +repositories { mavenCentral() } + +val googleJavaFormatVersion = "1.16.0" + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +intellij { + pluginName.set("google-java-format") + plugins.set(listOf("java")) + version.set("2021.3") +} + +tasks { + patchPluginXml { + version.set("${googleJavaFormatVersion}.2") + sinceBuild.set("213") + untilBuild.set("") + } + + publishPlugin { + val jetbrainsPluginRepoToken: String by project + token.set(jetbrainsPluginRepoToken) + } + + withType().configureEach { + jvmArgs( + "--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ) + } +} + +dependencies { + implementation("com.google.googlejavaformat:google-java-format:${googleJavaFormatVersion}") + testImplementation("junit:junit:4.13.2") + testImplementation("com.google.truth:truth:1.1.3") +} diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/CodeStyleManagerDecorator.java b/idea_plugin/src/com/google/googlejavaformat/intellij/CodeStyleManagerDecorator.java deleted file mode 100644 index af5da957a..000000000 --- a/idea_plugin/src/com/google/googlejavaformat/intellij/CodeStyleManagerDecorator.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2015 Google Inc. 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 - * - * http://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 com.google.googlejavaformat.intellij; - -import com.intellij.formatting.FormattingMode; -import com.intellij.lang.ASTNode; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.codeStyle.ChangedRangesInfo; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.codeStyle.DocCommentSettings; -import com.intellij.psi.codeStyle.FormattingModeAwareIndentAdjuster; -import com.intellij.psi.codeStyle.Indent; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.ThrowableRunnable; -import java.util.Collection; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.jetbrains.annotations.NotNull; - -/** - * Decorates the {@link CodeStyleManager} abstract class by delegating to a concrete implementation - * instance (likely IntelliJ's default instance). - */ -@SuppressWarnings("deprecation") -class CodeStyleManagerDecorator extends CodeStyleManager - implements FormattingModeAwareIndentAdjuster { - - private final CodeStyleManager delegate; - - CodeStyleManagerDecorator(CodeStyleManager delegate) { - this.delegate = delegate; - } - - CodeStyleManager getDelegate() { - return delegate; - } - - @Override - public @NotNull Project getProject() { - return delegate.getProject(); - } - - @Override - public @NotNull PsiElement reformat(@NotNull PsiElement element) - throws IncorrectOperationException { - return delegate.reformat(element); - } - - @Override - public @NotNull PsiElement reformat(@NotNull PsiElement element, boolean canChangeWhiteSpacesOnly) - throws IncorrectOperationException { - return delegate.reformat(element, canChangeWhiteSpacesOnly); - } - - @Override - public PsiElement reformatRange(@NotNull PsiElement element, int startOffset, int endOffset) - throws IncorrectOperationException { - return delegate.reformatRange(element, startOffset, endOffset); - } - - @Override - public PsiElement reformatRange( - @NotNull PsiElement element, int startOffset, int endOffset, boolean canChangeWhiteSpacesOnly) - throws IncorrectOperationException { - return delegate.reformatRange(element, startOffset, endOffset, canChangeWhiteSpacesOnly); - } - - @Override - public void reformatText(@NotNull PsiFile file, int startOffset, int endOffset) - throws IncorrectOperationException { - delegate.reformatText(file, startOffset, endOffset); - } - - @Override - public void reformatText(@NotNull PsiFile file, @NotNull Collection ranges) - throws IncorrectOperationException { - delegate.reformatText(file, ranges); - } - - @Override - public void reformatTextWithContext( - @NotNull PsiFile psiFile, @NotNull ChangedRangesInfo changedRangesInfo) - throws IncorrectOperationException { - delegate.reformatTextWithContext(psiFile, changedRangesInfo); - } - - @Override - public void reformatTextWithContext( - @NotNull PsiFile file, @NotNull Collection ranges) - throws IncorrectOperationException { - delegate.reformatTextWithContext(file, ranges); - } - - @Override - public void adjustLineIndent(@NotNull PsiFile file, TextRange rangeToAdjust) - throws IncorrectOperationException { - delegate.adjustLineIndent(file, rangeToAdjust); - } - - @Override - public int adjustLineIndent(@NotNull PsiFile file, int offset) - throws IncorrectOperationException { - return delegate.adjustLineIndent(file, offset); - } - - @Override - public int adjustLineIndent(@NotNull Document document, int offset) { - return delegate.adjustLineIndent(document, offset); - } - - public void scheduleIndentAdjustment(@NotNull Document document, int offset) { - delegate.scheduleIndentAdjustment(document, offset); - } - - @Override - public boolean isLineToBeIndented(@NotNull PsiFile file, int offset) { - return delegate.isLineToBeIndented(file, offset); - } - - @Override - @Nullable - public String getLineIndent(@NotNull PsiFile file, int offset) { - return delegate.getLineIndent(file, offset); - } - - @Override - @Nullable - public String getLineIndent(@NotNull PsiFile file, int offset, FormattingMode mode) { - return delegate.getLineIndent(file, offset, mode); - } - - @Override - @Nullable - public String getLineIndent(@NotNull Document document, int offset) { - return delegate.getLineIndent(document, offset); - } - - @Override - public Indent getIndent(String text, FileType fileType) { - return delegate.getIndent(text, fileType); - } - - @Override - public String fillIndent(Indent indent, FileType fileType) { - return delegate.fillIndent(indent, fileType); - } - - @Override - public Indent zeroIndent() { - return delegate.zeroIndent(); - } - - @Override - public void reformatNewlyAddedElement(@NotNull ASTNode block, @NotNull ASTNode addedElement) - throws IncorrectOperationException { - delegate.reformatNewlyAddedElement(block, addedElement); - } - - @Override - public boolean isSequentialProcessingAllowed() { - return delegate.isSequentialProcessingAllowed(); - } - - @Override - public void performActionWithFormatterDisabled(Runnable r) { - delegate.performActionWithFormatterDisabled(r); - } - - @Override - public void performActionWithFormatterDisabled(ThrowableRunnable r) - throws T { - delegate.performActionWithFormatterDisabled(r); - } - - @Override - public T performActionWithFormatterDisabled(Computable r) { - return delegate.performActionWithFormatterDisabled(r); - } - - @Override - public int getSpacing(@NotNull PsiFile file, int offset) { - return delegate.getSpacing(file, offset); - } - - @Override - public int getMinLineFeeds(@NotNull PsiFile file, int offset) { - return delegate.getMinLineFeeds(file, offset); - } - - @Override - public void runWithDocCommentFormattingDisabled( - @NotNull PsiFile file, @NotNull Runnable runnable) { - delegate.runWithDocCommentFormattingDisabled(file, runnable); - } - - @Override - public @NotNull DocCommentSettings getDocCommentSettings(@NotNull PsiFile file) { - return delegate.getDocCommentSettings(file); - } - - // From FormattingModeAwareIndentAdjuster - - /** Uses same fallback as {@link CodeStyleManager#getCurrentFormattingMode}. */ - @Override - public FormattingMode getCurrentFormattingMode() { - if (delegate instanceof FormattingModeAwareIndentAdjuster) { - return ((FormattingModeAwareIndentAdjuster) delegate).getCurrentFormattingMode(); - } - return FormattingMode.REFORMAT; - } - - @Override - public int adjustLineIndent( - final @NotNull Document document, final int offset, FormattingMode mode) - throws IncorrectOperationException { - if (delegate instanceof FormattingModeAwareIndentAdjuster) { - return ((FormattingModeAwareIndentAdjuster) delegate) - .adjustLineIndent(document, offset, mode); - } - return offset; - } - - @Override - public void scheduleReformatWhenSettingsComputed(@NotNull PsiFile file) { - delegate.scheduleReformatWhenSettingsComputed(file); - } -} diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/FormatterUtil.java b/idea_plugin/src/com/google/googlejavaformat/intellij/FormatterUtil.java deleted file mode 100644 index a5e69c93d..000000000 --- a/idea_plugin/src/com/google/googlejavaformat/intellij/FormatterUtil.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015 Google Inc. 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 - * - * http://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 com.google.googlejavaformat.intellij; - -import static com.google.common.base.Preconditions.checkState; - -import com.google.common.collect.BoundType; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Range; -import com.google.googlejavaformat.java.Formatter; -import com.google.googlejavaformat.java.FormatterException; -import com.intellij.openapi.util.TextRange; -import java.util.Collection; -import java.util.Map; -import java.util.stream.Collectors; - -final class FormatterUtil { - - private FormatterUtil() {} - - static Map getReplacements( - Formatter formatter, String text, Collection ranges) { - try { - ImmutableMap.Builder replacements = ImmutableMap.builder(); - formatter - .getFormatReplacements(text, toRanges(ranges)) - .forEach( - replacement -> - replacements.put( - toTextRange(replacement.getReplaceRange()), - replacement.getReplacementString())); - return replacements.build(); - } catch (FormatterException e) { - return ImmutableMap.of(); - } - } - - private static Collection> toRanges(Collection textRanges) { - return textRanges.stream() - .map(textRange -> Range.closedOpen(textRange.getStartOffset(), textRange.getEndOffset())) - .collect(Collectors.toList()); - } - - private static TextRange toTextRange(Range range) { - checkState( - range.lowerBoundType().equals(BoundType.CLOSED) - && range.upperBoundType().equals(BoundType.OPEN)); - return new TextRange(range.lowerEndpoint(), range.upperEndpoint()); - } -} diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatCodeStyleManager.java b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatCodeStyleManager.java deleted file mode 100644 index c9aa64a67..000000000 --- a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatCodeStyleManager.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2015 Google Inc. 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 - * - * http://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 com.google.googlejavaformat.intellij; - -import static java.util.Comparator.comparing; - -import com.google.common.collect.ImmutableList; -import com.google.googlejavaformat.java.Formatter; -import com.google.googlejavaformat.java.JavaFormatterOptions; -import com.google.googlejavaformat.java.JavaFormatterOptions.Style; -import com.intellij.ide.highlighter.JavaFileType; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.codeStyle.ChangedRangesInfo; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.impl.CheckUtil; -import com.intellij.util.IncorrectOperationException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; -import org.jetbrains.annotations.NotNull; - -/** - * A {@link CodeStyleManager} implementation which formats .java files with google-java-format. - * Formatting of all other types of files is delegated to IntelliJ's default implementation. - */ -class GoogleJavaFormatCodeStyleManager extends CodeStyleManagerDecorator { - - public GoogleJavaFormatCodeStyleManager(@NotNull CodeStyleManager original) { - super(original); - } - - @Override - public void reformatText(@NotNull PsiFile file, int startOffset, int endOffset) - throws IncorrectOperationException { - if (overrideFormatterForFile(file)) { - formatInternal(file, ImmutableList.of(new TextRange(startOffset, endOffset))); - } else { - super.reformatText(file, startOffset, endOffset); - } - } - - @Override - public void reformatText(@NotNull PsiFile file, @NotNull Collection ranges) - throws IncorrectOperationException { - if (overrideFormatterForFile(file)) { - formatInternal(file, ranges); - } else { - super.reformatText(file, ranges); - } - } - - @Override - public void reformatTextWithContext(@NotNull PsiFile file, @NotNull ChangedRangesInfo info) - throws IncorrectOperationException { - List ranges = new ArrayList<>(); - if (info.insertedRanges != null) { - ranges.addAll(info.insertedRanges); - } - ranges.addAll(info.allChangedRanges); - reformatTextWithContext(file, ranges); - } - - @Override - public void reformatTextWithContext( - @NotNull PsiFile file, @NotNull Collection ranges) { - if (overrideFormatterForFile(file)) { - formatInternal(file, ranges); - } else { - super.reformatTextWithContext(file, ranges); - } - } - - @Override - public PsiElement reformatRange( - @NotNull PsiElement element, - int startOffset, - int endOffset, - boolean canChangeWhiteSpacesOnly) { - // Only handle elements that are PsiFile for now -- otherwise we need to search for some - // element within the file at new locations given the original startOffset and endOffsets - // to serve as the return value. - PsiFile file = element instanceof PsiFile ? (PsiFile) element : null; - if (file != null && canChangeWhiteSpacesOnly && overrideFormatterForFile(file)) { - formatInternal(file, ImmutableList.of(new TextRange(startOffset, endOffset))); - return file; - } else { - return super.reformatRange(element, startOffset, endOffset, canChangeWhiteSpacesOnly); - } - } - - /** Return whether this formatter can handle formatting the given file. */ - private boolean overrideFormatterForFile(PsiFile file) { - return JavaFileType.INSTANCE.equals(file.getFileType()) - && GoogleJavaFormatSettings.getInstance(getProject()).isEnabled(); - } - - private void formatInternal(PsiFile file, Collection ranges) { - ApplicationManager.getApplication().assertWriteAccessAllowed(); - PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject()); - documentManager.commitAllDocuments(); - CheckUtil.checkWritable(file); - - Document document = documentManager.getDocument(file); - - if (document == null) { - return; - } - // If there are postponed PSI changes (e.g., during a refactoring), just abort. - // If we apply them now, then the incoming text ranges may no longer be valid. - if (documentManager.isDocumentBlockedByPsi(document)) { - return; - } - - format(document, ranges); - } - - /** - * Format the ranges of the given document. - * - *

Overriding methods will need to modify the document with the result of the external - * formatter (usually using {@link #performReplacements(Document, Map)}). - */ - private void format(Document document, Collection ranges) { - Style style = GoogleJavaFormatSettings.getInstance(getProject()).getStyle(); - Formatter formatter = - new Formatter(JavaFormatterOptions.builder().style(style).reorderModifiers(false).build()); - performReplacements( - document, FormatterUtil.getReplacements(formatter, document.getText(), ranges)); - } - - private void performReplacements( - final Document document, final Map replacements) { - - if (replacements.isEmpty()) { - return; - } - - TreeMap sorted = new TreeMap<>(comparing(TextRange::getStartOffset)); - sorted.putAll(replacements); - WriteCommandAction.runWriteCommandAction( - getProject(), - () -> { - for (Entry entry : sorted.descendingMap().entrySet()) { - document.replaceString( - entry.getKey().getStartOffset(), entry.getKey().getEndOffset(), entry.getValue()); - } - PsiDocumentManager.getInstance(getProject()).commitDocument(document); - }); - } -} diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatInstaller.java b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatInstaller.java deleted file mode 100644 index c60673602..000000000 --- a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatInstaller.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2016 Google Inc. 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 - * - * http://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 com.google.googlejavaformat.intellij; - -import static com.google.common.base.Preconditions.checkState; - -import com.intellij.ide.plugins.IdeaPluginDescriptor; -import com.intellij.ide.plugins.PluginManagerCore; -import com.intellij.openapi.extensions.PluginId; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManagerListener; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.serviceContainer.ComponentManagerImpl; -import org.jetbrains.annotations.NotNull; - -/** - * A component that replaces the default IntelliJ {@link CodeStyleManager} with one that formats via - * google-java-format. - */ -final class GoogleJavaFormatInstaller implements ProjectManagerListener { - - @Override - public void projectOpened(@NotNull Project project) { - installFormatter(project); - } - - private static void installFormatter(Project project) { - CodeStyleManager currentManager = CodeStyleManager.getInstance(project); - - if (currentManager instanceof GoogleJavaFormatCodeStyleManager) { - currentManager = ((GoogleJavaFormatCodeStyleManager) currentManager).getDelegate(); - } - - setManager(project, new GoogleJavaFormatCodeStyleManager(currentManager)); - } - - private static void setManager(Project project, CodeStyleManager newManager) { - ComponentManagerImpl platformComponentManager = (ComponentManagerImpl) project; - IdeaPluginDescriptor plugin = PluginManagerCore.getPlugin(PluginId.getId("google-java-format")); - checkState(plugin != null, "Couldn't locate our own PluginDescriptor."); - platformComponentManager.registerServiceInstance(CodeStyleManager.class, newManager, plugin); - } -} diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form similarity index 100% rename from idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form rename to idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java similarity index 100% rename from idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java rename to idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java new file mode 100644 index 000000000..9d2d7a595 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java @@ -0,0 +1,141 @@ +/* + * Copyright 2023 Google Inc. 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 + * + * http://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 com.google.googlejavaformat.intellij; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Range; +import com.google.googlejavaformat.java.Formatter; +import com.google.googlejavaformat.java.FormatterException; +import com.google.googlejavaformat.java.JavaFormatterOptions; +import com.google.googlejavaformat.java.JavaFormatterOptions.Style; +import com.intellij.formatting.service.AsyncDocumentFormattingService; +import com.intellij.formatting.service.AsyncFormattingRequest; +import com.intellij.ide.highlighter.JavaFileType; +import com.intellij.lang.ImportOptimizer; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiFile; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.jetbrains.annotations.NotNull; + +/** Uses {@code google-java-format} to reformat code. */ +public class GoogleJavaFormatFormattingService extends AsyncDocumentFormattingService { + + public static final ImmutableSet IMPORT_OPTIMIZERS = + ImmutableSet.of(new GoogleJavaFormatImportOptimizer()); + + @Override + protected FormattingTask createFormattingTask(AsyncFormattingRequest request) { + Project project = request.getContext().getProject(); + + if (!JreConfigurationChecker.checkJreConfiguration(project)) { + return null; + } + + Style style = GoogleJavaFormatSettings.getInstance(project).getStyle(); + Formatter formatter = createFormatter(style, request.canChangeWhitespaceOnly()); + return new GoogleJavaFormatFormattingTask(formatter, request); + } + + @Override + protected String getNotificationGroupId() { + return Notifications.PARSING_ERROR_NOTIFICATION_GROUP; + } + + @Override + protected String getName() { + return "google-java-format"; + } + + private static Formatter createFormatter(Style style, boolean canChangeWhiteSpaceOnly) { + JavaFormatterOptions.Builder optBuilder = JavaFormatterOptions.builder().style(style); + if (canChangeWhiteSpaceOnly) { + optBuilder.formatJavadoc(false).reorderModifiers(false); + } + return new Formatter(optBuilder.build()); + } + + @Override + public @NotNull Set getFeatures() { + return Set.of(Feature.FORMAT_FRAGMENTS, Feature.OPTIMIZE_IMPORTS); + } + + @Override + public boolean canFormat(@NotNull PsiFile file) { + return JavaFileType.INSTANCE.equals(file.getFileType()) + && GoogleJavaFormatSettings.getInstance(file.getProject()).isEnabled(); + } + + @Override + public @NotNull Set getImportOptimizers(@NotNull PsiFile file) { + return IMPORT_OPTIMIZERS; + } + + private static final class GoogleJavaFormatFormattingTask implements FormattingTask { + private final Formatter formatter; + private final AsyncFormattingRequest request; + + private GoogleJavaFormatFormattingTask(Formatter formatter, AsyncFormattingRequest request) { + this.formatter = formatter; + this.request = request; + } + + @Override + public void run() { + try { + String formattedText = formatter.formatSource(request.getDocumentText(), toRanges(request)); + request.onTextReady(formattedText); + } catch (FormatterException e) { + request.onError( + Notifications.PARSING_ERROR_TITLE, + Notifications.parsingErrorMessage(request.getContext().getContainingFile().getName())); + } + } + + private static Collection> toRanges(AsyncFormattingRequest request) { + if (isWholeFile(request)) { + // The IDE sometimes passes invalid ranges when the file is unsaved before invoking the + // formatter. So this is a workaround for that issue. + return ImmutableList.of(Range.closedOpen(0, request.getDocumentText().length())); + } + return request.getFormattingRanges().stream() + .map(textRange -> Range.closedOpen(textRange.getStartOffset(), textRange.getEndOffset())) + .collect(ImmutableList.toImmutableList()); + } + + private static boolean isWholeFile(AsyncFormattingRequest request) { + List ranges = request.getFormattingRanges(); + return ranges.size() == 1 + && ranges.get(0).getStartOffset() == 0 + // using greater than or equal because ranges are sometimes passed inaccurately + && ranges.get(0).getEndOffset() >= request.getDocumentText().length(); + } + + @Override + public boolean isRunUnderProgress() { + return true; + } + + @Override + public boolean cancel() { + return false; + } + } +} diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java new file mode 100644 index 000000000..498c88526 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Google Inc. 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 + * + * http://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 com.google.googlejavaformat.intellij; + +import com.google.common.util.concurrent.Runnables; +import com.google.googlejavaformat.java.FormatterException; +import com.google.googlejavaformat.java.ImportOrderer; +import com.google.googlejavaformat.java.JavaFormatterOptions; +import com.google.googlejavaformat.java.RemoveUnusedImports; +import com.intellij.ide.highlighter.JavaFileType; +import com.intellij.lang.ImportOptimizer; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +/** Uses {@code google-java-format} to optimize imports. */ +public class GoogleJavaFormatImportOptimizer implements ImportOptimizer { + + @Override + public boolean supports(@NotNull PsiFile file) { + return JavaFileType.INSTANCE.equals(file.getFileType()) + && GoogleJavaFormatSettings.getInstance(file.getProject()).isEnabled(); + } + + @Override + public @NotNull Runnable processFile(@NotNull PsiFile file) { + Project project = file.getProject(); + + if (!JreConfigurationChecker.checkJreConfiguration(file.getProject())) { + return Runnables.doNothing(); + } + + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + Document document = documentManager.getDocument(file); + + if (document == null) { + return Runnables.doNothing(); + } + + JavaFormatterOptions.Style style = GoogleJavaFormatSettings.getInstance(project).getStyle(); + + String text; + try { + text = + ImportOrderer.reorderImports( + RemoveUnusedImports.removeUnusedImports(document.getText()), style); + } catch (FormatterException e) { + Notifications.displayParsingErrorNotification(project, file.getName()); + return Runnables.doNothing(); + } + + return () -> document.setText(text); + } +} diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java similarity index 90% rename from idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java rename to idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java index 1e92a4bdd..ee187c00d 100644 --- a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java @@ -18,7 +18,6 @@ import com.google.googlejavaformat.java.JavaFormatterOptions; import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.project.Project; @@ -30,10 +29,16 @@ storages = {@Storage("google-java-format.xml")}) class GoogleJavaFormatSettings implements PersistentStateComponent { + private final Project project; + private State state = new State(); + GoogleJavaFormatSettings(Project project) { + this.project = project; + } + static GoogleJavaFormatSettings getInstance(Project project) { - return ServiceManager.getService(project, GoogleJavaFormatSettings.class); + return project.getService(GoogleJavaFormatSettings.class); } @Nullable @@ -56,6 +61,9 @@ void setEnabled(boolean enabled) { } void setEnabled(EnabledState enabled) { + if (enabled.equals(EnabledState.ENABLED)) { + JreConfigurationChecker.checkJreConfiguration(project); + } state.enabled = enabled; } diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/InitialConfigurationProjectManagerListener.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/InitialConfigurationStartupActivity.java similarity index 79% rename from idea_plugin/src/com/google/googlejavaformat/intellij/InitialConfigurationProjectManagerListener.java rename to idea_plugin/src/main/java/com/google/googlejavaformat/intellij/InitialConfigurationStartupActivity.java index 1906347f7..940def655 100644 --- a/idea_plugin/src/com/google/googlejavaformat/intellij/InitialConfigurationProjectManagerListener.java +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/InitialConfigurationStartupActivity.java @@ -21,22 +21,24 @@ import com.intellij.notification.NotificationGroupManager; import com.intellij.notification.NotificationType; import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManagerListener; +import com.intellij.openapi.startup.StartupActivity; import org.jetbrains.annotations.NotNull; -final class InitialConfigurationProjectManagerListener implements ProjectManagerListener { +final class InitialConfigurationStartupActivity implements StartupActivity.Background { private static final String NOTIFICATION_TITLE = "Enable google-java-format"; private static final NotificationGroup NOTIFICATION_GROUP = NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_TITLE); @Override - public void projectOpened(@NotNull Project project) { + public void runActivity(@NotNull Project project) { GoogleJavaFormatSettings settings = GoogleJavaFormatSettings.getInstance(project); if (settings.isUninitialized()) { settings.setEnabled(false); displayNewUserNotification(project, settings); + } else if (settings.isEnabled()) { + JreConfigurationChecker.checkJreConfiguration(project); } } @@ -47,11 +49,12 @@ private void displayNewUserNotification(Project project, GoogleJavaFormatSetting NOTIFICATION_TITLE, "The google-java-format plugin is disabled by default. " + "Enable for this project.", - NotificationType.INFORMATION, - (n, e) -> { - settings.setEnabled(true); - n.expire(); - }); + NotificationType.INFORMATION); + notification.setListener( + (n, e) -> { + settings.setEnabled(true); + n.expire(); + }); notification.notify(project); } } diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java new file mode 100644 index 000000000..5084b6a39 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java @@ -0,0 +1,104 @@ +/* + * Copyright 2023 Google Inc. 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 + * + * http://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 com.google.googlejavaformat.intellij; + +import com.google.common.base.Suppliers; +import com.intellij.ide.ui.IdeUiService; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import java.util.function.Supplier; + +class JreConfigurationChecker { + + private static final Supplier hasAccess = + Suppliers.memoize(JreConfigurationChecker::checkJreConfiguration); + private static final Logger logger = Logger.getInstance(JreConfigurationChecker.class); + + private final Project project; + + public JreConfigurationChecker(Project project) { + this.project = project; + } + + static boolean checkJreConfiguration(Project project) { + var success = hasAccess.get(); + if (!success) { + project.getService(JreConfigurationChecker.class).displayConfigurationErrorNotification(); + } + return success; + } + + /** + * Determine whether the JRE is configured to work with the google-java-format plugin. If not, + * display a notification with instructions and return false. + */ + private static boolean checkJreConfiguration() { + try { + return testClassAccess( + "com.sun.tools.javac.api.JavacTrees", + "com.sun.tools.javac.code.Flags", + "com.sun.tools.javac.file.JavacFileManager", + "com.sun.tools.javac.parser.JavacParser", + "com.sun.tools.javac.tree.JCTree", + "com.sun.tools.javac.util.Log"); + } catch (ClassNotFoundException e) { + logger.error("Error checking jre configuration for google-java-format", e); + return false; + } + } + + private static boolean testClassAccess(String... classNames) throws ClassNotFoundException { + for (String className : classNames) { + if (!testClassAccess(className)) { + return false; + } + } + return true; + } + + private static boolean testClassAccess(String className) throws ClassNotFoundException { + Class klass = Class.forName(className); + return klass + .getModule() + // isExported returns true if the package is either open or exported. Either one is + // sufficient + // to run the google-java-format code (even though the documentation specifies --add-opens). + .isExported( + klass.getPackageName(), + JreConfigurationChecker.class.getClassLoader().getUnnamedModule()); + } + + private void displayConfigurationErrorNotification() { + Notification notification = + new Notification( + "Configure JRE for google-java-format", + "Configure the JRE for google-java-format", + "The google-java-format plugin needs additional configuration before it can be used. " + + "Follow the instructions here.", + NotificationType.INFORMATION); + notification.setListener( + (n, e) -> { + IdeUiService.getInstance() + .browse( + "https://github.com/google/google-java-format/blob/master/README.md#intellij-jre-config"); + n.expire(); + }); + notification.notify(project); + } +} diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java new file mode 100644 index 000000000..d32aa98b9 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Google Inc. 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 + * + * http://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 com.google.googlejavaformat.intellij; + +import com.intellij.formatting.service.FormattingNotificationService; +import com.intellij.openapi.project.Project; + +class Notifications { + + static final String PARSING_ERROR_NOTIFICATION_GROUP = "google-java-format parsing error"; + static final String PARSING_ERROR_TITLE = PARSING_ERROR_NOTIFICATION_GROUP; + + static String parsingErrorMessage(String filename) { + return "google-java-format failed. Does " + filename + " have syntax errors?"; + } + + static void displayParsingErrorNotification(Project project, String filename) { + FormattingNotificationService.getInstance(project) + .reportError( + Notifications.PARSING_ERROR_NOTIFICATION_GROUP, + Notifications.PARSING_ERROR_TITLE, + Notifications.parsingErrorMessage(filename)); + } +} diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/UiFormatterStyle.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/UiFormatterStyle.java similarity index 100% rename from idea_plugin/src/com/google/googlejavaformat/intellij/UiFormatterStyle.java rename to idea_plugin/src/main/java/com/google/googlejavaformat/intellij/UiFormatterStyle.java diff --git a/idea_plugin/resources/META-INF/plugin.xml b/idea_plugin/src/main/resources/META-INF/plugin.xml similarity index 61% rename from idea_plugin/resources/META-INF/plugin.xml rename to idea_plugin/src/main/resources/META-INF/plugin.xml index 42d5f3b31..42011a208 100644 --- a/idea_plugin/resources/META-INF/plugin.xml +++ b/idea_plugin/src/main/resources/META-INF/plugin.xml @@ -22,13 +22,29 @@ Google - - com.intellij.java + com.intellij.modules.java + com.intellij.modules.lang + com.intellij.modules.platform + + + This plugin requires additional IDE configuration. For more information, + read + the documentation. + ]]> +

1.16.0.2
+
Disable AD_HOC_FORMATTING, which should stop the formatter from running so often when it wasn't specifically requested. +
1.16.0.1
+
When the plugin isn't configured correctly, show the error on every + format command. Previously it was only being shown at startup and going + unnoticed. +
1.16.0.0
+
Updated to use google-java-format 1.16.0.
+
Use the new IDE formatting APIs for a simplified plugin.
+
Optimize Imports now uses google-java-format.
1.15.0.0
Updated to use google-java-format 1.15.0.
1.14.0.0
@@ -62,23 +78,23 @@ ]]> - - - - - + + + + + diff --git a/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingServiceTest.java b/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingServiceTest.java new file mode 100644 index 000000000..fec086c68 --- /dev/null +++ b/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingServiceTest.java @@ -0,0 +1,250 @@ +/* + * Copyright 2023 Google Inc. 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 + * + * http://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 com.google.googlejavaformat.intellij; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.googlejavaformat.intellij.GoogleJavaFormatSettings.State; +import com.google.googlejavaformat.java.Formatter; +import com.google.googlejavaformat.java.JavaFormatterOptions; +import com.google.googlejavaformat.java.JavaFormatterOptions.Style; +import com.intellij.formatting.service.AsyncFormattingRequest; +import com.intellij.formatting.service.FormattingService; +import com.intellij.formatting.service.FormattingServiceUtil; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.testFramework.ExtensionTestUtil; +import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; +import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture; +import com.intellij.testFramework.fixtures.JavaTestFixtureFactory; +import com.intellij.testFramework.fixtures.TestFixtureBuilder; +import java.io.IOException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class GoogleJavaFormatFormattingServiceTest { + private JavaCodeInsightTestFixture fixture; + private GoogleJavaFormatSettings settings; + private DelegatingFormatter delegatingFormatter; + + @Before + public void setUp() throws Exception { + TestFixtureBuilder projectBuilder = + IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getClass().getName()); + fixture = + JavaTestFixtureFactory.getFixtureFactory() + .createCodeInsightFixture(projectBuilder.getFixture()); + fixture.setUp(); + + delegatingFormatter = new DelegatingFormatter(); + ExtensionTestUtil.maskExtensions( + FormattingService.EP_NAME, + ImmutableList.of(delegatingFormatter), + fixture.getProjectDisposable()); + + settings = GoogleJavaFormatSettings.getInstance(fixture.getProject()); + State resetState = new State(); + resetState.setEnabled("true"); + settings.loadState(resetState); + } + + @After + public void tearDown() throws Exception { + fixture.tearDown(); + } + + @Test + public void defaultFormatSettings() throws Exception { + PsiFile file = + createPsiFile( + "com/foo/FormatTest.java", + "package com.foo;", + "public class FormatTest {", + "static final String CONST_STR = \"Hello\";", + "}"); + String origText = file.getText(); + CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject()); + WriteCommandAction.runWriteCommandAction( + file.getProject(), () -> manager.reformatText(file, 0, file.getTextLength())); + + assertThat(file.getText()).isEqualTo(new Formatter().formatSource(origText)); + assertThat(delegatingFormatter.wasInvoked()).isTrue(); + } + + @Test + public void aospStyle() throws Exception { + settings.setStyle(Style.AOSP); + PsiFile file = + createPsiFile( + "com/foo/FormatTest.java", + "package com.foo;", + "public class FormatTest {", + "static final String CONST_STR = \"Hello\";", + "}"); + String origText = file.getText(); + CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject()); + WriteCommandAction.runWriteCommandAction( + file.getProject(), () -> manager.reformatText(file, 0, file.getTextLength())); + + assertThat(file.getText()) + .isEqualTo( + new Formatter(JavaFormatterOptions.builder().style(Style.AOSP).build()) + .formatSource(origText)); + assertThat(delegatingFormatter.wasInvoked()).isTrue(); + } + + @Test + public void canChangeWhitespaceOnlyDoesNotReorderModifiers() throws Exception { + settings.setStyle(Style.GOOGLE); + PsiFile file = + createPsiFile( + "com/foo/FormatTest.java", + "package com.foo;", + "public class FormatTest {", + "final static String CONST_STR = \"Hello\";", + "}"); + CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject()); + var offset = file.getText().indexOf("final static"); + WriteCommandAction.runWriteCommandAction( + file.getProject(), + () -> + FormattingServiceUtil.formatElement( + file.findElementAt(offset), /* canChangeWhitespaceOnly= */ true)); + + // In non-whitespace mode, this would flip the order of these modifiers. (Also check for leading + // spaces to make sure the formatter actually ran. + assertThat(file.getText()).containsMatch(" final static"); + assertThat(delegatingFormatter.wasInvoked()).isTrue(); + } + + @Test + public void canChangeWhitespaceOnlyDoesNotReformatJavadoc() throws Exception { + settings.setStyle(Style.GOOGLE); + PsiFile file = + createPsiFile( + "com/foo/FormatTest.java", + "package com.foo;", + "public class FormatTest {", + "/**", + " * hello", + " */", + "static final String CONST_STR = \"Hello\";", + "}"); + CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject()); + var offset = file.getText().indexOf("hello"); + WriteCommandAction.runWriteCommandAction( + file.getProject(), + () -> + FormattingServiceUtil.formatElement( + file.findElementAt(offset), /* canChangeWhitespaceOnly= */ true)); + + // In non-whitespace mode, this would join the Javadoc into a single line. + assertThat(file.getText()).containsMatch(" \\* hello"); + // Also check for leading spaces to make sure the formatter actually ran. (Technically, this is + // outside the range that we asked to be formatted, but gjf will still format it.) + assertThat(file.getText()).containsMatch(" static final"); + assertThat(delegatingFormatter.wasInvoked()).isTrue(); + } + + @Test + public void canChangeNonWhitespaceReordersModifiers() throws Exception { + settings.setStyle(Style.GOOGLE); + PsiFile file = + createPsiFile( + "com/foo/FormatTest.java", + "package com.foo;", + "public class FormatTest {", + "final static String CONST_STR = \"Hello\";", + "}"); + CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject()); + var offset = file.getText().indexOf("final static"); + WriteCommandAction.runWriteCommandAction( + file.getProject(), + () -> + FormattingServiceUtil.formatElement( + file.findElementAt(offset), /* canChangeWhitespaceOnly= */ false)); + + assertThat(file.getText()).containsMatch("static final"); + assertThat(delegatingFormatter.wasInvoked()).isTrue(); + } + + @Test + public void canChangeNonWhitespaceReformatsJavadoc() throws Exception { + settings.setStyle(Style.GOOGLE); + PsiFile file = + createPsiFile( + "com/foo/FormatTest.java", + "package com.foo;", + "public class FormatTest {", + "/**", + " * hello", + " */", + "static final String CONST_STR = \"Hello\";", + "}"); + CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject()); + var offset = file.getText().indexOf("hello"); + WriteCommandAction.runWriteCommandAction( + file.getProject(), + () -> + FormattingServiceUtil.formatElement( + file.findElementAt(offset), /* canChangeWhitespaceOnly= */ false)); + + assertThat(file.getText()).containsMatch("/\\*\\* hello \\*/"); + assertThat(delegatingFormatter.wasInvoked()).isTrue(); + } + + private PsiFile createPsiFile(String path, String... contents) throws IOException { + VirtualFile virtualFile = + fixture.getTempDirFixture().createFile(path, String.join("\n", contents)); + fixture.configureFromExistingVirtualFile(virtualFile); + PsiFile psiFile = fixture.getFile(); + assertThat(psiFile).isNotNull(); + return psiFile; + } + + private static final class DelegatingFormatter extends GoogleJavaFormatFormattingService { + + private boolean invoked = false; + + private boolean wasInvoked() { + return invoked; + } + + @Override + protected FormattingTask createFormattingTask(AsyncFormattingRequest request) { + FormattingTask delegateTask = super.createFormattingTask(request); + return new FormattingTask() { + @Override + public boolean cancel() { + return delegateTask.cancel(); + } + + @Override + public void run() { + invoked = true; + delegateTask.run(); + } + }; + } + } +} diff --git a/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizerTest.java b/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizerTest.java new file mode 100644 index 000000000..ad9fe2744 --- /dev/null +++ b/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizerTest.java @@ -0,0 +1,168 @@ +/* + * Copyright 2023 Google Inc. 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 + * + * http://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 com.google.googlejavaformat.intellij; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.googlejavaformat.intellij.GoogleJavaFormatSettings.State; +import com.intellij.codeInsight.actions.OptimizeImportsProcessor; +import com.intellij.formatting.service.FormattingService; +import com.intellij.lang.ImportOptimizer; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.testFramework.ExtensionTestUtil; +import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; +import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture; +import com.intellij.testFramework.fixtures.JavaTestFixtureFactory; +import com.intellij.testFramework.fixtures.TestFixtureBuilder; +import java.io.IOException; +import java.util.Set; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class GoogleJavaFormatImportOptimizerTest { + private JavaCodeInsightTestFixture fixture; + private DelegatingFormatter delegatingFormatter; + + @Before + public void setUp() throws Exception { + TestFixtureBuilder projectBuilder = + IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getClass().getName()); + fixture = + JavaTestFixtureFactory.getFixtureFactory() + .createCodeInsightFixture(projectBuilder.getFixture()); + fixture.setUp(); + + delegatingFormatter = new DelegatingFormatter(); + ExtensionTestUtil.maskExtensions( + FormattingService.EP_NAME, + ImmutableList.of(delegatingFormatter), + fixture.getProjectDisposable()); + + var settings = GoogleJavaFormatSettings.getInstance(fixture.getProject()); + State resetState = new State(); + resetState.setEnabled("true"); + settings.loadState(resetState); + } + + @After + public void tearDown() throws Exception { + fixture.tearDown(); + } + + @Test + public void removesUnusedImports() throws Exception { + PsiFile file = + createPsiFile( + "com/foo/ImportTest.java", + "package com.foo;", + "import java.util.List;", + "import java.util.ArrayList;", + "import java.util.Map;", + "public class ImportTest {", + "static final Map map;", + "}"); + OptimizeImportsProcessor processor = new OptimizeImportsProcessor(file.getProject(), file); + WriteCommandAction.runWriteCommandAction( + file.getProject(), + () -> { + processor.run(); + PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments(); + }); + + assertThat(file.getText()).doesNotContain("List"); + assertThat(file.getText()).contains("import java.util.Map;"); + assertThat(delegatingFormatter.wasInvoked()).isTrue(); + } + + @Test + public void reordersImports() throws Exception { + PsiFile file = + createPsiFile( + "com/foo/ImportTest.java", + "package com.foo;", + "import java.util.List;", + "import java.util.ArrayList;", + "import java.util.Map;", + "public class ImportTest {", + "static final ArrayList arrayList;", + "static final List list;", + "static final Map map;", + "}"); + OptimizeImportsProcessor processor = new OptimizeImportsProcessor(file.getProject(), file); + WriteCommandAction.runWriteCommandAction( + file.getProject(), + () -> { + processor.run(); + PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments(); + }); + + assertThat(file.getText()) + .contains( + "import java.util.ArrayList;\n" + + "import java.util.List;\n" + + "import java.util.Map;\n"); + assertThat(delegatingFormatter.wasInvoked()).isTrue(); + } + + private PsiFile createPsiFile(String path, String... contents) throws IOException { + VirtualFile virtualFile = + fixture.getTempDirFixture().createFile(path, String.join("\n", contents)); + fixture.configureFromExistingVirtualFile(virtualFile); + PsiFile psiFile = fixture.getFile(); + assertThat(psiFile).isNotNull(); + return psiFile; + } + + private static final class DelegatingFormatter extends GoogleJavaFormatFormattingService { + + private boolean invoked = false; + + private boolean wasInvoked() { + return invoked; + } + + @Override + public @NotNull Set getImportOptimizers(@NotNull PsiFile file) { + return super.getImportOptimizers(file).stream().map(this::wrap).collect(toImmutableSet()); + } + + private ImportOptimizer wrap(ImportOptimizer delegate) { + return new ImportOptimizer() { + @Override + public boolean supports(@NotNull PsiFile file) { + return delegate.supports(file); + } + + @Override + public @NotNull Runnable processFile(@NotNull PsiFile file) { + return () -> { + invoked = true; + delegate.processFile(file).run(); + }; + } + }; + } + } +} diff --git a/pom.xml b/pom.xml index 150173ee6..43842be2b 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ com.google.googlejavaformat google-java-format-parent pom - HEAD-SNAPSHOT + 1.17.0 core @@ -235,15 +235,12 @@ jdk.compiler/com.sun.tools.javac.api jdk.compiler/com.sun.tools.javac.code jdk.compiler/com.sun.tools.javac.file jdk.compiler/com.sun.tools.javac.main jdk.compiler/com.sun.tools.javac.model jdk.compiler/com.sun.tools.javac.parser jdk.compiler/com.sun.tools.javac.processing jdk.compiler/com.sun.tools.javac.tree jdk.compiler/com.sun.tools.javac.util - jdk.compiler/com.sun.tools.javac.code jdk.compiler/com.sun.tools.javac.comp