Merge tag 'google-java-format-1.8' into master am: 0e3175afa9 am: 66bca70ecd
Original change: https://android-review.googlesource.com/c/platform/external/google-java-format/+/1342207
Change-Id: Ia13bed6e92c85968362685391324497d7348081c
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..235f69d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+.idea/
+*.ims
+*.iml
+
+.classpath
+.project
+.factorypath
+.settings/
+.apt_generated/
+
+target/
+
+bin/
+out/
+eclipse_plugin/lib/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b8a7c33
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,42 @@
+language: java
+
+notifications:
+ email:
+ recipients:
+ - google-java-format-dev+ci@google.com
+ on_success: change
+ on_failure: always
+
+jdk:
+ - openjdk11
+ - openjdk14
+ - openjdk-ea
+
+matrix:
+ allow_failures:
+ - jdk: openjdk-ea
+
+# see https://github.com/travis-ci/travis-ci/issues/8408
+before_install:
+- unset _JAVA_OPTIONS
+
+install: echo "The default Travis install script is being skipped!"
+
+# use travis-ci docker based infrastructure
+sudo: false
+
+cache:
+ directories:
+ - $HOME/.m2
+
+script:
+- mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
+- mvn test -B
+
+env:
+ global:
+ - secure: KkUX74NDDk95WR60zwN6x6pz49KAfR0zUu1thxl8Kke0+WVoIv1EBo7/e4ZXTdBKxlzQCX9Aa0OlIyUlhGJeuNIGtX16lcNyZNmKSacfrT68MpZqi+nAiYp8tnOyW/zuI+shSKHkGQOFq6c9KTtR9vG8kjr1Q9dNl/H5QjGaG1ZMiU/mGH9ompf+0nQTMDLKaEWV+SpKGjK5U1Zs2p08I9KKePbZoi9L2oAw5cH9wW8Q3pQJds6Rkwy9aecxRd4xmTza7Lb04dmByjqY8gsIjrTN0onOndBmLKTHiH5NVLKf0ilEVGiMQ1x4eCQolcRpGzxdTTKI0ahiWS59UABVoy1sXYqkIbZjpmMuGhHvbRir7YEXaG8LRUAxdWd9drJfvKQeBphQlIJKwajHSiMAdc9zisQg1UW75HSGKoPDHpzq+P7YBil2PUjk+5yUy5OytX6IebFT4KdeCO2ayu338yqb2t8q98elMoD5jwFVD0tpkLQ6xsYodClSGfMCVfP2zTkB7c4sHZV7tJS68CiNt7sCwz9CTNApFiSWMBxLKkKQ7VSBTy9bAn+phvW0u/maGsrRnehmsV3PVPtEsMlrqeMGwaPqIwx1l6otVQCnGRt3e8z3HoxY6AaBPaX0Z8lH2y+BxYhWTYzGhRxyyV666u/9yekTXmH53c7at7mau6Q=
+ - secure: VWnZcPA4esdaMJgh0Mui7K5O++AGZY3AYswufd0UUbAmnK60O6cDBOSelnr7hImDgZ09L2RWMXIVCt4b+UFXoIhqrvZKVitUXPldS6uNJeGT9p6quFf36o8Wf0ppKWnPd66AY6ECnE75Ujn1Maw899kb3zY2SvIvzA7HlXqtmowHCVGoJ4ou6LQxJpVEJ4hjvS2gQMF9W31uOzRzMI1JhdZioYmqe6eq9sGmRZZiYON7jBqX8f4XW0tTZoK+dVRNwYQcwyqcvQpxeI15VWDq5cqjBw3ps5XSEYTNIFUXREnEEi+vLdCuw/YRZp1ij7LiQKp6bcb2KROXaWii4VpUNWxIAflm4Nvn/8pa/3CUwqIbxTSAL+Qkb2iEzuYuNPGLr72mQgGEnlSpaqzUx0miwjJ41x3Q8mf72ihIME7YQGMDJL7TA7/GjXFeSxroPk65tbssAGmbjwGGJX67NHUzeQPW2QPA2cohCHyopKB9GqhKgKwKjenkCUaBGCaZReZz9XkSkHTXlxxSakMTmgJnA9To9d2lPOy0nppUvrd/0uAbPuxxCZqXElRvOvHKzpV1ZpKpqSxvjh63mCQRTi2rFiPn8uFaajai9mHaPoGmNwQwIUbAviNqifuIEPpc6cOuyV0MWJwdFLo1SKamJya/MwQz+IwXuY2TX7Fmv9HovdM=
+
+after_success:
+- util/publish-snapshot-on-commit.sh
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..0f5b8cf
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,29 @@
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+
+Before we can use your code, you must sign the [Google Individual Contributor
+License
+Agreement](https://developers.google.com/open-source/cla/individual?csw=1)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose.
+
+### The small print
+
+Contributions made by corporations are covered by a different agreement than the
+one above, the Software Grant and Corporate Contributor License Agreement.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6c4ca5a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,272 @@
+The following Apache 2.0 license applies to all code in this package except
+google-java-format-diff.py.
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
+
+------------------------------------------------------------------------------
+
+The following NCSA license applies only to google-java-format-diff.py.
+
+==============================================================================
+LLVM Release License
+==============================================================================
+University of Illinois/NCSA
+Open Source License
+
+Copyright (c) 2007-2015 University of Illinois at Urbana-Champaign.
+All rights reserved.
+
+Developed by:
+
+ LLVM Team
+
+ University of Illinois at Urbana-Champaign
+
+ http://llvm.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal with
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimers.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimers in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the names of the LLVM Team, University of Illinois at
+ Urbana-Champaign, nor the names of its contributors may be used to
+ endorse or promote products derived from this Software without specific
+ prior written permission.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+SOFTWARE.
+
+==============================================================================
+The LLVM software contains code written by third parties. Such software will
+have its own individual LICENSE.TXT file in the directory in which it appears.
+This file will describe the copyrights, license, and restrictions which apply
+to that code.
+
+The disclaimer of warranty in the University of Illinois Open Source License
+applies to all code in the LLVM Distribution, and nothing in any of the
+other licenses gives permission to use the names of the LLVM Team or the
+University of Illinois to endorse or promote products derived from this
+Software.
+
+The following pieces of software have additional or alternate copyrights,
+licenses, and/or restrictions:
+
+Program Directory
+------- ---------
+<none yet>
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c5fdee2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,152 @@
+# google-java-format
+
+`google-java-format` is a program that reformats Java source code to comply with
+[Google Java Style][].
+
+[Google Java Style]: https://google.github.io/styleguide/javaguide.html
+
+## Using the formatter
+
+### from the command-line
+
+[Download the formatter](https://github.com/google/google-java-format/releases)
+and run it with:
+
+```
+java -jar /path/to/google-java-format-1.7-all-deps.jar <options> [files...]
+```
+
+The formatter can act on whole files, on limited lines (`--lines`), on specific
+offsets (`--offset`), passing through to standard-out (default) or altered
+in-place (`--replace`).
+
+To reformat changed lines in a specific patch, use
+[`google-java-format-diff.py`](https://github.com/google/google-java-format/blob/master/scripts/google-java-format-diff.py).
+
+***Note:*** *There is no configurability as to the formatter's algorithm for
+formatting. This is a deliberate design decision to unify our code formatting on
+a single format.*
+
+### IntelliJ, Android Studio, and other JetBrains IDEs
+
+A
+[google-java-format IntelliJ plugin](https://plugins.jetbrains.com/plugin/8527)
+is available from the plugin repository. To install it, go to your IDE's
+settings and select the `Plugins` category. Click the `Marketplace` tab, search
+for the `google-java-format` plugin, and click the `Install` button.
+
+The plugin will be disabled by default. To enable it in the current project, go
+to `File→Settings...→google-java-format Settings` (or `IntelliJ
+IDEA→Preferences...→Other Settings→google-java-format Settings` on macOS) and
+check the `Enable google-java-format` checkbox. (A notification will be
+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.
+
+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.
+
+### Eclipse
+
+A
+[google-java-format Eclipse plugin](https://github.com/google/google-java-format/releases/download/google-java-format-1.6/google-java-format-eclipse-plugin_1.6.0.jar)
+can be downloaded from the releases page. Drop it into the Eclipse
+[drop-ins folder](http://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fmisc%2Fp2_dropins_format.html)
+to activate the plugin.
+
+The plugin adds a `google-java-format` formatter implementation that can be
+configured in `Window > Preferences > Java > Code Style > Formatter > Formatter
+Implementation`.
+
+### Third-party integrations
+
+* Gradle plugins
+ * [Spotless](https://github.com/diffplug/spotless/tree/master/plugin-gradle#applying-to-java-source-google-java-format):
+ * [sherter/google-java-format-gradle-plugin](https://github.com/sherter/google-java-format-gradle-plugin)
+* Apache Maven plugins
+ * [coveo/fmt-maven-plugin](https://github.com/coveo/fmt-maven-plugin)
+ * [talios/googleformatter-maven-plugin](https://github.com/talios/googleformatter-maven-plugin)
+ * [Cosium/maven-git-code-format](https://github.com/Cosium/maven-git-code-format):
+ A maven plugin that automatically deploys google-java-format as a
+ pre-commit git hook.
+* SBT plugins
+ * [sbt/sbt-java-formatter](https://github.com/sbt/sbt-java-formatter)
+* [maltzj/google-style-precommit-hook](https://github.com/maltzj/google-style-precommit-hook):
+ A pre-commit (pre-commit.com) hook that will automatically run GJF whenever
+ you commit code to your repository
+
+### as a library
+
+The formatter can be used in software which generates java to output more
+legible java code. Just include the library in your maven/gradle/etc.
+configuration.
+
+#### Maven
+
+```xml
+<dependency>
+ <groupId>com.google.googlejavaformat</groupId>
+ <artifactId>google-java-format</artifactId>
+ <version>1.7</version>
+</dependency>
+```
+
+#### Gradle
+
+```groovy
+dependencies {
+ compile 'com.google.googlejavaformat:google-java-format:1.7'
+}
+```
+
+You can then use the formatter through the `formatSource` methods. E.g.
+
+```java
+String formattedSource = new Formatter().formatSource(sourceString);
+```
+
+or
+
+```java
+CharSource source = ...
+CharSink output = ...
+new Formatter().formatSource(source, output);
+```
+
+Your starting point should be the instance methods of
+`com.google.googlejavaformat.java.Formatter`.
+
+## Building from source
+
+```
+mvn install
+```
+
+## Contributing
+
+Please see [the contributors guide](CONTRIBUTING.md) for details.
+
+## License
+
+```text
+Copyright 2015 Google Inc.
+
+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.
+```
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..3522155
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,33 @@
+os: Visual Studio 2015
+install:
+ - ps: |
+ Add-Type -AssemblyName System.IO.Compression.FileSystem
+ if (!(Test-Path -Path "C:\maven" )) {
+ (new-object System.Net.WebClient).DownloadFile(
+ 'http://www.us.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.zip',
+ 'C:\maven-bin.zip'
+ )
+ [System.IO.Compression.ZipFile]::ExtractToDirectory("C:\maven-bin.zip", "C:\maven")
+ }
+ - cmd: SET JAVA_HOME=C:\Program Files\Java\jdk11
+ - cmd: SET PATH=C:\maven\apache-maven-3.3.9\bin;%JAVA_HOME%\bin;%PATH%
+ - cmd: SET MAVEN_OPTS=-XX:MaxPermSize=2g -Xmx4g
+ - cmd: SET JAVA_OPTS=-XX:MaxPermSize=2g -Xmx4g
+
+build_script:
+ - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
+
+test_script:
+ - mvn test -B
+
+cache:
+ - C:\maven\
+ - C:\Users\appveyor\.m2
+
+notifications:
+ - provider: Email
+ to:
+ - google-java-format-dev+ci@google.com
+ on_build_success: false
+ on_build_failure: true
+ on_build_status_changed: true
diff --git a/core/pom.xml b/core/pom.xml
new file mode 100644
index 0000000..88b3cf9
--- /dev/null
+++ b/core/pom.xml
@@ -0,0 +1,284 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015 Google Inc.
+ ~
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.google.googlejavaformat</groupId>
+ <artifactId>google-java-format-parent</artifactId>
+ <version>1.8-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>google-java-format</artifactId>
+
+ <name>Google Java Format</name>
+
+ <description>
+ A Java source code formatter that follows Google Java Style.
+ </description>
+
+ <dependencies>
+ <!-- Required runtime dependencies -->
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <!-- Compile-time dependencies -->
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>com.google.errorprone</groupId>
+ <artifactId>error_prone_annotations</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-testlib</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.testing.compile</groupId>
+ <artifactId>compile-testing</artifactId>
+ <version>0.15</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <source>11</source>
+ <encoding>UTF-8</encoding>
+ <docencoding>UTF-8</docencoding>
+ <charset>UTF-8</charset>
+ <links>
+ <link>https://guava.dev/releases/${guava.version}/api/docs/</link>
+ <link>https://docs.oracle.com/en/java/javase/11/docs/api</link>
+ </links>
+ <additionalJOptions>
+ <additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED,com.google.googlejavaformat</additionalJOption>
+ <additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED,com.google.googlejavaformat</additionalJOption>
+ <additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED,com.google.googlejavaformat</additionalJOption>
+ <additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED,com.google.googlejavaformat</additionalJOption>
+ <additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED,com.google.googlejavaformat</additionalJOption>
+ <additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED,com.google.googlejavaformat</additionalJOption>
+ <additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED,com.google.googlejavaformat</additionalJOption>
+ </additionalJOptions>
+ </configuration>
+ <executions>
+ <execution>
+ <id>attach-docs</id>
+ <phase>post-integration-test</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>2.4.3</version>
+ <executions>
+ <execution>
+ <id>shade-all-deps</id>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <shadedArtifactAttached>true</shadedArtifactAttached>
+ <shadedClassifierName>all-deps</shadedClassifierName>
+ <createDependencyReducedPom>false</createDependencyReducedPom>
+ <!-- http://stackoverflow.com/a/6743609 -->
+ <filters>
+ <filter>
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>com.google.googlejavaformat.java.Main</mainClass>
+ <addDefaultImplementationEntries>
+ true
+ </addDefaultImplementationEntries>
+ </manifest>
+ <manifestEntries>
+ <Automatic-Module-Name>
+ com.google.googlejavaformat
+ </Automatic-Module-Name>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.0.1</version>
+ <executions>
+ <execution>
+ <id>copy-resources</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>../eclipse_plugin/lib</outputDirectory>
+ <resources>
+ <resource>
+ <directory>target</directory>
+ <include>${project.artifactId}-${project.version}.jar</include>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>2.10</version>
+ <executions>
+ <execution>
+ <id>copy-dependencies</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>../eclipse_plugin/lib</outputDirectory>
+ <overWriteReleases>true</overWriteReleases>
+ <overWriteSnapshots>true</overWriteSnapshots>
+ <excludeTransitive>true</excludeTransitive>
+ <excludeArtifactIds>org.eclipse.jdt.core</excludeArtifactIds>
+ <excludeScope>compile</excludeScope>
+ <excludeScope>provided</excludeScope>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>com.google.code.maven-replacer-plugin</groupId>
+ <artifactId>replacer</artifactId>
+ <version>1.5.3</version>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>replace</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <file>${project.basedir}/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatVersion.java.template</file>
+ <outputFile>${project.build.directory}/generated-sources/java/com/google/googlejavaformat/java/GoogleJavaFormatVersion.java</outputFile>
+ <replacements>
+ <replacement>
+ <token>%VERSION%</token>
+ <value>${project.version}</value>
+ </replacement>
+ </replacements>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>3.0.0</version>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${project.build.directory}/generated-sources/java/</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>11</source>
+ <target>11</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>jdk11</id>
+ <activation>
+ <jdk>(,14)</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>**/Java14InputAstVisitor.java</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <excludePackageNames>com.google.googlejavaformat.java.java14</excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/core/src/main/java/com/google/googlejavaformat/CloseOp.java b/core/src/main/java/com/google/googlejavaformat/CloseOp.java
new file mode 100644
index 0000000..59e7e5a
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/CloseOp.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * A {@code CloseOp} closes a level. It is an {@link Op} in the sequence of {@link Op}s generated by
+ * {@link OpsBuilder}. When the sequence is turned into a {@link Doc} by {@link DocBuilder}, ranges
+ * delimited by {@link OpenOp}-{@code CloseOp} pairs turn into nested {@link Doc.Level}s.
+ */
+public enum CloseOp implements Op {
+ CLOSE;
+
+ /**
+ * Make a {@code CloseOp}, returning a singleton since they are all the same.
+ *
+ * @return the singleton {@code CloseOp}
+ */
+ public static Op make() {
+ return CLOSE;
+ }
+
+ @Override
+ public void add(DocBuilder builder) {
+ builder.close();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).toString();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/CommentsHelper.java b/core/src/main/java/com/google/googlejavaformat/CommentsHelper.java
new file mode 100644
index 0000000..45e507b
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/CommentsHelper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+/**
+ * Rewrite comments. This interface is implemented by {@link
+ * com.google.googlejavaformat.java.JavaCommentsHelper JavaCommentsHelper}.
+ */
+public interface CommentsHelper {
+ /**
+ * Try to rewrite comments, returning rewritten text.
+ *
+ * @param tok the comment's tok
+ * @param maxWidth the line length for the output
+ * @param column0 the current column
+ * @return the rewritten comment
+ */
+ String rewrite(Input.Tok tok, int maxWidth, int column0);
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/Doc.java b/core/src/main/java/com/google/googlejavaformat/Doc.java
new file mode 100644
index 0000000..e663c96
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/Doc.java
@@ -0,0 +1,770 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+import static com.google.common.collect.Iterables.getLast;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Range;
+import com.google.googlejavaformat.Output.BreakTag;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * {@link com.google.googlejavaformat.java.JavaInputAstVisitor JavaInputAstVisitor} outputs a
+ * sequence of {@link Op}s using {@link OpsBuilder}. This linear sequence is then transformed by
+ * {@link DocBuilder} into a tree-structured {@code Doc}. The top-level {@code Doc} is a {@link
+ * Level}, which contains a sequence of {@code Doc}s, including other {@link Level}s. Leaf {@code
+ * Doc}s are {@link Token}s, representing language-level tokens; {@link Tok}s, which may also
+ * represent non-token {@link Input.Tok}s, including comments and other white-space; {@link Space}s,
+ * representing single spaces; and {@link Break}s, which represent optional line-breaks.
+ */
+public abstract class Doc {
+ /**
+ * Each {@link Break} in a {@link Level} is either {@link FillMode#UNIFIED} or {@link
+ * FillMode#INDEPENDENT}.
+ */
+ public enum FillMode {
+ /**
+ * If a {@link Level} will not fit on one line, all of its {@code UNIFIED} {@link Break}s will
+ * be broken.
+ */
+ UNIFIED,
+
+ /**
+ * If a {@link Level} will not fit on one line, its {@code INDEPENDENT} {@link Break}s will be
+ * broken independently of each other, to fill in the {@link Level}.
+ */
+ INDEPENDENT,
+
+ /**
+ * A {@code FORCED} {@link Break} will always be broken, and a {@link Level} it appears in will
+ * not fit on one line.
+ */
+ FORCED
+ }
+
+ /** State for writing. */
+ public static final class State {
+ final int lastIndent;
+ final int indent;
+ final int column;
+ final boolean mustBreak;
+
+ State(int lastIndent, int indent, int column, boolean mustBreak) {
+ this.lastIndent = lastIndent;
+ this.indent = indent;
+ this.column = column;
+ this.mustBreak = mustBreak;
+ }
+
+ public State(int indent0, int column0) {
+ this(indent0, indent0, column0, false);
+ }
+
+ State withColumn(int column) {
+ return new State(lastIndent, indent, column, mustBreak);
+ }
+
+ State withMustBreak(boolean mustBreak) {
+ return new State(lastIndent, indent, column, mustBreak);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("lastIndent", lastIndent)
+ .add("indent", indent)
+ .add("column", column)
+ .add("mustBreak", mustBreak)
+ .toString();
+ }
+ }
+
+ private static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1);
+ private static final DiscreteDomain<Integer> INTEGERS = DiscreteDomain.integers();
+
+ // Memoized width; Float.POSITIVE_INFINITY if contains forced breaks.
+ private boolean widthComputed = false;
+ private float width = 0.0F;
+
+ // Memoized flat; not defined (and never computed) if contains forced breaks.
+ private boolean flatComputed = false;
+ private String flat = "";
+
+ // Memoized Range.
+ private boolean rangeComputed = false;
+ private Range<Integer> range = EMPTY_RANGE;
+
+ /**
+ * Return the width of a {@code Doc}, or {@code Float.POSITIVE_INFINITY} if it must be broken.
+ *
+ * @return the width
+ */
+ final float getWidth() {
+ if (!widthComputed) {
+ width = computeWidth();
+ widthComputed = true;
+ }
+ return width;
+ }
+
+ /**
+ * Return a {@code Doc}'s flat-string value; not defined (and never called) if the (@code Doc}
+ * contains forced breaks.
+ *
+ * @return the flat-string value
+ */
+ final String getFlat() {
+ if (!flatComputed) {
+ flat = computeFlat();
+ flatComputed = true;
+ }
+ return flat;
+ }
+
+ /**
+ * Return the {@link Range} of a {@code Doc}.
+ *
+ * @return the {@code Doc}'s {@link Range}
+ */
+ final Range<Integer> range() {
+ if (!rangeComputed) {
+ range = computeRange();
+ rangeComputed = true;
+ }
+ return range;
+ }
+
+ /**
+ * Compute the {@code Doc}'s width.
+ *
+ * @return the width, or {@code Float.POSITIVE_INFINITY} if it must be broken
+ */
+ abstract float computeWidth();
+
+ /**
+ * Compute the {@code Doc}'s flat value. Not defined (and never called) if contains forced breaks.
+ *
+ * @return the flat value
+ */
+ abstract String computeFlat();
+
+ /**
+ * Compute the {@code Doc}'s {@link Range} of {@link Input.Token}s.
+ *
+ * @return the {@link Range}
+ */
+ abstract Range<Integer> computeRange();
+
+ /**
+ * Make breaking decisions for a {@code Doc}.
+ *
+ * @param maxWidth the maximum line width
+ * @param state the current output state
+ * @return the new output state
+ */
+ public abstract State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state);
+
+ /** Write a {@code Doc} to an {@link Output}, after breaking decisions have been made. */
+ public abstract void write(Output output);
+
+ /** A {@code Level} inside a {@link Doc}. */
+ static final class Level extends Doc {
+ private final Indent plusIndent; // The extra indent following breaks.
+ private final List<Doc> docs = new ArrayList<>(); // The elements of the level.
+
+ private Level(Indent plusIndent) {
+ this.plusIndent = plusIndent;
+ }
+
+ /**
+ * Factory method for {@code Level}s.
+ *
+ * @param plusIndent the extra indent inside the {@code Level}
+ * @return the new {@code Level}
+ */
+ static Level make(Indent plusIndent) {
+ return new Level(plusIndent);
+ }
+
+ /**
+ * Add a {@link Doc} to the {@code Level}.
+ *
+ * @param doc the {@link Doc} to add
+ */
+ void add(Doc doc) {
+ docs.add(doc);
+ }
+
+ @Override
+ float computeWidth() {
+ float thisWidth = 0.0F;
+ for (Doc doc : docs) {
+ thisWidth += doc.getWidth();
+ }
+ return thisWidth;
+ }
+
+ @Override
+ String computeFlat() {
+ StringBuilder builder = new StringBuilder();
+ for (Doc doc : docs) {
+ builder.append(doc.getFlat());
+ }
+ return builder.toString();
+ }
+
+ @Override
+ Range<Integer> computeRange() {
+ Range<Integer> docRange = EMPTY_RANGE;
+ for (Doc doc : docs) {
+ docRange = union(docRange, doc.range());
+ }
+ return docRange;
+ }
+
+ // State that needs to be preserved between calculating breaks and
+ // writing output.
+ // TODO(cushon): represent phases as separate immutable data.
+
+ /** True if the entire {@link Level} fits on one line. */
+ boolean oneLine = false;
+
+ /**
+ * Groups of {@link Doc}s that are children of the current {@link Level}, separated by {@link
+ * Break}s.
+ */
+ List<List<Doc>> splits = new ArrayList<>();
+
+ /** {@link Break}s between {@link Doc}s in the current {@link Level}. */
+ List<Break> breaks = new ArrayList<>();
+
+ @Override
+ public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
+ float thisWidth = getWidth();
+ if (state.column + thisWidth <= maxWidth) {
+ oneLine = true;
+ return state.withColumn(state.column + (int) thisWidth);
+ }
+ State broken =
+ computeBroken(
+ commentsHelper, maxWidth, new State(state.indent + plusIndent.eval(), state.column));
+ return state.withColumn(broken.column);
+ }
+
+ private static void splitByBreaks(List<Doc> docs, List<List<Doc>> splits, List<Break> breaks) {
+ splits.clear();
+ breaks.clear();
+ splits.add(new ArrayList<>());
+ for (Doc doc : docs) {
+ if (doc instanceof Break) {
+ breaks.add((Break) doc);
+ splits.add(new ArrayList<>());
+ } else {
+ getLast(splits).add(doc);
+ }
+ }
+ }
+
+ /** Compute breaks for a {@link Level} that spans multiple lines. */
+ private State computeBroken(CommentsHelper commentsHelper, int maxWidth, State state) {
+ splitByBreaks(docs, splits, breaks);
+
+ state =
+ computeBreakAndSplit(
+ commentsHelper, maxWidth, state, /* optBreakDoc= */ Optional.empty(), splits.get(0));
+
+ // Handle following breaks and split.
+ for (int i = 0; i < breaks.size(); i++) {
+ state =
+ computeBreakAndSplit(
+ commentsHelper, maxWidth, state, Optional.of(breaks.get(i)), splits.get(i + 1));
+ }
+ return state;
+ }
+
+ /** Lay out a Break-separated group of Docs in the current Level. */
+ private static State computeBreakAndSplit(
+ CommentsHelper commentsHelper,
+ int maxWidth,
+ State state,
+ Optional<Break> optBreakDoc,
+ List<Doc> split) {
+ float breakWidth = optBreakDoc.isPresent() ? optBreakDoc.get().getWidth() : 0.0F;
+ float splitWidth = getWidth(split);
+ boolean shouldBreak =
+ (optBreakDoc.isPresent() && optBreakDoc.get().fillMode == FillMode.UNIFIED)
+ || state.mustBreak
+ || state.column + breakWidth + splitWidth > maxWidth;
+
+ if (optBreakDoc.isPresent()) {
+ state = optBreakDoc.get().computeBreaks(state, state.lastIndent, shouldBreak);
+ }
+ boolean enoughRoom = state.column + splitWidth <= maxWidth;
+ state = computeSplit(commentsHelper, maxWidth, split, state.withMustBreak(false));
+ if (!enoughRoom) {
+ state = state.withMustBreak(true); // Break after, too.
+ }
+ return state;
+ }
+
+ private static State computeSplit(
+ CommentsHelper commentsHelper, int maxWidth, List<Doc> docs, State state) {
+ for (Doc doc : docs) {
+ state = doc.computeBreaks(commentsHelper, maxWidth, state);
+ }
+ return state;
+ }
+
+ @Override
+ public void write(Output output) {
+ if (oneLine) {
+ output.append(getFlat(), range()); // This is defined because width is finite.
+ } else {
+ writeFilled(output);
+ }
+ }
+
+ private void writeFilled(Output output) {
+ // Handle first split.
+ for (Doc doc : splits.get(0)) {
+ doc.write(output);
+ }
+ // Handle following breaks and split.
+ for (int i = 0; i < breaks.size(); i++) {
+ breaks.get(i).write(output);
+ for (Doc doc : splits.get(i + 1)) {
+ doc.write(output);
+ }
+ }
+ }
+
+ /**
+ * Get the width of a sequence of {@link Doc}s.
+ *
+ * @param docs the {@link Doc}s
+ * @return the width, or {@code Float.POSITIVE_INFINITY} if any {@link Doc} must be broken
+ */
+ static float getWidth(List<Doc> docs) {
+ float width = 0.0F;
+ for (Doc doc : docs) {
+ width += doc.getWidth();
+ }
+ return width;
+ }
+
+ private static Range<Integer> union(Range<Integer> x, Range<Integer> y) {
+ return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(INTEGERS);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("plusIndent", plusIndent)
+ .add("docs", docs)
+ .toString();
+ }
+ }
+
+ /** A leaf {@link Doc} for a token. */
+ public static final class Token extends Doc implements Op {
+ /** Is a Token a real token, or imaginary (e.g., a token generated incorrectly, or an EOF)? */
+ public enum RealOrImaginary {
+ REAL,
+ IMAGINARY;
+
+ boolean isReal() {
+ return this == REAL;
+ }
+ }
+
+ private final Input.Token token;
+ private final RealOrImaginary realOrImaginary;
+ private final Indent plusIndentCommentsBefore;
+ private final Optional<Indent> breakAndIndentTrailingComment;
+
+ private Token(
+ Input.Token token,
+ RealOrImaginary realOrImaginary,
+ Indent plusIndentCommentsBefore,
+ Optional<Indent> breakAndIndentTrailingComment) {
+ this.token = token;
+ this.realOrImaginary = realOrImaginary;
+ this.plusIndentCommentsBefore = plusIndentCommentsBefore;
+ this.breakAndIndentTrailingComment = breakAndIndentTrailingComment;
+ }
+
+ /**
+ * How much extra to indent comments before the {@code Token}.
+ *
+ * @return the extra indent
+ */
+ Indent getPlusIndentCommentsBefore() {
+ return plusIndentCommentsBefore;
+ }
+
+ /** Force a line break and indent trailing javadoc or block comments. */
+ Optional<Indent> breakAndIndentTrailingComment() {
+ return breakAndIndentTrailingComment;
+ }
+
+ /**
+ * Make a {@code Token}.
+ *
+ * @param token the {@link Input.Token} to wrap
+ * @param realOrImaginary did this {@link Input.Token} appear in the input, or was it generated
+ * incorrectly?
+ * @param plusIndentCommentsBefore extra {@code plusIndent} for comments just before this token
+ * @return the new {@code Token}
+ */
+ static Op make(
+ Input.Token token,
+ Doc.Token.RealOrImaginary realOrImaginary,
+ Indent plusIndentCommentsBefore,
+ Optional<Indent> breakAndIndentTrailingComment) {
+ return new Token(
+ token, realOrImaginary, plusIndentCommentsBefore, breakAndIndentTrailingComment);
+ }
+
+ /**
+ * Return the wrapped {@link Input.Token}.
+ *
+ * @return the {@link Input.Token}
+ */
+ Input.Token getToken() {
+ return token;
+ }
+
+ /**
+ * Is the token good? That is, does it match an {@link Input.Token}?
+ *
+ * @return whether the @code Token} is good
+ */
+ RealOrImaginary realOrImaginary() {
+ return realOrImaginary;
+ }
+
+ @Override
+ public void add(DocBuilder builder) {
+ builder.add(this);
+ }
+
+ @Override
+ float computeWidth() {
+ return token.getTok().length();
+ }
+
+ @Override
+ String computeFlat() {
+ return token.getTok().getOriginalText();
+ }
+
+ @Override
+ Range<Integer> computeRange() {
+ return Range.singleton(token.getTok().getIndex()).canonical(INTEGERS);
+ }
+
+ @Override
+ public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
+ String text = token.getTok().getOriginalText();
+ return state.withColumn(state.column + text.length());
+ }
+
+ @Override
+ public void write(Output output) {
+ String text = token.getTok().getOriginalText();
+ output.append(text, range());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("token", token)
+ .add("realOrImaginary", realOrImaginary)
+ .add("plusIndentCommentsBefore", plusIndentCommentsBefore)
+ .toString();
+ }
+ }
+
+ /** A Leaf node in a {@link Doc} for a non-breaking space. */
+ static final class Space extends Doc implements Op {
+ private static final Space SPACE = new Space();
+
+ private Space() {}
+
+ /**
+ * Factor method for {@code Space}.
+ *
+ * @return the new {@code Space}
+ */
+ static Space make() {
+ return SPACE;
+ }
+
+ @Override
+ public void add(DocBuilder builder) {
+ builder.add(this);
+ }
+
+ @Override
+ float computeWidth() {
+ return 1.0F;
+ }
+
+ @Override
+ String computeFlat() {
+ return " ";
+ }
+
+ @Override
+ Range<Integer> computeRange() {
+ return EMPTY_RANGE;
+ }
+
+ @Override
+ public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
+ return state.withColumn(state.column + 1);
+ }
+
+ @Override
+ public void write(Output output) {
+ output.append(" ", range());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).toString();
+ }
+ }
+
+ /** A leaf node in a {@link Doc} for an optional break. */
+ public static final class Break extends Doc implements Op {
+ private final FillMode fillMode;
+ private final String flat;
+ private final Indent plusIndent;
+ private final Optional<BreakTag> optTag;
+
+ private Break(FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) {
+ this.fillMode = fillMode;
+ this.flat = flat;
+ this.plusIndent = plusIndent;
+ this.optTag = optTag;
+ }
+
+ /**
+ * Make a {@code Break}.
+ *
+ * @param fillMode the {@link FillMode}
+ * @param flat the text when not broken
+ * @param plusIndent extra indent if taken
+ * @return the new {@code Break}
+ */
+ public static Break make(FillMode fillMode, String flat, Indent plusIndent) {
+ return new Break(fillMode, flat, plusIndent, /* optTag= */ Optional.empty());
+ }
+
+ /**
+ * Make a {@code Break}.
+ *
+ * @param fillMode the {@link FillMode}
+ * @param flat the text when not broken
+ * @param plusIndent extra indent if taken
+ * @param optTag an optional tag for remembering whether the break was taken
+ * @return the new {@code Break}
+ */
+ public static Break make(
+ FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) {
+ return new Break(fillMode, flat, plusIndent, optTag);
+ }
+
+ /**
+ * Make a forced {@code Break}.
+ *
+ * @return the new forced {@code Break}
+ */
+ public static Break makeForced() {
+ return make(FillMode.FORCED, "", Indent.Const.ZERO);
+ }
+
+ /**
+ * Return the {@code Break}'s extra indent.
+ *
+ * @return the extra indent
+ */
+ int getPlusIndent() {
+ return plusIndent.eval();
+ }
+
+ /**
+ * Is the {@code Break} forced?
+ *
+ * @return whether the {@code Break} is forced
+ */
+ boolean isForced() {
+ return fillMode == FillMode.FORCED;
+ }
+
+ @Override
+ public void add(DocBuilder builder) {
+ builder.breakDoc(this);
+ }
+
+ @Override
+ float computeWidth() {
+ return isForced() ? Float.POSITIVE_INFINITY : (float) flat.length();
+ }
+
+ @Override
+ String computeFlat() {
+ return flat;
+ }
+
+ @Override
+ Range<Integer> computeRange() {
+ return EMPTY_RANGE;
+ }
+
+ /** Was this break taken? */
+ boolean broken;
+
+ /** New indent after this break. */
+ int newIndent;
+
+ public State computeBreaks(State state, int lastIndent, boolean broken) {
+ if (optTag.isPresent()) {
+ optTag.get().recordBroken(broken);
+ }
+
+ if (broken) {
+ this.broken = true;
+ this.newIndent = Math.max(lastIndent + plusIndent.eval(), 0);
+ return state.withColumn(newIndent);
+ } else {
+ this.broken = false;
+ this.newIndent = -1;
+ return state.withColumn(state.column + flat.length());
+ }
+ }
+
+ @Override
+ public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
+ // Updating the state for {@link Break}s requires deciding if the break
+ // should be taken.
+ // TODO(cushon): this hierarchy is wrong, create a separate interface
+ // for unbreakable Docs?
+ throw new UnsupportedOperationException("Did you mean computeBreaks(State, int, boolean)?");
+ }
+
+ @Override
+ public void write(Output output) {
+ if (broken) {
+ output.append("\n", EMPTY_RANGE);
+ output.indent(newIndent);
+ } else {
+ output.append(flat, range());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("fillMode", fillMode)
+ .add("flat", flat)
+ .add("plusIndent", plusIndent)
+ .add("optTag", optTag)
+ .toString();
+ }
+ }
+
+ /** A leaf node in a {@link Doc} for a non-token. */
+ static final class Tok extends Doc implements Op {
+ private final Input.Tok tok;
+
+ private Tok(Input.Tok tok) {
+ this.tok = tok;
+ }
+
+ /**
+ * Factory method for a {@code Tok}.
+ *
+ * @param tok the {@link Input.Tok} to wrap
+ * @return the new {@code Tok}
+ */
+ static Tok make(Input.Tok tok) {
+ return new Tok(tok);
+ }
+
+ @Override
+ public void add(DocBuilder builder) {
+ builder.add(this);
+ }
+
+ @Override
+ float computeWidth() {
+ int idx = Newlines.firstBreak(tok.getOriginalText());
+ // only count the first line of multi-line block comments
+ if (tok.isComment()) {
+ if (idx > 0) {
+ return idx;
+ } else if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) {
+ // Account for line comments with missing spaces, see computeFlat.
+ return tok.length() + 1;
+ } else {
+ return tok.length();
+ }
+ }
+ return idx != -1 ? Float.POSITIVE_INFINITY : (float) tok.length();
+ }
+
+ @Override
+ String computeFlat() {
+ // TODO(cushon): commentsHelper.rewrite doesn't get called for spans that fit in a single
+ // line. That's fine for multi-line comment reflowing, but problematic for adding missing
+ // spaces in line comments.
+ if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) {
+ return "// " + tok.getOriginalText().substring("//".length());
+ }
+ return tok.getOriginalText();
+ }
+
+ @Override
+ Range<Integer> computeRange() {
+ return Range.singleton(tok.getIndex()).canonical(INTEGERS);
+ }
+
+ String text;
+
+ @Override
+ public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
+ text = commentsHelper.rewrite(tok, maxWidth, state.column);
+ int firstLineLength = text.length() - Iterators.getLast(Newlines.lineOffsetIterator(text));
+ return state.withColumn(state.column + firstLineLength);
+ }
+
+ @Override
+ public void write(Output output) {
+ output.append(text, range());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("tok", tok).toString();
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/DocBuilder.java b/core/src/main/java/com/google/googlejavaformat/DocBuilder.java
new file mode 100644
index 0000000..31cc798
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/DocBuilder.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import java.util.ArrayDeque;
+import java.util.List;
+
+/** A {@code DocBuilder} converts a sequence of {@link Op}s into a {@link Doc}. */
+public final class DocBuilder {
+ private final Doc.Level base = Doc.Level.make(Indent.Const.ZERO);
+ private final ArrayDeque<Doc.Level> stack = new ArrayDeque<>();
+
+ /**
+ * A possibly earlier {@link Doc.Level} for appending text, à la Philip Wadler.
+ *
+ * <p>Processing {@link Doc}s presents a subtle problem. Suppose we have a {@link Doc} for to an
+ * assignment node, {@code a = b}, with an optional {@link Doc.Break} following the {@code =}.
+ * Suppose we have 5 characters to write it, so that we think we don't need the break.
+ * Unfortunately, this {@link Doc} lies in an expression statement {@link Doc} for the statement
+ * {@code a = b;} and this statement does not fit in 3 characters. This is why many formatters
+ * sometimes emit lines that are too long, or cheat by using a narrower line length to avoid such
+ * problems.
+ *
+ * <p>One solution to this problem is not to decide whether a {@link Doc.Level} should be broken
+ * until later (in this case, after the semicolon has been seen). A simpler approach is to rewrite
+ * the {@link Doc} as here, so that the semicolon moves inside the inner {@link Doc}, and we can
+ * decide whether to break that {@link Doc} without seeing later text.
+ */
+ private Doc.Level appendLevel = base;
+
+ /** Start to build a {@code DocBuilder}. */
+ public DocBuilder() {
+ stack.addLast(base);
+ }
+
+ /**
+ * Add a list of {@link Op}s to the {@link OpsBuilder}.
+ *
+ * @param ops the {@link Op}s
+ * @return the {@link OpsBuilder}
+ */
+ public DocBuilder withOps(List<Op> ops) {
+ for (Op op : ops) {
+ op.add(this); // These operations call the operations below to build the doc.
+ }
+ return this;
+ }
+
+ /**
+ * Open a new {@link Doc.Level}.
+ *
+ * @param plusIndent the extra indent for the {@link Doc.Level}
+ */
+ void open(Indent plusIndent) {
+ Doc.Level level = Doc.Level.make(plusIndent);
+ stack.addLast(level);
+ }
+
+ /** Close the current {@link Doc.Level}. */
+ void close() {
+ Doc.Level top = stack.removeLast();
+ stack.peekLast().add(top);
+ }
+
+ /**
+ * Add a {@link Doc} to the current {@link Doc.Level}.
+ *
+ * @param doc the {@link Doc}
+ */
+ void add(Doc doc) {
+ appendLevel.add(doc);
+ }
+
+ /**
+ * Add a {@link Doc.Break} to the current {@link Doc.Level}.
+ *
+ * @param breakDoc the {@link Doc.Break}
+ */
+ void breakDoc(Doc.Break breakDoc) {
+ appendLevel = stack.peekLast();
+ appendLevel.add(breakDoc);
+ }
+
+ /**
+ * Return the {@link Doc}.
+ *
+ * @return the {@link Doc}
+ */
+ public Doc build() {
+ return base;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("base", base)
+ .add("stack", stack)
+ .add("appendLevel", appendLevel)
+ .toString();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/FormatterDiagnostic.java b/core/src/main/java/com/google/googlejavaformat/FormatterDiagnostic.java
new file mode 100644
index 0000000..be7f8a6
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/FormatterDiagnostic.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/** An error that prevented formatting from succeeding. */
+public class FormatterDiagnostic {
+ private final int lineNumber;
+ private final String message;
+ private final int column;
+
+ public static FormatterDiagnostic create(String message) {
+ return new FormatterDiagnostic(-1, -1, message);
+ }
+
+ public static FormatterDiagnostic create(int lineNumber, int column, String message) {
+ checkArgument(lineNumber >= 0);
+ checkArgument(column >= 0);
+ checkNotNull(message);
+ return new FormatterDiagnostic(lineNumber, column, message);
+ }
+
+ private FormatterDiagnostic(int lineNumber, int column, String message) {
+ this.lineNumber = lineNumber;
+ this.column = column;
+ this.message = message;
+ }
+
+ /**
+ * Returns the line number on which the error occurred, or {@code -1} if the error does not have a
+ * line number.
+ */
+ public int line() {
+ return lineNumber;
+ }
+
+ /**
+ * Returns the 0-indexed column number on which the error occurred, or {@code -1} if the error
+ * does not have a column.
+ */
+ public int column() {
+ return column;
+ }
+
+ /** Returns a description of the problem that prevented formatting from succeeding. */
+ public String message() {
+ return message;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (lineNumber >= 0) {
+ sb.append(lineNumber).append(':');
+ }
+ if (column >= 0) {
+ // internal column numbers are 0-based, but diagnostics use 1-based indexing by convention
+ sb.append(column + 1).append(':');
+ }
+ if (lineNumber >= 0 || column >= 0) {
+ sb.append(' ');
+ }
+ sb.append("error: ").append(message);
+ return sb.toString();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/FormattingError.java b/core/src/main/java/com/google/googlejavaformat/FormattingError.java
new file mode 100644
index 0000000..50381d0
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/FormattingError.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+
+/** An unchecked formatting error. */
+public class FormattingError extends Error {
+
+ private final ImmutableList<FormatterDiagnostic> diagnostics;
+
+ public FormattingError(FormatterDiagnostic diagnostic) {
+ this(ImmutableList.of(diagnostic));
+ }
+
+ public FormattingError(Iterable<FormatterDiagnostic> diagnostics) {
+ super(Joiner.on("\n").join(diagnostics) + "\n");
+ this.diagnostics = ImmutableList.copyOf(diagnostics);
+ }
+
+ public ImmutableList<FormatterDiagnostic> diagnostics() {
+ return diagnostics;
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/Indent.java b/core/src/main/java/com/google/googlejavaformat/Indent.java
new file mode 100644
index 0000000..44b56cd
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/Indent.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import com.google.googlejavaformat.Output.BreakTag;
+
+/**
+ * An indent for a {@link Doc.Level} or {@link Doc.Break}. The indent is either a constant {@code
+ * int}, or a conditional expression whose value depends on whether or not a {@link Doc.Break} has
+ * been broken.
+ */
+public abstract class Indent {
+
+ abstract int eval();
+
+ /** A constant function, returning a constant indent. */
+ public static final class Const extends Indent {
+ private final int n;
+
+ public static final Const ZERO = new Const(+0);
+
+ private Const(int n) {
+ this.n = n;
+ }
+
+ public static Const make(int n, int indentMultiplier) {
+ return new Const(n * indentMultiplier);
+ }
+
+ @Override
+ int eval() {
+ return n;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("n", n).toString();
+ }
+ }
+
+ /** A conditional function, whose value depends on whether a break was taken. */
+ public static final class If extends Indent {
+ private final BreakTag condition;
+ private final Indent thenIndent;
+ private final Indent elseIndent;
+
+ private If(BreakTag condition, Indent thenIndent, Indent elseIndent) {
+ this.condition = condition;
+ this.thenIndent = thenIndent;
+ this.elseIndent = elseIndent;
+ }
+
+ public static If make(BreakTag condition, Indent thenIndent, Indent elseIndent) {
+ return new If(condition, thenIndent, elseIndent);
+ }
+
+ @Override
+ int eval() {
+ return (condition.wasBreakTaken() ? thenIndent : elseIndent).eval();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("condition", condition)
+ .add("thenIndent", thenIndent)
+ .add("elseIndent", elseIndent)
+ .toString();
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/Input.java b/core/src/main/java/com/google/googlejavaformat/Input.java
new file mode 100644
index 0000000..9e17c2b
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/Input.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableRangeMap;
+
+/** An input to the formatter. */
+public abstract class Input extends InputOutput {
+ /**
+ * A {@code Tok} ("tock") is a token, or a comment, or a newline, or a maximal string of blanks. A
+ * token {@code Tok} underlies a {@link Token}, and each other {@code Tok} is attached to a single
+ * {@code Token}. Tokens and comments have indices; white space {@code Tok}s do not.
+ */
+ public interface Tok {
+ /**
+ * Return the {@code Tok}'s index.
+ *
+ * @return its index
+ */
+ int getIndex();
+
+ /**
+ * Return the {@code Tok}'s {@code 0}-based position.
+ *
+ * @return its position
+ */
+ int getPosition();
+
+ /**
+ * Return the {@code Tok}'s {@code 0}-based column number.
+ *
+ * @return its column number
+ */
+ int getColumn();
+
+ /** The {@code Tok}'s text. */
+ String getText();
+
+ /** The {@code Tok}'s original text (before processing escapes). */
+ String getOriginalText();
+
+ /** The length of the {@code Tok}'s original text. */
+ int length();
+
+ /** Is the {@code Tok} a newline? */
+ boolean isNewline();
+
+ /** Is the {@code Tok} a "//" comment? */
+ boolean isSlashSlashComment();
+
+ /** Is the {@code Tok} a "//" comment? */
+ boolean isSlashStarComment();
+
+ /** Is the {@code Tok} a javadoc comment? */
+ boolean isJavadocComment();
+
+ /** Is the {@code Tok} a comment? */
+ boolean isComment();
+ }
+
+ /** A {@code Token} is a language-level token. */
+ public interface Token {
+ /**
+ * Get the token's {@link Tok}.
+ *
+ * @return the token's {@link Tok}
+ */
+ Tok getTok();
+
+ /**
+ * Get the earlier {@link Tok}s assigned to this {@code Token}.
+ *
+ * @return the earlier {@link Tok}s assigned to this {@code Token}
+ */
+ ImmutableList<? extends Tok> getToksBefore();
+
+ /**
+ * Get the later {@link Tok}s assigned to this {@code Token}.
+ *
+ * @return the later {@link Tok}s assigned to this {@code Token}
+ */
+ ImmutableList<? extends Tok> getToksAfter();
+ }
+
+ /**
+ * Get the input tokens.
+ *
+ * @return the input tokens
+ */
+ public abstract ImmutableList<? extends Token> getTokens();
+
+ /** A map from [start, end] position ranges to {@link Token}s. */
+ public abstract ImmutableRangeMap<Integer, ? extends Token> getPositionTokenMap();
+
+ public abstract ImmutableMap<Integer, Integer> getPositionToColumnMap();
+
+ public abstract String getText();
+
+ /**
+ * Get the number of toks.
+ *
+ * @return the number of toks, including the EOF tok
+ */
+ public abstract int getkN();
+
+ /**
+ * Get the Token by index.
+ *
+ * @param k the token index
+ */
+ public abstract Token getToken(int k);
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("super", super.toString()).toString();
+ }
+
+ /** Converts a character offset in the input to a line number. */
+ public abstract int getLineNumber(int inputPosition);
+
+ /** Converts a character offset in the input to a 0-based column number. */
+ public abstract int getColumnNumber(int inputPosition);
+
+ /**
+ * Construct a diagnostic. Populates the input filename, and converts character offsets to
+ * numbers.
+ */
+ public FormatterDiagnostic createDiagnostic(int inputPosition, String message) {
+ return FormatterDiagnostic.create(
+ getLineNumber(inputPosition), getColumnNumber(inputPosition), message);
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/InputOutput.java b/core/src/main/java/com/google/googlejavaformat/InputOutput.java
new file mode 100644
index 0000000..46dc70b
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/InputOutput.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Range;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** This interface defines methods common to an {@link Input} or an {@link Output}. */
+public abstract class InputOutput {
+ private ImmutableList<String> lines = ImmutableList.of();
+
+ protected static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1);
+ private static final DiscreteDomain<Integer> INTEGERS = DiscreteDomain.integers();
+
+ /** Set the lines. */
+ protected final void setLines(ImmutableList<String> lines) {
+ this.lines = lines;
+ }
+
+ /**
+ * Get the line count.
+ *
+ * @return the line count
+ */
+ public final int getLineCount() {
+ return lines.size();
+ }
+
+ /**
+ * Get a line.
+ *
+ * @param lineI the line number
+ * @return the line
+ */
+ public final String getLine(int lineI) {
+ return lines.get(lineI);
+ }
+
+ /** The {@link Range}s of the tokens or comments lying on each line, in any part. */
+ protected final List<Range<Integer>> ranges = new ArrayList<>();
+
+ private static void addToRanges(List<Range<Integer>> ranges, int i, int k) {
+ while (ranges.size() <= i) {
+ ranges.add(EMPTY_RANGE);
+ }
+ Range<Integer> oldValue = ranges.get(i);
+ ranges.set(i, Range.closedOpen(oldValue.isEmpty() ? k : oldValue.lowerEndpoint(), k + 1));
+ }
+
+ protected final void computeRanges(List<? extends Input.Tok> toks) {
+ int lineI = 0;
+ for (Input.Tok tok : toks) {
+ String txt = tok.getOriginalText();
+ int lineI0 = lineI;
+ lineI += Newlines.count(txt);
+ int k = tok.getIndex();
+ if (k >= 0) {
+ for (int i = lineI0; i <= lineI; i++) {
+ addToRanges(ranges, i, k);
+ }
+ }
+ }
+ }
+
+ /**
+ * Given an {@code InputOutput}, compute the map from tok indices to line ranges.
+ *
+ * @param put the {@code InputOutput}
+ * @return the map from {@code com.google.googlejavaformat.java.JavaInput.Tok} indices to line
+ * ranges in this {@code put}
+ */
+ public static Map<Integer, Range<Integer>> makeKToIJ(InputOutput put) {
+ Map<Integer, Range<Integer>> map = new HashMap<>();
+ int ijN = put.getLineCount();
+ for (int ij = 0; ij <= ijN; ij++) {
+ Range<Integer> range = put.getRanges(ij).canonical(INTEGERS);
+ for (int k = range.lowerEndpoint(); k < range.upperEndpoint(); k++) {
+ if (map.containsKey(k)) {
+ map.put(k, Range.closedOpen(map.get(k).lowerEndpoint(), ij + 1));
+ } else {
+ map.put(k, Range.closedOpen(ij, ij + 1));
+ }
+ }
+ }
+ return map;
+ }
+
+ /**
+ * Get the {@link Range} of {@link Input.Tok}s lying in any part on a line.
+ *
+ * @param lineI the line number
+ * @return the {@link Range} of {@link Input.Tok}s on the specified line
+ */
+ public final Range<Integer> getRanges(int lineI) {
+ return 0 <= lineI && lineI < ranges.size() ? ranges.get(lineI) : EMPTY_RANGE;
+ }
+
+ @Override
+ public String toString() {
+ return "InputOutput{" + "lines=" + lines + ", ranges=" + ranges + '}';
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/Newlines.java b/core/src/main/java/com/google/googlejavaformat/Newlines.java
new file mode 100644
index 0000000..dbb82d3
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/Newlines.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/** Platform-independent newline handling. */
+public class Newlines {
+
+ /** Returns the number of line breaks in the input. */
+ public static int count(String input) {
+ return Iterators.size(lineOffsetIterator(input)) - 1;
+ }
+
+ /** Returns the index of the first break in the input, or {@code -1}. */
+ public static int firstBreak(String input) {
+ Iterator<Integer> it = lineOffsetIterator(input);
+ it.next();
+ return it.hasNext() ? it.next() : -1;
+ }
+
+ private static final ImmutableSet<String> BREAKS = ImmutableSet.of("\r\n", "\n", "\r");
+
+ /** Returns true if the entire input string is a recognized line break. */
+ public static boolean isNewline(String input) {
+ return BREAKS.contains(input);
+ }
+
+ /** Returns the length of the newline sequence at the current offset, or {@code -1}. */
+ public static int hasNewlineAt(String input, int idx) {
+ for (String b : BREAKS) {
+ if (input.startsWith(b, idx)) {
+ return b.length();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the terminating line break in the input, or {@code null} if the input does not end in a
+ * break.
+ */
+ public static String getLineEnding(String input) {
+ for (String b : BREAKS) {
+ if (input.endsWith(b)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the first line separator in the text, or {@code "\n"} if the text does not contain a
+ * single line separator.
+ */
+ public static String guessLineSeparator(String text) {
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ switch (c) {
+ case '\r':
+ if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
+ return "\r\n";
+ }
+ return "\r";
+ case '\n':
+ return "\n";
+ default:
+ break;
+ }
+ }
+ return "\n";
+ }
+
+ /** Returns true if the input contains any line breaks. */
+ public static boolean containsBreaks(String text) {
+ return CharMatcher.anyOf("\n\r").matchesAnyOf(text);
+ }
+
+ /** Returns an iterator over the start offsets of lines in the input. */
+ public static Iterator<Integer> lineOffsetIterator(String input) {
+ return new LineOffsetIterator(input);
+ }
+
+ /** Returns an iterator over lines in the input, including trailing whitespace. */
+ public static Iterator<String> lineIterator(String input) {
+ return new LineIterator(input);
+ }
+
+ private static class LineOffsetIterator implements Iterator<Integer> {
+
+ private int curr = 0;
+ private int idx = 0;
+ private final String input;
+
+ private LineOffsetIterator(String input) {
+ this.input = input;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return curr != -1;
+ }
+
+ @Override
+ public Integer next() {
+ if (curr == -1) {
+ throw new NoSuchElementException();
+ }
+ int result = curr;
+ advance();
+ return result;
+ }
+
+ private void advance() {
+ for (; idx < input.length(); idx++) {
+ char c = input.charAt(idx);
+ switch (c) {
+ case '\r':
+ if (idx + 1 < input.length() && input.charAt(idx + 1) == '\n') {
+ idx++;
+ }
+ // falls through
+ case '\n':
+ idx++;
+ curr = idx;
+ return;
+ default:
+ break;
+ }
+ }
+ curr = -1;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+ }
+
+ private static class LineIterator implements Iterator<String> {
+
+ int idx;
+ String curr;
+
+ private final String input;
+ private final Iterator<Integer> indices;
+
+ private LineIterator(String input) {
+ this.input = input;
+ this.indices = lineOffsetIterator(input);
+ idx = indices.next(); // read leading 0
+ }
+
+ private void advance() {
+ int last = idx;
+ if (indices.hasNext()) {
+ idx = indices.next();
+ } else if (hasNext()) {
+ // no terminal line break
+ idx = input.length();
+ } else {
+ throw new NoSuchElementException();
+ }
+ curr = input.substring(last, idx);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return idx < input.length();
+ }
+
+ @Override
+ public String next() {
+ advance();
+ return curr;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/Op.java b/core/src/main/java/com/google/googlejavaformat/Op.java
new file mode 100644
index 0000000..a484541
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/Op.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+/**
+ * An {@code Op} is a member of the sequence of formatting operations emitted by {@link OpsBuilder}
+ * and transformed by {@link DocBuilder} into a {@link Doc}. Leaf subclasses of {@link Doc}
+ * implement {@code Op}; {@link Doc.Level} is the only non-leaf, and is represented by paired {@link
+ * OpenOp}-{@link CloseOp} {@code Op}s.
+ */
+public interface Op {
+ /**
+ * Add an {@code Op} to a {@link DocBuilder}.
+ *
+ * @param builder the {@link DocBuilder}
+ */
+ void add(DocBuilder builder);
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/OpenOp.java b/core/src/main/java/com/google/googlejavaformat/OpenOp.java
new file mode 100644
index 0000000..6498201
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/OpenOp.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * An {@code OpenOp} opens a level. It is an {@link Op} in the sequence of {@link Op}s generated by
+ * {@link OpsBuilder}. When the sequence is turned into a {@link Doc} by {@link DocBuilder}, {@link
+ * Input.Tok}s delimited by {@code OpenOp}-{@link CloseOp} pairs turn into nested {@link
+ * Doc.Level}s.
+ */
+public final class OpenOp implements Op {
+ private final Indent plusIndent;
+
+ private OpenOp(Indent plusIndent) {
+ this.plusIndent = plusIndent;
+ }
+
+ /**
+ * Make an ordinary {@code OpenOp}.
+ *
+ * @param plusIndent the indent for breaks at this level
+ * @return the {@code OpenOp}
+ */
+ public static Op make(Indent plusIndent) {
+ return new OpenOp(plusIndent);
+ }
+
+ @Override
+ public void add(DocBuilder builder) {
+ builder.open(plusIndent);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("plusIndent", plusIndent).toString();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java b/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java
new file mode 100644
index 0000000..e8e100f
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java
@@ -0,0 +1,631 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.googlejavaformat.Indent.Const;
+import com.google.googlejavaformat.Input.Tok;
+import com.google.googlejavaformat.Input.Token;
+import com.google.googlejavaformat.Output.BreakTag;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * An {@code OpsBuilder} creates a list of {@link Op}s, which is turned into a {@link Doc} by {@link
+ * DocBuilder}.
+ */
+public final class OpsBuilder {
+
+ /** @return the actual size of the AST node at position, including comments. */
+ public int actualSize(int position, int length) {
+ Token startToken = input.getPositionTokenMap().get(position);
+ int start = startToken.getTok().getPosition();
+ for (Tok tok : startToken.getToksBefore()) {
+ if (tok.isComment()) {
+ start = Math.min(start, tok.getPosition());
+ }
+ }
+ Token endToken = input.getPositionTokenMap().get(position + length - 1);
+ int end = endToken.getTok().getPosition() + endToken.getTok().length();
+ for (Tok tok : endToken.getToksAfter()) {
+ if (tok.isComment()) {
+ end = Math.max(end, tok.getPosition() + tok.length());
+ }
+ }
+ return end - start;
+ }
+
+ /** @return the start column of the token at {@code position}, including leading comments. */
+ public Integer actualStartColumn(int position) {
+ Token startToken = input.getPositionTokenMap().get(position);
+ int start = startToken.getTok().getPosition();
+ int line0 = input.getLineNumber(start);
+ for (Tok tok : startToken.getToksBefore()) {
+ if (line0 != input.getLineNumber(tok.getPosition())) {
+ return start;
+ }
+ if (tok.isComment()) {
+ start = Math.min(start, tok.getPosition());
+ }
+ }
+ return start;
+ }
+
+ /** A request to add or remove a blank line in the output. */
+ public abstract static class BlankLineWanted {
+
+ /** Always emit a blank line. */
+ public static final BlankLineWanted YES = new SimpleBlankLine(Optional.of(true));
+
+ /** Never emit a blank line. */
+ public static final BlankLineWanted NO = new SimpleBlankLine(Optional.of(false));
+
+ /**
+ * Explicitly preserve blank lines from the input (e.g. before the first member in a class
+ * declaration). Overrides conditional blank lines.
+ */
+ public static final BlankLineWanted PRESERVE =
+ new SimpleBlankLine(/* wanted= */ Optional.empty());
+
+ /** Is the blank line wanted? */
+ public abstract Optional<Boolean> wanted();
+
+ /** Merge this blank line request with another. */
+ public abstract BlankLineWanted merge(BlankLineWanted wanted);
+
+ /** Emit a blank line if the given break is taken. */
+ public static BlankLineWanted conditional(BreakTag breakTag) {
+ return new ConditionalBlankLine(ImmutableList.of(breakTag));
+ }
+
+ private static final class SimpleBlankLine extends BlankLineWanted {
+ private final Optional<Boolean> wanted;
+
+ SimpleBlankLine(Optional<Boolean> wanted) {
+ this.wanted = wanted;
+ }
+
+ @Override
+ public Optional<Boolean> wanted() {
+ return wanted;
+ }
+
+ @Override
+ public BlankLineWanted merge(BlankLineWanted other) {
+ return this;
+ }
+ }
+
+ private static final class ConditionalBlankLine extends BlankLineWanted {
+
+ private final ImmutableList<BreakTag> tags;
+
+ ConditionalBlankLine(Iterable<BreakTag> tags) {
+ this.tags = ImmutableList.copyOf(tags);
+ }
+
+ @Override
+ public Optional<Boolean> wanted() {
+ for (BreakTag tag : tags) {
+ if (tag.wasBreakTaken()) {
+ return Optional.of(true);
+ }
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public BlankLineWanted merge(BlankLineWanted other) {
+ if (!(other instanceof ConditionalBlankLine)) {
+ return other;
+ }
+ return new ConditionalBlankLine(
+ Iterables.concat(this.tags, ((ConditionalBlankLine) other).tags));
+ }
+ }
+ }
+
+ private final Input input;
+ private final List<Op> ops = new ArrayList<>();
+ private final Output output;
+ private static final Indent.Const ZERO = Indent.Const.ZERO;
+
+ private int tokenI = 0;
+ private int inputPosition = Integer.MIN_VALUE;
+
+ /** The number of unclosed open ops in the input stream. */
+ int depth = 0;
+
+ /** Add an {@link Op}, and record open/close ops for later validation of unclosed levels. */
+ private void add(Op op) {
+ if (op instanceof OpenOp) {
+ depth++;
+ } else if (op instanceof CloseOp) {
+ depth--;
+ if (depth < 0) {
+ throw new AssertionError();
+ }
+ }
+ ops.add(op);
+ }
+
+ /** Add a list of {@link Op}s. */
+ public final void addAll(List<Op> ops) {
+ for (Op op : ops) {
+ add(op);
+ }
+ }
+
+ /**
+ * The {@code OpsBuilder} constructor.
+ *
+ * @param input the {@link Input}, used for retrieve information from the AST
+ * @param output the {@link Output}, used here only to record blank-line information
+ */
+ public OpsBuilder(Input input, Output output) {
+ this.input = input;
+ this.output = output;
+ }
+
+ /** Get the {@code OpsBuilder}'s {@link Input}. */
+ public final Input getInput() {
+ return input;
+ }
+
+ /** Returns the number of unclosed open ops in the input stream. */
+ public int depth() {
+ return depth;
+ }
+
+ /**
+ * Checks that all open ops in the op stream have matching close ops.
+ *
+ * @throws FormattingError if any ops were unclosed
+ */
+ public void checkClosed(int previous) {
+ if (depth != previous) {
+ throw new FormattingError(diagnostic(String.format("saw %d unclosed ops", depth)));
+ }
+ }
+
+ /** Create a {@link FormatterDiagnostic} at the current position. */
+ public FormatterDiagnostic diagnostic(String message) {
+ return input.createDiagnostic(inputPosition, message);
+ }
+
+ /**
+ * Sync to position in the input. If we've skipped outputting any tokens that were present in the
+ * input tokens, output them here and optionally complain.
+ *
+ * @param inputPosition the {@code 0}-based input position
+ */
+ public final void sync(int inputPosition) {
+ if (inputPosition > this.inputPosition) {
+ ImmutableList<? extends Input.Token> tokens = input.getTokens();
+ int tokensN = tokens.size();
+ this.inputPosition = inputPosition;
+ if (tokenI < tokensN && inputPosition > tokens.get(tokenI).getTok().getPosition()) {
+ // Found a missing input token. Insert it and mark it missing (usually not good).
+ Input.Token token = tokens.get(tokenI++);
+ throw new FormattingError(
+ diagnostic(String.format("did not generate token \"%s\"", token.getTok().getText())));
+ }
+ }
+ }
+
+ /** Output any remaining tokens from the input stream (e.g. terminal whitespace). */
+ public final void drain() {
+ int inputPosition = input.getText().length() + 1;
+ if (inputPosition > this.inputPosition) {
+ ImmutableList<? extends Input.Token> tokens = input.getTokens();
+ int tokensN = tokens.size();
+ while (tokenI < tokensN && inputPosition > tokens.get(tokenI).getTok().getPosition()) {
+ Input.Token token = tokens.get(tokenI++);
+ add(
+ Doc.Token.make(
+ token,
+ Doc.Token.RealOrImaginary.IMAGINARY,
+ ZERO,
+ /* breakAndIndentTrailingComment= */ Optional.empty()));
+ }
+ }
+ this.inputPosition = inputPosition;
+ checkClosed(0);
+ }
+
+ /**
+ * Open a new level by emitting an {@link OpenOp}.
+ *
+ * @param plusIndent the extra indent for the new level
+ */
+ public final void open(Indent plusIndent) {
+ add(OpenOp.make(plusIndent));
+ }
+
+ /** Close the current level, by emitting a {@link CloseOp}. */
+ public final void close() {
+ add(CloseOp.make());
+ }
+
+ /** Return the text of the next {@link Input.Token}, or absent if there is none. */
+ public final Optional<String> peekToken() {
+ return peekToken(0);
+ }
+
+ /** Return the text of an upcoming {@link Input.Token}, or absent if there is none. */
+ public final Optional<String> peekToken(int skip) {
+ ImmutableList<? extends Input.Token> tokens = input.getTokens();
+ int idx = tokenI + skip;
+ return idx < tokens.size()
+ ? Optional.of(tokens.get(idx).getTok().getOriginalText())
+ : Optional.empty();
+ }
+
+ /**
+ * Emit an optional token iff it exists on the input. This is used to emit tokens whose existence
+ * has been lost in the AST.
+ *
+ * @param token the optional token
+ */
+ public final void guessToken(String token) {
+ token(
+ token,
+ Doc.Token.RealOrImaginary.IMAGINARY,
+ ZERO,
+ /* breakAndIndentTrailingComment= */ Optional.empty());
+ }
+
+ public final void token(
+ String token,
+ Doc.Token.RealOrImaginary realOrImaginary,
+ Indent plusIndentCommentsBefore,
+ Optional<Indent> breakAndIndentTrailingComment) {
+ ImmutableList<? extends Input.Token> tokens = input.getTokens();
+ if (token.equals(peekToken().orElse(null))) { // Found the input token. Output it.
+ add(
+ Doc.Token.make(
+ tokens.get(tokenI++),
+ Doc.Token.RealOrImaginary.REAL,
+ plusIndentCommentsBefore,
+ breakAndIndentTrailingComment));
+ } else {
+ /*
+ * Generated a "bad" token, which doesn't exist on the input. Drop it, and complain unless
+ * (for example) we're guessing at an optional token.
+ */
+ if (realOrImaginary.isReal()) {
+ throw new FormattingError(
+ diagnostic(
+ String.format(
+ "expected token: '%s'; generated %s instead",
+ peekToken().orElse(null), token)));
+ }
+ }
+ }
+
+ /**
+ * Emit a single- or multi-character op by breaking it into single-character {@link Doc.Token}s.
+ *
+ * @param op the operator to emit
+ */
+ public final void op(String op) {
+ int opN = op.length();
+ for (int i = 0; i < opN; i++) {
+ token(
+ op.substring(i, i + 1),
+ Doc.Token.RealOrImaginary.REAL,
+ ZERO,
+ /* breakAndIndentTrailingComment= */ Optional.empty());
+ }
+ }
+
+ /** Emit a {@link Doc.Space}. */
+ public final void space() {
+ add(Doc.Space.make());
+ }
+
+ /** Emit a {@link Doc.Break}. */
+ public final void breakOp() {
+ breakOp(Doc.FillMode.UNIFIED, "", ZERO);
+ }
+
+ /**
+ * Emit a {@link Doc.Break}.
+ *
+ * @param plusIndent extra indent if taken
+ */
+ public final void breakOp(Indent plusIndent) {
+ breakOp(Doc.FillMode.UNIFIED, "", plusIndent);
+ }
+
+ /** Emit a filled {@link Doc.Break}. */
+ public final void breakToFill() {
+ breakOp(Doc.FillMode.INDEPENDENT, "", ZERO);
+ }
+
+ /** Emit a forced {@link Doc.Break}. */
+ public final void forcedBreak() {
+ breakOp(Doc.FillMode.FORCED, "", ZERO);
+ }
+
+ /**
+ * Emit a forced {@link Doc.Break}.
+ *
+ * @param plusIndent extra indent if taken
+ */
+ public final void forcedBreak(Indent plusIndent) {
+ breakOp(Doc.FillMode.FORCED, "", plusIndent);
+ }
+
+ /**
+ * Emit a {@link Doc.Break}, with a specified {@code flat} value (e.g., {@code " "}).
+ *
+ * @param flat the {@link Doc.Break} when not broken
+ */
+ public final void breakOp(String flat) {
+ breakOp(Doc.FillMode.UNIFIED, flat, ZERO);
+ }
+
+ /**
+ * Emit a {@link Doc.Break}, with a specified {@code flat} value (e.g., {@code " "}).
+ *
+ * @param flat the {@link Doc.Break} when not broken
+ */
+ public final void breakToFill(String flat) {
+ breakOp(Doc.FillMode.INDEPENDENT, flat, ZERO);
+ }
+
+ /**
+ * Emit a generic {@link Doc.Break}.
+ *
+ * @param fillMode the {@link Doc.FillMode}
+ * @param flat the {@link Doc.Break} when not broken
+ * @param plusIndent extra indent if taken
+ */
+ public final void breakOp(Doc.FillMode fillMode, String flat, Indent plusIndent) {
+ breakOp(fillMode, flat, plusIndent, /* optionalTag= */ Optional.empty());
+ }
+
+ /**
+ * Emit a generic {@link Doc.Break}.
+ *
+ * @param fillMode the {@link Doc.FillMode}
+ * @param flat the {@link Doc.Break} when not broken
+ * @param plusIndent extra indent if taken
+ * @param optionalTag an optional tag for remembering whether the break was taken
+ */
+ public final void breakOp(
+ Doc.FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optionalTag) {
+ add(Doc.Break.make(fillMode, flat, plusIndent, optionalTag));
+ }
+
+ private int lastPartialFormatBoundary = -1;
+
+ /**
+ * Make the boundary of a region that can be partially formatted. The boundary will be included in
+ * the following region, e.g.: [[boundary0, boundary1), [boundary1, boundary2), ...].
+ */
+ public void markForPartialFormat() {
+ if (lastPartialFormatBoundary == -1) {
+ lastPartialFormatBoundary = tokenI;
+ return;
+ }
+ if (tokenI == lastPartialFormatBoundary) {
+ return;
+ }
+ Token start = input.getTokens().get(lastPartialFormatBoundary);
+ Token end = input.getTokens().get(tokenI - 1);
+ output.markForPartialFormat(start, end);
+ lastPartialFormatBoundary = tokenI;
+ }
+
+ /**
+ * Force or suppress a blank line here in the output.
+ *
+ * @param wanted whether to force ({@code true}) or suppress {@code false}) the blank line
+ */
+ public final void blankLineWanted(BlankLineWanted wanted) {
+ output.blankLine(getI(input.getTokens().get(tokenI)), wanted);
+ }
+
+ private static int getI(Input.Token token) {
+ for (Input.Tok tok : token.getToksBefore()) {
+ if (tok.getIndex() >= 0) {
+ return tok.getIndex();
+ }
+ }
+ return token.getTok().getIndex();
+ }
+
+ private static final Doc.Space SPACE = Doc.Space.make();
+
+ /**
+ * Build a list of {@link Op}s from the {@code OpsBuilder}.
+ *
+ * @return the list of {@link Op}s
+ */
+ public final ImmutableList<Op> build() {
+ markForPartialFormat();
+ // Rewrite the ops to insert comments.
+ Multimap<Integer, Op> tokOps = ArrayListMultimap.create();
+ int opsN = ops.size();
+ for (int i = 0; i < opsN; i++) {
+ Op op = ops.get(i);
+ if (op instanceof Doc.Token) {
+ /*
+ * Token ops can have associated non-tokens, including comments, which we need to insert.
+ * They can also cause line breaks, so we insert them before or after the current level,
+ * when possible.
+ */
+ Doc.Token tokenOp = (Doc.Token) op;
+ Input.Token token = tokenOp.getToken();
+ int j = i; // Where to insert toksBefore before.
+ while (0 < j && ops.get(j - 1) instanceof OpenOp) {
+ --j;
+ }
+ int k = i; // Where to insert toksAfter after.
+ while (k + 1 < opsN && ops.get(k + 1) instanceof CloseOp) {
+ ++k;
+ }
+ if (tokenOp.realOrImaginary().isReal()) {
+ /*
+ * Regular input token. Copy out toksBefore before token, and toksAfter after it. Insert
+ * this token's toksBefore at position j.
+ */
+ int newlines = 0; // Count of newlines in a row.
+ boolean space = false; // Do we need an extra space after a previous "/*" comment?
+ boolean lastWasComment = false; // Was the last thing we output a comment?
+ boolean allowBlankAfterLastComment = false;
+ for (Input.Tok tokBefore : token.getToksBefore()) {
+ if (tokBefore.isNewline()) {
+ newlines++;
+ } else if (tokBefore.isComment()) {
+ tokOps.put(
+ j,
+ Doc.Break.make(
+ tokBefore.isSlashSlashComment() ? Doc.FillMode.FORCED : Doc.FillMode.UNIFIED,
+ "",
+ tokenOp.getPlusIndentCommentsBefore()));
+ tokOps.putAll(j, makeComment(tokBefore));
+ space = tokBefore.isSlashStarComment();
+ newlines = 0;
+ lastWasComment = true;
+ if (tokBefore.isJavadocComment()) {
+ tokOps.put(j, Doc.Break.makeForced());
+ }
+ allowBlankAfterLastComment =
+ tokBefore.isSlashSlashComment()
+ || (tokBefore.isSlashStarComment() && !tokBefore.isJavadocComment());
+ }
+ }
+ if (allowBlankAfterLastComment && newlines > 1) {
+ // Force a line break after two newlines in a row following a line or block comment
+ output.blankLine(token.getTok().getIndex(), BlankLineWanted.YES);
+ }
+ if (lastWasComment && newlines > 0) {
+ tokOps.put(j, Doc.Break.makeForced());
+ } else if (space) {
+ tokOps.put(j, SPACE);
+ }
+ // Now we've seen the Token; output the toksAfter.
+ for (Input.Tok tokAfter : token.getToksAfter()) {
+ if (tokAfter.isComment()) {
+ boolean breakAfter =
+ tokAfter.isJavadocComment()
+ || (tokAfter.isSlashStarComment()
+ && tokenOp.breakAndIndentTrailingComment().isPresent());
+ if (breakAfter) {
+ tokOps.put(
+ k + 1,
+ Doc.Break.make(
+ Doc.FillMode.FORCED,
+ "",
+ tokenOp.breakAndIndentTrailingComment().orElse(Const.ZERO)));
+ } else {
+ tokOps.put(k + 1, SPACE);
+ }
+ tokOps.putAll(k + 1, makeComment(tokAfter));
+ if (breakAfter) {
+ tokOps.put(k + 1, Doc.Break.make(Doc.FillMode.FORCED, "", ZERO));
+ }
+ }
+ }
+ } else {
+ /*
+ * This input token was mistakenly not generated for output. As no whitespace or comments
+ * were generated (presumably), copy all input non-tokens literally, even spaces and
+ * newlines.
+ */
+ int newlines = 0;
+ boolean lastWasComment = false;
+ for (Input.Tok tokBefore : token.getToksBefore()) {
+ if (tokBefore.isNewline()) {
+ newlines++;
+ } else if (tokBefore.isComment()) {
+ newlines = 0;
+ lastWasComment = tokBefore.isComment();
+ }
+ if (lastWasComment && newlines > 0) {
+ tokOps.put(j, Doc.Break.makeForced());
+ }
+ tokOps.put(j, Doc.Tok.make(tokBefore));
+ }
+ for (Input.Tok tokAfter : token.getToksAfter()) {
+ tokOps.put(k + 1, Doc.Tok.make(tokAfter));
+ }
+ }
+ }
+ }
+ /*
+ * Construct new list of ops, splicing in the comments. If a comment is inserted immediately
+ * before a space, suppress the space.
+ */
+ ImmutableList.Builder<Op> newOps = ImmutableList.builder();
+ boolean afterForcedBreak = false; // Was the last Op a forced break? If so, suppress spaces.
+ for (int i = 0; i < opsN; i++) {
+ for (Op op : tokOps.get(i)) {
+ if (!(afterForcedBreak && op instanceof Doc.Space)) {
+ newOps.add(op);
+ afterForcedBreak = isForcedBreak(op);
+ }
+ }
+ Op op = ops.get(i);
+ if (afterForcedBreak
+ && (op instanceof Doc.Space
+ || (op instanceof Doc.Break
+ && ((Doc.Break) op).getPlusIndent() == 0
+ && " ".equals(((Doc) op).getFlat())))) {
+ continue;
+ }
+ newOps.add(op);
+ if (!(op instanceof OpenOp)) {
+ afterForcedBreak = isForcedBreak(op);
+ }
+ }
+ for (Op op : tokOps.get(opsN)) {
+ if (!(afterForcedBreak && op instanceof Doc.Space)) {
+ newOps.add(op);
+ afterForcedBreak = isForcedBreak(op);
+ }
+ }
+ return newOps.build();
+ }
+
+ private static boolean isForcedBreak(Op op) {
+ return op instanceof Doc.Break && ((Doc.Break) op).isForced();
+ }
+
+ private static List<Op> makeComment(Input.Tok comment) {
+ return comment.isSlashStarComment()
+ ? ImmutableList.of(Doc.Tok.make(comment))
+ : ImmutableList.of(Doc.Tok.make(comment), Doc.Break.makeForced());
+ }
+
+ @Override
+ public final String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("input", input)
+ .add("ops", ops)
+ .add("output", output)
+ .add("tokenI", tokenI)
+ .add("inputPosition", inputPosition)
+ .toString();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/Output.java b/core/src/main/java/com/google/googlejavaformat/Output.java
new file mode 100644
index 0000000..ea039fa
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/Output.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Range;
+import com.google.googlejavaformat.OpsBuilder.BlankLineWanted;
+import java.util.Optional;
+
+/** An output from the formatter. */
+public abstract class Output extends InputOutput {
+ /** Unique identifier for a break. */
+ public static final class BreakTag {
+
+ Optional<Boolean> taken = Optional.empty();
+
+ public void recordBroken(boolean broken) {
+ // TODO(cushon): enforce invariants.
+ // Currently we rely on setting Breaks multiple times, e.g. when deciding
+ // whether a Level should be flowed. Using separate data structures
+ // instead of mutation or adding an explicit 'reset' step would allow
+ // a useful invariant to be enforced here.
+ taken = Optional.of(broken);
+ }
+
+ public boolean wasBreakTaken() {
+ return taken.orElse(false);
+ }
+ }
+
+ /**
+ * Indent by outputting {@code indent} spaces.
+ *
+ * @param indent the current indent
+ */
+ public abstract void indent(int indent);
+
+ /**
+ * Output a string.
+ *
+ * @param text the string
+ * @param range the {@link Range} corresponding to the string
+ */
+ public abstract void append(String text, Range<Integer> range);
+
+ /**
+ * A blank line is or is not wanted here.
+ *
+ * @param k the {@link Input.Tok} index
+ * @param wanted whether a blank line is wanted here
+ */
+ public abstract void blankLine(int k, BlankLineWanted wanted);
+
+ /** Marks a region that can be partially formatted. */
+ public abstract void markForPartialFormat(Input.Token start, Input.Token end);
+
+ /**
+ * Get the {@link CommentsHelper}.
+ *
+ * @return the {@link CommentsHelper}
+ */
+ public abstract CommentsHelper getCommentsHelper();
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("super", super.toString()).toString();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/CommandLineOptions.java b/core/src/main/java/com/google/googlejavaformat/java/CommandLineOptions.java
new file mode 100644
index 0000000..5a23328
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/CommandLineOptions.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableRangeSet;
+import java.util.Optional;
+
+/**
+ * Command line options for google-java-format.
+ *
+ * <p>google-java-format doesn't depend on AutoValue, to allow AutoValue to depend on
+ * google-java-format.
+ */
+final class CommandLineOptions {
+
+ private final ImmutableList<String> files;
+ private final boolean inPlace;
+ private final ImmutableRangeSet<Integer> lines;
+ private final ImmutableList<Integer> offsets;
+ private final ImmutableList<Integer> lengths;
+ private final boolean aosp;
+ private final boolean version;
+ private final boolean help;
+ private final boolean stdin;
+ private final boolean fixImportsOnly;
+ private final boolean sortImports;
+ private final boolean removeUnusedImports;
+ private final boolean dryRun;
+ private final boolean setExitIfChanged;
+ private final Optional<String> assumeFilename;
+ private final boolean reflowLongStrings;
+ private final boolean formatJavadoc;
+
+ CommandLineOptions(
+ ImmutableList<String> files,
+ boolean inPlace,
+ ImmutableRangeSet<Integer> lines,
+ ImmutableList<Integer> offsets,
+ ImmutableList<Integer> lengths,
+ boolean aosp,
+ boolean version,
+ boolean help,
+ boolean stdin,
+ boolean fixImportsOnly,
+ boolean sortImports,
+ boolean removeUnusedImports,
+ boolean dryRun,
+ boolean setExitIfChanged,
+ Optional<String> assumeFilename,
+ boolean reflowLongStrings,
+ boolean formatJavadoc) {
+ this.files = files;
+ this.inPlace = inPlace;
+ this.lines = lines;
+ this.offsets = offsets;
+ this.lengths = lengths;
+ this.aosp = aosp;
+ this.version = version;
+ this.help = help;
+ this.stdin = stdin;
+ this.fixImportsOnly = fixImportsOnly;
+ this.sortImports = sortImports;
+ this.removeUnusedImports = removeUnusedImports;
+ this.dryRun = dryRun;
+ this.setExitIfChanged = setExitIfChanged;
+ this.assumeFilename = assumeFilename;
+ this.reflowLongStrings = reflowLongStrings;
+ this.formatJavadoc = formatJavadoc;
+ }
+
+ /** The files to format. */
+ ImmutableList<String> files() {
+ return files;
+ }
+
+ /** Format files in place. */
+ boolean inPlace() {
+ return inPlace;
+ }
+
+ /** Line ranges to format. */
+ ImmutableRangeSet<Integer> lines() {
+ return lines;
+ }
+
+ /** Character offsets for partial formatting, paired with {@code lengths}. */
+ ImmutableList<Integer> offsets() {
+ return offsets;
+ }
+
+ /** Partial formatting region lengths, paired with {@code offsets}. */
+ ImmutableList<Integer> lengths() {
+ return lengths;
+ }
+
+ /** Use AOSP style instead of Google Style (4-space indentation). */
+ boolean aosp() {
+ return aosp;
+ }
+
+ /** Print the version. */
+ boolean version() {
+ return version;
+ }
+
+ /** Print usage information. */
+ boolean help() {
+ return help;
+ }
+
+ /** Format input from stdin. */
+ boolean stdin() {
+ return stdin;
+ }
+
+ /** Fix imports, but do no formatting. */
+ boolean fixImportsOnly() {
+ return fixImportsOnly;
+ }
+
+ /** Sort imports. */
+ boolean sortImports() {
+ return sortImports;
+ }
+
+ /** Remove unused imports. */
+ boolean removeUnusedImports() {
+ return removeUnusedImports;
+ }
+
+ /**
+ * Print the paths of the files whose contents would change if the formatter were run normally.
+ */
+ boolean dryRun() {
+ return dryRun;
+ }
+
+ /** Return exit code 1 if there are any formatting changes. */
+ boolean setExitIfChanged() {
+ return setExitIfChanged;
+ }
+
+ /** Return the name to use for diagnostics when formatting standard input. */
+ Optional<String> assumeFilename() {
+ return assumeFilename;
+ }
+
+ boolean reflowLongStrings() {
+ return reflowLongStrings;
+ }
+
+ /** Returns true if partial formatting was selected. */
+ boolean isSelection() {
+ return !lines().isEmpty() || !offsets().isEmpty() || !lengths().isEmpty();
+ }
+
+ boolean formatJavadoc() {
+ return formatJavadoc;
+ }
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ static class Builder {
+
+ private final ImmutableList.Builder<String> files = ImmutableList.builder();
+ private final ImmutableRangeSet.Builder<Integer> lines = ImmutableRangeSet.builder();
+ private final ImmutableList.Builder<Integer> offsets = ImmutableList.builder();
+ private final ImmutableList.Builder<Integer> lengths = ImmutableList.builder();
+ private boolean inPlace = false;
+ private boolean aosp = false;
+ private boolean version = false;
+ private boolean help = false;
+ private boolean stdin = false;
+ private boolean fixImportsOnly = false;
+ private boolean sortImports = true;
+ private boolean removeUnusedImports = true;
+ private boolean dryRun = false;
+ private boolean setExitIfChanged = false;
+ private Optional<String> assumeFilename = Optional.empty();
+ private boolean reflowLongStrings = true;
+ private boolean formatJavadoc = true;
+
+ ImmutableList.Builder<String> filesBuilder() {
+ return files;
+ }
+
+ Builder inPlace(boolean inPlace) {
+ this.inPlace = inPlace;
+ return this;
+ }
+
+ ImmutableRangeSet.Builder<Integer> linesBuilder() {
+ return lines;
+ }
+
+ Builder addOffset(Integer offset) {
+ offsets.add(offset);
+ return this;
+ }
+
+ Builder addLength(Integer length) {
+ lengths.add(length);
+ return this;
+ }
+
+ Builder aosp(boolean aosp) {
+ this.aosp = aosp;
+ return this;
+ }
+
+ Builder version(boolean version) {
+ this.version = version;
+ return this;
+ }
+
+ Builder help(boolean help) {
+ this.help = help;
+ return this;
+ }
+
+ Builder stdin(boolean stdin) {
+ this.stdin = stdin;
+ return this;
+ }
+
+ Builder fixImportsOnly(boolean fixImportsOnly) {
+ this.fixImportsOnly = fixImportsOnly;
+ return this;
+ }
+
+ Builder sortImports(boolean sortImports) {
+ this.sortImports = sortImports;
+ return this;
+ }
+
+ Builder removeUnusedImports(boolean removeUnusedImports) {
+ this.removeUnusedImports = removeUnusedImports;
+ return this;
+ }
+
+ Builder dryRun(boolean dryRun) {
+ this.dryRun = dryRun;
+ return this;
+ }
+
+ Builder setExitIfChanged(boolean setExitIfChanged) {
+ this.setExitIfChanged = setExitIfChanged;
+ return this;
+ }
+
+ Builder assumeFilename(String assumeFilename) {
+ this.assumeFilename = Optional.of(assumeFilename);
+ return this;
+ }
+
+ Builder reflowLongStrings(boolean reflowLongStrings) {
+ this.reflowLongStrings = reflowLongStrings;
+ return this;
+ }
+
+ Builder formatJavadoc(boolean formatJavadoc) {
+ this.formatJavadoc = formatJavadoc;
+ return this;
+ }
+
+ CommandLineOptions build() {
+ return new CommandLineOptions(
+ files.build(),
+ inPlace,
+ lines.build(),
+ offsets.build(),
+ lengths.build(),
+ aosp,
+ version,
+ help,
+ stdin,
+ fixImportsOnly,
+ sortImports,
+ removeUnusedImports,
+ dryRun,
+ setExitIfChanged,
+ assumeFilename,
+ reflowLongStrings,
+ formatJavadoc);
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java b/core/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java
new file mode 100644
index 0000000..2023826
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableRangeSet;
+import com.google.common.collect.Range;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/** A parser for {@link CommandLineOptions}. */
+final class CommandLineOptionsParser {
+
+ private static final Splitter COMMA_SPLITTER = Splitter.on(',');
+ private static final Splitter COLON_SPLITTER = Splitter.on(':');
+ private static final Splitter ARG_SPLITTER =
+ Splitter.on(CharMatcher.breakingWhitespace()).omitEmptyStrings().trimResults();
+
+ /** Parses {@link CommandLineOptions}. */
+ static CommandLineOptions parse(Iterable<String> options) {
+ CommandLineOptions.Builder optionsBuilder = CommandLineOptions.builder();
+ List<String> expandedOptions = new ArrayList<>();
+ expandParamsFiles(options, expandedOptions);
+ Iterator<String> it = expandedOptions.iterator();
+ while (it.hasNext()) {
+ String option = it.next();
+ if (!option.startsWith("-")) {
+ optionsBuilder.filesBuilder().add(option).addAll(it);
+ break;
+ }
+ String flag;
+ String value;
+ int idx = option.indexOf('=');
+ if (idx >= 0) {
+ flag = option.substring(0, idx);
+ value = option.substring(idx + 1, option.length());
+ } else {
+ flag = option;
+ value = null;
+ }
+ // NOTE: update usage information in UsageException when new flags are added
+ switch (flag) {
+ case "-i":
+ case "-r":
+ case "-replace":
+ case "--replace":
+ optionsBuilder.inPlace(true);
+ break;
+ case "--lines":
+ case "-lines":
+ case "--line":
+ case "-line":
+ parseRangeSet(optionsBuilder.linesBuilder(), getValue(flag, it, value));
+ break;
+ case "--offset":
+ case "-offset":
+ optionsBuilder.addOffset(parseInteger(it, flag, value));
+ break;
+ case "--length":
+ case "-length":
+ optionsBuilder.addLength(parseInteger(it, flag, value));
+ break;
+ case "--aosp":
+ case "-aosp":
+ case "-a":
+ optionsBuilder.aosp(true);
+ break;
+ case "--version":
+ case "-version":
+ case "-v":
+ optionsBuilder.version(true);
+ break;
+ case "--help":
+ case "-help":
+ case "-h":
+ optionsBuilder.help(true);
+ break;
+ case "--fix-imports-only":
+ optionsBuilder.fixImportsOnly(true);
+ break;
+ case "--skip-sorting-imports":
+ optionsBuilder.sortImports(false);
+ break;
+ case "--skip-removing-unused-imports":
+ optionsBuilder.removeUnusedImports(false);
+ break;
+ case "--skip-reflowing-long-strings":
+ optionsBuilder.reflowLongStrings(false);
+ break;
+ case "--skip-javadoc-formatting":
+ optionsBuilder.formatJavadoc(false);
+ break;
+ case "-":
+ optionsBuilder.stdin(true);
+ break;
+ case "-n":
+ case "--dry-run":
+ optionsBuilder.dryRun(true);
+ break;
+ case "--set-exit-if-changed":
+ optionsBuilder.setExitIfChanged(true);
+ break;
+ case "-assume-filename":
+ case "--assume-filename":
+ optionsBuilder.assumeFilename(getValue(flag, it, value));
+ break;
+ default:
+ throw new IllegalArgumentException("unexpected flag: " + flag);
+ }
+ }
+ return optionsBuilder.build();
+ }
+
+ private static Integer parseInteger(Iterator<String> it, String flag, String value) {
+ try {
+ return Integer.valueOf(getValue(flag, it, value));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ String.format("invalid integer value for %s: %s", flag, value), e);
+ }
+ }
+
+ private static String getValue(String flag, Iterator<String> it, String value) {
+ if (value != null) {
+ return value;
+ }
+ if (!it.hasNext()) {
+ throw new IllegalArgumentException("required value was not provided for: " + flag);
+ }
+ return it.next();
+ }
+
+ /**
+ * Parse multiple --lines flags, like {"1:12,14,20:36", "40:45,50"}. Multiple ranges can be given
+ * with multiple --lines flags or separated by commas. A single line can be set by a single
+ * number. Line numbers are {@code 1}-based, but are converted to the {@code 0}-based numbering
+ * used internally by google-java-format.
+ */
+ private static void parseRangeSet(ImmutableRangeSet.Builder<Integer> result, String ranges) {
+ for (String range : COMMA_SPLITTER.split(ranges)) {
+ result.add(parseRange(range));
+ }
+ }
+
+ /**
+ * Parse a range, as in "1:12" or "42". Line numbers provided are {@code 1}-based, but are
+ * converted here to {@code 0}-based.
+ */
+ private static Range<Integer> parseRange(String arg) {
+ List<String> args = COLON_SPLITTER.splitToList(arg);
+ switch (args.size()) {
+ case 1:
+ int line = Integer.parseInt(args.get(0)) - 1;
+ return Range.closedOpen(line, line + 1);
+ case 2:
+ int line0 = Integer.parseInt(args.get(0)) - 1;
+ int line1 = Integer.parseInt(args.get(1)) - 1;
+ return Range.closedOpen(line0, line1 + 1);
+ default:
+ throw new IllegalArgumentException(arg);
+ }
+ }
+
+ /**
+ * Pre-processes an argument list, expanding arguments of the form {@code @filename} by reading
+ * the content of the file and appending whitespace-delimited options to {@code arguments}.
+ */
+ private static void expandParamsFiles(Iterable<String> args, List<String> expanded) {
+ for (String arg : args) {
+ if (arg.isEmpty()) {
+ continue;
+ }
+ if (!arg.startsWith("@")) {
+ expanded.add(arg);
+ } else if (arg.startsWith("@@")) {
+ expanded.add(arg.substring(1));
+ } else {
+ Path path = Paths.get(arg.substring(1));
+ try {
+ String sequence = new String(Files.readAllBytes(path), UTF_8);
+ expandParamsFiles(ARG_SPLITTER.split(sequence), expanded);
+ } catch (IOException e) {
+ throw new UncheckedIOException(path + ": could not read file: " + e.getMessage(), e);
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/DimensionHelpers.java b/core/src/main/java/com/google/googlejavaformat/java/DimensionHelpers.java
new file mode 100644
index 0000000..4bd19be
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/DimensionHelpers.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import com.google.common.collect.ImmutableList;
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.tree.JCTree;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+
+/**
+ * Utilities for working with array dimensions.
+ *
+ * <p>javac's parser does not preserve concrete syntax for mixed-notation arrays, so we have to
+ * re-lex the input to extra it.
+ *
+ * <p>For example, {@code int [] a;} cannot be distinguished from {@code int [] a [];} in the AST.
+ */
+class DimensionHelpers {
+
+ /** The array dimension specifiers (including any type annotations) associated with a type. */
+ static class TypeWithDims {
+ final Tree node;
+ final ImmutableList<List<AnnotationTree>> dims;
+
+ public TypeWithDims(Tree node, ImmutableList<List<AnnotationTree>> dims) {
+ this.node = node;
+ this.dims = dims;
+ }
+ }
+
+ enum SortedDims {
+ YES,
+ NO
+ }
+
+ /** Returns a (possibly re-ordered) {@link TypeWithDims} for the given type. */
+ static TypeWithDims extractDims(Tree node, SortedDims sorted) {
+ Deque<List<AnnotationTree>> builder = new ArrayDeque<>();
+ node = extractDims(builder, node);
+ Iterable<List<AnnotationTree>> dims;
+ if (sorted == SortedDims.YES) {
+ dims = reorderBySourcePosition(builder);
+ } else {
+ dims = builder;
+ }
+ return new TypeWithDims(node, ImmutableList.copyOf(dims));
+ }
+
+ /**
+ * Rotate the list of dimension specifiers until all dimensions with type annotations appear in
+ * source order.
+ *
+ * <p>javac reorders dimension specifiers in method declarations with mixed-array notation, which
+ * means that any type annotations don't appear in source order.
+ *
+ * <p>For example, the type of {@code int @A [] f() @B [] {}} is parsed as {@code @B [] @A []}.
+ *
+ * <p>This doesn't handle cases with un-annotated dimension specifiers, so the formatting logic
+ * checks the token stream to figure out which side of the method name they appear on.
+ */
+ private static Iterable<List<AnnotationTree>> reorderBySourcePosition(
+ Deque<List<AnnotationTree>> dims) {
+ int lastAnnotation = -1;
+ int lastPos = -1;
+ int idx = 0;
+ for (List<AnnotationTree> dim : dims) {
+ if (!dim.isEmpty()) {
+ int pos = ((JCTree) dim.get(0)).getStartPosition();
+ if (pos < lastPos) {
+ List<List<AnnotationTree>> list = new ArrayList<>(dims);
+ Collections.rotate(list, -(lastAnnotation + 1));
+ return list;
+ }
+ lastPos = pos;
+ lastAnnotation = idx;
+ }
+ idx++;
+ }
+ return dims;
+ }
+
+ /**
+ * Accumulates a flattened list of array dimensions specifiers with type annotations, and returns
+ * the base type.
+ *
+ * <p>Given {@code int @A @B [][] @C []}, adds {@code [[@A, @B], [@C]]} to dims and returns {@code
+ * int}.
+ */
+ private static Tree extractDims(Deque<List<AnnotationTree>> dims, Tree node) {
+ switch (node.getKind()) {
+ case ARRAY_TYPE:
+ return extractDims(dims, ((ArrayTypeTree) node).getType());
+ case ANNOTATED_TYPE:
+ AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) node;
+ if (annotatedTypeTree.getUnderlyingType().getKind() != Tree.Kind.ARRAY_TYPE) {
+ return node;
+ }
+ node = extractDims(dims, annotatedTypeTree.getUnderlyingType());
+ dims.addFirst(ImmutableList.copyOf(annotatedTypeTree.getAnnotations()));
+ return node;
+ default:
+ return node;
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java b/core/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java
new file mode 100644
index 0000000..9d8ae41
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+import java.util.concurrent.Callable;
+
+/**
+ * Encapsulates information about a file to be formatted, including which parts of the file to
+ * format.
+ */
+class FormatFileCallable implements Callable<String> {
+ private final String input;
+ private final CommandLineOptions parameters;
+ private final JavaFormatterOptions options;
+
+ public FormatFileCallable(
+ CommandLineOptions parameters, String input, JavaFormatterOptions options) {
+ this.input = input;
+ this.parameters = parameters;
+ this.options = options;
+ }
+
+ @Override
+ public String call() throws FormatterException {
+ if (parameters.fixImportsOnly()) {
+ return fixImports(input);
+ }
+
+ Formatter formatter = new Formatter(options);
+ String formatted = formatter.formatSource(input, characterRanges(input).asRanges());
+ formatted = fixImports(formatted);
+ if (parameters.reflowLongStrings()) {
+ formatted = StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, formatted, formatter);
+ }
+ return formatted;
+ }
+
+ private String fixImports(String input) throws FormatterException {
+ if (parameters.removeUnusedImports()) {
+ input = RemoveUnusedImports.removeUnusedImports(input);
+ }
+ if (parameters.sortImports()) {
+ input = ImportOrderer.reorderImports(input, options.style());
+ }
+ return input;
+ }
+
+ private RangeSet<Integer> characterRanges(String input) {
+ final RangeSet<Integer> characterRanges = TreeRangeSet.create();
+
+ if (parameters.lines().isEmpty() && parameters.offsets().isEmpty()) {
+ characterRanges.add(Range.closedOpen(0, input.length()));
+ return characterRanges;
+ }
+
+ characterRanges.addAll(Formatter.lineRangesToCharRanges(input, parameters.lines()));
+
+ for (int i = 0; i < parameters.offsets().size(); i++) {
+ Integer length = parameters.lengths().get(i);
+ if (length == 0) {
+ // 0 stands for "format the line under the cursor"
+ length = 1;
+ }
+ characterRanges.add(
+ Range.closedOpen(parameters.offsets().get(i), parameters.offsets().get(i) + length));
+ }
+
+ return characterRanges;
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/Formatter.java b/core/src/main/java/com/google/googlejavaformat/java/Formatter.java
new file mode 100644
index 0000000..3e97395
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/Formatter.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION;
+import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+import com.google.common.io.CharSink;
+import com.google.common.io.CharSource;
+import com.google.errorprone.annotations.Immutable;
+import com.google.googlejavaformat.Doc;
+import com.google.googlejavaformat.DocBuilder;
+import com.google.googlejavaformat.FormattingError;
+import com.google.googlejavaformat.Newlines;
+import com.google.googlejavaformat.Op;
+import com.google.googlejavaformat.OpsBuilder;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.parser.JavacParser;
+import com.sun.tools.javac.parser.ParserFactory;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Log;
+import com.sun.tools.javac.util.Options;
+import java.io.IOError;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardLocation;
+
+/**
+ * This is google-java-format, a new Java formatter that follows the Google Java Style Guide quite
+ * precisely---to the letter and to the spirit.
+ *
+ * <p>This formatter uses the javac parser to generate an AST. Because the AST loses information
+ * about the non-tokens in the input (including newlines, comments, etc.), and even some tokens
+ * (e.g., optional commas or semicolons), this formatter lexes the input again and follows along in
+ * the resulting list of tokens. Its lexer splits all multi-character operators (like ">>")
+ * into multiple single-character operators. Each non-token is assigned to a token---non-tokens
+ * following a token on the same line go with that token; those following go with the next token---
+ * and there is a final EOF token to hold final comments.
+ *
+ * <p>The formatter walks the AST to generate a Greg Nelson/Derek Oppen-style list of formatting
+ * {@link Op}s [1--2] that then generates a structured {@link Doc}. Each AST node type has a visitor
+ * to emit a sequence of {@link Op}s for the node.
+ *
+ * <p>Some data-structure operations are easier in the list of {@link Op}s, while others become
+ * easier in the {@link Doc}. The {@link Op}s are walked to attach the comments. As the {@link Op}s
+ * are generated, missing input tokens are inserted and incorrect output tokens are dropped,
+ * ensuring that the output matches the input even in the face of formatter errors. Finally, the
+ * formatter walks the {@link Doc} to format it in the given width.
+ *
+ * <p>This formatter also produces data structures of which tokens and comments appear where on the
+ * input, and on the output, to help output a partial reformatting of a slightly edited input.
+ *
+ * <p>Instances of the formatter are immutable and thread-safe.
+ *
+ * <p>[1] Nelson, Greg, and John DeTreville. Personal communication.
+ *
+ * <p>[2] Oppen, Derek C. "Prettyprinting". ACM Transactions on Programming Languages and Systems,
+ * Volume 2 Issue 4, Oct. 1980, pp. 465–483.
+ */
+@Immutable
+public final class Formatter {
+
+ public static final int MAX_LINE_LENGTH = 100;
+
+ static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1);
+
+ private final JavaFormatterOptions options;
+
+ /** A new Formatter instance with default options. */
+ public Formatter() {
+ this(JavaFormatterOptions.defaultOptions());
+ }
+
+ public Formatter(JavaFormatterOptions options) {
+ this.options = options;
+ }
+
+ /**
+ * Construct a {@code Formatter} given a Java compilation unit. Parses the code; builds a {@link
+ * JavaInput} and the corresponding {@link JavaOutput}.
+ *
+ * @param javaInput the input, a Java compilation unit
+ * @param javaOutput the {@link JavaOutput}
+ * @param options the {@link JavaFormatterOptions}
+ */
+ static void format(final JavaInput javaInput, JavaOutput javaOutput, JavaFormatterOptions options)
+ throws FormatterException {
+ Context context = new Context();
+ DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
+ context.put(DiagnosticListener.class, diagnostics);
+ Options.instance(context).put("allowStringFolding", "false");
+ Options.instance(context).put("--enable-preview", "true");
+ JCCompilationUnit unit;
+ JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8);
+ try {
+ fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of());
+ } catch (IOException e) {
+ // impossible
+ throw new IOError(e);
+ }
+ SimpleJavaFileObject source =
+ new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) {
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return javaInput.getText();
+ }
+ };
+ Log.instance(context).useSource(source);
+ ParserFactory parserFactory = ParserFactory.instance(context);
+ JavacParser parser =
+ parserFactory.newParser(
+ javaInput.getText(),
+ /*keepDocComments=*/ true,
+ /*keepEndPos=*/ true,
+ /*keepLineMap=*/ true);
+ unit = parser.parseCompilationUnit();
+ unit.sourcefile = source;
+
+ javaInput.setCompilationUnit(unit);
+ Iterable<Diagnostic<? extends JavaFileObject>> errorDiagnostics =
+ Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic);
+ if (!Iterables.isEmpty(errorDiagnostics)) {
+ throw FormatterException.fromJavacDiagnostics(errorDiagnostics);
+ }
+ OpsBuilder builder = new OpsBuilder(javaInput, javaOutput);
+ // Output the compilation unit.
+ JavaInputAstVisitor visitor;
+ if (getMajor() >= 14) {
+ try {
+ visitor =
+ Class.forName("com.google.googlejavaformat.java.java14.Java14InputAstVisitor")
+ .asSubclass(JavaInputAstVisitor.class)
+ .getConstructor(OpsBuilder.class, int.class)
+ .newInstance(builder, options.indentationMultiplier());
+ } catch (ReflectiveOperationException e) {
+ throw new LinkageError(e.getMessage(), e);
+ }
+ } else {
+ visitor = new JavaInputAstVisitor(builder, options.indentationMultiplier());
+ }
+ visitor.scan(unit, null);
+ builder.sync(javaInput.getText().length());
+ builder.drain();
+ Doc doc = new DocBuilder().withOps(builder.build()).build();
+ doc.computeBreaks(javaOutput.getCommentsHelper(), MAX_LINE_LENGTH, new Doc.State(+0, 0));
+ doc.write(javaOutput);
+ javaOutput.flush();
+ }
+
+ // Runtime.Version was added in JDK 9, so use reflection to access it to preserve source
+ // compatibility with Java 8.
+ private static int getMajor() {
+ try {
+ Method versionMethod = Runtime.class.getMethod("version");
+ Object version = versionMethod.invoke(null);
+ return (int) version.getClass().getMethod("major").invoke(version);
+ } catch (Exception e) {
+ // continue below
+ }
+ int version = (int) Double.parseDouble(JAVA_CLASS_VERSION.value());
+ if (49 <= version && version <= 52) {
+ return version - (49 - 5);
+ }
+ throw new IllegalStateException("Unknown Java version: " + JAVA_SPECIFICATION_VERSION.value());
+ }
+
+ static boolean errorDiagnostic(Diagnostic<?> input) {
+ if (input.getKind() != Diagnostic.Kind.ERROR) {
+ return false;
+ }
+ switch (input.getCode()) {
+ case "compiler.err.invalid.meth.decl.ret.type.req":
+ // accept constructor-like method declarations that don't match the name of their
+ // enclosing class
+ return false;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Format the given input (a Java compilation unit) into the output stream.
+ *
+ * @throws FormatterException if the input cannot be parsed
+ */
+ public void formatSource(CharSource input, CharSink output)
+ throws FormatterException, IOException {
+ // TODO(cushon): proper support for streaming input/output. Input may
+ // not be feasible (parsing) but output should be easier.
+ output.write(formatSource(input.read()));
+ }
+
+ /**
+ * Format an input string (a Java compilation unit) into an output string.
+ *
+ * <p>Leaves import statements untouched.
+ *
+ * @param input the input string
+ * @return the output string
+ * @throws FormatterException if the input string cannot be parsed
+ */
+ public String formatSource(String input) throws FormatterException {
+ return formatSource(input, ImmutableList.of(Range.closedOpen(0, input.length())));
+ }
+
+ /**
+ * Formats an input string (a Java compilation unit) and fixes imports.
+ *
+ * <p>Fixing imports includes ordering, spacing, and removal of unused import statements.
+ *
+ * @param input the input string
+ * @return the output string
+ * @throws FormatterException if the input string cannot be parsed
+ * @see <a
+ * href="https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing">
+ * Google Java Style Guide - 3.3.3 Import ordering and spacing</a>
+ */
+ public String formatSourceAndFixImports(String input) throws FormatterException {
+ input = ImportOrderer.reorderImports(input, options.style());
+ input = RemoveUnusedImports.removeUnusedImports(input);
+ String formatted = formatSource(input);
+ formatted = StringWrapper.wrap(formatted, this);
+ return formatted;
+ }
+
+ /**
+ * Format an input string (a Java compilation unit), for only the specified character ranges.
+ * These ranges are extended as necessary (e.g., to encompass whole lines).
+ *
+ * @param input the input string
+ * @param characterRanges the character ranges to be reformatted
+ * @return the output string
+ * @throws FormatterException if the input string cannot be parsed
+ */
+ public String formatSource(String input, Collection<Range<Integer>> characterRanges)
+ throws FormatterException {
+ return JavaOutput.applyReplacements(input, getFormatReplacements(input, characterRanges));
+ }
+
+ /**
+ * Emit a list of {@link Replacement}s to convert from input to output.
+ *
+ * @param input the input compilation unit
+ * @param characterRanges the character ranges to reformat
+ * @return a list of {@link Replacement}s, sorted from low index to high index, without overlaps
+ * @throws FormatterException if the input string cannot be parsed
+ */
+ public ImmutableList<Replacement> getFormatReplacements(
+ String input, Collection<Range<Integer>> characterRanges) throws FormatterException {
+ JavaInput javaInput = new JavaInput(input);
+
+ // TODO(cushon): this is only safe because the modifier ordering doesn't affect whitespace,
+ // and doesn't change the replacements that are output. This is not true in general for
+ // 'de-linting' changes (e.g. import ordering).
+ javaInput = ModifierOrderer.reorderModifiers(javaInput, characterRanges);
+
+ String lineSeparator = Newlines.guessLineSeparator(input);
+ JavaOutput javaOutput =
+ new JavaOutput(lineSeparator, javaInput, new JavaCommentsHelper(lineSeparator, options));
+ try {
+ format(javaInput, javaOutput, options);
+ } catch (FormattingError e) {
+ throw new FormatterException(e.diagnostics());
+ }
+ RangeSet<Integer> tokenRangeSet = javaInput.characterRangesToTokenRanges(characterRanges);
+ return javaOutput.getFormatReplacements(tokenRangeSet);
+ }
+
+ /**
+ * Converts zero-indexed, [closed, open) line ranges in the given source file to character ranges.
+ */
+ public static RangeSet<Integer> lineRangesToCharRanges(
+ String input, RangeSet<Integer> lineRanges) {
+ List<Integer> lines = new ArrayList<>();
+ Iterators.addAll(lines, Newlines.lineOffsetIterator(input));
+ lines.add(input.length() + 1);
+
+ final RangeSet<Integer> characterRanges = TreeRangeSet.create();
+ for (Range<Integer> lineRange :
+ lineRanges.subRangeSet(Range.closedOpen(0, lines.size() - 1)).asRanges()) {
+ int lineStart = lines.get(lineRange.lowerEndpoint());
+ // Exclude the trailing newline. This isn't strictly necessary, but handling blank lines
+ // as empty ranges is convenient.
+ int lineEnd = lines.get(lineRange.upperEndpoint()) - 1;
+ Range<Integer> range = Range.closedOpen(lineStart, lineEnd);
+ characterRanges.add(range);
+ }
+ return characterRanges;
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/FormatterException.java b/core/src/main/java/com/google/googlejavaformat/java/FormatterException.java
new file mode 100644
index 0000000..3ccb44a
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/FormatterException.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static java.util.Locale.ENGLISH;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.googlejavaformat.FormatterDiagnostic;
+import java.util.List;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+/** Checked exception class for formatter errors. */
+public final class FormatterException extends Exception {
+
+ private ImmutableList<FormatterDiagnostic> diagnostics;
+
+ public FormatterException(String message) {
+ this(FormatterDiagnostic.create(message));
+ }
+
+ public FormatterException(FormatterDiagnostic diagnostic) {
+ this(ImmutableList.of(diagnostic));
+ }
+
+ public FormatterException(Iterable<FormatterDiagnostic> diagnostics) {
+ super(diagnostics.iterator().next().toString());
+ this.diagnostics = ImmutableList.copyOf(diagnostics);
+ }
+
+ public List<FormatterDiagnostic> diagnostics() {
+ return diagnostics;
+ }
+
+ public static FormatterException fromJavacDiagnostics(
+ Iterable<Diagnostic<? extends JavaFileObject>> diagnostics) {
+ return new FormatterException(Iterables.transform(diagnostics, d -> toFormatterDiagnostic(d)));
+ }
+
+ private static FormatterDiagnostic toFormatterDiagnostic(Diagnostic<?> input) {
+ return FormatterDiagnostic.create(
+ (int) input.getLineNumber(), (int) input.getColumnNumber(), input.getMessage(ENGLISH));
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatVersion.java.template b/core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatVersion.java.template
new file mode 100644
index 0000000..88706fb
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatVersion.java.template
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+class GoogleJavaFormatVersion {
+
+ static String version() {
+ return "%VERSION%";
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java b/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java
new file mode 100644
index 0000000..a82715e
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.collect.Iterables.getLast;
+import static com.google.common.primitives.Booleans.trueFirst;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.googlejavaformat.Newlines;
+import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
+import com.google.googlejavaformat.java.JavaInput.Tok;
+import com.sun.tools.javac.parser.Tokens.TokenKind;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.stream.Stream;
+
+/** Orders imports in Java source code. */
+public class ImportOrderer {
+
+ private static final Splitter DOT_SPLITTER = Splitter.on('.');
+
+ /**
+ * Reorder the inputs in {@code text}, a complete Java program. On success, another complete Java
+ * program is returned, which is the same as the original except the imports are in order.
+ *
+ * @throws FormatterException if the input could not be parsed.
+ */
+ public static String reorderImports(String text, Style style) throws FormatterException {
+ ImmutableList<Tok> toks = JavaInput.buildToks(text, CLASS_START);
+ return new ImportOrderer(text, toks, style).reorderImports();
+ }
+
+ /**
+ * Reorder the inputs in {@code text}, a complete Java program, in Google style. On success,
+ * another complete Java program is returned, which is the same as the original except the imports
+ * are in order.
+ *
+ * @deprecated Use {@link #reorderImports(String, Style)} instead
+ * @throws FormatterException if the input could not be parsed.
+ */
+ @Deprecated
+ public static String reorderImports(String text) throws FormatterException {
+ return reorderImports(text, Style.GOOGLE);
+ }
+
+ private String reorderImports() throws FormatterException {
+ int firstImportStart;
+ Optional<Integer> maybeFirstImport = findIdentifier(0, IMPORT_OR_CLASS_START);
+ if (!maybeFirstImport.isPresent() || !tokenAt(maybeFirstImport.get()).equals("import")) {
+ // No imports, so nothing to do.
+ return text;
+ }
+ firstImportStart = maybeFirstImport.get();
+ int unindentedFirstImportStart = unindent(firstImportStart);
+
+ ImportsAndIndex imports = scanImports(firstImportStart);
+ int afterLastImport = imports.index;
+
+ // Make sure there are no more imports before the next class (etc) definition.
+ Optional<Integer> maybeLaterImport = findIdentifier(afterLastImport, IMPORT_OR_CLASS_START);
+ if (maybeLaterImport.isPresent() && tokenAt(maybeLaterImport.get()).equals("import")) {
+ throw new FormatterException("Imports not contiguous (perhaps a comment separates them?)");
+ }
+
+ StringBuilder result = new StringBuilder();
+ String prefix = tokString(0, unindentedFirstImportStart);
+ result.append(prefix);
+ if (!prefix.isEmpty() && Newlines.getLineEnding(prefix) == null) {
+ result.append(lineSeparator).append(lineSeparator);
+ }
+ result.append(reorderedImportsString(imports.imports));
+
+ List<String> tail = new ArrayList<>();
+ tail.add(CharMatcher.whitespace().trimLeadingFrom(tokString(afterLastImport, toks.size())));
+ if (!toks.isEmpty()) {
+ Tok lastTok = getLast(toks);
+ int tailStart = lastTok.getPosition() + lastTok.length();
+ tail.add(text.substring(tailStart));
+ }
+ if (tail.stream().anyMatch(s -> !s.isEmpty())) {
+ result.append(lineSeparator);
+ tail.forEach(result::append);
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * {@link TokenKind}s that indicate the start of a type definition. We use this to avoid scanning
+ * the whole file, since we know that imports must precede any type definition.
+ */
+ private static final ImmutableSet<TokenKind> CLASS_START =
+ ImmutableSet.of(TokenKind.CLASS, TokenKind.INTERFACE, TokenKind.ENUM);
+
+ /**
+ * We use this set to find the first import, and again to check that there are no imports after
+ * the place we stopped gathering them. An annotation definition ({@code @interface}) is two
+ * tokens, the second which is {@code interface}, so we don't need a separate entry for that.
+ */
+ private static final ImmutableSet<String> IMPORT_OR_CLASS_START =
+ ImmutableSet.of("import", "class", "interface", "enum");
+
+ /**
+ * A {@link Comparator} that orders {@link Import}s by Google Style, defined at
+ * https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing.
+ */
+ private static final Comparator<Import> GOOGLE_IMPORT_COMPARATOR =
+ Comparator.comparing(Import::isStatic, trueFirst()).thenComparing(Import::imported);
+
+ /**
+ * A {@link Comparator} that orders {@link Import}s by AOSP Style, defined at
+ * https://source.android.com/setup/contribute/code-style#order-import-statements and implemented
+ * in IntelliJ at
+ * https://android.googlesource.com/platform/development/+/master/ide/intellij/codestyles/AndroidStyle.xml.
+ */
+ private static final Comparator<Import> AOSP_IMPORT_COMPARATOR =
+ Comparator.comparing(Import::isStatic, trueFirst())
+ .thenComparing(Import::isAndroid, trueFirst())
+ .thenComparing(Import::isThirdParty, trueFirst())
+ .thenComparing(Import::isJava, trueFirst())
+ .thenComparing(Import::imported);
+
+ /**
+ * Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link
+ * Import}s based on Google style.
+ */
+ private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) {
+ return prev.isStatic() && !curr.isStatic();
+ }
+
+ /**
+ * Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link
+ * Import}s based on AOSP style.
+ */
+ private static boolean shouldInsertBlankLineAosp(Import prev, Import curr) {
+ if (prev.isStatic() && !curr.isStatic()) {
+ return true;
+ }
+ // insert blank line between "com.android" from "com.anythingelse"
+ if (prev.isAndroid() && !curr.isAndroid()) {
+ return true;
+ }
+ return !prev.topLevel().equals(curr.topLevel());
+ }
+
+ private final String text;
+ private final ImmutableList<Tok> toks;
+ private final String lineSeparator;
+ private final Comparator<Import> importComparator;
+ private final BiFunction<Import, Import, Boolean> shouldInsertBlankLineFn;
+
+ private ImportOrderer(String text, ImmutableList<Tok> toks, Style style) {
+ this.text = text;
+ this.toks = toks;
+ this.lineSeparator = Newlines.guessLineSeparator(text);
+ if (style.equals(Style.GOOGLE)) {
+ this.importComparator = GOOGLE_IMPORT_COMPARATOR;
+ this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineGoogle;
+ } else if (style.equals(Style.AOSP)) {
+ this.importComparator = AOSP_IMPORT_COMPARATOR;
+ this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineAosp;
+ } else {
+ throw new IllegalArgumentException("Unsupported code style: " + style);
+ }
+ }
+
+ /** An import statement. */
+ class Import {
+ private final String imported;
+ private final boolean isStatic;
+ private final String trailing;
+
+ Import(String imported, String trailing, boolean isStatic) {
+ this.imported = imported;
+ this.trailing = trailing;
+ this.isStatic = isStatic;
+ }
+
+ /** The name being imported, for example {@code java.util.List}. */
+ String imported() {
+ return imported;
+ }
+
+ /** True if this is {@code import static}. */
+ boolean isStatic() {
+ return isStatic;
+ }
+
+ /** The top-level package of the import. */
+ String topLevel() {
+ return DOT_SPLITTER.split(imported()).iterator().next();
+ }
+
+ /** True if this is an Android import per AOSP style. */
+ boolean isAndroid() {
+ return Stream.of("android.", "androidx.", "dalvik.", "libcore.", "com.android.")
+ .anyMatch(imported::startsWith);
+ }
+
+ /** True if this is a Java import per AOSP style. */
+ boolean isJava() {
+ switch (topLevel()) {
+ case "java":
+ case "javax":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * The {@code //} comment lines after the final {@code ;}, up to and including the line
+ * terminator of the last one. Note: In case two imports were separated by a space (which is
+ * disallowed by the style guide), the trailing whitespace of the first import does not include
+ * a line terminator.
+ */
+ String trailing() {
+ return trailing;
+ }
+
+ /** True if this is a third-party import per AOSP style. */
+ public boolean isThirdParty() {
+ return !(isAndroid() || isJava());
+ }
+
+ // One or multiple lines, the import itself and following comments, including the line
+ // terminator.
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("import ");
+ if (isStatic()) {
+ sb.append("static ");
+ }
+ sb.append(imported()).append(';');
+ if (trailing().trim().isEmpty()) {
+ sb.append(lineSeparator);
+ } else {
+ sb.append(trailing());
+ }
+ return sb.toString();
+ }
+ }
+
+ private String tokString(int start, int end) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = start; i < end; i++) {
+ sb.append(toks.get(i).getOriginalText());
+ }
+ return sb.toString();
+ }
+
+ private static class ImportsAndIndex {
+ final ImmutableSortedSet<Import> imports;
+ final int index;
+
+ ImportsAndIndex(ImmutableSortedSet<Import> imports, int index) {
+ this.imports = imports;
+ this.index = index;
+ }
+ }
+
+ /**
+ * Scans a sequence of import lines. The parsing uses this approximate grammar:
+ *
+ * <pre>{@code
+ * <imports> -> (<end-of-line> | <import>)*
+ * <import> -> "import" <whitespace> ("static" <whitespace>)?
+ * <identifier> ("." <identifier>)* ("." "*")? <whitespace>? ";"
+ * <whitespace>? <end-of-line>? (<line-comment> <end-of-line>)*
+ * }</pre>
+ *
+ * @param i the index to start parsing at.
+ * @return the result of parsing the imports.
+ * @throws FormatterException if imports could not parsed according to the grammar.
+ */
+ private ImportsAndIndex scanImports(int i) throws FormatterException {
+ int afterLastImport = i;
+ ImmutableSortedSet.Builder<Import> imports = ImmutableSortedSet.orderedBy(importComparator);
+ // JavaInput.buildToks appends a zero-width EOF token after all tokens. It won't match any
+ // of our tests here and protects us from running off the end of the toks list. Since it is
+ // zero-width it doesn't matter if we include it in our string concatenation at the end.
+ while (i < toks.size() && tokenAt(i).equals("import")) {
+ i++;
+ if (isSpaceToken(i)) {
+ i++;
+ }
+ boolean isStatic = tokenAt(i).equals("static");
+ if (isStatic) {
+ i++;
+ if (isSpaceToken(i)) {
+ i++;
+ }
+ }
+ if (!isIdentifierToken(i)) {
+ throw new FormatterException("Unexpected token after import: " + tokenAt(i));
+ }
+ StringAndIndex imported = scanImported(i);
+ String importedName = imported.string;
+ i = imported.index;
+ if (isSpaceToken(i)) {
+ i++;
+ }
+ if (!tokenAt(i).equals(";")) {
+ throw new FormatterException("Expected ; after import");
+ }
+ while (tokenAt(i).equals(";")) {
+ // Extra semicolons are not allowed by the JLS but are accepted by javac.
+ i++;
+ }
+ StringBuilder trailing = new StringBuilder();
+ if (isSpaceToken(i)) {
+ trailing.append(tokenAt(i));
+ i++;
+ }
+ if (isNewlineToken(i)) {
+ trailing.append(tokenAt(i));
+ i++;
+ }
+ // Gather (if any) all single line comments and accompanied line terminators following this
+ // import
+ while (isSlashSlashCommentToken(i)) {
+ trailing.append(tokenAt(i));
+ i++;
+ if (isNewlineToken(i)) {
+ trailing.append(tokenAt(i));
+ i++;
+ }
+ }
+ imports.add(new Import(importedName, trailing.toString(), isStatic));
+ // Remember the position just after the import we just saw, before skipping blank lines.
+ // If the next thing after the blank lines is not another import then we don't want to
+ // include those blank lines in the text to be replaced.
+ afterLastImport = i;
+ while (isNewlineToken(i) || isSpaceToken(i)) {
+ i++;
+ }
+ }
+ return new ImportsAndIndex(imports.build(), afterLastImport);
+ }
+
+ // Produces the sorted output based on the imports we have scanned.
+ private String reorderedImportsString(ImmutableSortedSet<Import> imports) {
+ Preconditions.checkArgument(!imports.isEmpty(), "imports");
+
+ // Pretend that the first import was preceded by another import of the same kind, so we don't
+ // insert a newline there.
+ Import prevImport = imports.iterator().next();
+
+ StringBuilder sb = new StringBuilder();
+ for (Import currImport : imports) {
+ if (shouldInsertBlankLineFn.apply(prevImport, currImport)) {
+ // Blank line between static and non-static imports.
+ sb.append(lineSeparator);
+ }
+ sb.append(currImport);
+ prevImport = currImport;
+ }
+ return sb.toString();
+ }
+
+ private static class StringAndIndex {
+ private final String string;
+ private final int index;
+
+ StringAndIndex(String string, int index) {
+ this.string = string;
+ this.index = index;
+ }
+ }
+
+ /**
+ * Scans the imported thing, the dot-separated name that comes after import [static] and before
+ * the semicolon. We don't allow spaces inside the dot-separated name. Wildcard imports are
+ * supported: if the input is {@code import java.util.*;} then the returned string will be {@code
+ * java.util.*}.
+ *
+ * @param start the index of the start of the identifier. If the import is {@code import
+ * java.util.List;} then this index points to the token {@code java}.
+ * @return the parsed import ({@code java.util.List} in the example) and the index of the first
+ * token after the imported thing ({@code ;} in the example).
+ * @throws FormatterException if the imported name could not be parsed.
+ */
+ private StringAndIndex scanImported(int start) throws FormatterException {
+ int i = start;
+ StringBuilder imported = new StringBuilder();
+ // At the start of each iteration of this loop, i points to an identifier.
+ // On exit from the loop, i points to a token after an identifier or after *.
+ while (true) {
+ Preconditions.checkState(isIdentifierToken(i));
+ imported.append(tokenAt(i));
+ i++;
+ if (!tokenAt(i).equals(".")) {
+ return new StringAndIndex(imported.toString(), i);
+ }
+ imported.append('.');
+ i++;
+ if (tokenAt(i).equals("*")) {
+ imported.append('*');
+ return new StringAndIndex(imported.toString(), i + 1);
+ } else if (!isIdentifierToken(i)) {
+ throw new FormatterException("Could not parse imported name, at: " + tokenAt(i));
+ }
+ }
+ }
+
+ /**
+ * Returns the index of the first place where one of the given identifiers occurs, or {@code
+ * Optional.empty()} if there is none.
+ *
+ * @param start the index to start looking at
+ * @param identifiers the identifiers to look for
+ */
+ private Optional<Integer> findIdentifier(int start, ImmutableSet<String> identifiers) {
+ for (int i = start; i < toks.size(); i++) {
+ if (isIdentifierToken(i)) {
+ String id = tokenAt(i);
+ if (identifiers.contains(id)) {
+ return Optional.of(i);
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /** Returns the given token, or the preceding token if it is a whitespace token. */
+ private int unindent(int i) {
+ if (i > 0 && isSpaceToken(i - 1)) {
+ return i - 1;
+ } else {
+ return i;
+ }
+ }
+
+ private String tokenAt(int i) {
+ return toks.get(i).getOriginalText();
+ }
+
+ private boolean isIdentifierToken(int i) {
+ String s = tokenAt(i);
+ return !s.isEmpty() && Character.isJavaIdentifierStart(s.codePointAt(0));
+ }
+
+ private boolean isSpaceToken(int i) {
+ String s = tokenAt(i);
+ if (s.isEmpty()) {
+ return false;
+ } else {
+ return " \t\f".indexOf(s.codePointAt(0)) >= 0;
+ }
+ }
+
+ private boolean isSlashSlashCommentToken(int i) {
+ return toks.get(i).isSlashSlashComment();
+ }
+
+ private boolean isNewlineToken(int i) {
+ return toks.get(i).isNewline();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java b/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java
new file mode 100644
index 0000000..346324a
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Strings;
+import com.google.googlejavaformat.CommentsHelper;
+import com.google.googlejavaformat.Input.Tok;
+import com.google.googlejavaformat.Newlines;
+import com.google.googlejavaformat.java.javadoc.JavadocFormatter;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** {@code JavaCommentsHelper} extends {@link CommentsHelper} to rewrite Java comments. */
+public final class JavaCommentsHelper implements CommentsHelper {
+
+ private final String lineSeparator;
+ private final JavaFormatterOptions options;
+
+ public JavaCommentsHelper(String lineSeparator, JavaFormatterOptions options) {
+ this.lineSeparator = lineSeparator;
+ this.options = options;
+ }
+
+ @Override
+ public String rewrite(Tok tok, int maxWidth, int column0) {
+ if (!tok.isComment()) {
+ return tok.getOriginalText();
+ }
+ String text = tok.getOriginalText();
+ if (tok.isJavadocComment() && options.formatJavadoc()) {
+ text = JavadocFormatter.formatJavadoc(text, column0);
+ }
+ List<String> lines = new ArrayList<>();
+ Iterator<String> it = Newlines.lineIterator(text);
+ while (it.hasNext()) {
+ lines.add(CharMatcher.whitespace().trimTrailingFrom(it.next()));
+ }
+ if (tok.isSlashSlashComment()) {
+ return indentLineComments(lines, column0);
+ } else if (javadocShaped(lines)) {
+ return indentJavadoc(lines, column0);
+ } else {
+ return preserveIndentation(lines, column0);
+ }
+ }
+
+ // For non-javadoc-shaped block comments, shift the entire block to the correct
+ // column, but do not adjust relative indentation.
+ private String preserveIndentation(List<String> lines, int column0) {
+ StringBuilder builder = new StringBuilder();
+
+ // find the leftmost non-whitespace character in all trailing lines
+ int startCol = -1;
+ for (int i = 1; i < lines.size(); i++) {
+ int lineIdx = CharMatcher.whitespace().negate().indexIn(lines.get(i));
+ if (lineIdx >= 0 && (startCol == -1 || lineIdx < startCol)) {
+ startCol = lineIdx;
+ }
+ }
+
+ // output the first line at the current column
+ builder.append(lines.get(0));
+
+ // output all trailing lines with plausible indentation
+ for (int i = 1; i < lines.size(); ++i) {
+ builder.append(lineSeparator).append(Strings.repeat(" ", column0));
+ // check that startCol is valid index, e.g. for blank lines
+ if (lines.get(i).length() >= startCol) {
+ builder.append(lines.get(i).substring(startCol));
+ } else {
+ builder.append(lines.get(i));
+ }
+ }
+ return builder.toString();
+ }
+
+ // Wraps and re-indents line comments.
+ private String indentLineComments(List<String> lines, int column0) {
+ lines = wrapLineComments(lines, column0);
+ StringBuilder builder = new StringBuilder();
+ builder.append(lines.get(0).trim());
+ String indentString = Strings.repeat(" ", column0);
+ for (int i = 1; i < lines.size(); ++i) {
+ builder.append(lineSeparator).append(indentString).append(lines.get(i).trim());
+ }
+ return builder.toString();
+ }
+
+ // Preserve special `//noinspection` and `//$NON-NLS-x$` comments used by IDEs, which cannot
+ // contain leading spaces.
+ private static final Pattern LINE_COMMENT_MISSING_SPACE_PREFIX =
+ Pattern.compile("^(//+)(?!noinspection|\\$NON-NLS-\\d+\\$)[^\\s/]");
+
+ private List<String> wrapLineComments(List<String> lines, int column0) {
+ List<String> result = new ArrayList<>();
+ for (String line : lines) {
+ // Add missing leading spaces to line comments: `//foo` -> `// foo`.
+ Matcher matcher = LINE_COMMENT_MISSING_SPACE_PREFIX.matcher(line);
+ if (matcher.find()) {
+ int length = matcher.group(1).length();
+ line = Strings.repeat("/", length) + " " + line.substring(length);
+ }
+ if (line.startsWith("// MOE:")) {
+ // don't wrap comments for https://github.com/google/MOE
+ result.add(line);
+ continue;
+ }
+ while (line.length() + column0 > Formatter.MAX_LINE_LENGTH) {
+ int idx = Formatter.MAX_LINE_LENGTH - column0;
+ // only break on whitespace characters, and ignore the leading `// `
+ while (idx >= 2 && !CharMatcher.whitespace().matches(line.charAt(idx))) {
+ idx--;
+ }
+ if (idx <= 2) {
+ break;
+ }
+ result.add(line.substring(0, idx));
+ line = "//" + line.substring(idx);
+ }
+ result.add(line);
+ }
+ return result;
+ }
+
+ // Remove leading whitespace (trailing was already removed), and re-indent.
+ // Add a +1 indent before '*', and add the '*' if necessary.
+ private String indentJavadoc(List<String> lines, int column0) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(lines.get(0).trim());
+ int indent = column0 + 1;
+ String indentString = Strings.repeat(" ", indent);
+ for (int i = 1; i < lines.size(); ++i) {
+ builder.append(lineSeparator).append(indentString);
+ String line = lines.get(i).trim();
+ if (!line.startsWith("*")) {
+ builder.append("* ");
+ }
+ builder.append(line);
+ }
+ return builder.toString();
+ }
+
+ // Returns true if the comment looks like javadoc
+ private static boolean javadocShaped(List<String> lines) {
+ Iterator<String> it = lines.iterator();
+ if (!it.hasNext()) {
+ return false;
+ }
+ String first = it.next().trim();
+ // if it's actually javadoc, we're done
+ if (first.startsWith("/**")) {
+ return true;
+ }
+ // if it's a block comment, check all trailing lines for '*'
+ if (!first.startsWith("/*")) {
+ return false;
+ }
+ while (it.hasNext()) {
+ if (!it.next().trim().startsWith("*")) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java b/core/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java
new file mode 100644
index 0000000..4d3d30d
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import com.google.errorprone.annotations.Immutable;
+
+/**
+ * Options for a google-java-format invocation.
+ *
+ * <p>Like gofmt, the google-java-format CLI exposes <em>no</em> configuration options (aside from
+ * {@code --aosp}).
+ *
+ * <p>The goal of google-java-format is to provide consistent formatting, and to free developers
+ * from arguments over style choices. It is an explicit non-goal to support developers' individual
+ * preferences, and in fact it would work directly against our primary goals.
+ */
+@Immutable
+public class JavaFormatterOptions {
+
+ public enum Style {
+ /** The default Google Java Style configuration. */
+ GOOGLE(1),
+
+ /** The AOSP-compliant configuration. */
+ AOSP(2);
+
+ private final int indentationMultiplier;
+
+ Style(int indentationMultiplier) {
+ this.indentationMultiplier = indentationMultiplier;
+ }
+
+ int indentationMultiplier() {
+ return indentationMultiplier;
+ }
+ }
+
+ private final Style style;
+ private final boolean formatJavadoc;
+
+ private JavaFormatterOptions(Style style, boolean formatJavadoc) {
+ this.style = style;
+ this.formatJavadoc = formatJavadoc;
+ }
+
+ /** Returns the multiplier for the unit of indent. */
+ public int indentationMultiplier() {
+ return style.indentationMultiplier();
+ }
+
+ boolean formatJavadoc() {
+ return formatJavadoc;
+ }
+
+ /** Returns the code style. */
+ public Style style() {
+ return style;
+ }
+
+ /** Returns the default formatting options. */
+ public static JavaFormatterOptions defaultOptions() {
+ return builder().build();
+ }
+
+ /** Returns a builder for {@link JavaFormatterOptions}. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** A builder for {@link JavaFormatterOptions}. */
+ public static class Builder {
+ private Style style = Style.GOOGLE;
+ private boolean formatJavadoc = true;
+
+ private Builder() {}
+
+ public Builder style(Style style) {
+ this.style = style;
+ return this;
+ }
+
+ Builder formatJavadoc(boolean formatJavadoc) {
+ this.formatJavadoc = formatJavadoc;
+ return this;
+ }
+
+ public JavaFormatterOptions build() {
+ return new JavaFormatterOptions(style, formatJavadoc);
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java b/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java
new file mode 100644
index 0000000..999c8fb
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java
@@ -0,0 +1,676 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.getLast;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Verify;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableRangeMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+import com.google.googlejavaformat.Input;
+import com.google.googlejavaformat.Newlines;
+import com.google.googlejavaformat.java.JavacTokens.RawTok;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.parser.Tokens.TokenKind;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Log;
+import com.sun.tools.javac.util.Log.DeferredDiagnosticHandler;
+import com.sun.tools.javac.util.Options;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+
+/** {@code JavaInput} extends {@link Input} to represent a Java input document. */
+public final class JavaInput extends Input {
+ /**
+ * A {@code JavaInput} is a sequence of {@link Tok}s that cover the Java input. A {@link Tok} is
+ * either a token (if {@code isToken()}), or a non-token, which is a comment (if {@code
+ * isComment()}) or a newline (if {@code isNewline()}) or a maximal sequence of other whitespace
+ * characters (if {@code isSpaces()}). Each {@link Tok} contains a sequence of characters, an
+ * index (sequential starting at {@code 0} for tokens and comments, else {@code -1}), and a
+ * ({@code 0}-origin) position in the input. The concatenation of the texts of all the {@link
+ * Tok}s equals the input. Each Input ends with a token EOF {@link Tok}, with empty text.
+ *
+ * <p>A {@code /*} comment possibly contains newlines; a {@code //} comment does not contain the
+ * terminating newline character, but is followed by a newline {@link Tok}.
+ */
+ static final class Tok implements Input.Tok {
+ private final int index;
+ private final String originalText;
+ private final String text;
+ private final int position;
+ private final int columnI;
+ private final boolean isToken;
+ private final TokenKind kind;
+
+ /**
+ * The {@code Tok} constructor.
+ *
+ * @param index its index
+ * @param originalText its original text, before removing Unicode escapes
+ * @param text its text after removing Unicode escapes
+ * @param position its {@code 0}-origin position in the input
+ * @param columnI its {@code 0}-origin column number in the input
+ * @param isToken whether the {@code Tok} is a token
+ * @param kind the token kind
+ */
+ Tok(
+ int index,
+ String originalText,
+ String text,
+ int position,
+ int columnI,
+ boolean isToken,
+ TokenKind kind) {
+ this.index = index;
+ this.originalText = originalText;
+ this.text = text;
+ this.position = position;
+ this.columnI = columnI;
+ this.isToken = isToken;
+ this.kind = kind;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public String getText() {
+ return text;
+ }
+
+ @Override
+ public String getOriginalText() {
+ return originalText;
+ }
+
+ @Override
+ public int length() {
+ return originalText.length();
+ }
+
+ @Override
+ public int getPosition() {
+ return position;
+ }
+
+ @Override
+ public int getColumn() {
+ return columnI;
+ }
+
+ boolean isToken() {
+ return isToken;
+ }
+
+ @Override
+ public boolean isNewline() {
+ return Newlines.isNewline(text);
+ }
+
+ @Override
+ public boolean isSlashSlashComment() {
+ return text.startsWith("//");
+ }
+
+ @Override
+ public boolean isSlashStarComment() {
+ return text.startsWith("/*");
+ }
+
+ @Override
+ public boolean isJavadocComment() {
+ // comments like `/***` are also javadoc, but their formatting probably won't be improved
+ // by the javadoc formatter
+ return text.startsWith("/**") && text.charAt("/**".length()) != '*' && text.length() > 4;
+ }
+
+ @Override
+ public boolean isComment() {
+ return isSlashSlashComment() || isSlashStarComment();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("index", index)
+ .add("text", text)
+ .add("position", position)
+ .add("columnI", columnI)
+ .add("isToken", isToken)
+ .toString();
+ }
+
+ public TokenKind kind() {
+ return kind;
+ }
+ }
+
+ /**
+ * A {@link Token} contains a token {@link Tok} and its associated non-tokens; each non-token
+ * {@link Tok} belongs to one {@link Token}. Each {@link Token} has an immutable list of its
+ * non-tokens that appear before it, and another list of its non-tokens that appear after it. The
+ * concatenation of the texts of all the {@link Token}s' {@link Tok}s, each preceded by the texts
+ * of its {@code toksBefore} and followed by the texts of its {@code toksAfter}, equals the input.
+ */
+ static final class Token implements Input.Token {
+ private final Tok tok;
+ private final ImmutableList<Tok> toksBefore;
+ private final ImmutableList<Tok> toksAfter;
+
+ /**
+ * Token constructor.
+ *
+ * @param toksBefore the earlier non-token {link Tok}s assigned to this {@code Token}
+ * @param tok this token {@link Tok}
+ * @param toksAfter the later non-token {link Tok}s assigned to this {@code Token}
+ */
+ Token(List<Tok> toksBefore, Tok tok, List<Tok> toksAfter) {
+ this.toksBefore = ImmutableList.copyOf(toksBefore);
+ this.tok = tok;
+ this.toksAfter = ImmutableList.copyOf(toksAfter);
+ }
+
+ /**
+ * Get the token's {@link Tok}.
+ *
+ * @return the token's {@link Tok}
+ */
+ @Override
+ public Tok getTok() {
+ return tok;
+ }
+
+ /**
+ * Get the earlier {@link Tok}s assigned to this {@code Token}.
+ *
+ * @return the earlier {@link Tok}s assigned to this {@code Token}
+ */
+ @Override
+ public ImmutableList<? extends Input.Tok> getToksBefore() {
+ return toksBefore;
+ }
+
+ /**
+ * Get the later {@link Tok}s assigned to this {@code Token}.
+ *
+ * @return the later {@link Tok}s assigned to this {@code Token}
+ */
+ @Override
+ public ImmutableList<? extends Input.Tok> getToksAfter() {
+ return toksAfter;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("tok", tok)
+ .add("toksBefore", toksBefore)
+ .add("toksAfter", toksAfter)
+ .toString();
+ }
+ }
+
+ private final String text; // The input.
+ private int kN; // The number of numbered toks (tokens or comments), excluding the EOF.
+
+ /*
+ * The following lists record the sequential indices of the {@code Tok}s on each input line. (Only
+ * tokens and comments have sequential indices.) Tokens and {@code //} comments lie on just one
+ * line; {@code /*} comments can lie on multiple lines. These data structures (along with
+ * equivalent ones for the formatted output) let us compute correspondences between the input and
+ * output.
+ */
+
+ private final ImmutableMap<Integer, Integer> positionToColumnMap; // Map Tok position to column.
+ private final ImmutableList<Token> tokens; // The Tokens for this input.
+ private final ImmutableRangeMap<Integer, Token> positionTokenMap; // Map position to Token.
+
+ /** Map from Tok index to the associated Token. */
+ private final Token[] kToToken;
+
+ /**
+ * Input constructor.
+ *
+ * @param text the input text
+ * @throws FormatterException if the input cannot be parsed
+ */
+ public JavaInput(String text) throws FormatterException {
+ this.text = checkNotNull(text);
+ setLines(ImmutableList.copyOf(Newlines.lineIterator(text)));
+ ImmutableList<Tok> toks = buildToks(text);
+ positionToColumnMap = makePositionToColumnMap(toks);
+ tokens = buildTokens(toks);
+ ImmutableRangeMap.Builder<Integer, Token> tokenLocations = ImmutableRangeMap.builder();
+ for (Token token : tokens) {
+ Input.Tok end = JavaOutput.endTok(token);
+ int upper = end.getPosition();
+ if (!end.getText().isEmpty()) {
+ upper += end.length() - 1;
+ }
+ tokenLocations.put(Range.closed(JavaOutput.startTok(token).getPosition(), upper), token);
+ }
+ positionTokenMap = tokenLocations.build();
+
+ // adjust kN for EOF
+ kToToken = new Token[kN + 1];
+ for (Token token : tokens) {
+ for (Input.Tok tok : token.getToksBefore()) {
+ if (tok.getIndex() < 0) {
+ continue;
+ }
+ kToToken[tok.getIndex()] = token;
+ }
+ kToToken[token.getTok().getIndex()] = token;
+ for (Input.Tok tok : token.getToksAfter()) {
+ if (tok.getIndex() < 0) {
+ continue;
+ }
+ kToToken[tok.getIndex()] = token;
+ }
+ }
+ }
+
+ private static ImmutableMap<Integer, Integer> makePositionToColumnMap(List<Tok> toks) {
+ ImmutableMap.Builder<Integer, Integer> builder = ImmutableMap.builder();
+ for (Tok tok : toks) {
+ builder.put(tok.getPosition(), tok.getColumn());
+ }
+ return builder.build();
+ }
+
+ /**
+ * Get the input text.
+ *
+ * @return the input text
+ */
+ @Override
+ public String getText() {
+ return text;
+ }
+
+ @Override
+ public ImmutableMap<Integer, Integer> getPositionToColumnMap() {
+ return positionToColumnMap;
+ }
+
+ /** Lex the input and build the list of toks. */
+ private ImmutableList<Tok> buildToks(String text) throws FormatterException {
+ ImmutableList<Tok> toks = buildToks(text, ImmutableSet.of());
+ kN = getLast(toks).getIndex();
+ computeRanges(toks);
+ return toks;
+ }
+
+ /**
+ * Lex the input and build the list of toks.
+ *
+ * @param text the text to be lexed.
+ * @param stopTokens a set of tokens which should cause lexing to stop. If one of these is found,
+ * the returned list will include tokens up to but not including that token.
+ */
+ static ImmutableList<Tok> buildToks(String text, ImmutableSet<TokenKind> stopTokens)
+ throws FormatterException {
+ stopTokens = ImmutableSet.<TokenKind>builder().addAll(stopTokens).add(TokenKind.EOF).build();
+ Context context = new Context();
+ Options.instance(context).put("--enable-preview", "true");
+ new JavacFileManager(context, true, UTF_8);
+ DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
+ context.put(DiagnosticListener.class, diagnosticCollector);
+ Log log = Log.instance(context);
+ log.useSource(
+ new SimpleJavaFileObject(URI.create("Source.java"), Kind.SOURCE) {
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return text;
+ }
+ });
+ DeferredDiagnosticHandler diagnostics = new DeferredDiagnosticHandler(log);
+ ImmutableList<RawTok> rawToks = JavacTokens.getTokens(text, context, stopTokens);
+ if (diagnostics.getDiagnostics().stream().anyMatch(d -> d.getKind() == Diagnostic.Kind.ERROR)) {
+ return ImmutableList.of(new Tok(0, "", "", 0, 0, true, null)); // EOF
+ }
+ int kN = 0;
+ List<Tok> toks = new ArrayList<>();
+ int charI = 0;
+ int columnI = 0;
+ for (RawTok t : rawToks) {
+ if (stopTokens.contains(t.kind())) {
+ break;
+ }
+ int charI0 = t.pos();
+ // Get string, possibly with Unicode escapes.
+ String originalTokText = text.substring(charI0, t.endPos());
+ String tokText =
+ t.kind() == TokenKind.STRINGLITERAL
+ ? t.stringVal() // Unicode escapes removed.
+ : originalTokText;
+ char tokText0 = tokText.charAt(0); // The token's first character.
+ final boolean isToken; // Is this tok a token?
+ final boolean isNumbered; // Is this tok numbered? (tokens and comments)
+ String extraNewline = null; // Extra newline at end?
+ List<String> strings = new ArrayList<>();
+ if (Character.isWhitespace(tokText0)) {
+ isToken = false;
+ isNumbered = false;
+ Iterator<String> it = Newlines.lineIterator(originalTokText);
+ while (it.hasNext()) {
+ String line = it.next();
+ String newline = Newlines.getLineEnding(line);
+ if (newline != null) {
+ String spaces = line.substring(0, line.length() - newline.length());
+ if (!spaces.isEmpty()) {
+ strings.add(spaces);
+ }
+ strings.add(newline);
+ } else if (!line.isEmpty()) {
+ strings.add(line);
+ }
+ }
+ } else if (tokText.startsWith("'") || tokText.startsWith("\"")) {
+ isToken = true;
+ isNumbered = true;
+ strings.add(originalTokText);
+ } else if (tokText.startsWith("//") || tokText.startsWith("/*")) {
+ // For compatibility with an earlier lexer, the newline after a // comment is its own tok.
+ if (tokText.startsWith("//")
+ && (originalTokText.endsWith("\n") || originalTokText.endsWith("\r"))) {
+ extraNewline = Newlines.getLineEnding(originalTokText);
+ tokText = tokText.substring(0, tokText.length() - extraNewline.length());
+ originalTokText =
+ originalTokText.substring(0, originalTokText.length() - extraNewline.length());
+ }
+ isToken = false;
+ isNumbered = true;
+ strings.add(originalTokText);
+ } else if (Character.isJavaIdentifierStart(tokText0)
+ || Character.isDigit(tokText0)
+ || (tokText0 == '.' && tokText.length() > 1 && Character.isDigit(tokText.charAt(1)))) {
+ // Identifier, keyword, or numeric literal (a dot may begin a number, as in .2D).
+ isToken = true;
+ isNumbered = true;
+ strings.add(tokText);
+ } else {
+ // Other tokens ("+" or "++" or ">>" are broken into one-character toks, because ">>"
+ // cannot be lexed without syntactic knowledge. This implementation fails if the token
+ // contains Unicode escapes.
+ isToken = true;
+ isNumbered = true;
+ for (char c : tokText.toCharArray()) {
+ strings.add(String.valueOf(c));
+ }
+ }
+ if (strings.size() == 1) {
+ toks.add(
+ new Tok(
+ isNumbered ? kN++ : -1,
+ originalTokText,
+ tokText,
+ charI,
+ columnI,
+ isToken,
+ t.kind()));
+ charI += originalTokText.length();
+ columnI = updateColumn(columnI, originalTokText);
+
+ } else {
+ if (strings.size() != 1 && !tokText.equals(originalTokText)) {
+ throw new FormatterException(
+ "Unicode escapes not allowed in whitespace or multi-character operators");
+ }
+ for (String str : strings) {
+ toks.add(new Tok(isNumbered ? kN++ : -1, str, str, charI, columnI, isToken, null));
+ charI += str.length();
+ columnI = updateColumn(columnI, originalTokText);
+ }
+ }
+ if (extraNewline != null) {
+ toks.add(new Tok(-1, extraNewline, extraNewline, charI, columnI, false, null));
+ columnI = 0;
+ charI += extraNewline.length();
+ }
+ }
+ toks.add(new Tok(kN, "", "", charI, columnI, true, null)); // EOF tok.
+ return ImmutableList.copyOf(toks);
+ }
+
+ private static int updateColumn(int columnI, String originalTokText) {
+ Integer last = Iterators.getLast(Newlines.lineOffsetIterator(originalTokText));
+ if (last > 0) {
+ columnI = originalTokText.length() - last;
+ } else {
+ columnI += originalTokText.length();
+ }
+ return columnI;
+ }
+
+ private static ImmutableList<Token> buildTokens(List<Tok> toks) {
+ ImmutableList.Builder<Token> tokens = ImmutableList.builder();
+ int k = 0;
+ int kN = toks.size();
+
+ // Remaining non-tokens before the token go here.
+ ImmutableList.Builder<Tok> toksBefore = ImmutableList.builder();
+
+ OUTERMOST:
+ while (k < kN) {
+ while (!toks.get(k).isToken()) {
+ Tok tok = toks.get(k++);
+ toksBefore.add(tok);
+ if (isParamComment(tok)) {
+ while (toks.get(k).isNewline()) {
+ // drop newlines after parameter comments
+ k++;
+ }
+ }
+ }
+ Tok tok = toks.get(k++);
+
+ // Non-tokens starting on the same line go here too.
+ ImmutableList.Builder<Tok> toksAfter = ImmutableList.builder();
+ OUTER:
+ while (k < kN && !toks.get(k).isToken()) {
+ // Don't attach inline comments to certain leading tokens, e.g. for `f(/*flag1=*/true).
+ //
+ // Attaching inline comments to the right token is hard, and this barely
+ // scratches the surface. But it's enough to do a better job with parameter
+ // name comments.
+ //
+ // TODO(cushon): find a better strategy.
+ if (toks.get(k).isSlashStarComment()) {
+ switch (tok.getText()) {
+ case "(":
+ case "<":
+ case ".":
+ break OUTER;
+ default:
+ break;
+ }
+ }
+ if (toks.get(k).isJavadocComment()) {
+ switch (tok.getText()) {
+ case ";":
+ break OUTER;
+ default:
+ break;
+ }
+ }
+ if (isParamComment(toks.get(k))) {
+ tokens.add(new Token(toksBefore.build(), tok, toksAfter.build()));
+ toksBefore = ImmutableList.<Tok>builder().add(toks.get(k++));
+ // drop newlines after parameter comments
+ while (toks.get(k).isNewline()) {
+ k++;
+ }
+ continue OUTERMOST;
+ }
+ Tok nonTokenAfter = toks.get(k++);
+ toksAfter.add(nonTokenAfter);
+ if (Newlines.containsBreaks(nonTokenAfter.getText())) {
+ break;
+ }
+ }
+ tokens.add(new Token(toksBefore.build(), tok, toksAfter.build()));
+ toksBefore = ImmutableList.builder();
+ }
+ return tokens.build();
+ }
+
+ private static boolean isParamComment(Tok tok) {
+ return tok.isSlashStarComment()
+ && tok.getText().matches("\\/\\*[A-Za-z0-9\\s_\\-]+=\\s*\\*\\/");
+ }
+
+ /**
+ * Convert from an offset and length flag pair to a token range.
+ *
+ * @param offset the {@code 0}-based offset in characters
+ * @param length the length in characters
+ * @return the {@code 0}-based {@link Range} of tokens
+ * @throws FormatterException if offset + length is outside the file
+ */
+ Range<Integer> characterRangeToTokenRange(int offset, int length) throws FormatterException {
+ int requiredLength = offset + length;
+ if (requiredLength > text.length()) {
+ throw new FormatterException(
+ String.format(
+ "error: invalid length %d, offset + length (%d) is outside the file",
+ length, requiredLength));
+ }
+ if (length < 0) {
+ return EMPTY_RANGE;
+ }
+ if (length == 0) {
+ // 0 stands for "format the line under the cursor"
+ length = 1;
+ }
+ ImmutableCollection<Token> enclosed =
+ getPositionTokenMap()
+ .subRangeMap(Range.closedOpen(offset, offset + length))
+ .asMapOfRanges()
+ .values();
+ if (enclosed.isEmpty()) {
+ return EMPTY_RANGE;
+ }
+ return Range.closedOpen(
+ enclosed.iterator().next().getTok().getIndex(), getLast(enclosed).getTok().getIndex() + 1);
+ }
+
+ /**
+ * Get the number of toks.
+ *
+ * @return the number of toks, including the EOF tok
+ */
+ @Override
+ public int getkN() {
+ return kN;
+ }
+
+ /**
+ * Get the Token by index.
+ *
+ * @param k the token index
+ */
+ @Override
+ public Token getToken(int k) {
+ return kToToken[k];
+ }
+
+ /**
+ * Get the input tokens.
+ *
+ * @return the input tokens
+ */
+ @Override
+ public ImmutableList<? extends Input.Token> getTokens() {
+ return tokens;
+ }
+
+ /**
+ * Get the navigable map from position to {@link Token}. Used to look for tokens following a given
+ * one, and to implement the --offset and --length flags to reformat a character range in the
+ * input file.
+ *
+ * @return the navigable map from position to {@link Token}
+ */
+ @Override
+ public ImmutableRangeMap<Integer, Token> getPositionTokenMap() {
+ return positionTokenMap;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("tokens", tokens)
+ .add("super", super.toString())
+ .toString();
+ }
+
+ private JCCompilationUnit unit;
+
+ @Override
+ public int getLineNumber(int inputPosition) {
+ Verify.verifyNotNull(unit, "Expected compilation unit to be set.");
+ return unit.getLineMap().getLineNumber(inputPosition);
+ }
+
+ @Override
+ public int getColumnNumber(int inputPosition) {
+ Verify.verifyNotNull(unit, "Expected compilation unit to be set.");
+ return unit.getLineMap().getColumnNumber(inputPosition);
+ }
+
+ // TODO(cushon): refactor JavaInput so the CompilationUnit can be passed into
+ // the constructor.
+ public void setCompilationUnit(JCCompilationUnit unit) {
+ this.unit = unit;
+ }
+
+ public RangeSet<Integer> characterRangesToTokenRanges(Collection<Range<Integer>> characterRanges)
+ throws FormatterException {
+ RangeSet<Integer> tokenRangeSet = TreeRangeSet.create();
+ for (Range<Integer> characterRange0 : characterRanges) {
+ Range<Integer> characterRange = characterRange0.canonical(DiscreteDomain.integers());
+ tokenRangeSet.add(
+ characterRangeToTokenRange(
+ characterRange.lowerEndpoint(),
+ characterRange.upperEndpoint() - characterRange.lowerEndpoint()));
+ }
+ return tokenRangeSet;
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java
new file mode 100644
index 0000000..6ce0f66
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java
@@ -0,0 +1,3676 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.collect.Iterables.getLast;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.googlejavaformat.Doc.FillMode.INDEPENDENT;
+import static com.google.googlejavaformat.Doc.FillMode.UNIFIED;
+import static com.google.googlejavaformat.Indent.If.make;
+import static com.google.googlejavaformat.OpsBuilder.BlankLineWanted.PRESERVE;
+import static com.google.googlejavaformat.OpsBuilder.BlankLineWanted.YES;
+import static com.google.googlejavaformat.java.Trees.getEndPosition;
+import static com.google.googlejavaformat.java.Trees.getLength;
+import static com.google.googlejavaformat.java.Trees.getMethodName;
+import static com.google.googlejavaformat.java.Trees.getSourceForNode;
+import static com.google.googlejavaformat.java.Trees.getStartPosition;
+import static com.google.googlejavaformat.java.Trees.operatorName;
+import static com.google.googlejavaformat.java.Trees.precedence;
+import static com.google.googlejavaformat.java.Trees.skipParen;
+import static com.sun.source.tree.Tree.Kind.ANNOTATION;
+import static com.sun.source.tree.Tree.Kind.ARRAY_ACCESS;
+import static com.sun.source.tree.Tree.Kind.ASSIGNMENT;
+import static com.sun.source.tree.Tree.Kind.BLOCK;
+import static com.sun.source.tree.Tree.Kind.EXTENDS_WILDCARD;
+import static com.sun.source.tree.Tree.Kind.IF;
+import static com.sun.source.tree.Tree.Kind.METHOD_INVOCATION;
+import static com.sun.source.tree.Tree.Kind.NEW_ARRAY;
+import static com.sun.source.tree.Tree.Kind.NEW_CLASS;
+import static com.sun.source.tree.Tree.Kind.STRING_LITERAL;
+import static com.sun.source.tree.Tree.Kind.UNION_TYPE;
+import static com.sun.source.tree.Tree.Kind.VARIABLE;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Predicate;
+import com.google.common.base.Throwables;
+import com.google.common.base.Verify;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.PeekingIterator;
+import com.google.common.collect.Streams;
+import com.google.googlejavaformat.CloseOp;
+import com.google.googlejavaformat.Doc;
+import com.google.googlejavaformat.Doc.FillMode;
+import com.google.googlejavaformat.FormattingError;
+import com.google.googlejavaformat.Indent;
+import com.google.googlejavaformat.Input;
+import com.google.googlejavaformat.Op;
+import com.google.googlejavaformat.OpenOp;
+import com.google.googlejavaformat.OpsBuilder;
+import com.google.googlejavaformat.OpsBuilder.BlankLineWanted;
+import com.google.googlejavaformat.Output.BreakTag;
+import com.google.googlejavaformat.java.DimensionHelpers.SortedDims;
+import com.google.googlejavaformat.java.DimensionHelpers.TypeWithDims;
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.AssertTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.BreakTree;
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.CatchTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ContinueTree;
+import com.sun.source.tree.DirectiveTree;
+import com.sun.source.tree.DoWhileLoopTree;
+import com.sun.source.tree.EmptyStatementTree;
+import com.sun.source.tree.EnhancedForLoopTree;
+import com.sun.source.tree.ExportsTree;
+import com.sun.source.tree.ExpressionStatementTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.ForLoopTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.IntersectionTypeTree;
+import com.sun.source.tree.LabeledStatementTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.source.tree.ModuleTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.OpensTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.ProvidesTree;
+import com.sun.source.tree.RequiresTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.SynchronizedTree;
+import com.sun.source.tree.ThrowTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TryTree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.UnionTypeTree;
+import com.sun.source.tree.UsesTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.tree.WhileLoopTree;
+import com.sun.source.tree.WildcardTree;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.TreeScanner;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+import javax.lang.model.element.Name;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * An AST visitor that builds a stream of {@link Op}s to format from the given {@link
+ * CompilationUnitTree}.
+ */
+public class JavaInputAstVisitor extends TreePathScanner<Void, Void> {
+
+ /** Direction for Annotations (usually VERTICAL). */
+ protected enum Direction {
+ VERTICAL,
+ HORIZONTAL;
+
+ boolean isVertical() {
+ return this == VERTICAL;
+ }
+ }
+
+ /** Whether to break or not. */
+ enum BreakOrNot {
+ YES,
+ NO;
+
+ boolean isYes() {
+ return this == YES;
+ }
+ }
+
+ /** Whether to collapse empty blocks. */
+ enum CollapseEmptyOrNot {
+ YES,
+ NO;
+
+ static CollapseEmptyOrNot valueOf(boolean b) {
+ return b ? YES : NO;
+ }
+
+ boolean isYes() {
+ return this == YES;
+ }
+ }
+
+ /** Whether to allow leading blank lines in blocks. */
+ enum AllowLeadingBlankLine {
+ YES,
+ NO;
+
+ static AllowLeadingBlankLine valueOf(boolean b) {
+ return b ? YES : NO;
+ }
+ }
+
+ /** Whether to allow trailing blank lines in blocks. */
+ enum AllowTrailingBlankLine {
+ YES,
+ NO;
+
+ static AllowTrailingBlankLine valueOf(boolean b) {
+ return b ? YES : NO;
+ }
+ }
+
+ /** Whether to include braces. */
+ protected enum BracesOrNot {
+ YES,
+ NO;
+
+ boolean isYes() {
+ return this == YES;
+ }
+ }
+
+ /** Whether or not to include dimensions. */
+ enum DimensionsOrNot {
+ YES,
+ NO;
+
+ boolean isYes() {
+ return this == YES;
+ }
+ }
+
+ /** Whether or not the declaration is Varargs. */
+ enum VarArgsOrNot {
+ YES,
+ NO;
+
+ static VarArgsOrNot valueOf(boolean b) {
+ return b ? YES : NO;
+ }
+
+ boolean isYes() {
+ return this == YES;
+ }
+
+ static VarArgsOrNot fromVariable(VariableTree node) {
+ return valueOf((((JCTree.JCVariableDecl) node).mods.flags & Flags.VARARGS) == Flags.VARARGS);
+ }
+ }
+
+ /** Whether the formal parameter declaration is a receiver. */
+ enum ReceiverParameter {
+ YES,
+ NO;
+
+ boolean isYes() {
+ return this == YES;
+ }
+ }
+
+ /** Whether these declarations are the first in the block. */
+ protected enum FirstDeclarationsOrNot {
+ YES,
+ NO;
+
+ boolean isYes() {
+ return this == YES;
+ }
+ }
+
+ protected final OpsBuilder builder;
+
+ protected static final Indent.Const ZERO = Indent.Const.ZERO;
+ protected final int indentMultiplier;
+ protected final Indent.Const minusTwo;
+ protected final Indent.Const minusFour;
+ protected final Indent.Const plusTwo;
+ protected final Indent.Const plusFour;
+
+ private static final ImmutableList<Op> breakList(Optional<BreakTag> breakTag) {
+ return ImmutableList.of(Doc.Break.make(Doc.FillMode.UNIFIED, " ", ZERO, breakTag));
+ }
+
+ private static final ImmutableList<Op> breakFillList(Optional<BreakTag> breakTag) {
+ return ImmutableList.of(
+ OpenOp.make(ZERO),
+ Doc.Break.make(Doc.FillMode.INDEPENDENT, " ", ZERO, breakTag),
+ CloseOp.make());
+ }
+
+ private static final ImmutableList<Op> forceBreakList(Optional<BreakTag> breakTag) {
+ return ImmutableList.of(Doc.Break.make(FillMode.FORCED, "", Indent.Const.ZERO, breakTag));
+ }
+
+ private static final ImmutableList<Op> EMPTY_LIST = ImmutableList.of();
+
+ /**
+ * Allow multi-line filling (of array initializers, argument lists, and boolean expressions) for
+ * items with length less than or equal to this threshold.
+ */
+ private static final int MAX_ITEM_LENGTH_FOR_FILLING = 10;
+
+ /**
+ * The {@code Visitor} constructor.
+ *
+ * @param builder the {@link OpsBuilder}
+ */
+ public JavaInputAstVisitor(OpsBuilder builder, int indentMultiplier) {
+ this.builder = builder;
+ this.indentMultiplier = indentMultiplier;
+ minusTwo = Indent.Const.make(-2, indentMultiplier);
+ minusFour = Indent.Const.make(-4, indentMultiplier);
+ plusTwo = Indent.Const.make(+2, indentMultiplier);
+ plusFour = Indent.Const.make(+4, indentMultiplier);
+ }
+
+ /** A record of whether we have visited into an expression. */
+ private final Deque<Boolean> inExpression = new ArrayDeque<>(ImmutableList.of(false));
+
+ private boolean inExpression() {
+ return inExpression.peekLast();
+ }
+
+ @Override
+ public Void scan(Tree tree, Void unused) {
+ inExpression.addLast(tree instanceof ExpressionTree || inExpression.peekLast());
+ int previous = builder.depth();
+ try {
+ super.scan(tree, null);
+ } catch (FormattingError e) {
+ throw e;
+ } catch (Throwable t) {
+ throw new FormattingError(builder.diagnostic(Throwables.getStackTraceAsString(t)));
+ } finally {
+ inExpression.removeLast();
+ }
+ builder.checkClosed(previous);
+ return null;
+ }
+
+ @Override
+ public Void visitCompilationUnit(CompilationUnitTree node, Void unused) {
+ boolean first = true;
+ if (node.getPackageName() != null) {
+ markForPartialFormat();
+ visitPackage(node.getPackageName(), node.getPackageAnnotations());
+ builder.forcedBreak();
+ first = false;
+ }
+ dropEmptyDeclarations();
+ if (!node.getImports().isEmpty()) {
+ if (!first) {
+ builder.blankLineWanted(BlankLineWanted.YES);
+ }
+ for (ImportTree importDeclaration : node.getImports()) {
+ markForPartialFormat();
+ builder.blankLineWanted(PRESERVE);
+ scan(importDeclaration, null);
+ builder.forcedBreak();
+ }
+ first = false;
+ }
+ dropEmptyDeclarations();
+ for (Tree type : node.getTypeDecls()) {
+ if (type.getKind() == Tree.Kind.IMPORT) {
+ // javac treats extra semicolons in the import list as type declarations
+ // TODO(cushon): remove this if https://bugs.openjdk.java.net/browse/JDK-8027682 is fixed
+ continue;
+ }
+ if (!first) {
+ builder.blankLineWanted(BlankLineWanted.YES);
+ }
+ markForPartialFormat();
+ scan(type, null);
+ builder.forcedBreak();
+ first = false;
+ dropEmptyDeclarations();
+ }
+ // set a partial format marker at EOF to make sure we can format the entire file
+ markForPartialFormat();
+ return null;
+ }
+
+ /** Skips over extra semi-colons at the top-level, or in a class member declaration lists. */
+ protected void dropEmptyDeclarations() {
+ if (builder.peekToken().equals(Optional.of(";"))) {
+ while (builder.peekToken().equals(Optional.of(";"))) {
+ builder.forcedBreak();
+ markForPartialFormat();
+ token(";");
+ }
+ }
+ }
+
+ @Override
+ public Void visitClass(ClassTree tree, Void unused) {
+ switch (tree.getKind()) {
+ case ANNOTATION_TYPE:
+ visitAnnotationType(tree);
+ break;
+ case CLASS:
+ case INTERFACE:
+ visitClassDeclaration(tree);
+ break;
+ case ENUM:
+ visitEnumDeclaration(tree);
+ break;
+ default:
+ throw new AssertionError(tree.getKind());
+ }
+ return null;
+ }
+
+ public void visitAnnotationType(ClassTree node) {
+ sync(node);
+ builder.open(ZERO);
+ visitAndBreakModifiers(
+ node.getModifiers(),
+ Direction.VERTICAL,
+ /* declarationAnnotationBreak= */ Optional.empty());
+ builder.open(ZERO);
+ token("@");
+ token("interface");
+ builder.breakOp(" ");
+ visit(node.getSimpleName());
+ builder.close();
+ builder.close();
+ if (node.getMembers() == null) {
+ builder.open(plusFour);
+ token(";");
+ builder.close();
+ } else {
+ addBodyDeclarations(node.getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES);
+ }
+ builder.guessToken(";");
+ }
+
+ @Override
+ public Void visitArrayAccess(ArrayAccessTree node, Void unused) {
+ sync(node);
+ visitDot(node);
+ return null;
+ }
+
+ @Override
+ public Void visitNewArray(NewArrayTree node, Void unused) {
+ if (node.getType() != null) {
+ builder.open(plusFour);
+ token("new");
+ builder.space();
+
+ TypeWithDims extractedDims = DimensionHelpers.extractDims(node.getType(), SortedDims.YES);
+ Tree base = extractedDims.node;
+
+ Deque<ExpressionTree> dimExpressions = new ArrayDeque<>(node.getDimensions());
+
+ Deque<List<? extends AnnotationTree>> annotations = new ArrayDeque<>();
+ annotations.add(ImmutableList.copyOf(node.getAnnotations()));
+ annotations.addAll(node.getDimAnnotations());
+ annotations.addAll(extractedDims.dims);
+
+ scan(base, null);
+ builder.open(ZERO);
+ maybeAddDims(dimExpressions, annotations);
+ builder.close();
+ builder.close();
+ }
+ if (node.getInitializers() != null) {
+ if (node.getType() != null) {
+ builder.space();
+ }
+ visitArrayInitializer(node.getInitializers());
+ }
+ return null;
+ }
+
+ public boolean visitArrayInitializer(List<? extends ExpressionTree> expressions) {
+ int cols;
+ if (expressions.isEmpty()) {
+ tokenBreakTrailingComment("{", plusTwo);
+ if (builder.peekToken().equals(Optional.of(","))) {
+ token(",");
+ }
+ token("}", plusTwo);
+ } else if ((cols = argumentsAreTabular(expressions)) != -1) {
+ builder.open(plusTwo);
+ token("{");
+ builder.forcedBreak();
+ boolean first = true;
+ for (Iterable<? extends ExpressionTree> row : Iterables.partition(expressions, cols)) {
+ if (!first) {
+ builder.forcedBreak();
+ }
+ builder.open(row.iterator().next().getKind() == NEW_ARRAY || cols == 1 ? ZERO : plusFour);
+ boolean firstInRow = true;
+ for (ExpressionTree item : row) {
+ if (!firstInRow) {
+ token(",");
+ builder.breakToFill(" ");
+ }
+ scan(item, null);
+ firstInRow = false;
+ }
+ builder.guessToken(",");
+ builder.close();
+ first = false;
+ }
+ builder.breakOp(minusTwo);
+ builder.close();
+ token("}", plusTwo);
+ } else {
+ // Special-case the formatting of array initializers inside annotations
+ // to more eagerly use a one-per-line layout.
+ boolean inMemberValuePair = false;
+ // walk up past the enclosing NewArrayTree (and maybe an enclosing AssignmentTree)
+ TreePath path = getCurrentPath();
+ for (int i = 0; i < 2; i++) {
+ if (path == null) {
+ break;
+ }
+ if (path.getLeaf().getKind() == ANNOTATION) {
+ inMemberValuePair = true;
+ break;
+ }
+ path = path.getParentPath();
+ }
+ boolean shortItems = hasOnlyShortItems(expressions);
+ boolean allowFilledElementsOnOwnLine = shortItems || !inMemberValuePair;
+
+ builder.open(plusTwo);
+ tokenBreakTrailingComment("{", plusTwo);
+ boolean hasTrailingComma = hasTrailingToken(builder.getInput(), expressions, ",");
+ builder.breakOp(hasTrailingComma ? FillMode.FORCED : FillMode.UNIFIED, "", ZERO);
+ if (allowFilledElementsOnOwnLine) {
+ builder.open(ZERO);
+ }
+ boolean first = true;
+ FillMode fillMode = shortItems ? FillMode.INDEPENDENT : FillMode.UNIFIED;
+ for (ExpressionTree expression : expressions) {
+ if (!first) {
+ token(",");
+ builder.breakOp(fillMode, " ", ZERO);
+ }
+ scan(expression, null);
+ first = false;
+ }
+ builder.guessToken(",");
+ if (allowFilledElementsOnOwnLine) {
+ builder.close();
+ }
+ builder.breakOp(minusTwo);
+ builder.close();
+ token("}", plusTwo);
+ }
+ return false;
+ }
+
+ private boolean hasOnlyShortItems(List<? extends ExpressionTree> expressions) {
+ for (ExpressionTree expression : expressions) {
+ int startPosition = getStartPosition(expression);
+ if (builder.actualSize(
+ startPosition, getEndPosition(expression, getCurrentPath()) - startPosition)
+ >= MAX_ITEM_LENGTH_FOR_FILLING) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Void visitArrayType(ArrayTypeTree node, Void unused) {
+ sync(node);
+ visitAnnotatedArrayType(node);
+ return null;
+ }
+
+ private void visitAnnotatedArrayType(Tree node) {
+ TypeWithDims extractedDims = DimensionHelpers.extractDims(node, SortedDims.YES);
+ builder.open(plusFour);
+ scan(extractedDims.node, null);
+ Deque<List<? extends AnnotationTree>> dims = new ArrayDeque<>(extractedDims.dims);
+ maybeAddDims(dims);
+ Verify.verify(dims.isEmpty());
+ builder.close();
+ }
+
+ @Override
+ public Void visitAssert(AssertTree node, Void unused) {
+ sync(node);
+ builder.open(ZERO);
+ token("assert");
+ builder.space();
+ builder.open(node.getDetail() == null ? ZERO : plusFour);
+ scan(node.getCondition(), null);
+ if (node.getDetail() != null) {
+ builder.breakOp(" ");
+ token(":");
+ builder.space();
+ scan(node.getDetail(), null);
+ }
+ builder.close();
+ builder.close();
+ token(";");
+ return null;
+ }
+
+ @Override
+ public Void visitAssignment(AssignmentTree node, Void unused) {
+ sync(node);
+ builder.open(plusFour);
+ scan(node.getVariable(), null);
+ builder.space();
+ splitToken(operatorName(node));
+ builder.breakOp(" ");
+ scan(node.getExpression(), null);
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitBlock(BlockTree node, Void unused) {
+ visitBlock(node, CollapseEmptyOrNot.NO, AllowLeadingBlankLine.NO, AllowTrailingBlankLine.NO);
+ return null;
+ }
+
+ @Override
+ public Void visitCompoundAssignment(CompoundAssignmentTree node, Void unused) {
+ sync(node);
+ builder.open(plusFour);
+ scan(node.getVariable(), null);
+ builder.space();
+ splitToken(operatorName(node));
+ builder.breakOp(" ");
+ scan(node.getExpression(), null);
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitBreak(BreakTree node, Void unused) {
+ sync(node);
+ builder.open(plusFour);
+ token("break");
+ if (node.getLabel() != null) {
+ builder.breakOp(" ");
+ visit(node.getLabel());
+ }
+ builder.close();
+ token(";");
+ return null;
+ }
+
+ @Override
+ public Void visitTypeCast(TypeCastTree node, Void unused) {
+ sync(node);
+ builder.open(plusFour);
+ token("(");
+ scan(node.getType(), null);
+ token(")");
+ builder.breakOp(" ");
+ scan(node.getExpression(), null);
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitNewClass(NewClassTree node, Void unused) {
+ sync(node);
+ builder.open(ZERO);
+ if (node.getEnclosingExpression() != null) {
+ scan(node.getEnclosingExpression(), null);
+ builder.breakOp();
+ token(".");
+ }
+ token("new");
+ builder.space();
+ addTypeArguments(node.getTypeArguments(), plusFour);
+ if (node.getClassBody() != null) {
+ builder.addAll(
+ visitModifiers(
+ node.getClassBody().getModifiers(), Direction.HORIZONTAL, Optional.empty()));
+ }
+ scan(node.getIdentifier(), null);
+ addArguments(node.getArguments(), plusFour);
+ builder.close();
+ if (node.getClassBody() != null) {
+ addBodyDeclarations(
+ node.getClassBody().getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitConditionalExpression(ConditionalExpressionTree node, Void unused) {
+ sync(node);
+ builder.open(plusFour);
+ scan(node.getCondition(), null);
+ builder.breakOp(" ");
+ token("?");
+ builder.space();
+ scan(node.getTrueExpression(), null);
+ builder.breakOp(" ");
+ token(":");
+ builder.space();
+ scan(node.getFalseExpression(), null);
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitContinue(ContinueTree node, Void unused) {
+ sync(node);
+ builder.open(plusFour);
+ token("continue");
+ if (node.getLabel() != null) {
+ builder.breakOp(" ");
+ visit(node.getLabel());
+ }
+ token(";");
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitDoWhileLoop(DoWhileLoopTree node, Void unused) {
+ sync(node);
+ token("do");
+ visitStatement(
+ node.getStatement(),
+ CollapseEmptyOrNot.YES,
+ AllowLeadingBlankLine.YES,
+ AllowTrailingBlankLine.YES);
+ if (node.getStatement().getKind() == BLOCK) {
+ builder.space();
+ } else {
+ builder.breakOp(" ");
+ }
+ token("while");
+ builder.space();
+ token("(");
+ scan(skipParen(node.getCondition()), null);
+ token(")");
+ token(";");
+ return null;
+ }
+
+ @Override
+ public Void visitEmptyStatement(EmptyStatementTree node, Void unused) {
+ sync(node);
+ dropEmptyDeclarations();
+ return null;
+ }
+
+ @Override
+ public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void unused) {
+ sync(node);
+ builder.open(ZERO);
+ token("for");
+ builder.space();
+ token("(");
+ builder.open(ZERO);
+ visitToDeclare(
+ DeclarationKind.NONE,
+ Direction.HORIZONTAL,
+ node.getVariable(),
+ Optional.of(node.getExpression()),
+ ":",
+ /* trailing= */ Optional.empty());
+ builder.close();
+ token(")");
+ builder.close();
+ visitStatement(
+ node.getStatement(),
+ CollapseEmptyOrNot.YES,
+ AllowLeadingBlankLine.YES,
+ AllowTrailingBlankLine.NO);
+ return null;
+ }
+
+ private void visitEnumConstantDeclaration(VariableTree enumConstant) {
+ for (AnnotationTree annotation : enumConstant.getModifiers().getAnnotations()) {
+ scan(annotation, null);
+ builder.forcedBreak();
+ }
+ visit(enumConstant.getName());
+ NewClassTree init = ((NewClassTree) enumConstant.getInitializer());
+ if (init.getArguments().isEmpty()) {
+ builder.guessToken("(");
+ builder.guessToken(")");
+ } else {
+ addArguments(init.getArguments(), plusFour);
+ }
+ if (init.getClassBody() != null) {
+ addBodyDeclarations(
+ init.getClassBody().getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES);
+ }
+ }
+
+ public boolean visitEnumDeclaration(ClassTree node) {
+ sync(node);
+ builder.open(ZERO);
+ visitAndBreakModifiers(
+ node.getModifiers(),
+ Direction.VERTICAL,
+ /* declarationAnnotationBreak= */ Optional.empty());
+ builder.open(plusFour);
+ token("enum");
+ builder.breakOp(" ");
+ visit(node.getSimpleName());
+ builder.close();
+ builder.close();
+ if (!node.getImplementsClause().isEmpty()) {
+ builder.open(plusFour);
+ builder.breakOp(" ");
+ builder.open(plusFour);
+ token("implements");
+ builder.breakOp(" ");
+ builder.open(ZERO);
+ boolean first = true;
+ for (Tree superInterfaceType : node.getImplementsClause()) {
+ if (!first) {
+ token(",");
+ builder.breakToFill(" ");
+ }
+ scan(superInterfaceType, null);
+ first = false;
+ }
+ builder.close();
+ builder.close();
+ builder.close();
+ }
+ builder.space();
+ tokenBreakTrailingComment("{", plusTwo);
+ ArrayList<VariableTree> enumConstants = new ArrayList<>();
+ ArrayList<Tree> members = new ArrayList<>();
+ for (Tree member : node.getMembers()) {
+ if (member instanceof JCTree.JCVariableDecl) {
+ JCTree.JCVariableDecl variableDecl = (JCTree.JCVariableDecl) member;
+ if ((variableDecl.mods.flags & Flags.ENUM) == Flags.ENUM) {
+ enumConstants.add(variableDecl);
+ continue;
+ }
+ }
+ members.add(member);
+ }
+ if (enumConstants.isEmpty() && members.isEmpty()) {
+ if (builder.peekToken().equals(Optional.of(";"))) {
+ builder.open(plusTwo);
+ builder.forcedBreak();
+ token(";");
+ builder.forcedBreak();
+ dropEmptyDeclarations();
+ builder.close();
+ builder.open(ZERO);
+ builder.forcedBreak();
+ builder.blankLineWanted(BlankLineWanted.NO);
+ token("}", plusTwo);
+ builder.close();
+ } else {
+ builder.open(ZERO);
+ builder.blankLineWanted(BlankLineWanted.NO);
+ token("}");
+ builder.close();
+ }
+ } else {
+ builder.open(plusTwo);
+ builder.blankLineWanted(BlankLineWanted.NO);
+ builder.forcedBreak();
+ builder.open(ZERO);
+ boolean first = true;
+ for (VariableTree enumConstant : enumConstants) {
+ if (!first) {
+ token(",");
+ builder.forcedBreak();
+ builder.blankLineWanted(BlankLineWanted.PRESERVE);
+ }
+ markForPartialFormat();
+ visitEnumConstantDeclaration(enumConstant);
+ first = false;
+ }
+ if (builder.peekToken().orElse("").equals(",")) {
+ token(",");
+ builder.forcedBreak(); // The ";" goes on its own line.
+ }
+ builder.close();
+ builder.close();
+ if (builder.peekToken().equals(Optional.of(";"))) {
+ builder.open(plusTwo);
+ token(";");
+ builder.forcedBreak();
+ dropEmptyDeclarations();
+ builder.close();
+ }
+ builder.open(ZERO);
+ addBodyDeclarations(members, BracesOrNot.NO, FirstDeclarationsOrNot.NO);
+ builder.forcedBreak();
+ builder.blankLineWanted(BlankLineWanted.NO);
+ token("}", plusTwo);
+ builder.close();
+ }
+ builder.guessToken(";");
+ return false;
+ }
+
+ @Override
+ public Void visitMemberReference(MemberReferenceTree node, Void unused) {
+ sync(node);
+ builder.open(plusFour);
+ scan(node.getQualifierExpression(), null);
+ builder.breakOp();
+ builder.op("::");
+ addTypeArguments(node.getTypeArguments(), plusFour);
+ switch (node.getMode()) {
+ case INVOKE:
+ visit(node.getName());
+ break;
+ case NEW:
+ token("new");
+ break;
+ default:
+ throw new AssertionError(node.getMode());
+ }
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitExpressionStatement(ExpressionStatementTree node, Void unused) {
+ sync(node);
+ scan(node.getExpression(), null);
+ token(";");
+ return null;
+ }
+
+ @Override
+ public Void visitVariable(VariableTree node, Void unused) {
+ sync(node);
+ visitVariables(
+ ImmutableList.of(node),
+ DeclarationKind.NONE,
+ fieldAnnotationDirection(node.getModifiers()));
+ return null;
+ }
+
+ void visitVariables(
+ List<VariableTree> fragments,
+ DeclarationKind declarationKind,
+ Direction annotationDirection) {
+ if (fragments.size() == 1) {
+ VariableTree fragment = fragments.get(0);
+ declareOne(
+ declarationKind,
+ annotationDirection,
+ Optional.of(fragment.getModifiers()),
+ fragment.getType(),
+ /* name= */ fragment.getName(),
+ "",
+ "=",
+ Optional.ofNullable(fragment.getInitializer()),
+ Optional.of(";"),
+ /* receiverExpression= */ Optional.empty(),
+ Optional.ofNullable(variableFragmentDims(true, 0, fragment.getType())));
+
+ } else {
+ declareMany(fragments, annotationDirection);
+ }
+ }
+
+ private TypeWithDims variableFragmentDims(boolean first, int leadingDims, Tree type) {
+ if (type == null) {
+ return null;
+ }
+ if (first) {
+ return DimensionHelpers.extractDims(type, SortedDims.YES);
+ }
+ TypeWithDims dims = DimensionHelpers.extractDims(type, SortedDims.NO);
+ return new TypeWithDims(
+ null, leadingDims > 0 ? dims.dims.subList(0, dims.dims.size() - leadingDims) : dims.dims);
+ }
+
+ @Override
+ public Void visitForLoop(ForLoopTree node, Void unused) {
+ sync(node);
+ token("for");
+ builder.space();
+ token("(");
+ builder.open(plusFour);
+ builder.open(
+ node.getInitializer().size() > 1
+ && node.getInitializer().get(0).getKind() == Tree.Kind.EXPRESSION_STATEMENT
+ ? plusFour
+ : ZERO);
+ if (!node.getInitializer().isEmpty()) {
+ if (node.getInitializer().get(0).getKind() == VARIABLE) {
+ PeekingIterator<StatementTree> it =
+ Iterators.peekingIterator(node.getInitializer().iterator());
+ visitVariables(
+ variableFragments(it, it.next()), DeclarationKind.NONE, Direction.HORIZONTAL);
+ } else {
+ boolean first = true;
+ builder.open(ZERO);
+ for (StatementTree t : node.getInitializer()) {
+ if (!first) {
+ token(",");
+ builder.breakOp(" ");
+ }
+ scan(((ExpressionStatementTree) t).getExpression(), null);
+ first = false;
+ }
+ token(";");
+ builder.close();
+ }
+ } else {
+ token(";");
+ }
+ builder.close();
+ builder.breakOp(" ");
+ if (node.getCondition() != null) {
+ scan(node.getCondition(), null);
+ }
+ token(";");
+ if (!node.getUpdate().isEmpty()) {
+ builder.breakOp(" ");
+ builder.open(node.getUpdate().size() <= 1 ? ZERO : plusFour);
+ boolean firstUpdater = true;
+ for (ExpressionStatementTree updater : node.getUpdate()) {
+ if (!firstUpdater) {
+ token(",");
+ builder.breakToFill(" ");
+ }
+ scan(updater.getExpression(), null);
+ firstUpdater = false;
+ }
+ builder.guessToken(";");
+ builder.close();
+ } else {
+ builder.space();
+ }
+ builder.close();
+ token(")");
+ visitStatement(
+ node.getStatement(),
+ CollapseEmptyOrNot.YES,
+ AllowLeadingBlankLine.YES,
+ AllowTrailingBlankLine.NO);
+ return null;
+ }
+
+ @Override
+ public Void visitIf(IfTree node, Void unused) {
+ sync(node);
+ // Collapse chains of else-ifs.
+ List<ExpressionTree> expressions = new ArrayList<>();
+ List<StatementTree> statements = new ArrayList<>();
+ while (true) {
+ expressions.add(node.getCondition());
+ statements.add(node.getThenStatement());
+ if (node.getElseStatement() != null && node.getElseStatement().getKind() == IF) {
+ node = (IfTree) node.getElseStatement();
+ } else {
+ break;
+ }
+ }
+ builder.open(ZERO);
+ boolean first = true;
+ boolean followingBlock = false;
+ int expressionsN = expressions.size();
+ for (int i = 0; i < expressionsN; i++) {
+ if (!first) {
+ if (followingBlock) {
+ builder.space();
+ } else {
+ builder.forcedBreak();
+ }
+ token("else");
+ builder.space();
+ }
+ token("if");
+ builder.space();
+ token("(");
+ scan(skipParen(expressions.get(i)), null);
+ token(")");
+ // An empty block can collapse to "{}" if there are no if/else or else clauses
+ boolean onlyClause = expressionsN == 1 && node.getElseStatement() == null;
+ // Trailing blank lines are permitted if this isn't the last clause
+ boolean trailingClauses = i < expressionsN - 1 || node.getElseStatement() != null;
+ visitStatement(
+ statements.get(i),
+ CollapseEmptyOrNot.valueOf(onlyClause),
+ AllowLeadingBlankLine.YES,
+ AllowTrailingBlankLine.valueOf(trailingClauses));
+ followingBlock = statements.get(i).getKind() == BLOCK;
+ first = false;
+ }
+ if (node.getElseStatement() != null) {
+ if (followingBlock) {
+ builder.space();
+ } else {
+ builder.forcedBreak();
+ }
+ token("else");
+ visitStatement(
+ node.getElseStatement(),
+ CollapseEmptyOrNot.NO,
+ AllowLeadingBlankLine.YES,
+ AllowTrailingBlankLine.NO);
+ }
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitImport(ImportTree node, Void unused) {
+ sync(node);
+ token("import");
+ builder.space();
+ if (node.isStatic()) {
+ token("static");
+ builder.space();
+ }
+ visitName(node.getQualifiedIdentifier());
+ token(";");
+ // TODO(cushon): remove this if https://bugs.openjdk.java.net/browse/JDK-8027682 is fixed
+ dropEmptyDeclarations();
+ return null;
+ }
+
+ @Override
+ public Void visitBinary(BinaryTree node, Void unused) {
+ sync(node);
+ /*
+ * Collect together all operators with same precedence to clean up indentation.
+ */
+ List<ExpressionTree> operands = new ArrayList<>();
+ List<String> operators = new ArrayList<>();
+ walkInfix(precedence(node), node, operands, operators);
+ FillMode fillMode = hasOnlyShortItems(operands) ? INDEPENDENT : UNIFIED;
+ builder.open(plusFour);
+ scan(operands.get(0), null);
+ int operatorsN = operators.size();
+ for (int i = 0; i < operatorsN; i++) {
+ builder.breakOp(fillMode, " ", ZERO);
+ builder.op(operators.get(i));
+ builder.space();
+ scan(operands.get(i + 1), null);
+ }
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitInstanceOf(InstanceOfTree node, Void unused) {
+ sync(node);
+ builder.open(plusFour);
+ scan(node.getExpression(), null);
+ builder.breakOp(" ");
+ builder.open(ZERO);
+ token("instanceof");
+ builder.breakOp(" ");
+ scan(node.getType(), null);
+ builder.close();
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitIntersectionType(IntersectionTypeTree node, Void unused) {
+ sync(node);
+ builder.open(plusFour);
+ boolean first = true;
+ for (Tree type : node.getBounds()) {
+ if (!first) {
+ builder.breakToFill(" ");
+ token("&");
+ builder.space();
+ }
+ scan(type, null);
+ first = false;
+ }
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitLabeledStatement(LabeledStatementTree node, Void unused) {
+ sync(node);
+ builder.open(ZERO);
+ visit(node.getLabel());
+ token(":");
+ builder.forcedBreak();
+ builder.close();
+ scan(node.getStatement(), null);
+ return null;
+ }
+
+ @Override
+ public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) {
+ sync(node);
+ boolean statementBody = node.getBodyKind() == LambdaExpressionTree.BodyKind.STATEMENT;
+ boolean parens = builder.peekToken().equals(Optional.of("("));
+ builder.open(parens ? plusFour : ZERO);
+ if (parens) {
+ token("(");
+ }
+ boolean first = true;
+ for (VariableTree parameter : node.getParameters()) {
+ if (!first) {
+ token(",");
+ builder.breakOp(" ");
+ }
+ scan(parameter, null);
+ first = false;
+ }
+ if (parens) {
+ token(")");
+ }
+ builder.close();
+ builder.space();
+ builder.op("->");
+ builder.open(statementBody ? ZERO : plusFour);
+ if (statementBody) {
+ builder.space();
+ } else {
+ builder.breakOp(" ");
+ }
+ if (node.getBody().getKind() == Tree.Kind.BLOCK) {
+ visitBlock(
+ (BlockTree) node.getBody(),
+ CollapseEmptyOrNot.YES,
+ AllowLeadingBlankLine.NO,
+ AllowTrailingBlankLine.NO);
+ } else {
+ scan(node.getBody(), null);
+ }
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitAnnotation(AnnotationTree node, Void unused) {
+ sync(node);
+
+ if (visitSingleMemberAnnotation(node)) {
+ return null;
+ }
+
+ builder.open(ZERO);
+ token("@");
+ scan(node.getAnnotationType(), null);
+ if (!node.getArguments().isEmpty()) {
+ builder.open(plusFour);
+ token("(");
+ builder.breakOp();
+ boolean first = true;
+
+ // Format the member value pairs one-per-line if any of them are
+ // initialized with arrays.
+ boolean hasArrayInitializer =
+ Iterables.any(node.getArguments(), JavaInputAstVisitor::isArrayValue);
+ for (ExpressionTree argument : node.getArguments()) {
+ if (!first) {
+ token(",");
+ if (hasArrayInitializer) {
+ builder.forcedBreak();
+ } else {
+ builder.breakOp(" ");
+ }
+ }
+ if (argument instanceof AssignmentTree) {
+ visitAnnotationArgument((AssignmentTree) argument);
+ } else {
+ scan(argument, null);
+ }
+ first = false;
+ }
+ token(")");
+ builder.close();
+ builder.close();
+ return null;
+
+ } else if (builder.peekToken().equals(Optional.of("("))) {
+ token("(");
+ token(")");
+ }
+ builder.close();
+ return null;
+ }
+
+ private static boolean isArrayValue(ExpressionTree argument) {
+ if (!(argument instanceof AssignmentTree)) {
+ return false;
+ }
+ ExpressionTree expression = ((AssignmentTree) argument).getExpression();
+ return expression instanceof NewArrayTree && ((NewArrayTree) expression).getType() == null;
+ }
+
+ public void visitAnnotationArgument(AssignmentTree node) {
+ boolean isArrayInitializer = node.getExpression().getKind() == NEW_ARRAY;
+ sync(node);
+ builder.open(isArrayInitializer ? ZERO : plusFour);
+ scan(node.getVariable(), null);
+ builder.space();
+ token("=");
+ if (isArrayInitializer) {
+ builder.space();
+ } else {
+ builder.breakOp(" ");
+ }
+ scan(node.getExpression(), null);
+ builder.close();
+ }
+
+ @Override
+ public Void visitAnnotatedType(AnnotatedTypeTree node, Void unused) {
+ sync(node);
+ ExpressionTree base = node.getUnderlyingType();
+ if (base instanceof MemberSelectTree) {
+ MemberSelectTree selectTree = (MemberSelectTree) base;
+ scan(selectTree.getExpression(), null);
+ token(".");
+ visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.NO);
+ builder.breakToFill(" ");
+ visit(selectTree.getIdentifier());
+ } else if (base instanceof ArrayTypeTree) {
+ visitAnnotatedArrayType(node);
+ } else {
+ visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.NO);
+ builder.breakToFill(" ");
+ scan(base, null);
+ }
+ return null;
+ }
+
+ // TODO(cushon): Use Flags if/when we drop support for Java 11
+
+ protected static final long COMPACT_RECORD_CONSTRUCTOR = 1L << 51;
+
+ protected static final long RECORD = 1L << 61;
+
+ @Override
+ public Void visitMethod(MethodTree node, Void unused) {
+ sync(node);
+ List<? extends AnnotationTree> annotations = node.getModifiers().getAnnotations();
+ List<? extends AnnotationTree> returnTypeAnnotations = ImmutableList.of();
+
+ boolean isRecordConstructor =
+ (((JCMethodDecl) node).mods.flags & COMPACT_RECORD_CONSTRUCTOR)
+ == COMPACT_RECORD_CONSTRUCTOR;
+
+ if (!node.getTypeParameters().isEmpty() && !annotations.isEmpty()) {
+ int typeParameterStart = getStartPosition(node.getTypeParameters().get(0));
+ for (int i = 0; i < annotations.size(); i++) {
+ if (getStartPosition(annotations.get(i)) > typeParameterStart) {
+ returnTypeAnnotations = annotations.subList(i, annotations.size());
+ annotations = annotations.subList(0, i);
+ break;
+ }
+ }
+ }
+ builder.addAll(
+ visitModifiers(
+ annotations, Direction.VERTICAL, /* declarationAnnotationBreak= */ Optional.empty()));
+
+ Tree baseReturnType = null;
+ Deque<List<? extends AnnotationTree>> dims = null;
+ if (node.getReturnType() != null) {
+ TypeWithDims extractedDims =
+ DimensionHelpers.extractDims(node.getReturnType(), SortedDims.YES);
+ baseReturnType = extractedDims.node;
+ dims = new ArrayDeque<>(extractedDims.dims);
+ }
+
+ builder.open(plusFour);
+ BreakTag breakBeforeName = genSym();
+ BreakTag breakBeforeType = genSym();
+ builder.open(ZERO);
+ {
+ boolean first = true;
+ if (!node.getTypeParameters().isEmpty()) {
+ token("<");
+ typeParametersRest(node.getTypeParameters(), plusFour);
+ if (!returnTypeAnnotations.isEmpty()) {
+ builder.breakToFill(" ");
+ visitAnnotations(returnTypeAnnotations, BreakOrNot.NO, BreakOrNot.NO);
+ }
+ first = false;
+ }
+
+ boolean openedNameAndTypeScope = false;
+ // constructor-like declarations that don't match the name of the enclosing class are
+ // parsed as method declarations with a null return type
+ if (baseReturnType != null) {
+ if (!first) {
+ builder.breakOp(INDEPENDENT, " ", ZERO, Optional.of(breakBeforeType));
+ } else {
+ first = false;
+ }
+ if (!openedNameAndTypeScope) {
+ builder.open(make(breakBeforeType, plusFour, ZERO));
+ openedNameAndTypeScope = true;
+ }
+ scan(baseReturnType, null);
+ maybeAddDims(dims);
+ }
+ if (!first) {
+ builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, Optional.of(breakBeforeName));
+ } else {
+ first = false;
+ }
+ if (!openedNameAndTypeScope) {
+ builder.open(ZERO);
+ openedNameAndTypeScope = true;
+ }
+ String name = node.getName().toString();
+ if (name.equals("<init>")) {
+ name = builder.peekToken().get();
+ }
+ token(name);
+ if (!isRecordConstructor) {
+ token("(");
+ }
+ // end of name and type scope
+ builder.close();
+ }
+ builder.close();
+
+ builder.open(Indent.If.make(breakBeforeName, plusFour, ZERO));
+ builder.open(Indent.If.make(breakBeforeType, plusFour, ZERO));
+ builder.open(ZERO);
+ {
+ if (!isRecordConstructor) {
+ if (!node.getParameters().isEmpty() || node.getReceiverParameter() != null) {
+ // Break before args.
+ builder.breakToFill("");
+ visitFormals(Optional.ofNullable(node.getReceiverParameter()), node.getParameters());
+ }
+ token(")");
+ }
+ if (dims != null) {
+ maybeAddDims(dims);
+ }
+ if (!node.getThrows().isEmpty()) {
+ builder.breakToFill(" ");
+ builder.open(plusFour);
+ {
+ visitThrowsClause(node.getThrows());
+ }
+ builder.close();
+ }
+ if (node.getDefaultValue() != null) {
+ builder.space();
+ token("default");
+ if (node.getDefaultValue().getKind() == Tree.Kind.NEW_ARRAY) {
+ builder.open(minusFour);
+ {
+ builder.space();
+ scan(node.getDefaultValue(), null);
+ }
+ builder.close();
+ } else {
+ builder.open(ZERO);
+ {
+ builder.breakToFill(" ");
+ scan(node.getDefaultValue(), null);
+ }
+ builder.close();
+ }
+ }
+ }
+ builder.close();
+ builder.close();
+ builder.close();
+ if (node.getBody() == null) {
+ token(";");
+ } else {
+ builder.space();
+ builder.token("{", Doc.Token.RealOrImaginary.REAL, plusTwo, Optional.of(plusTwo));
+ }
+ builder.close();
+
+ if (node.getBody() != null) {
+ methodBody(node);
+ }
+
+ return null;
+ }
+
+ private void methodBody(MethodTree node) {
+ if (node.getBody().getStatements().isEmpty()) {
+ builder.blankLineWanted(BlankLineWanted.NO);
+ } else {
+ builder.open(plusTwo);
+ builder.forcedBreak();
+ builder.blankLineWanted(BlankLineWanted.PRESERVE);
+ visitStatements(node.getBody().getStatements());
+ builder.close();
+ builder.forcedBreak();
+ builder.blankLineWanted(BlankLineWanted.NO);
+ markForPartialFormat();
+ }
+ token("}", plusTwo);
+ }
+
+ @Override
+ public Void visitMethodInvocation(MethodInvocationTree node, Void unused) {
+ sync(node);
+ if (handleLogStatement(node)) {
+ return null;
+ }
+ visitDot(node);
+ return null;
+ }
+
+ /**
+ * Special-cases log statements, to output:
+ *
+ * <pre>{@code
+ * logger.atInfo().log(
+ * "Number of foos: %d, foos.size());
+ * }</pre>
+ *
+ * <p>Instead of:
+ *
+ * <pre>{@code
+ * logger
+ * .atInfo()
+ * .log(
+ * "Number of foos: %d, foos.size());
+ * }</pre>
+ */
+ private boolean handleLogStatement(MethodInvocationTree node) {
+ if (!getMethodName(node).contentEquals("log")) {
+ return false;
+ }
+ Deque<ExpressionTree> parts = new ArrayDeque<>();
+ ExpressionTree curr = node;
+ while (curr instanceof MethodInvocationTree) {
+ MethodInvocationTree method = (MethodInvocationTree) curr;
+ parts.addFirst(method);
+ if (!LOG_METHODS.contains(getMethodName(method).toString())) {
+ return false;
+ }
+ curr = Trees.getMethodReceiver(method);
+ }
+ if (!(curr instanceof IdentifierTree)) {
+ return false;
+ }
+ parts.addFirst(curr);
+ visitDotWithPrefix(
+ ImmutableList.copyOf(parts), false, ImmutableList.of(parts.size() - 1), INDEPENDENT);
+ return true;
+ }
+
+ static final ImmutableSet<String> LOG_METHODS =
+ ImmutableSet.of(
+ "at",
+ "atConfig",
+ "atDebug",
+ "atFine",
+ "atFiner",
+ "atFinest",
+ "atInfo",
+ "atMostEvery",
+ "atSevere",
+ "atWarning",
+ "every",
+ "log",
+ "logVarargs",
+ "perUnique",
+ "withCause",
+ "withStackTrace");
+
+ private static List<Long> handleStream(List<ExpressionTree> parts) {
+ return indexes(
+ parts.stream(),
+ p -> {
+ if (!(p instanceof MethodInvocationTree)) {
+ return false;
+ }
+ Name name = getMethodName((MethodInvocationTree) p);
+ return Stream.of("stream", "parallelStream", "toBuilder")
+ .anyMatch(name::contentEquals);
+ })
+ .collect(toList());
+ }
+
+ private static <T> Stream<Long> indexes(Stream<T> stream, Predicate<T> predicate) {
+ return Streams.mapWithIndex(stream, (x, i) -> predicate.apply(x) ? i : -1).filter(x -> x != -1);
+ }
+
+ @Override
+ public Void visitMemberSelect(MemberSelectTree node, Void unused) {
+ sync(node);
+ visitDot(node);
+ return null;
+ }
+
+ @Override
+ public Void visitLiteral(LiteralTree node, Void unused) {
+ sync(node);
+ String sourceForNode = getSourceForNode(node, getCurrentPath());
+ // A negative numeric literal -n is usually represented as unary minus on n,
+ // but that doesn't work for integer or long MIN_VALUE. The parser works
+ // around that by representing it directly as a signed literal (with no
+ // unary minus), but the lexer still expects two tokens.
+ if (sourceForNode.startsWith("-")) {
+ token("-");
+ sourceForNode = sourceForNode.substring(1).trim();
+ }
+ token(sourceForNode);
+ return null;
+ }
+
+ private void visitPackage(
+ ExpressionTree packageName, List<? extends AnnotationTree> packageAnnotations) {
+ if (!packageAnnotations.isEmpty()) {
+ for (AnnotationTree annotation : packageAnnotations) {
+ builder.forcedBreak();
+ scan(annotation, null);
+ }
+ builder.forcedBreak();
+ }
+ builder.open(plusFour);
+ token("package");
+ builder.space();
+ visitName(packageName);
+ builder.close();
+ token(";");
+ }
+
+ @Override
+ public Void visitParameterizedType(ParameterizedTypeTree node, Void unused) {
+ sync(node);
+ if (node.getTypeArguments().isEmpty()) {
+ scan(node.getType(), null);
+ token("<");
+ token(">");
+ } else {
+ builder.open(plusFour);
+ scan(node.getType(), null);
+ token("<");
+ builder.breakOp();
+ builder.open(ZERO);
+ boolean first = true;
+ for (Tree typeArgument : node.getTypeArguments()) {
+ if (!first) {
+ token(",");
+ builder.breakOp(" ");
+ }
+ scan(typeArgument, null);
+ first = false;
+ }
+ builder.close();
+ builder.close();
+ token(">");
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitParenthesized(ParenthesizedTree node, Void unused) {
+ token("(");
+ scan(node.getExpression(), null);
+ token(")");
+ return null;
+ }
+
+ @Override
+ public Void visitUnary(UnaryTree node, Void unused) {
+ sync(node);
+ String operatorName = operatorName(node);
+ if (((JCTree) node).getTag().isPostUnaryOp()) {
+ scan(node.getExpression(), null);
+ splitToken(operatorName);
+ } else {
+ splitToken(operatorName);
+ if (ambiguousUnaryOperator(node, operatorName)) {
+ builder.space();
+ }
+ scan(node.getExpression(), null);
+ }
+ return null;
+ }
+
+ private void splitToken(String operatorName) {
+ for (int i = 0; i < operatorName.length(); i++) {
+ token(String.valueOf(operatorName.charAt(i)));
+ }
+ }
+
+ private boolean ambiguousUnaryOperator(UnaryTree node, String operatorName) {
+ switch (node.getKind()) {
+ case UNARY_MINUS:
+ case UNARY_PLUS:
+ break;
+ default:
+ return false;
+ }
+ if (!(node.getExpression() instanceof UnaryTree)) {
+ return false;
+ }
+ JCTree.Tag tag = ((JCTree) node.getExpression()).getTag();
+ if (tag.isPostUnaryOp()) {
+ return false;
+ }
+ if (!operatorName(node).startsWith(operatorName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public Void visitPrimitiveType(PrimitiveTypeTree node, Void unused) {
+ sync(node);
+ switch (node.getPrimitiveTypeKind()) {
+ case BOOLEAN:
+ token("boolean");
+ break;
+ case BYTE:
+ token("byte");
+ break;
+ case SHORT:
+ token("short");
+ break;
+ case INT:
+ token("int");
+ break;
+ case LONG:
+ token("long");
+ break;
+ case CHAR:
+ token("char");
+ break;
+ case FLOAT:
+ token("float");
+ break;
+ case DOUBLE:
+ token("double");
+ break;
+ case VOID:
+ token("void");
+ break;
+ default:
+ throw new AssertionError(node.getPrimitiveTypeKind());
+ }
+ return null;
+ }
+
+ public boolean visit(Name name) {
+ token(name.toString());
+ return false;
+ }
+
+ @Override
+ public Void visitReturn(ReturnTree node, Void unused) {
+ sync(node);
+ token("return");
+ if (node.getExpression() != null) {
+ builder.space();
+ scan(node.getExpression(), null);
+ }
+ token(";");
+ return null;
+ }
+
+ // TODO(cushon): is this worth special-casing?
+ boolean visitSingleMemberAnnotation(AnnotationTree node) {
+ if (node.getArguments().size() != 1) {
+ return false;
+ }
+ ExpressionTree value = getOnlyElement(node.getArguments());
+ if (value.getKind() == ASSIGNMENT) {
+ return false;
+ }
+ boolean isArrayInitializer = value.getKind() == NEW_ARRAY;
+ builder.open(isArrayInitializer ? ZERO : plusFour);
+ token("@");
+ scan(node.getAnnotationType(), null);
+ token("(");
+ if (!isArrayInitializer) {
+ builder.breakOp();
+ }
+ scan(value, null);
+ builder.close();
+ token(")");
+ return true;
+ }
+
+ @Override
+ public Void visitCase(CaseTree node, Void unused) {
+ sync(node);
+ markForPartialFormat();
+ builder.forcedBreak();
+ if (node.getExpression() == null) {
+ token("default", plusTwo);
+ token(":");
+ } else {
+ token("case", plusTwo);
+ builder.space();
+ scan(node.getExpression(), null);
+ token(":");
+ }
+ builder.open(plusTwo);
+ visitStatements(node.getStatements());
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitSwitch(SwitchTree node, Void unused) {
+ sync(node);
+ visitSwitch(node.getExpression(), node.getCases());
+ return null;
+ }
+
+ protected void visitSwitch(ExpressionTree expression, List<? extends CaseTree> cases) {
+ token("switch");
+ builder.space();
+ token("(");
+ scan(skipParen(expression), null);
+ token(")");
+ builder.space();
+ tokenBreakTrailingComment("{", plusTwo);
+ builder.blankLineWanted(BlankLineWanted.NO);
+ builder.open(plusTwo);
+ boolean first = true;
+ for (CaseTree caseTree : cases) {
+ if (!first) {
+ builder.blankLineWanted(BlankLineWanted.PRESERVE);
+ }
+ scan(caseTree, null);
+ first = false;
+ }
+ builder.close();
+ builder.forcedBreak();
+ builder.blankLineWanted(BlankLineWanted.NO);
+ token("}", plusFour);
+ }
+
+ @Override
+ public Void visitSynchronized(SynchronizedTree node, Void unused) {
+ sync(node);
+ token("synchronized");
+ builder.space();
+ token("(");
+ builder.open(plusFour);
+ builder.breakOp();
+ scan(skipParen(node.getExpression()), null);
+ builder.close();
+ token(")");
+ builder.space();
+ scan(node.getBlock(), null);
+ return null;
+ }
+
+ @Override
+ public Void visitThrow(ThrowTree node, Void unused) {
+ sync(node);
+ token("throw");
+ builder.space();
+ scan(node.getExpression(), null);
+ token(";");
+ return null;
+ }
+
+ @Override
+ public Void visitTry(TryTree node, Void unused) {
+ sync(node);
+ builder.open(ZERO);
+ token("try");
+ builder.space();
+ if (!node.getResources().isEmpty()) {
+ token("(");
+ builder.open(node.getResources().size() > 1 ? plusFour : ZERO);
+ boolean first = true;
+ for (Tree resource : node.getResources()) {
+ if (!first) {
+ builder.forcedBreak();
+ }
+ if (resource instanceof VariableTree) {
+ VariableTree variableTree = (VariableTree) resource;
+ declareOne(
+ DeclarationKind.PARAMETER,
+ fieldAnnotationDirection(variableTree.getModifiers()),
+ Optional.of(variableTree.getModifiers()),
+ variableTree.getType(),
+ /* name= */ variableTree.getName(),
+ "",
+ "=",
+ Optional.ofNullable(variableTree.getInitializer()),
+ /* trailing= */ Optional.empty(),
+ /* receiverExpression= */ Optional.empty(),
+ /* typeWithDims= */ Optional.empty());
+ } else {
+ // TODO(cushon): think harder about what to do with `try (resource1; resource2) {}`
+ scan(resource, null);
+ }
+ if (builder.peekToken().equals(Optional.of(";"))) {
+ token(";");
+ builder.space();
+ }
+ first = false;
+ }
+ if (builder.peekToken().equals(Optional.of(";"))) {
+ token(";");
+ builder.space();
+ }
+ token(")");
+ builder.close();
+ builder.space();
+ }
+ // An empty try-with-resources body can collapse to "{}" if there are no trailing catch or
+ // finally blocks.
+ boolean trailingClauses = !node.getCatches().isEmpty() || node.getFinallyBlock() != null;
+ visitBlock(
+ node.getBlock(),
+ CollapseEmptyOrNot.valueOf(!trailingClauses),
+ AllowLeadingBlankLine.YES,
+ AllowTrailingBlankLine.valueOf(trailingClauses));
+ for (int i = 0; i < node.getCatches().size(); i++) {
+ CatchTree catchClause = node.getCatches().get(i);
+ trailingClauses = i < node.getCatches().size() - 1 || node.getFinallyBlock() != null;
+ visitCatchClause(catchClause, AllowTrailingBlankLine.valueOf(trailingClauses));
+ }
+ if (node.getFinallyBlock() != null) {
+ builder.space();
+ token("finally");
+ builder.space();
+ visitBlock(
+ node.getFinallyBlock(),
+ CollapseEmptyOrNot.NO,
+ AllowLeadingBlankLine.YES,
+ AllowTrailingBlankLine.NO);
+ }
+ builder.close();
+ return null;
+ }
+
+ public void visitClassDeclaration(ClassTree node) {
+ sync(node);
+ List<Op> breaks =
+ visitModifiers(
+ node.getModifiers(),
+ Direction.VERTICAL,
+ /* declarationAnnotationBreak= */ Optional.empty());
+ boolean hasSuperclassType = node.getExtendsClause() != null;
+ boolean hasSuperInterfaceTypes = !node.getImplementsClause().isEmpty();
+ builder.addAll(breaks);
+ token(node.getKind() == Tree.Kind.INTERFACE ? "interface" : "class");
+ builder.space();
+ visit(node.getSimpleName());
+ if (!node.getTypeParameters().isEmpty()) {
+ token("<");
+ }
+ builder.open(plusFour);
+ {
+ if (!node.getTypeParameters().isEmpty()) {
+ typeParametersRest(
+ node.getTypeParameters(),
+ hasSuperclassType || hasSuperInterfaceTypes ? plusFour : ZERO);
+ }
+ if (hasSuperclassType) {
+ builder.breakToFill(" ");
+ token("extends");
+ builder.space();
+ scan(node.getExtendsClause(), null);
+ }
+ if (hasSuperInterfaceTypes) {
+ builder.breakToFill(" ");
+ builder.open(node.getImplementsClause().size() > 1 ? plusFour : ZERO);
+ token(node.getKind() == Tree.Kind.INTERFACE ? "extends" : "implements");
+ builder.space();
+ boolean first = true;
+ for (Tree superInterfaceType : node.getImplementsClause()) {
+ if (!first) {
+ token(",");
+ builder.breakOp(" ");
+ }
+ scan(superInterfaceType, null);
+ first = false;
+ }
+ builder.close();
+ }
+ }
+ builder.close();
+ if (node.getMembers() == null) {
+ token(";");
+ } else {
+ addBodyDeclarations(node.getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES);
+ }
+ dropEmptyDeclarations();
+ }
+
+ @Override
+ public Void visitTypeParameter(TypeParameterTree node, Void unused) {
+ sync(node);
+ builder.open(ZERO);
+ visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.YES);
+ visit(node.getName());
+ if (!node.getBounds().isEmpty()) {
+ builder.space();
+ token("extends");
+ builder.open(plusFour);
+ builder.breakOp(" ");
+ builder.open(plusFour);
+ boolean first = true;
+ for (Tree typeBound : node.getBounds()) {
+ if (!first) {
+ builder.breakToFill(" ");
+ token("&");
+ builder.space();
+ }
+ scan(typeBound, null);
+ first = false;
+ }
+ builder.close();
+ builder.close();
+ }
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitUnionType(UnionTypeTree node, Void unused) {
+ throw new IllegalStateException("expected manual descent into union types");
+ }
+
+ @Override
+ public Void visitWhileLoop(WhileLoopTree node, Void unused) {
+ sync(node);
+ token("while");
+ builder.space();
+ token("(");
+ scan(skipParen(node.getCondition()), null);
+ token(")");
+ visitStatement(
+ node.getStatement(),
+ CollapseEmptyOrNot.YES,
+ AllowLeadingBlankLine.YES,
+ AllowTrailingBlankLine.NO);
+ return null;
+ }
+
+ @Override
+ public Void visitWildcard(WildcardTree node, Void unused) {
+ sync(node);
+ builder.open(ZERO);
+ token("?");
+ if (node.getBound() != null) {
+ builder.open(plusFour);
+ builder.space();
+ token(node.getKind() == EXTENDS_WILDCARD ? "extends" : "super");
+ builder.breakOp(" ");
+ scan(node.getBound(), null);
+ builder.close();
+ }
+ builder.close();
+ return null;
+ }
+
+ // Helper methods.
+
+ /** Helper method for annotations. */
+ void visitAnnotations(
+ List<? extends AnnotationTree> annotations, BreakOrNot breakBefore, BreakOrNot breakAfter) {
+ if (!annotations.isEmpty()) {
+ if (breakBefore.isYes()) {
+ builder.breakToFill(" ");
+ }
+ boolean first = true;
+ for (AnnotationTree annotation : annotations) {
+ if (!first) {
+ builder.breakToFill(" ");
+ }
+ scan(annotation, null);
+ first = false;
+ }
+ if (breakAfter.isYes()) {
+ builder.breakToFill(" ");
+ }
+ }
+ }
+
+ /** Helper method for blocks. */
+ private void visitBlock(
+ BlockTree node,
+ CollapseEmptyOrNot collapseEmptyOrNot,
+ AllowLeadingBlankLine allowLeadingBlankLine,
+ AllowTrailingBlankLine allowTrailingBlankLine) {
+ sync(node);
+ if (node.isStatic()) {
+ token("static");
+ builder.space();
+ }
+ if (collapseEmptyOrNot.isYes() && node.getStatements().isEmpty()) {
+ if (builder.peekToken().equals(Optional.of(";"))) {
+ // TODO(cushon): is this needed?
+ token(";");
+ } else {
+ tokenBreakTrailingComment("{", plusTwo);
+ builder.blankLineWanted(BlankLineWanted.NO);
+ token("}", plusTwo);
+ }
+ } else {
+ builder.open(ZERO);
+ builder.open(plusTwo);
+ tokenBreakTrailingComment("{", plusTwo);
+ if (allowLeadingBlankLine == AllowLeadingBlankLine.NO) {
+ builder.blankLineWanted(BlankLineWanted.NO);
+ } else {
+ builder.blankLineWanted(BlankLineWanted.PRESERVE);
+ }
+ visitStatements(node.getStatements());
+ builder.close();
+ builder.forcedBreak();
+ builder.close();
+ if (allowTrailingBlankLine == AllowTrailingBlankLine.NO) {
+ builder.blankLineWanted(BlankLineWanted.NO);
+ } else {
+ builder.blankLineWanted(BlankLineWanted.PRESERVE);
+ }
+ markForPartialFormat();
+ token("}", plusTwo);
+ }
+ }
+
+ /** Helper method for statements. */
+ private void visitStatement(
+ StatementTree node,
+ CollapseEmptyOrNot collapseEmptyOrNot,
+ AllowLeadingBlankLine allowLeadingBlank,
+ AllowTrailingBlankLine allowTrailingBlank) {
+ sync(node);
+ switch (node.getKind()) {
+ case BLOCK:
+ builder.space();
+ visitBlock((BlockTree) node, collapseEmptyOrNot, allowLeadingBlank, allowTrailingBlank);
+ break;
+ default:
+ builder.open(plusTwo);
+ builder.breakOp(" ");
+ scan(node, null);
+ builder.close();
+ }
+ }
+
+ protected void visitStatements(List<? extends StatementTree> statements) {
+ boolean first = true;
+ PeekingIterator<StatementTree> it = Iterators.peekingIterator(statements.iterator());
+ dropEmptyDeclarations();
+ while (it.hasNext()) {
+ StatementTree tree = it.next();
+ builder.forcedBreak();
+ if (!first) {
+ builder.blankLineWanted(BlankLineWanted.PRESERVE);
+ }
+ markForPartialFormat();
+ first = false;
+ List<VariableTree> fragments = variableFragments(it, tree);
+ if (!fragments.isEmpty()) {
+ visitVariables(
+ fragments,
+ DeclarationKind.NONE,
+ canLocalHaveHorizontalAnnotations(fragments.get(0).getModifiers()));
+ } else {
+ scan(tree, null);
+ }
+ }
+ }
+
+ /** Output combined modifiers and annotations and the trailing break. */
+ void visitAndBreakModifiers(
+ ModifiersTree modifiers,
+ Direction annotationDirection,
+ Optional<BreakTag> declarationAnnotationBreak) {
+ builder.addAll(visitModifiers(modifiers, annotationDirection, declarationAnnotationBreak));
+ }
+
+ @Override
+ public Void visitModifiers(ModifiersTree node, Void unused) {
+ throw new IllegalStateException("expected manual descent into modifiers");
+ }
+
+ /** Output combined modifiers and annotations and returns the trailing break. */
+ protected List<Op> visitModifiers(
+ ModifiersTree modifiersTree,
+ Direction annotationsDirection,
+ Optional<BreakTag> declarationAnnotationBreak) {
+ return visitModifiers(
+ modifiersTree.getAnnotations(), annotationsDirection, declarationAnnotationBreak);
+ }
+
+ protected List<Op> visitModifiers(
+ List<? extends AnnotationTree> annotationTrees,
+ Direction annotationsDirection,
+ Optional<BreakTag> declarationAnnotationBreak) {
+ if (annotationTrees.isEmpty() && !nextIsModifier()) {
+ return EMPTY_LIST;
+ }
+ Deque<AnnotationTree> annotations = new ArrayDeque<>(annotationTrees);
+ builder.open(ZERO);
+ boolean first = true;
+ boolean lastWasAnnotation = false;
+ while (!annotations.isEmpty()) {
+ if (nextIsModifier()) {
+ break;
+ }
+ if (!first) {
+ builder.addAll(
+ annotationsDirection.isVertical()
+ ? forceBreakList(declarationAnnotationBreak)
+ : breakList(declarationAnnotationBreak));
+ }
+ scan(annotations.removeFirst(), null);
+ first = false;
+ lastWasAnnotation = true;
+ }
+ builder.close();
+ ImmutableList<Op> trailingBreak =
+ annotationsDirection.isVertical()
+ ? forceBreakList(declarationAnnotationBreak)
+ : breakList(declarationAnnotationBreak);
+ if (annotations.isEmpty() && !nextIsModifier()) {
+ return trailingBreak;
+ }
+ if (lastWasAnnotation) {
+ builder.addAll(trailingBreak);
+ }
+
+ builder.open(ZERO);
+ first = true;
+ while (nextIsModifier() || !annotations.isEmpty()) {
+ if (!first) {
+ builder.addAll(breakFillList(Optional.empty()));
+ }
+ if (nextIsModifier()) {
+ token(builder.peekToken().get());
+ } else {
+ scan(annotations.removeFirst(), null);
+ lastWasAnnotation = true;
+ }
+ first = false;
+ }
+ builder.close();
+ return breakFillList(Optional.empty());
+ }
+
+ boolean nextIsModifier() {
+ switch (builder.peekToken().get()) {
+ case "public":
+ case "protected":
+ case "private":
+ case "abstract":
+ case "static":
+ case "final":
+ case "transient":
+ case "volatile":
+ case "synchronized":
+ case "native":
+ case "strictfp":
+ case "default":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public Void visitCatch(CatchTree node, Void unused) {
+ throw new IllegalStateException("expected manual descent into catch trees");
+ }
+
+ /** Helper method for {@link CatchTree}s. */
+ private void visitCatchClause(CatchTree node, AllowTrailingBlankLine allowTrailingBlankLine) {
+ sync(node);
+ builder.space();
+ token("catch");
+ builder.space();
+ token("(");
+ builder.open(plusFour);
+ VariableTree ex = node.getParameter();
+ if (ex.getType().getKind() == UNION_TYPE) {
+ builder.open(ZERO);
+ visitUnionType(ex);
+ builder.close();
+ } else {
+ // TODO(cushon): don't break after here for consistency with for, while, etc.
+ builder.breakToFill();
+ builder.open(ZERO);
+ scan(ex, null);
+ builder.close();
+ }
+ builder.close();
+ token(")");
+ builder.space();
+ visitBlock(
+ node.getBlock(), CollapseEmptyOrNot.NO, AllowLeadingBlankLine.YES, allowTrailingBlankLine);
+ }
+
+ /** Formats a union type declaration in a catch clause. */
+ private void visitUnionType(VariableTree declaration) {
+ UnionTypeTree type = (UnionTypeTree) declaration.getType();
+ builder.open(ZERO);
+ sync(declaration);
+ visitAndBreakModifiers(
+ declaration.getModifiers(),
+ Direction.HORIZONTAL,
+ /* declarationAnnotationBreak= */ Optional.empty());
+ List<? extends Tree> union = type.getTypeAlternatives();
+ boolean first = true;
+ for (int i = 0; i < union.size() - 1; i++) {
+ if (!first) {
+ builder.breakOp(" ");
+ token("|");
+ builder.space();
+ } else {
+ first = false;
+ }
+ scan(union.get(i), null);
+ }
+ builder.breakOp(" ");
+ token("|");
+ builder.space();
+ Tree last = union.get(union.size() - 1);
+ declareOne(
+ DeclarationKind.NONE,
+ Direction.HORIZONTAL,
+ /* modifiers= */ Optional.empty(),
+ last,
+ /* name= */ declaration.getName(),
+ /* op= */ "",
+ "=",
+ Optional.ofNullable(declaration.getInitializer()),
+ /* trailing= */ Optional.empty(),
+ /* receiverExpression= */ Optional.empty(),
+ /* typeWithDims= */ Optional.empty());
+ builder.close();
+ }
+
+ /** Accumulate the operands and operators. */
+ private static void walkInfix(
+ int precedence,
+ ExpressionTree expression,
+ List<ExpressionTree> operands,
+ List<String> operators) {
+ if (expression instanceof BinaryTree) {
+ BinaryTree binaryTree = (BinaryTree) expression;
+ if (precedence(binaryTree) == precedence) {
+ walkInfix(precedence, binaryTree.getLeftOperand(), operands, operators);
+ operators.add(operatorName(expression));
+ walkInfix(precedence, binaryTree.getRightOperand(), operands, operators);
+ } else {
+ operands.add(expression);
+ }
+ } else {
+ operands.add(expression);
+ }
+ }
+
+ protected void visitFormals(
+ Optional<VariableTree> receiver, List<? extends VariableTree> parameters) {
+ if (!receiver.isPresent() && parameters.isEmpty()) {
+ return;
+ }
+ builder.open(ZERO);
+ boolean first = true;
+ if (receiver.isPresent()) {
+ // TODO(jdd): Use builders.
+ declareOne(
+ DeclarationKind.PARAMETER,
+ Direction.HORIZONTAL,
+ Optional.of(receiver.get().getModifiers()),
+ receiver.get().getType(),
+ /* name= */ receiver.get().getName(),
+ "",
+ "",
+ /* initializer= */ Optional.empty(),
+ !parameters.isEmpty() ? Optional.of(",") : Optional.empty(),
+ Optional.of(receiver.get().getNameExpression()),
+ /* typeWithDims= */ Optional.empty());
+ first = false;
+ }
+ for (int i = 0; i < parameters.size(); i++) {
+ VariableTree parameter = parameters.get(i);
+ if (!first) {
+ builder.breakOp(" ");
+ }
+ visitToDeclare(
+ DeclarationKind.PARAMETER,
+ Direction.HORIZONTAL,
+ parameter,
+ /* initializer= */ Optional.empty(),
+ "=",
+ i < parameters.size() - 1 ? Optional.of(",") : /* a= */ Optional.empty());
+ first = false;
+ }
+ builder.close();
+ }
+
+ // /** Helper method for {@link MethodDeclaration}s. */
+ private void visitThrowsClause(List<? extends ExpressionTree> thrownExceptionTypes) {
+ token("throws");
+ builder.breakToFill(" ");
+ boolean first = true;
+ for (ExpressionTree thrownExceptionType : thrownExceptionTypes) {
+ if (!first) {
+ token(",");
+ builder.breakToFill(" ");
+ }
+ scan(thrownExceptionType, null);
+ first = false;
+ }
+ }
+
+ @Override
+ public Void visitIdentifier(IdentifierTree node, Void unused) {
+ sync(node);
+ token(node.getName().toString());
+ return null;
+ }
+
+ @Override
+ public Void visitModule(ModuleTree node, Void unused) {
+ for (AnnotationTree annotation : node.getAnnotations()) {
+ scan(annotation, null);
+ builder.forcedBreak();
+ }
+ if (node.getModuleType() == ModuleTree.ModuleKind.OPEN) {
+ token("open");
+ builder.space();
+ }
+ token("module");
+ builder.space();
+ scan(node.getName(), null);
+ builder.space();
+ if (node.getDirectives().isEmpty()) {
+ tokenBreakTrailingComment("{", plusTwo);
+ builder.blankLineWanted(BlankLineWanted.NO);
+ token("}", plusTwo);
+ } else {
+ builder.open(plusTwo);
+ token("{");
+ builder.forcedBreak();
+ Optional<Tree.Kind> previousDirective = Optional.empty();
+ for (DirectiveTree directiveTree : node.getDirectives()) {
+ markForPartialFormat();
+ builder.blankLineWanted(
+ previousDirective.map(k -> !k.equals(directiveTree.getKind())).orElse(false)
+ ? BlankLineWanted.YES
+ : BlankLineWanted.NO);
+ builder.forcedBreak();
+ scan(directiveTree, null);
+ previousDirective = Optional.of(directiveTree.getKind());
+ }
+ builder.close();
+ builder.forcedBreak();
+ token("}");
+ }
+ return null;
+ }
+
+ private void visitDirective(
+ String name,
+ String separator,
+ ExpressionTree nameExpression,
+ @Nullable List<? extends ExpressionTree> items) {
+ token(name);
+ builder.space();
+ scan(nameExpression, null);
+ if (items != null) {
+ builder.open(plusFour);
+ builder.space();
+ token(separator);
+ builder.forcedBreak();
+ boolean first = true;
+ for (ExpressionTree item : items) {
+ if (!first) {
+ token(",");
+ builder.forcedBreak();
+ }
+ scan(item, null);
+ first = false;
+ }
+ token(";");
+ builder.close();
+ } else {
+ token(";");
+ }
+ }
+
+ @Override
+ public Void visitExports(ExportsTree node, Void unused) {
+ visitDirective("exports", "to", node.getPackageName(), node.getModuleNames());
+ return null;
+ }
+
+ @Override
+ public Void visitOpens(OpensTree node, Void unused) {
+ visitDirective("opens", "to", node.getPackageName(), node.getModuleNames());
+ return null;
+ }
+
+ @Override
+ public Void visitProvides(ProvidesTree node, Void unused) {
+ visitDirective("provides", "with", node.getServiceName(), node.getImplementationNames());
+ return null;
+ }
+
+ @Override
+ public Void visitRequires(RequiresTree node, Void unused) {
+ token("requires");
+ builder.space();
+ while (true) {
+ if (builder.peekToken().equals(Optional.of("static"))) {
+ token("static");
+ builder.space();
+ } else if (builder.peekToken().equals(Optional.of("transitive"))) {
+ token("transitive");
+ builder.space();
+ } else {
+ break;
+ }
+ }
+ scan(node.getModuleName(), null);
+ token(";");
+ return null;
+ }
+
+ @Override
+ public Void visitUses(UsesTree node, Void unused) {
+ token("uses");
+ builder.space();
+ scan(node.getServiceName(), null);
+ token(";");
+ return null;
+ }
+
+ /** Helper method for import declarations, names, and qualified names. */
+ private void visitName(Tree node) {
+ Deque<Name> stack = new ArrayDeque<>();
+ for (; node instanceof MemberSelectTree; node = ((MemberSelectTree) node).getExpression()) {
+ stack.addFirst(((MemberSelectTree) node).getIdentifier());
+ }
+ stack.addFirst(((IdentifierTree) node).getName());
+ boolean first = true;
+ for (Name name : stack) {
+ if (!first) {
+ token(".");
+ }
+ token(name.toString());
+ first = false;
+ }
+ }
+
+ private void visitToDeclare(
+ DeclarationKind kind,
+ Direction annotationsDirection,
+ VariableTree node,
+ Optional<ExpressionTree> initializer,
+ String equals,
+ Optional<String> trailing) {
+ sync(node);
+ Optional<TypeWithDims> typeWithDims;
+ Tree type;
+ if (node.getType() != null) {
+ TypeWithDims extractedDims = DimensionHelpers.extractDims(node.getType(), SortedDims.YES);
+ typeWithDims = Optional.of(extractedDims);
+ type = extractedDims.node;
+ } else {
+ typeWithDims = Optional.empty();
+ type = null;
+ }
+ declareOne(
+ kind,
+ annotationsDirection,
+ Optional.of(node.getModifiers()),
+ type,
+ node.getName(),
+ "",
+ equals,
+ initializer,
+ trailing,
+ /* receiverExpression= */ Optional.empty(),
+ typeWithDims);
+ }
+
+ /** Does not omit the leading {@code "<"}, which should be associated with the type name. */
+ protected void typeParametersRest(
+ List<? extends TypeParameterTree> typeParameters, Indent plusIndent) {
+ builder.open(plusIndent);
+ builder.breakOp();
+ builder.open(ZERO);
+ boolean first = true;
+ for (TypeParameterTree typeParameter : typeParameters) {
+ if (!first) {
+ token(",");
+ builder.breakOp(" ");
+ }
+ scan(typeParameter, null);
+ first = false;
+ }
+ token(">");
+ builder.close();
+ builder.close();
+ }
+
+ /** Collapse chains of {@code .} operators, across multiple {@link ASTNode} types. */
+
+ /**
+ * Output a "." node.
+ *
+ * @param node0 the "." node
+ */
+ void visitDot(ExpressionTree node0) {
+ ExpressionTree node = node0;
+
+ // collect a flattened list of "."-separated items
+ // e.g. ImmutableList.builder().add(1).build() -> [ImmutableList, builder(), add(1), build()]
+ Deque<ExpressionTree> stack = new ArrayDeque<>();
+ LOOP:
+ do {
+ stack.addFirst(node);
+ if (node.getKind() == ARRAY_ACCESS) {
+ node = getArrayBase(node);
+ }
+ switch (node.getKind()) {
+ case MEMBER_SELECT:
+ node = ((MemberSelectTree) node).getExpression();
+ break;
+ case METHOD_INVOCATION:
+ node = getMethodReceiver((MethodInvocationTree) node);
+ break;
+ case IDENTIFIER:
+ node = null;
+ break LOOP;
+ default:
+ // If the dot chain starts with a primary expression
+ // (e.g. a class instance creation, or a conditional expression)
+ // then remove it from the list and deal with it first.
+ node = stack.removeFirst();
+ break LOOP;
+ }
+ } while (node != null);
+ List<ExpressionTree> items = new ArrayList<>(stack);
+
+ boolean needDot = false;
+
+ // The dot chain started with a primary expression: output it normally, and indent
+ // the rest of the chain +4.
+ if (node != null) {
+ // Exception: if it's an anonymous class declaration, we don't need to
+ // break and indent after the trailing '}'.
+ if (node.getKind() == NEW_CLASS && ((NewClassTree) node).getClassBody() != null) {
+ builder.open(ZERO);
+ scan(getArrayBase(node), null);
+ token(".");
+ } else {
+ builder.open(plusFour);
+ scan(getArrayBase(node), null);
+ builder.breakOp();
+ needDot = true;
+ }
+ formatArrayIndices(getArrayIndices(node));
+ if (stack.isEmpty()) {
+ builder.close();
+ return;
+ }
+ }
+
+ Set<Integer> prefixes = new LinkedHashSet<>();
+
+ // Check if the dot chain has a prefix that looks like a type name, so we can
+ // treat the type name-shaped part as a single syntactic unit.
+ TypeNameClassifier.typePrefixLength(simpleNames(stack)).ifPresent(prefixes::add);
+
+ int invocationCount = 0;
+ int firstInvocationIndex = -1;
+ {
+ for (int i = 0; i < items.size(); i++) {
+ ExpressionTree expression = items.get(i);
+ if (expression.getKind() == METHOD_INVOCATION) {
+ if (i > 0 || node != null) {
+ // we only want dereference invocations
+ invocationCount++;
+ }
+ if (firstInvocationIndex < 0) {
+ firstInvocationIndex = i;
+ }
+ }
+ }
+ }
+
+ // If there's only one invocation, treat leading field accesses as a single
+ // unit. In the normal case we want to preserve the alignment of subsequent
+ // method calls, and would emit e.g.:
+ //
+ // myField
+ // .foo()
+ // .bar();
+ //
+ // But if there's no 'bar()' to worry about the alignment of we prefer:
+ //
+ // myField.foo();
+ //
+ // to:
+ //
+ // myField
+ // .foo();
+ //
+ if (invocationCount == 1 && firstInvocationIndex > 0) {
+ prefixes.add(firstInvocationIndex);
+ }
+
+ if (prefixes.isEmpty() && items.get(0) instanceof IdentifierTree) {
+ switch (((IdentifierTree) items.get(0)).getName().toString()) {
+ case "this":
+ case "super":
+ prefixes.add(1);
+ break;
+ default:
+ break;
+ }
+ }
+
+ List<Long> streamPrefixes = handleStream(items);
+ streamPrefixes.forEach(x -> prefixes.add(x.intValue()));
+ if (!prefixes.isEmpty()) {
+ visitDotWithPrefix(
+ items, needDot, prefixes, streamPrefixes.isEmpty() ? INDEPENDENT : UNIFIED);
+ } else {
+ visitRegularDot(items, needDot);
+ }
+
+ if (node != null) {
+ builder.close();
+ }
+ }
+
+ /**
+ * Output a "regular" chain of dereferences, possibly in builder-style. Break before every dot.
+ *
+ * @param items in the chain
+ * @param needDot whether a leading dot is needed
+ */
+ private void visitRegularDot(List<ExpressionTree> items, boolean needDot) {
+ boolean trailingDereferences = items.size() > 1;
+ boolean needDot0 = needDot;
+ if (!needDot0) {
+ builder.open(plusFour);
+ }
+ // don't break after the first element if it is every small, unless the
+ // chain starts with another expression
+ int minLength = indentMultiplier * 4;
+ int length = needDot0 ? minLength : 0;
+ for (ExpressionTree e : items) {
+ if (needDot) {
+ if (length > minLength) {
+ builder.breakOp(FillMode.UNIFIED, "", ZERO);
+ }
+ token(".");
+ length++;
+ }
+ if (!fillFirstArgument(e, items, trailingDereferences ? ZERO : minusFour)) {
+ BreakTag tyargTag = genSym();
+ dotExpressionUpToArgs(e, Optional.of(tyargTag));
+ Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO);
+ dotExpressionArgsAndParen(
+ e, tyargIndent, (trailingDereferences || needDot) ? plusFour : ZERO);
+ }
+ length += getLength(e, getCurrentPath());
+ needDot = true;
+ }
+ if (!needDot0) {
+ builder.close();
+ }
+ }
+
+ // avoid formattings like:
+ //
+ // when(
+ // something
+ // .happens())
+ // .thenReturn(result);
+ //
+ private boolean fillFirstArgument(ExpressionTree e, List<ExpressionTree> items, Indent indent) {
+ // is there a trailing dereference?
+ if (items.size() < 2) {
+ return false;
+ }
+ // don't special-case calls nested inside expressions
+ if (e.getKind() != METHOD_INVOCATION) {
+ return false;
+ }
+ MethodInvocationTree methodInvocation = (MethodInvocationTree) e;
+ Name name = getMethodName(methodInvocation);
+ if (!(methodInvocation.getMethodSelect() instanceof IdentifierTree)
+ || name.length() > 4
+ || !methodInvocation.getTypeArguments().isEmpty()
+ || methodInvocation.getArguments().size() != 1) {
+ return false;
+ }
+ builder.open(ZERO);
+ builder.open(indent);
+ visit(name);
+ token("(");
+ ExpressionTree arg = getOnlyElement(methodInvocation.getArguments());
+ scan(arg, null);
+ builder.close();
+ token(")");
+ builder.close();
+ return true;
+ }
+
+ /**
+ * Output a chain of dereferences where some prefix should be treated as a single syntactic unit,
+ * either because it looks like a type name or because there is only a single method invocation in
+ * the chain.
+ *
+ * @param items in the chain
+ * @param needDot whether a leading dot is needed
+ * @param prefixes the terminal indices of 'prefixes' of the expression that should be treated as
+ * a syntactic unit
+ */
+ private void visitDotWithPrefix(
+ List<ExpressionTree> items,
+ boolean needDot,
+ Collection<Integer> prefixes,
+ FillMode prefixFillMode) {
+ // Are there method invocations or field accesses after the prefix?
+ boolean trailingDereferences = !prefixes.isEmpty() && getLast(prefixes) < items.size() - 1;
+
+ builder.open(plusFour);
+ for (int times = 0; times < prefixes.size(); times++) {
+ builder.open(ZERO);
+ }
+
+ Deque<Integer> unconsumedPrefixes = new ArrayDeque<>(ImmutableSortedSet.copyOf(prefixes));
+ BreakTag nameTag = genSym();
+ for (int i = 0; i < items.size(); i++) {
+ ExpressionTree e = items.get(i);
+ if (needDot) {
+ FillMode fillMode;
+ if (!unconsumedPrefixes.isEmpty() && i <= unconsumedPrefixes.peekFirst()) {
+ fillMode = prefixFillMode;
+ } else {
+ fillMode = FillMode.UNIFIED;
+ }
+
+ builder.breakOp(fillMode, "", ZERO, Optional.of(nameTag));
+ token(".");
+ }
+ BreakTag tyargTag = genSym();
+ dotExpressionUpToArgs(e, Optional.of(tyargTag));
+ if (!unconsumedPrefixes.isEmpty() && i == unconsumedPrefixes.peekFirst()) {
+ builder.close();
+ unconsumedPrefixes.removeFirst();
+ }
+
+ Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO);
+ Indent argsIndent = Indent.If.make(nameTag, plusFour, trailingDereferences ? plusFour : ZERO);
+ dotExpressionArgsAndParen(e, tyargIndent, argsIndent);
+
+ needDot = true;
+ }
+
+ builder.close();
+ }
+
+ /** Returns the simple names of expressions in a "." chain. */
+ private List<String> simpleNames(Deque<ExpressionTree> stack) {
+ ImmutableList.Builder<String> simpleNames = ImmutableList.builder();
+ OUTER:
+ for (ExpressionTree expression : stack) {
+ boolean isArray = expression.getKind() == ARRAY_ACCESS;
+ expression = getArrayBase(expression);
+ switch (expression.getKind()) {
+ case MEMBER_SELECT:
+ simpleNames.add(((MemberSelectTree) expression).getIdentifier().toString());
+ break;
+ case IDENTIFIER:
+ simpleNames.add(((IdentifierTree) expression).getName().toString());
+ break;
+ case METHOD_INVOCATION:
+ simpleNames.add(getMethodName((MethodInvocationTree) expression).toString());
+ break OUTER;
+ default:
+ break OUTER;
+ }
+ if (isArray) {
+ break OUTER;
+ }
+ }
+ return simpleNames.build();
+ }
+
+ private void dotExpressionUpToArgs(ExpressionTree expression, Optional<BreakTag> tyargTag) {
+ expression = getArrayBase(expression);
+ switch (expression.getKind()) {
+ case MEMBER_SELECT:
+ MemberSelectTree fieldAccess = (MemberSelectTree) expression;
+ visit(fieldAccess.getIdentifier());
+ break;
+ case METHOD_INVOCATION:
+ MethodInvocationTree methodInvocation = (MethodInvocationTree) expression;
+ if (!methodInvocation.getTypeArguments().isEmpty()) {
+ builder.open(plusFour);
+ addTypeArguments(methodInvocation.getTypeArguments(), ZERO);
+ // TODO(jdd): Should indent the name -4.
+ builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO, tyargTag);
+ builder.close();
+ }
+ visit(getMethodName(methodInvocation));
+ break;
+ case IDENTIFIER:
+ visit(((IdentifierTree) expression).getName());
+ break;
+ default:
+ scan(expression, null);
+ break;
+ }
+ }
+
+ /**
+ * Returns the base expression of an erray access, e.g. given {@code foo[0][0]} returns {@code
+ * foo}.
+ */
+ private ExpressionTree getArrayBase(ExpressionTree node) {
+ while (node instanceof ArrayAccessTree) {
+ node = ((ArrayAccessTree) node).getExpression();
+ }
+ return node;
+ }
+
+ private ExpressionTree getMethodReceiver(MethodInvocationTree methodInvocation) {
+ ExpressionTree select = methodInvocation.getMethodSelect();
+ return select instanceof MemberSelectTree ? ((MemberSelectTree) select).getExpression() : null;
+ }
+
+ private void dotExpressionArgsAndParen(
+ ExpressionTree expression, Indent tyargIndent, Indent indent) {
+ Deque<ExpressionTree> indices = getArrayIndices(expression);
+ expression = getArrayBase(expression);
+ switch (expression.getKind()) {
+ case METHOD_INVOCATION:
+ builder.open(tyargIndent);
+ MethodInvocationTree methodInvocation = (MethodInvocationTree) expression;
+ addArguments(methodInvocation.getArguments(), indent);
+ builder.close();
+ break;
+ default:
+ break;
+ }
+ formatArrayIndices(indices);
+ }
+
+ /** Lays out one or more array indices. Does not output the expression for the array itself. */
+ private void formatArrayIndices(Deque<ExpressionTree> indices) {
+ if (indices.isEmpty()) {
+ return;
+ }
+ builder.open(ZERO);
+ do {
+ token("[");
+ builder.breakToFill();
+ scan(indices.removeLast(), null);
+ token("]");
+ } while (!indices.isEmpty());
+ builder.close();
+ }
+
+ /**
+ * Returns all array indices for the given expression, e.g. given {@code foo[0][0]} returns the
+ * expressions for {@code [0][0]}.
+ */
+ private Deque<ExpressionTree> getArrayIndices(ExpressionTree expression) {
+ Deque<ExpressionTree> indices = new ArrayDeque<>();
+ while (expression instanceof ArrayAccessTree) {
+ ArrayAccessTree array = (ArrayAccessTree) expression;
+ indices.addLast(array.getIndex());
+ expression = array.getExpression();
+ }
+ return indices;
+ }
+
+ /** Helper methods for method invocations. */
+ void addTypeArguments(List<? extends Tree> typeArguments, Indent plusIndent) {
+ if (typeArguments == null || typeArguments.isEmpty()) {
+ return;
+ }
+ token("<");
+ builder.open(plusIndent);
+ boolean first = true;
+ for (Tree typeArgument : typeArguments) {
+ if (!first) {
+ token(",");
+ builder.breakToFill(" ");
+ }
+ scan(typeArgument, null);
+ first = false;
+ }
+ builder.close();
+ token(">");
+ }
+
+ /**
+ * Add arguments to a method invocation, etc. The arguments indented {@code plusFour}, filled,
+ * from the current indent. The arguments may be output two at a time if they seem to be arguments
+ * to a map constructor, etc.
+ *
+ * @param arguments the arguments
+ * @param plusIndent the extra indent for the arguments
+ */
+ void addArguments(List<? extends ExpressionTree> arguments, Indent plusIndent) {
+ builder.open(plusIndent);
+ token("(");
+ if (!arguments.isEmpty()) {
+ if (arguments.size() % 2 == 0 && argumentsAreTabular(arguments) == 2) {
+ builder.forcedBreak();
+ builder.open(ZERO);
+ boolean first = true;
+ for (int i = 0; i < arguments.size() - 1; i += 2) {
+ ExpressionTree argument0 = arguments.get(i);
+ ExpressionTree argument1 = arguments.get(i + 1);
+ if (!first) {
+ token(",");
+ builder.forcedBreak();
+ }
+ builder.open(plusFour);
+ scan(argument0, null);
+ token(",");
+ builder.breakOp(" ");
+ scan(argument1, null);
+ builder.close();
+ first = false;
+ }
+ builder.close();
+ } else if (isFormatMethod(arguments)) {
+ builder.breakOp();
+ builder.open(ZERO);
+ scan(arguments.get(0), null);
+ token(",");
+ builder.breakOp(" ");
+ builder.open(ZERO);
+ argList(arguments.subList(1, arguments.size()));
+ builder.close();
+ builder.close();
+ } else {
+ builder.breakOp();
+ argList(arguments);
+ }
+ }
+ token(")");
+ builder.close();
+ }
+
+ private void argList(List<? extends ExpressionTree> arguments) {
+ builder.open(ZERO);
+ boolean first = true;
+ FillMode fillMode = hasOnlyShortItems(arguments) ? FillMode.INDEPENDENT : FillMode.UNIFIED;
+ for (ExpressionTree argument : arguments) {
+ if (!first) {
+ token(",");
+ builder.breakOp(fillMode, " ", ZERO);
+ }
+ scan(argument, null);
+ first = false;
+ }
+ builder.close();
+ }
+
+ /**
+ * Identifies String formatting methods like {@link String#format} which we prefer to format as:
+ *
+ * <pre>{@code
+ * String.format(
+ * "the format string: %s %s %s",
+ * arg, arg, arg);
+ * }</pre>
+ *
+ * <p>And not:
+ *
+ * <pre>{@code
+ * String.format(
+ * "the format string: %s %s %s",
+ * arg,
+ * arg,
+ * arg);
+ * }</pre>
+ */
+ private boolean isFormatMethod(List<? extends ExpressionTree> arguments) {
+ if (arguments.size() < 2) {
+ return false;
+ }
+ return isStringConcat(arguments.get(0));
+ }
+
+ private static final Pattern FORMAT_SPECIFIER = Pattern.compile("%|\\{[0-9]\\}");
+
+ private boolean isStringConcat(ExpressionTree first) {
+ final boolean[] stringLiteral = {true};
+ final boolean[] formatString = {false};
+ new TreeScanner() {
+ @Override
+ public void scan(JCTree tree) {
+ if (tree == null) {
+ return;
+ }
+ switch (tree.getKind()) {
+ case STRING_LITERAL:
+ break;
+ case PLUS:
+ super.scan(tree);
+ break;
+ default:
+ stringLiteral[0] = false;
+ break;
+ }
+ if (tree.getKind() == STRING_LITERAL) {
+ Object value = ((LiteralTree) tree).getValue();
+ if (value instanceof String && FORMAT_SPECIFIER.matcher(value.toString()).find()) {
+ formatString[0] = true;
+ }
+ }
+ }
+ }.scan((JCTree) first);
+ return stringLiteral[0] && formatString[0];
+ }
+
+ /** Returns the number of columns if the arguments arg laid out in a grid, or else {@code -1}. */
+ private int argumentsAreTabular(List<? extends ExpressionTree> arguments) {
+ if (arguments.isEmpty()) {
+ return -1;
+ }
+ List<List<ExpressionTree>> rows = new ArrayList<>();
+ PeekingIterator<ExpressionTree> it = Iterators.peekingIterator(arguments.iterator());
+ int start0 = actualColumn(it.peek());
+ {
+ List<ExpressionTree> row = new ArrayList<>();
+ row.add(it.next());
+ while (it.hasNext() && actualColumn(it.peek()) > start0) {
+ row.add(it.next());
+ }
+ if (!it.hasNext()) {
+ return -1;
+ }
+ if (rowLength(row) <= 1) {
+ return -1;
+ }
+ rows.add(row);
+ }
+ while (it.hasNext()) {
+ List<ExpressionTree> row = new ArrayList<>();
+ int start = actualColumn(it.peek());
+ if (start != start0) {
+ return -1;
+ }
+ row.add(it.next());
+ while (it.hasNext() && actualColumn(it.peek()) > start0) {
+ row.add(it.next());
+ }
+ rows.add(row);
+ }
+ int size0 = rows.get(0).size();
+ if (!expressionsAreParallel(rows, 0, rows.size())) {
+ return -1;
+ }
+ for (int i = 1; i < size0; i++) {
+ if (!expressionsAreParallel(rows, i, rows.size() / 2 + 1)) {
+ return -1;
+ }
+ }
+ // if there are only two rows, they must be the same length
+ if (rows.size() == 2) {
+ if (size0 == rows.get(1).size()) {
+ return size0;
+ }
+ return -1;
+ }
+ // allow a ragged trailing row for >= 3 columns
+ for (int i = 1; i < rows.size() - 1; i++) {
+ if (size0 != rows.get(i).size()) {
+ return -1;
+ }
+ }
+ if (size0 < getLast(rows).size()) {
+ return -1;
+ }
+ return size0;
+ }
+
+ static int rowLength(List<? extends ExpressionTree> row) {
+ int size = 0;
+ for (ExpressionTree tree : row) {
+ if (tree.getKind() != NEW_ARRAY) {
+ size++;
+ continue;
+ }
+ NewArrayTree array = (NewArrayTree) tree;
+ if (array.getInitializers() == null) {
+ size++;
+ continue;
+ }
+ size += rowLength(array.getInitializers());
+ }
+ return size;
+ }
+
+ private Integer actualColumn(ExpressionTree expression) {
+ Map<Integer, Integer> positionToColumnMap = builder.getInput().getPositionToColumnMap();
+ return positionToColumnMap.get(builder.actualStartColumn(getStartPosition(expression)));
+ }
+
+ /** Returns true if {@code atLeastM} of the expressions in the given column are the same kind. */
+ private static boolean expressionsAreParallel(
+ List<List<ExpressionTree>> rows, int column, int atLeastM) {
+ Multiset<Tree.Kind> nodeTypes = HashMultiset.create();
+ for (List<? extends ExpressionTree> row : rows) {
+ if (column >= row.size()) {
+ continue;
+ }
+ // Treat UnaryTree expressions as their underlying type for the comparison (so, for example
+ // -ve and +ve numeric literals are considered the same).
+ if (row.get(column) instanceof UnaryTree) {
+ nodeTypes.add(((UnaryTree) row.get(column)).getExpression().getKind());
+ } else {
+ nodeTypes.add(row.get(column).getKind());
+ }
+ }
+ for (Multiset.Entry<Tree.Kind> nodeType : nodeTypes.entrySet()) {
+ if (nodeType.getCount() >= atLeastM) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // General helper functions.
+
+ enum DeclarationKind {
+ NONE,
+ FIELD,
+ PARAMETER
+ }
+
+ /** Declare one variable or variable-like thing. */
+ int declareOne(
+ DeclarationKind kind,
+ Direction annotationsDirection,
+ Optional<ModifiersTree> modifiers,
+ Tree type,
+ Name name,
+ String op,
+ String equals,
+ Optional<ExpressionTree> initializer,
+ Optional<String> trailing,
+ Optional<ExpressionTree> receiverExpression,
+ Optional<TypeWithDims> typeWithDims) {
+
+ BreakTag typeBreak = genSym();
+ BreakTag verticalAnnotationBreak = genSym();
+
+ // If the node is a field declaration, try to output any declaration
+ // annotations in-line. If the entire declaration doesn't fit on a single
+ // line, fall back to one-per-line.
+ boolean isField = kind == DeclarationKind.FIELD;
+
+ if (isField) {
+ builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak));
+ }
+
+ Deque<List<? extends AnnotationTree>> dims =
+ new ArrayDeque<>(
+ typeWithDims.isPresent() ? typeWithDims.get().dims : Collections.emptyList());
+ int baseDims = 0;
+
+ builder.open(
+ kind == DeclarationKind.PARAMETER
+ && (modifiers.isPresent() && !modifiers.get().getAnnotations().isEmpty())
+ ? plusFour
+ : ZERO);
+ {
+ if (modifiers.isPresent()) {
+ visitAndBreakModifiers(
+ modifiers.get(), annotationsDirection, Optional.of(verticalAnnotationBreak));
+ }
+ boolean isVar =
+ builder.peekToken().get().equals("var")
+ && (!name.contentEquals("var") || builder.peekToken(1).get().equals("var"));
+ boolean hasType = type != null || isVar;
+ builder.open(hasType ? plusFour : ZERO);
+ {
+ builder.open(ZERO);
+ {
+ builder.open(ZERO);
+ {
+ if (typeWithDims.isPresent() && typeWithDims.get().node != null) {
+ scan(typeWithDims.get().node, null);
+ int totalDims = dims.size();
+ builder.open(plusFour);
+ maybeAddDims(dims);
+ builder.close();
+ baseDims = totalDims - dims.size();
+ } else if (isVar) {
+ token("var");
+ } else {
+ scan(type, null);
+ }
+ }
+ builder.close();
+
+ if (hasType) {
+ builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, Optional.of(typeBreak));
+ }
+
+ // conditionally ident the name and initializer +4 if the type spans
+ // multiple lines
+ builder.open(Indent.If.make(typeBreak, plusFour, ZERO));
+ if (receiverExpression.isPresent()) {
+ scan(receiverExpression.get(), null);
+ } else {
+ visit(name);
+ }
+ builder.op(op);
+ }
+ maybeAddDims(dims);
+ builder.close();
+ }
+ builder.close();
+
+ if (initializer.isPresent()) {
+ builder.space();
+ token(equals);
+ if (initializer.get().getKind() == Tree.Kind.NEW_ARRAY
+ && ((NewArrayTree) initializer.get()).getType() == null) {
+ builder.open(minusFour);
+ builder.space();
+ initializer.get().accept(this, null);
+ builder.close();
+ } else {
+ builder.open(Indent.If.make(typeBreak, plusFour, ZERO));
+ {
+ builder.breakToFill(" ");
+ scan(initializer.get(), null);
+ }
+ builder.close();
+ }
+ }
+ if (trailing.isPresent() && builder.peekToken().equals(trailing)) {
+ builder.guessToken(trailing.get());
+ }
+
+ // end of conditional name and initializer indent
+ builder.close();
+ }
+ builder.close();
+
+ if (isField) {
+ builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak));
+ }
+
+ return baseDims;
+ }
+
+ private void maybeAddDims(Deque<List<? extends AnnotationTree>> annotations) {
+ maybeAddDims(new ArrayDeque<>(), annotations);
+ }
+
+ /**
+ * The compiler does not always preserve the concrete syntax of annotated array dimensions, and
+ * mixed-notation array dimensions. Use look-ahead to preserve the original syntax.
+ *
+ * <p>It is assumed that any number of regular dimension specifiers ({@code []} with no
+ * annotations) may be present in the input.
+ *
+ * @param dimExpressions an ordered list of dimension expressions (e.g. the {@code 0} in {@code
+ * new int[0]}
+ * @param annotations an ordered list of type annotations grouped by dimension (e.g. {@code
+ * [[@A, @B], [@C]]} for {@code int @A [] @B @C []}
+ */
+ private void maybeAddDims(
+ Deque<ExpressionTree> dimExpressions, Deque<List<? extends AnnotationTree>> annotations) {
+ boolean lastWasAnnotation = false;
+ while (builder.peekToken().isPresent()) {
+ switch (builder.peekToken().get()) {
+ case "@":
+ if (annotations.isEmpty()) {
+ return;
+ }
+ List<? extends AnnotationTree> dimAnnotations = annotations.removeFirst();
+ if (dimAnnotations.isEmpty()) {
+ continue;
+ }
+ builder.breakToFill(" ");
+ visitAnnotations(dimAnnotations, BreakOrNot.NO, BreakOrNot.NO);
+ lastWasAnnotation = true;
+ break;
+ case "[":
+ if (lastWasAnnotation) {
+ builder.breakToFill(" ");
+ } else {
+ builder.breakToFill();
+ }
+ token("[");
+ if (!builder.peekToken().get().equals("]")) {
+ scan(dimExpressions.removeFirst(), null);
+ }
+ token("]");
+ lastWasAnnotation = false;
+ break;
+ case ".":
+ if (!builder.peekToken().get().equals(".") || !builder.peekToken(1).get().equals(".")) {
+ return;
+ }
+ if (lastWasAnnotation) {
+ builder.breakToFill(" ");
+ } else {
+ builder.breakToFill();
+ }
+ builder.op("...");
+ lastWasAnnotation = false;
+ break;
+ default:
+ return;
+ }
+ }
+ }
+
+ private void declareMany(List<VariableTree> fragments, Direction annotationDirection) {
+ builder.open(ZERO);
+
+ ModifiersTree modifiers = fragments.get(0).getModifiers();
+ Tree type = fragments.get(0).getType();
+
+ visitAndBreakModifiers(
+ modifiers, annotationDirection, /* declarationAnnotationBreak= */ Optional.empty());
+ builder.open(plusFour);
+ builder.open(ZERO);
+ TypeWithDims extractedDims = DimensionHelpers.extractDims(type, SortedDims.YES);
+ Deque<List<? extends AnnotationTree>> dims = new ArrayDeque<>(extractedDims.dims);
+ scan(extractedDims.node, null);
+ int baseDims = dims.size();
+ maybeAddDims(dims);
+ baseDims = baseDims - dims.size();
+ boolean first = true;
+ for (VariableTree fragment : fragments) {
+ if (!first) {
+ token(",");
+ }
+ TypeWithDims fragmentDims = variableFragmentDims(first, baseDims, fragment.getType());
+ dims = new ArrayDeque<>(fragmentDims.dims);
+ builder.breakOp(" ");
+ builder.open(ZERO);
+ maybeAddDims(dims);
+ visit(fragment.getName());
+ maybeAddDims(dims);
+ ExpressionTree initializer = fragment.getInitializer();
+ if (initializer != null) {
+ builder.space();
+ token("=");
+ builder.open(plusFour);
+ builder.breakOp(" ");
+ scan(initializer, null);
+ builder.close();
+ }
+ builder.close();
+ if (first) {
+ builder.close();
+ }
+ first = false;
+ }
+ builder.close();
+ token(";");
+ builder.close();
+ }
+
+ /** Add a list of declarations. */
+ protected void addBodyDeclarations(
+ List<? extends Tree> bodyDeclarations, BracesOrNot braces, FirstDeclarationsOrNot first0) {
+ if (bodyDeclarations.isEmpty()) {
+ if (braces.isYes()) {
+ builder.space();
+ tokenBreakTrailingComment("{", plusTwo);
+ builder.blankLineWanted(BlankLineWanted.NO);
+ builder.open(ZERO);
+ token("}", plusTwo);
+ builder.close();
+ }
+ } else {
+ if (braces.isYes()) {
+ builder.space();
+ tokenBreakTrailingComment("{", plusTwo);
+ builder.open(ZERO);
+ }
+ builder.open(plusTwo);
+ boolean first = first0.isYes();
+ boolean lastOneGotBlankLineBefore = false;
+ PeekingIterator<Tree> it = Iterators.peekingIterator(bodyDeclarations.iterator());
+ while (it.hasNext()) {
+ Tree bodyDeclaration = it.next();
+ dropEmptyDeclarations();
+ builder.forcedBreak();
+ boolean thisOneGetsBlankLineBefore =
+ bodyDeclaration.getKind() != VARIABLE || hasJavaDoc(bodyDeclaration);
+ if (first) {
+ builder.blankLineWanted(PRESERVE);
+ } else if (!first && (thisOneGetsBlankLineBefore || lastOneGotBlankLineBefore)) {
+ builder.blankLineWanted(YES);
+ }
+ markForPartialFormat();
+
+ if (bodyDeclaration.getKind() == VARIABLE) {
+ visitVariables(
+ variableFragments(it, bodyDeclaration),
+ DeclarationKind.FIELD,
+ fieldAnnotationDirection(((VariableTree) bodyDeclaration).getModifiers()));
+ } else {
+ scan(bodyDeclaration, null);
+ }
+ first = false;
+ lastOneGotBlankLineBefore = thisOneGetsBlankLineBefore;
+ }
+ dropEmptyDeclarations();
+ builder.forcedBreak();
+ builder.close();
+ builder.forcedBreak();
+ markForPartialFormat();
+ if (braces.isYes()) {
+ builder.blankLineWanted(BlankLineWanted.NO);
+ token("}", plusTwo);
+ builder.close();
+ }
+ }
+ }
+
+ /**
+ * The parser expands multi-variable declarations into separate single-variable declarations. All
+ * of the fragments in the original declaration have the same start position, so we use that as a
+ * signal to collect them and preserve the multi-variable declaration in the output.
+ *
+ * <p>e.g. {@code int x, y;} is parsed as {@code int x; int y;}.
+ */
+ private List<VariableTree> variableFragments(PeekingIterator<? extends Tree> it, Tree first) {
+ List<VariableTree> fragments = new ArrayList<>();
+ if (first.getKind() == VARIABLE) {
+ int start = getStartPosition(first);
+ fragments.add((VariableTree) first);
+ while (it.hasNext()
+ && it.peek().getKind() == VARIABLE
+ && getStartPosition(it.peek()) == start) {
+ fragments.add((VariableTree) it.next());
+ }
+ }
+ return fragments;
+ }
+
+ /** Does this declaration have javadoc preceding it? */
+ private boolean hasJavaDoc(Tree bodyDeclaration) {
+ int position = ((JCTree) bodyDeclaration).getStartPosition();
+ Input.Token token = builder.getInput().getPositionTokenMap().get(position);
+ if (token != null) {
+ for (Input.Tok tok : token.getToksBefore()) {
+ if (tok.getText().startsWith("/**")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static Optional<? extends Input.Token> getNextToken(Input input, int position) {
+ return Optional.ofNullable(input.getPositionTokenMap().get(position));
+ }
+
+ /** Does this list of trees end with the specified token? */
+ private boolean hasTrailingToken(Input input, List<? extends Tree> nodes, String token) {
+ if (nodes.isEmpty()) {
+ return false;
+ }
+ Tree lastNode = getLast(nodes);
+ Optional<? extends Input.Token> nextToken =
+ getNextToken(input, getEndPosition(lastNode, getCurrentPath()));
+ return nextToken.isPresent() && nextToken.get().getTok().getText().equals(token);
+ }
+
+ /**
+ * Can a local with a set of modifiers be declared with horizontal annotations? This is currently
+ * true if there is at most one parameterless annotation, and no others.
+ *
+ * @param modifiers the list of {@link ModifiersTree}s
+ * @return whether the local can be declared with horizontal annotations
+ */
+ private Direction canLocalHaveHorizontalAnnotations(ModifiersTree modifiers) {
+ int parameterlessAnnotations = 0;
+ for (AnnotationTree annotation : modifiers.getAnnotations()) {
+ if (annotation.getArguments().isEmpty()) {
+ parameterlessAnnotations++;
+ }
+ }
+ return parameterlessAnnotations <= 1
+ && parameterlessAnnotations == modifiers.getAnnotations().size()
+ ? Direction.HORIZONTAL
+ : Direction.VERTICAL;
+ }
+
+ /**
+ * Should a field with a set of modifiers be declared with horizontal annotations? This is
+ * currently true if all annotations are parameterless annotations.
+ */
+ private Direction fieldAnnotationDirection(ModifiersTree modifiers) {
+ for (AnnotationTree annotation : modifiers.getAnnotations()) {
+ if (!annotation.getArguments().isEmpty()) {
+ return Direction.VERTICAL;
+ }
+ }
+ return Direction.HORIZONTAL;
+ }
+
+ /**
+ * Emit a {@link Doc.Token}.
+ *
+ * @param token the {@link String} to wrap in a {@link Doc.Token}
+ */
+ protected final void token(String token) {
+ builder.token(
+ token,
+ Doc.Token.RealOrImaginary.REAL,
+ ZERO,
+ /* breakAndIndentTrailingComment= */ Optional.empty());
+ }
+
+ /**
+ * Emit a {@link Doc.Token}.
+ *
+ * @param token the {@link String} to wrap in a {@link Doc.Token}
+ * @param plusIndentCommentsBefore extra indent for comments before this token
+ */
+ protected final void token(String token, Indent plusIndentCommentsBefore) {
+ builder.token(
+ token,
+ Doc.Token.RealOrImaginary.REAL,
+ plusIndentCommentsBefore,
+ /* breakAndIndentTrailingComment= */ Optional.empty());
+ }
+
+ /** Emit a {@link Doc.Token}, and breaks and indents trailing javadoc or block comments. */
+ final void tokenBreakTrailingComment(String token, Indent breakAndIndentTrailingComment) {
+ builder.token(
+ token, Doc.Token.RealOrImaginary.REAL, ZERO, Optional.of(breakAndIndentTrailingComment));
+ }
+
+ protected void markForPartialFormat() {
+ if (!inExpression()) {
+ builder.markForPartialFormat();
+ }
+ }
+
+ /**
+ * Sync to position in the input. If we've skipped outputting any tokens that were present in the
+ * input tokens, output them here and complain.
+ *
+ * @param node the ASTNode holding the input position
+ */
+ protected final void sync(Tree node) {
+ builder.sync(((JCTree) node).getStartPosition());
+ }
+
+ final BreakTag genSym() {
+ return new BreakTag();
+ }
+
+ @Override
+ public final String toString() {
+ return MoreObjects.toStringHelper(this).add("builder", builder).toString();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java b/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java
new file mode 100644
index 0000000..c059318
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static java.util.Comparator.comparing;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+import com.google.googlejavaformat.CommentsHelper;
+import com.google.googlejavaformat.Input;
+import com.google.googlejavaformat.Input.Token;
+import com.google.googlejavaformat.Newlines;
+import com.google.googlejavaformat.OpsBuilder.BlankLineWanted;
+import com.google.googlejavaformat.Output;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/*
+ * Throughout this file, {@code i} is an index for input lines, {@code j} is an index for output
+ * lines, {@code ij} is an index into either input or output lines, and {@code k} is an index for
+ * toks.
+ */
+
+/**
+ * {@code JavaOutput} extends {@link Output Output} to represent a Java output document. It includes
+ * methods to emit the output document.
+ */
+public final class JavaOutput extends Output {
+ private final String lineSeparator;
+ private final Input javaInput; // Used to follow along while emitting the output.
+ private final CommentsHelper commentsHelper; // Used to re-flow comments.
+ private final Map<Integer, BlankLineWanted> blankLines = new HashMap<>(); // Info on blank lines.
+ private final RangeSet<Integer> partialFormatRanges = TreeRangeSet.create();
+
+ private final List<String> mutableLines = new ArrayList<>();
+ private final int kN; // The number of tokens or comments in the input, excluding the EOF.
+ private int iLine = 0; // Closest corresponding line number on input.
+ private int lastK = -1; // Last {@link Tok} index output.
+ private int newlinesPending = 0;
+ private StringBuilder lineBuilder = new StringBuilder();
+ private StringBuilder spacesPending = new StringBuilder();
+
+ /**
+ * {@code JavaOutput} constructor.
+ *
+ * @param javaInput the {@link Input}, used to match up blank lines in the output
+ * @param commentsHelper the {@link CommentsHelper}, used to rewrite comments
+ */
+ public JavaOutput(String lineSeparator, Input javaInput, CommentsHelper commentsHelper) {
+ this.lineSeparator = lineSeparator;
+ this.javaInput = javaInput;
+ this.commentsHelper = commentsHelper;
+ kN = javaInput.getkN();
+ }
+
+ @Override
+ public void blankLine(int k, BlankLineWanted wanted) {
+ if (blankLines.containsKey(k)) {
+ blankLines.put(k, blankLines.get(k).merge(wanted));
+ } else {
+ blankLines.put(k, wanted);
+ }
+ }
+
+ @Override
+ public void markForPartialFormat(Token start, Token end) {
+ int lo = JavaOutput.startTok(start).getIndex();
+ int hi = JavaOutput.endTok(end).getIndex();
+ partialFormatRanges.add(Range.closed(lo, hi));
+ }
+
+ // TODO(jdd): Add invariant.
+ @Override
+ public void append(String text, Range<Integer> range) {
+ if (!range.isEmpty()) {
+ boolean sawNewlines = false;
+ // Skip over input line we've passed.
+ int iN = javaInput.getLineCount();
+ while (iLine < iN
+ && (javaInput.getRanges(iLine).isEmpty()
+ || javaInput.getRanges(iLine).upperEndpoint() <= range.lowerEndpoint())) {
+ if (javaInput.getRanges(iLine).isEmpty()) {
+ // Skipped over a blank line.
+ sawNewlines = true;
+ }
+ ++iLine;
+ }
+ /*
+ * Output blank line if we've called {@link OpsBuilder#blankLine}{@code (true)} here, or if
+ * 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)) {
+ ++newlinesPending;
+ }
+ }
+ if (Newlines.isNewline(text)) {
+ /*
+ * Don't update range information, and swallow extra newlines. The case below for '\n' is for
+ * block comments.
+ */
+ if (newlinesPending == 0) {
+ ++newlinesPending;
+ }
+ spacesPending = new StringBuilder();
+ } else {
+ boolean rangesSet = false;
+ int textN = text.length();
+ for (int i = 0; i < textN; i++) {
+ char c = text.charAt(i);
+ switch (c) {
+ case ' ':
+ spacesPending.append(' ');
+ break;
+ case '\t':
+ spacesPending.append('\t');
+ break;
+ case '\r':
+ if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
+ i++;
+ }
+ // falls through
+ case '\n':
+ spacesPending = new StringBuilder();
+ ++newlinesPending;
+ break;
+ default:
+ while (newlinesPending > 0) {
+ // drop leading blank lines
+ if (!mutableLines.isEmpty() || lineBuilder.length() > 0) {
+ mutableLines.add(lineBuilder.toString());
+ }
+ lineBuilder = new StringBuilder();
+ rangesSet = false;
+ --newlinesPending;
+ }
+ if (spacesPending.length() > 0) {
+ lineBuilder.append(spacesPending);
+ spacesPending = new StringBuilder();
+ }
+ lineBuilder.append(c);
+ if (!range.isEmpty()) {
+ if (!rangesSet) {
+ while (ranges.size() <= mutableLines.size()) {
+ ranges.add(Formatter.EMPTY_RANGE);
+ }
+ ranges.set(mutableLines.size(), union(ranges.get(mutableLines.size()), range));
+ rangesSet = true;
+ }
+ }
+ }
+ }
+ }
+ if (!range.isEmpty()) {
+ lastK = range.upperEndpoint();
+ }
+ }
+
+ @Override
+ public void indent(int indent) {
+ spacesPending.append(Strings.repeat(" ", indent));
+ }
+
+ /** Flush any incomplete last line, then add the EOF token into our data structures. */
+ public void flush() {
+ String lastLine = lineBuilder.toString();
+ if (!CharMatcher.whitespace().matchesAllOf(lastLine)) {
+ mutableLines.add(lastLine);
+ }
+ int jN = mutableLines.size();
+ Range<Integer> eofRange = Range.closedOpen(kN, kN + 1);
+ while (ranges.size() < jN) {
+ ranges.add(Formatter.EMPTY_RANGE);
+ }
+ ranges.add(eofRange);
+ setLines(ImmutableList.copyOf(mutableLines));
+ }
+
+ // The following methods can be used after the Output has been built.
+
+ @Override
+ public CommentsHelper getCommentsHelper() {
+ return commentsHelper;
+ }
+
+ /**
+ * Emit a list of {@link Replacement}s to convert from input to output.
+ *
+ * @return a list of {@link Replacement}s, sorted by start index, without overlaps
+ */
+ public ImmutableList<Replacement> getFormatReplacements(RangeSet<Integer> iRangeSet0) {
+ ImmutableList.Builder<Replacement> result = ImmutableList.builder();
+ Map<Integer, Range<Integer>> kToJ = JavaOutput.makeKToIJ(this);
+
+ // Expand the token ranges to align with re-formattable boundaries.
+ RangeSet<Integer> breakableRanges = TreeRangeSet.create();
+ RangeSet<Integer> iRangeSet = iRangeSet0.subRangeSet(Range.closed(0, javaInput.getkN()));
+ for (Range<Integer> iRange : iRangeSet.asRanges()) {
+ Range<Integer> range = expandToBreakableRegions(iRange.canonical(DiscreteDomain.integers()));
+ if (range.equals(EMPTY_RANGE)) {
+ // the range contains only whitespace
+ continue;
+ }
+ breakableRanges.add(range);
+ }
+
+ // Construct replacements for each reformatted region.
+ for (Range<Integer> range : breakableRanges.asRanges()) {
+
+ Input.Tok startTok = startTok(javaInput.getToken(range.lowerEndpoint()));
+ Input.Tok endTok = endTok(javaInput.getToken(range.upperEndpoint() - 1));
+
+ // Add all output lines in the given token range to the replacement.
+ StringBuilder replacement = new StringBuilder();
+
+ int replaceFrom = startTok.getPosition();
+ // Replace leading whitespace in the input with the whitespace from the formatted file
+ while (replaceFrom > 0) {
+ char previous = javaInput.getText().charAt(replaceFrom - 1);
+ if (!CharMatcher.whitespace().matches(previous)) {
+ break;
+ }
+ replaceFrom--;
+ }
+
+ int i = kToJ.get(startTok.getIndex()).lowerEndpoint();
+ // Include leading blank lines from the formatted output, unless the formatted range
+ // starts at the beginning of the file.
+ while (i > 0 && getLine(i - 1).isEmpty()) {
+ i--;
+ }
+ // Write out the formatted range.
+ for (; i < kToJ.get(endTok.getIndex()).upperEndpoint(); i++) {
+ // It's possible to run out of output lines (e.g. if the input ended with
+ // multiple trailing newlines).
+ if (i < getLineCount()) {
+ if (i > 0) {
+ replacement.append(lineSeparator);
+ }
+ replacement.append(getLine(i));
+ }
+ }
+
+ int replaceTo =
+ Math.min(endTok.getPosition() + endTok.length(), javaInput.getText().length());
+ // If the formatted ranged ended in the trailing trivia of the last token before EOF,
+ // format all the way up to EOF to deal with trailing whitespace correctly.
+ if (endTok.getIndex() == javaInput.getkN() - 1) {
+ replaceTo = javaInput.getText().length();
+ }
+ // Replace trailing whitespace in the input with the whitespace from the formatted file.
+ // If the trailing whitespace in the input includes one or more line breaks, preserve the
+ // whitespace after the last newline to avoid re-indenting the line following the formatted
+ // line.
+ int newline = -1;
+ while (replaceTo < javaInput.getText().length()) {
+ char next = javaInput.getText().charAt(replaceTo);
+ if (!CharMatcher.whitespace().matches(next)) {
+ break;
+ }
+ int newlineLength = Newlines.hasNewlineAt(javaInput.getText(), replaceTo);
+ if (newlineLength != -1) {
+ newline = replaceTo;
+ // Skip over the entire newline; don't count the second character of \r\n as a newline.
+ replaceTo += newlineLength;
+ } else {
+ replaceTo++;
+ }
+ }
+ if (newline != -1) {
+ replaceTo = newline;
+ }
+
+ if (newline == -1) {
+ // There wasn't an existing trailing newline; add one.
+ replacement.append(lineSeparator);
+ }
+ for (; i < getLineCount(); i++) {
+ String after = getLine(i);
+ int idx = CharMatcher.whitespace().negate().indexIn(after);
+ if (idx == -1) {
+ // Write out trailing empty lines from the formatted output.
+ replacement.append(lineSeparator);
+ } else {
+ if (newline == -1) {
+ // If there wasn't a trailing newline in the input, indent the next line.
+ replacement.append(after.substring(0, idx));
+ }
+ break;
+ }
+ }
+
+ result.add(Replacement.create(replaceFrom, replaceTo, replacement.toString()));
+ }
+ return result.build();
+ }
+
+ /**
+ * Expand a token range to start and end on acceptable boundaries for re-formatting.
+ *
+ * @param iRange the {@link Range} of tokens
+ * @return the expanded token range
+ */
+ private Range<Integer> expandToBreakableRegions(Range<Integer> iRange) {
+ // The original line range.
+ int loTok = iRange.lowerEndpoint();
+ int hiTok = iRange.upperEndpoint() - 1;
+
+ // Expand the token indices to formattable boundaries (e.g. edges of statements).
+ if (!partialFormatRanges.contains(loTok) || !partialFormatRanges.contains(hiTok)) {
+ return EMPTY_RANGE;
+ }
+ loTok = partialFormatRanges.rangeContaining(loTok).lowerEndpoint();
+ hiTok = partialFormatRanges.rangeContaining(hiTok).upperEndpoint();
+ return Range.closedOpen(loTok, hiTok + 1);
+ }
+
+ public static String applyReplacements(String input, List<Replacement> replacements) {
+ replacements = new ArrayList<>(replacements);
+ replacements.sort(comparing((Replacement r) -> r.getReplaceRange().lowerEndpoint()).reversed());
+ StringBuilder writer = new StringBuilder(input);
+ for (Replacement replacement : replacements) {
+ writer.replace(
+ replacement.getReplaceRange().lowerEndpoint(),
+ replacement.getReplaceRange().upperEndpoint(),
+ replacement.getReplacementString());
+ }
+ return writer.toString();
+ }
+
+ /** The earliest position of any Tok in the Token, including leading whitespace. */
+ public static int startPosition(Token token) {
+ int min = token.getTok().getPosition();
+ for (Input.Tok tok : token.getToksBefore()) {
+ min = Math.min(min, tok.getPosition());
+ }
+ return min;
+ }
+
+ /** The earliest non-whitespace Tok in the Token. */
+ public static Input.Tok startTok(Token token) {
+ for (Input.Tok tok : token.getToksBefore()) {
+ if (tok.getIndex() >= 0) {
+ return tok;
+ }
+ }
+ return token.getTok();
+ }
+
+ /** The last non-whitespace Tok in the Token. */
+ public static Input.Tok endTok(Token token) {
+ for (int i = token.getToksAfter().size() - 1; i >= 0; i--) {
+ Input.Tok tok = token.getToksAfter().get(i);
+ if (tok.getIndex() >= 0) {
+ return tok;
+ }
+ }
+ return token.getTok();
+ }
+
+ private boolean isComment(String text) {
+ return text.startsWith("//") || text.startsWith("/*");
+ }
+
+ private static Range<Integer> union(Range<Integer> x, Range<Integer> y) {
+ return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(DiscreteDomain.integers());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iLine", iLine)
+ .add("lastK", lastK)
+ .add("spacesPending", spacesPending.toString().replace("\t", "\\t"))
+ .add("newlinesPending", newlinesPending)
+ .add("blankLines", blankLines)
+ .add("super", super.toString())
+ .toString();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavacTokens.java b/core/src/main/java/com/google/googlejavaformat/java/JavacTokens.java
new file mode 100644
index 0000000..a8c9efd
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/JavacTokens.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.sun.tools.javac.parser.JavaTokenizer;
+import com.sun.tools.javac.parser.Scanner;
+import com.sun.tools.javac.parser.ScannerFactory;
+import com.sun.tools.javac.parser.Tokens.Comment;
+import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
+import com.sun.tools.javac.parser.Tokens.Token;
+import com.sun.tools.javac.parser.Tokens.TokenKind;
+import com.sun.tools.javac.parser.UnicodeReader;
+import com.sun.tools.javac.util.Context;
+import java.util.Set;
+
+/** A wrapper around javac's lexer. */
+class JavacTokens {
+
+ /** The lexer eats terminal comments, so feed it one we don't care about. */
+ // TODO(b/33103797): fix javac and remove the work-around
+ private static final CharSequence EOF_COMMENT = "\n//EOF";
+
+ /** An unprocessed input token, including whitespace and comments. */
+ static class RawTok {
+ private final String stringVal;
+ private final TokenKind kind;
+ private final int pos;
+ private final int endPos;
+
+ RawTok(String stringVal, TokenKind kind, int pos, int endPos) {
+ this.stringVal = stringVal;
+ this.kind = kind;
+ this.pos = pos;
+ this.endPos = endPos;
+ }
+
+ /** The token kind, or {@code null} for whitespace and comments. */
+ public TokenKind kind() {
+ return kind;
+ }
+
+ /** The start position. */
+ public int pos() {
+ return pos;
+ }
+
+ /** The end position. */
+ public int endPos() {
+ return endPos;
+ }
+
+ /** The escaped string value of a literal, or {@code null} for other tokens. */
+ public String stringVal() {
+ return stringVal;
+ }
+ }
+
+ /** Lex the input and return a list of {@link RawTok}s. */
+ public static ImmutableList<RawTok> getTokens(
+ String source, Context context, Set<TokenKind> stopTokens) {
+ if (source == null) {
+ return ImmutableList.of();
+ }
+ ScannerFactory fac = ScannerFactory.instance(context);
+ char[] buffer = (source + EOF_COMMENT).toCharArray();
+ Scanner scanner =
+ new AccessibleScanner(fac, new CommentSavingTokenizer(fac, buffer, buffer.length));
+ ImmutableList.Builder<RawTok> tokens = ImmutableList.builder();
+ int end = source.length();
+ int last = 0;
+ do {
+ scanner.nextToken();
+ Token t = scanner.token();
+ if (t.comments != null) {
+ for (Comment c : Lists.reverse(t.comments)) {
+ if (last < c.getSourcePos(0)) {
+ tokens.add(new RawTok(null, null, last, c.getSourcePos(0)));
+ }
+ tokens.add(
+ new RawTok(null, null, c.getSourcePos(0), c.getSourcePos(0) + c.getText().length()));
+ last = c.getSourcePos(0) + c.getText().length();
+ }
+ }
+ if (stopTokens.contains(t.kind)) {
+ if (t.kind != TokenKind.EOF) {
+ end = t.pos;
+ }
+ break;
+ }
+ if (last < t.pos) {
+ tokens.add(new RawTok(null, null, last, t.pos));
+ }
+ tokens.add(
+ new RawTok(
+ t.kind == TokenKind.STRINGLITERAL ? "\"" + t.stringVal() + "\"" : null,
+ t.kind,
+ t.pos,
+ t.endPos));
+ last = t.endPos;
+ } while (scanner.token().kind != TokenKind.EOF);
+ if (last < end) {
+ tokens.add(new RawTok(null, null, last, end));
+ }
+ return tokens.build();
+ }
+
+ /** A {@link JavaTokenizer} that saves comments. */
+ static class CommentSavingTokenizer extends JavaTokenizer {
+ CommentSavingTokenizer(ScannerFactory fac, char[] buffer, int length) {
+ super(fac, buffer, length);
+ }
+
+ @Override
+ protected Comment processComment(int pos, int endPos, CommentStyle style) {
+ char[] buf = reader.getRawCharacters(pos, endPos);
+ return new CommentWithTextAndPosition(
+ pos, endPos, new AccessibleReader(fac, buf, buf.length), style);
+ }
+ }
+
+ /** A {@link Comment} that saves its text and start position. */
+ static class CommentWithTextAndPosition implements Comment {
+
+ private final int pos;
+ private final int endPos;
+ private final AccessibleReader reader;
+ private final CommentStyle style;
+
+ private String text = null;
+
+ public CommentWithTextAndPosition(
+ int pos, int endPos, AccessibleReader reader, CommentStyle style) {
+ this.pos = pos;
+ this.endPos = endPos;
+ this.reader = reader;
+ this.style = style;
+ }
+
+ /**
+ * Returns the source position of the character at index {@code index} in the comment text.
+ *
+ * <p>The handling of javadoc comments in javac has more logic to skip over leading whitespace
+ * and '*' characters when indexing into doc comments, but we don't need any of that.
+ */
+ @Override
+ public int getSourcePos(int index) {
+ checkArgument(
+ 0 <= index && index < (endPos - pos),
+ "Expected %s in the range [0, %s)",
+ index,
+ endPos - pos);
+ return pos + index;
+ }
+
+ @Override
+ public CommentStyle getStyle() {
+ return style;
+ }
+
+ @Override
+ public String getText() {
+ String text = this.text;
+ if (text == null) {
+ this.text = text = new String(reader.getRawCharacters());
+ }
+ return text;
+ }
+
+ /**
+ * We don't care about {@code @deprecated} javadoc tags (see the DepAnn check).
+ *
+ * @return false
+ */
+ @Override
+ public boolean isDeprecated() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Comment: '%s'", getText());
+ }
+ }
+
+ // Scanner(ScannerFactory, JavaTokenizer) is package-private
+ static class AccessibleScanner extends Scanner {
+ protected AccessibleScanner(ScannerFactory fac, JavaTokenizer tokenizer) {
+ super(fac, tokenizer);
+ }
+ }
+
+ // UnicodeReader(ScannerFactory, char[], int) is package-private
+ static class AccessibleReader extends UnicodeReader {
+ protected AccessibleReader(ScannerFactory fac, char[] buffer, int length) {
+ super(fac, buffer, length);
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/Main.java b/core/src/main/java/com/google/googlejavaformat/java/Main.java
new file mode 100644
index 0000000..9231bda
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/Main.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.io.ByteStreams;
+import com.google.googlejavaformat.FormatterDiagnostic;
+import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
+import java.io.IOError;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/** The main class for the Java formatter CLI. */
+public final class Main {
+ private static final int MAX_THREADS = 20;
+ private static final String STDIN_FILENAME = "<stdin>";
+
+ static final String versionString() {
+ return "google-java-format: Version " + GoogleJavaFormatVersion.version();
+ }
+
+ private final PrintWriter outWriter;
+ private final PrintWriter errWriter;
+ private final InputStream inStream;
+
+ public Main(PrintWriter outWriter, PrintWriter errWriter, InputStream inStream) {
+ this.outWriter = outWriter;
+ this.errWriter = errWriter;
+ this.inStream = inStream;
+ }
+
+ /**
+ * The main method for the formatter, with some number of file names to format. We process them in
+ * parallel, but we must be careful; if multiple file names refer to the same file (which is hard
+ * to determine), we must serialize their updates.
+ *
+ * @param args the command-line arguments
+ */
+ public static void main(String[] args) {
+ int result;
+ PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out, UTF_8));
+ PrintWriter err = new PrintWriter(new OutputStreamWriter(System.err, UTF_8));
+ try {
+ Main formatter = new Main(out, err, System.in);
+ result = formatter.format(args);
+ } catch (UsageException e) {
+ err.print(e.getMessage());
+ result = 0;
+ } finally {
+ err.flush();
+ out.flush();
+ }
+ System.exit(result);
+ }
+
+ /**
+ * The main entry point for the formatter, with some number of file names to format. We process
+ * them in parallel, but we must be careful; if multiple file names refer to the same file (which
+ * is hard to determine), we must serialize their update.
+ *
+ * @param args the command-line arguments
+ */
+ public int format(String... args) throws UsageException {
+ CommandLineOptions parameters = processArgs(args);
+ if (parameters.version()) {
+ errWriter.println(versionString());
+ return 0;
+ }
+ if (parameters.help()) {
+ throw new UsageException();
+ }
+
+ JavaFormatterOptions options =
+ JavaFormatterOptions.builder()
+ .style(parameters.aosp() ? Style.AOSP : Style.GOOGLE)
+ .formatJavadoc(parameters.formatJavadoc())
+ .build();
+
+ if (parameters.stdin()) {
+ return formatStdin(parameters, options);
+ } else {
+ return formatFiles(parameters, options);
+ }
+ }
+
+ private int formatFiles(CommandLineOptions parameters, JavaFormatterOptions options) {
+ int numThreads = Math.min(MAX_THREADS, parameters.files().size());
+ ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
+
+ Map<Path, String> inputs = new LinkedHashMap<>();
+ Map<Path, Future<String>> results = new LinkedHashMap<>();
+ boolean allOk = true;
+
+ for (String fileName : parameters.files()) {
+ if (!fileName.endsWith(".java")) {
+ errWriter.println("Skipping non-Java file: " + fileName);
+ continue;
+ }
+ Path path = Paths.get(fileName);
+ String input;
+ try {
+ input = new String(Files.readAllBytes(path), UTF_8);
+ inputs.put(path, input);
+ results.put(
+ path, executorService.submit(new FormatFileCallable(parameters, input, options)));
+ } catch (IOException e) {
+ errWriter.println(fileName + ": could not read file: " + e.getMessage());
+ allOk = false;
+ }
+ }
+
+ for (Map.Entry<Path, Future<String>> result : results.entrySet()) {
+ Path path = result.getKey();
+ String formatted;
+ try {
+ formatted = result.getValue().get();
+ } catch (InterruptedException e) {
+ errWriter.println(e.getMessage());
+ allOk = false;
+ continue;
+ } catch (ExecutionException e) {
+ if (e.getCause() instanceof FormatterException) {
+ for (FormatterDiagnostic diagnostic : ((FormatterException) e.getCause()).diagnostics()) {
+ errWriter.println(path + ":" + diagnostic.toString());
+ }
+ } else {
+ errWriter.println(path + ": error: " + e.getCause().getMessage());
+ e.getCause().printStackTrace(errWriter);
+ }
+ allOk = false;
+ continue;
+ }
+ boolean changed = !formatted.equals(inputs.get(path));
+ if (changed && parameters.setExitIfChanged()) {
+ allOk = false;
+ }
+ if (parameters.inPlace()) {
+ if (!changed) {
+ continue; // preserve original file
+ }
+ try {
+ Files.write(path, formatted.getBytes(UTF_8));
+ } catch (IOException e) {
+ errWriter.println(path + ": could not write file: " + e.getMessage());
+ allOk = false;
+ continue;
+ }
+ } else if (parameters.dryRun()) {
+ if (changed) {
+ outWriter.println(path);
+ }
+ } else {
+ outWriter.write(formatted);
+ }
+ }
+ return allOk ? 0 : 1;
+ }
+
+ private int formatStdin(CommandLineOptions parameters, JavaFormatterOptions options) {
+ String input;
+ try {
+ input = new String(ByteStreams.toByteArray(inStream), UTF_8);
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ String stdinFilename = parameters.assumeFilename().orElse(STDIN_FILENAME);
+ boolean ok = true;
+ try {
+ String output = new FormatFileCallable(parameters, input, options).call();
+ boolean changed = !input.equals(output);
+ if (changed && parameters.setExitIfChanged()) {
+ ok = false;
+ }
+ if (parameters.dryRun()) {
+ if (changed) {
+ outWriter.println(stdinFilename);
+ }
+ } else {
+ outWriter.write(output);
+ }
+ } catch (FormatterException e) {
+ for (FormatterDiagnostic diagnostic : e.diagnostics()) {
+ errWriter.println(stdinFilename + ":" + diagnostic.toString());
+ }
+ ok = false;
+ // TODO(cpovirk): Catch other types of exception (as we do in the formatFiles case).
+ }
+ return ok ? 0 : 1;
+ }
+
+ /** Parses and validates command-line flags. */
+ public static CommandLineOptions processArgs(String... args) throws UsageException {
+ CommandLineOptions parameters;
+ try {
+ parameters = CommandLineOptionsParser.parse(Arrays.asList(args));
+ } catch (IllegalArgumentException e) {
+ throw new UsageException(e.getMessage());
+ } catch (Throwable t) {
+ t.printStackTrace();
+ throw new UsageException(t.getMessage());
+ }
+ int filesToFormat = parameters.files().size();
+ if (parameters.stdin()) {
+ filesToFormat++;
+ }
+
+ if (parameters.inPlace() && parameters.files().isEmpty()) {
+ throw new UsageException("in-place formatting was requested but no files were provided");
+ }
+ if (parameters.isSelection() && filesToFormat != 1) {
+ throw new UsageException("partial formatting is only support for a single file");
+ }
+ if (parameters.offsets().size() != parameters.lengths().size()) {
+ throw new UsageException("-offsets and -lengths flags must be provided in matching pairs");
+ }
+ if (filesToFormat <= 0 && !parameters.version() && !parameters.help()) {
+ throw new UsageException("no files were provided");
+ }
+ if (parameters.stdin() && !parameters.files().isEmpty()) {
+ throw new UsageException("cannot format from standard input and files simultaneously");
+ }
+ if (parameters.assumeFilename().isPresent() && !parameters.stdin()) {
+ throw new UsageException(
+ "--assume-filename is only supported when formatting standard input");
+ }
+ if (parameters.dryRun() && parameters.inPlace()) {
+ throw new UsageException("cannot use --dry-run and --in-place at the same time");
+ }
+ return parameters;
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/ModifierOrderer.java b/core/src/main/java/com/google/googlejavaformat/java/ModifierOrderer.java
new file mode 100644
index 0000000..f7f610b
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/ModifierOrderer.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeMap;
+import com.google.googlejavaformat.Input.Tok;
+import com.google.googlejavaformat.Input.Token;
+import com.sun.tools.javac.parser.Tokens.TokenKind;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.lang.model.element.Modifier;
+
+/** Fixes sequences of modifiers to be in JLS order. */
+final class ModifierOrderer {
+
+ /**
+ * Returns the {@link javax.lang.model.element.Modifier} for the given token kind, or {@code
+ * null}.
+ */
+ private static Modifier getModifier(TokenKind kind) {
+ if (kind == null) {
+ return null;
+ }
+ switch (kind) {
+ case PUBLIC:
+ return Modifier.PUBLIC;
+ case PROTECTED:
+ return Modifier.PROTECTED;
+ case PRIVATE:
+ return Modifier.PRIVATE;
+ case ABSTRACT:
+ return Modifier.ABSTRACT;
+ case STATIC:
+ return Modifier.STATIC;
+ case DEFAULT:
+ return Modifier.DEFAULT;
+ case FINAL:
+ return Modifier.FINAL;
+ case TRANSIENT:
+ return Modifier.TRANSIENT;
+ case VOLATILE:
+ return Modifier.VOLATILE;
+ case SYNCHRONIZED:
+ return Modifier.SYNCHRONIZED;
+ case NATIVE:
+ return Modifier.NATIVE;
+ case STRICTFP:
+ return Modifier.STRICTFP;
+ default:
+ return null;
+ }
+ }
+
+ /** Reorders all modifiers in the given text to be in JLS order. */
+ static JavaInput reorderModifiers(String text) throws FormatterException {
+ return reorderModifiers(
+ new JavaInput(text), ImmutableList.of(Range.closedOpen(0, text.length())));
+ }
+
+ /**
+ * Reorders all modifiers in the given text and within the given character ranges to be in JLS
+ * order.
+ */
+ static JavaInput reorderModifiers(JavaInput javaInput, Collection<Range<Integer>> characterRanges)
+ throws FormatterException {
+ if (javaInput.getTokens().isEmpty()) {
+ // There weren't any tokens, possible because of a lexing error.
+ // Errors about invalid input will be reported later after parsing.
+ return javaInput;
+ }
+ RangeSet<Integer> tokenRanges = javaInput.characterRangesToTokenRanges(characterRanges);
+ Iterator<? extends Token> it = javaInput.getTokens().iterator();
+ TreeRangeMap<Integer, String> replacements = TreeRangeMap.create();
+ while (it.hasNext()) {
+ Token token = it.next();
+ if (!tokenRanges.contains(token.getTok().getIndex())) {
+ continue;
+ }
+ Modifier mod = asModifier(token);
+ if (mod == null) {
+ continue;
+ }
+
+ List<Token> modifierTokens = new ArrayList<>();
+ List<Modifier> mods = new ArrayList<>();
+
+ int begin = token.getTok().getPosition();
+ mods.add(mod);
+ modifierTokens.add(token);
+
+ int end = -1;
+ while (it.hasNext()) {
+ token = it.next();
+ mod = asModifier(token);
+ if (mod == null) {
+ break;
+ }
+ mods.add(mod);
+ modifierTokens.add(token);
+ end = token.getTok().getPosition() + token.getTok().length();
+ }
+
+ if (!Ordering.natural().isOrdered(mods)) {
+ Collections.sort(mods);
+ StringBuilder replacement = new StringBuilder();
+ for (int i = 0; i < mods.size(); i++) {
+ if (i > 0) {
+ addTrivia(replacement, modifierTokens.get(i).getToksBefore());
+ }
+ replacement.append(mods.get(i).toString());
+ if (i < (modifierTokens.size() - 1)) {
+ addTrivia(replacement, modifierTokens.get(i).getToksAfter());
+ }
+ }
+ replacements.put(Range.closedOpen(begin, end), replacement.toString());
+ }
+ }
+ return applyReplacements(javaInput, replacements);
+ }
+
+ private static void addTrivia(StringBuilder replacement, ImmutableList<? extends Tok> toks) {
+ for (Tok tok : toks) {
+ replacement.append(tok.getText());
+ }
+ }
+
+ /**
+ * Returns the given token as a {@link javax.lang.model.element.Modifier}, or {@code null} if it
+ * is not a modifier.
+ */
+ private static Modifier asModifier(Token token) {
+ return getModifier(((JavaInput.Tok) token.getTok()).kind());
+ }
+
+ /** Applies replacements to the given string. */
+ private static JavaInput applyReplacements(
+ JavaInput javaInput, TreeRangeMap<Integer, String> replacementMap) throws FormatterException {
+ // process in descending order so the replacement ranges aren't perturbed if any replacements
+ // differ in size from the input
+ Map<Range<Integer>, String> ranges = replacementMap.asDescendingMapOfRanges();
+ if (ranges.isEmpty()) {
+ return javaInput;
+ }
+ StringBuilder sb = new StringBuilder(javaInput.getText());
+ for (Entry<Range<Integer>, String> entry : ranges.entrySet()) {
+ Range<Integer> range = entry.getKey();
+ sb.replace(range.lowerEndpoint(), range.upperEndpoint(), entry.getValue());
+ }
+ return new JavaInput(sb.toString());
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java b/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java
new file mode 100644
index 0000000..d939480
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java
@@ -0,0 +1,321 @@
+/*
+ * 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.java;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeMap;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeMap;
+import com.google.common.collect.TreeRangeSet;
+import com.google.googlejavaformat.Newlines;
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.ReferenceTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.DocTreePath;
+import com.sun.source.util.DocTreePathScanner;
+import com.sun.source.util.TreePathScanner;
+import com.sun.source.util.TreeScanner;
+import com.sun.tools.javac.api.JavacTrees;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.parser.JavacParser;
+import com.sun.tools.javac.parser.ParserFactory;
+import com.sun.tools.javac.tree.DCTree;
+import com.sun.tools.javac.tree.DCTree.DCReference;
+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;
+import com.sun.tools.javac.util.Options;
+import java.io.IOError;
+import java.io.IOException;
+import java.net.URI;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardLocation;
+
+/**
+ * Removes unused imports from a source file. Imports that are only used in javadoc are also
+ * removed, and the references in javadoc are replaced with fully qualified names.
+ */
+public class RemoveUnusedImports {
+
+ // Visits an AST, recording all simple names that could refer to imported
+ // types and also any javadoc references that could refer to imported
+ // types (`@link`, `@see`, `@throws`, etc.)
+ //
+ // No attempt is made to determine whether simple names occur in contexts
+ // where they are type names, so there will be false positives. For example,
+ // `List` is not identified as unused import below:
+ //
+ // ```
+ // import java.util.List;
+ // class List {}
+ // ```
+ //
+ // This is still reasonably effective in practice because type names differ
+ // from other kinds of names in casing convention, and simple name
+ // clashes between imported and declared types are rare.
+ private static class UnusedImportScanner extends TreePathScanner<Void, Void> {
+
+ private final Set<String> usedNames = new LinkedHashSet<>();
+ private final Multimap<String, Range<Integer>> usedInJavadoc = HashMultimap.create();
+ final JavacTrees trees;
+ final DocTreeScanner docTreeSymbolScanner;
+
+ private UnusedImportScanner(JavacTrees trees) {
+ this.trees = trees;
+ docTreeSymbolScanner = new DocTreeScanner();
+ }
+
+ /** Skip the imports themselves when checking for usage. */
+ @Override
+ public Void visitImport(ImportTree importTree, Void usedSymbols) {
+ return null;
+ }
+
+ @Override
+ public Void visitIdentifier(IdentifierTree tree, Void unused) {
+ if (tree == null) {
+ return null;
+ }
+ usedNames.add(tree.getName().toString());
+ return null;
+ }
+
+ @Override
+ public Void scan(Tree tree, Void unused) {
+ if (tree == null) {
+ return null;
+ }
+ scanJavadoc();
+ return super.scan(tree, unused);
+ }
+
+ private void scanJavadoc() {
+ if (getCurrentPath() == null) {
+ return;
+ }
+ DocCommentTree commentTree = trees.getDocCommentTree(getCurrentPath());
+ if (commentTree == null) {
+ return;
+ }
+ docTreeSymbolScanner.scan(new DocTreePath(getCurrentPath(), commentTree), null);
+ }
+
+ // scan javadoc comments, checking for references to imported types
+ class DocTreeScanner extends DocTreePathScanner<Void, Void> {
+ @Override
+ public Void visitIdentifier(com.sun.source.doctree.IdentifierTree node, Void aVoid) {
+ return null;
+ }
+
+ @Override
+ public Void visitReference(ReferenceTree referenceTree, Void unused) {
+ DCReference reference = (DCReference) referenceTree;
+ long basePos =
+ reference.getSourcePosition((DCTree.DCDocComment) getCurrentPath().getDocComment());
+ // the position of trees inside the reference node aren't stored, but the qualifier's
+ // start position is the beginning of the reference node
+ if (reference.qualifierExpression != null) {
+ new ReferenceScanner(basePos).scan(reference.qualifierExpression, null);
+ }
+ // Record uses inside method parameters. The javadoc tool doesn't use these, but
+ // IntelliJ does.
+ if (reference.paramTypes != null) {
+ for (JCTree param : reference.paramTypes) {
+ // TODO(cushon): get start positions for the parameters
+ new ReferenceScanner(-1).scan(param, null);
+ }
+ }
+ return null;
+ }
+
+ // scans the qualifier and parameters of a javadoc reference for possible type names
+ private class ReferenceScanner extends TreeScanner<Void, Void> {
+ private final long basePos;
+
+ public ReferenceScanner(long basePos) {
+ this.basePos = basePos;
+ }
+
+ @Override
+ public Void visitIdentifier(IdentifierTree node, Void aVoid) {
+ usedInJavadoc.put(
+ node.getName().toString(),
+ basePos != -1
+ ? Range.closedOpen((int) basePos, (int) basePos + node.getName().length())
+ : null);
+ return super.visitIdentifier(node, aVoid);
+ }
+ }
+ }
+ }
+
+ public static String removeUnusedImports(final String contents) throws FormatterException {
+ Context context = new Context();
+ JCCompilationUnit unit = parse(context, contents);
+ if (unit == null) {
+ // error handling is done during formatting
+ return contents;
+ }
+ UnusedImportScanner scanner = new UnusedImportScanner(JavacTrees.instance(context));
+ scanner.scan(unit, null);
+ return applyReplacements(
+ contents, buildReplacements(contents, unit, scanner.usedNames, scanner.usedInJavadoc));
+ }
+
+ private static JCCompilationUnit parse(Context context, String javaInput)
+ throws FormatterException {
+ DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
+ context.put(DiagnosticListener.class, diagnostics);
+ Options.instance(context).put("--enable-preview", "true");
+ Options.instance(context).put("allowStringFolding", "false");
+ JCCompilationUnit unit;
+ JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8);
+ try {
+ fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of());
+ } catch (IOException e) {
+ // impossible
+ throw new IOError(e);
+ }
+ SimpleJavaFileObject source =
+ new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) {
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return javaInput;
+ }
+ };
+ Log.instance(context).useSource(source);
+ ParserFactory parserFactory = ParserFactory.instance(context);
+ JavacParser parser =
+ parserFactory.newParser(
+ javaInput, /*keepDocComments=*/ true, /*keepEndPos=*/ true, /*keepLineMap=*/ true);
+ unit = parser.parseCompilationUnit();
+ unit.sourcefile = source;
+ Iterable<Diagnostic<? extends JavaFileObject>> errorDiagnostics =
+ Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic);
+ if (!Iterables.isEmpty(errorDiagnostics)) {
+ // error handling is done during formatting
+ throw FormatterException.fromJavacDiagnostics(errorDiagnostics);
+ }
+ return unit;
+ }
+
+ /** Construct replacements to fix unused imports. */
+ private static RangeMap<Integer, String> buildReplacements(
+ String contents,
+ JCCompilationUnit unit,
+ Set<String> usedNames,
+ Multimap<String, Range<Integer>> usedInJavadoc) {
+ RangeMap<Integer, String> replacements = TreeRangeMap.create();
+ for (JCImport importTree : unit.getImports()) {
+ String simpleName = getSimpleName(importTree);
+ if (!isUnused(unit, usedNames, usedInJavadoc, importTree, simpleName)) {
+ continue;
+ }
+ // delete the import
+ int endPosition = importTree.getEndPosition(unit.endPositions);
+ endPosition = Math.max(CharMatcher.isNot(' ').indexIn(contents, endPosition), endPosition);
+ String sep = Newlines.guessLineSeparator(contents);
+ if (endPosition + sep.length() < contents.length()
+ && contents.subSequence(endPosition, endPosition + sep.length()).toString().equals(sep)) {
+ endPosition += sep.length();
+ }
+ replacements.put(Range.closedOpen(importTree.getStartPosition(), endPosition), "");
+ }
+ return replacements;
+ }
+
+ private static String getSimpleName(JCImport importTree) {
+ return importTree.getQualifiedIdentifier() instanceof JCIdent
+ ? ((JCIdent) importTree.getQualifiedIdentifier()).getName().toString()
+ : ((JCFieldAccess) importTree.getQualifiedIdentifier()).getIdentifier().toString();
+ }
+
+ private static boolean isUnused(
+ JCCompilationUnit unit,
+ Set<String> usedNames,
+ Multimap<String, Range<Integer>> usedInJavadoc,
+ JCImport importTree,
+ String simpleName) {
+ String qualifier =
+ ((JCFieldAccess) importTree.getQualifiedIdentifier()).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("*")) {
+ return false;
+ }
+
+ if (usedNames.contains(simpleName)) {
+ return false;
+ }
+ if (usedInJavadoc.containsKey(simpleName)) {
+ return false;
+ }
+ return true;
+ }
+
+ /** Applies the replacements to the given source, and re-format any edited javadoc. */
+ private static String applyReplacements(String source, RangeMap<Integer, String> replacements) {
+ // save non-empty fixed ranges for reformatting after fixes are applied
+ RangeSet<Integer> fixedRanges = TreeRangeSet.create();
+
+ // Apply the fixes in increasing order, adjusting ranges to account for
+ // earlier fixes that change the length of the source. The output ranges are
+ // needed so we can reformat fixed regions, otherwise the fixes could just
+ // be applied in descending order without adjusting offsets.
+ StringBuilder sb = new StringBuilder(source);
+ int offset = 0;
+ for (Map.Entry<Range<Integer>, String> replacement : replacements.asMapOfRanges().entrySet()) {
+ Range<Integer> range = replacement.getKey();
+ String replaceWith = replacement.getValue();
+ int start = offset + range.lowerEndpoint();
+ int end = offset + range.upperEndpoint();
+ sb.replace(start, end, replaceWith);
+ if (!replaceWith.isEmpty()) {
+ fixedRanges.add(Range.closedOpen(start, end));
+ }
+ offset += replaceWith.length() - (range.upperEndpoint() - range.lowerEndpoint());
+ }
+ return sb.toString();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/Replacement.java b/core/src/main/java/com/google/googlejavaformat/java/Replacement.java
new file mode 100644
index 0000000..5df0991
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/Replacement.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014 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.java;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.Range;
+import java.util.Objects;
+
+/**
+ * Represents a range in the original source and replacement text for that range.
+ *
+ * <p>google-java-format doesn't depend on AutoValue, to allow AutoValue to depend on
+ * google-java-format.
+ */
+public final class Replacement {
+
+ public static Replacement create(int startPosition, int endPosition, String replaceWith) {
+ checkArgument(startPosition >= 0, "startPosition must be non-negative");
+ checkArgument(startPosition <= endPosition, "startPosition cannot be after endPosition");
+ return new Replacement(Range.closedOpen(startPosition, endPosition), replaceWith);
+ }
+
+ private final Range<Integer> replaceRange;
+ private final String replacementString;
+
+ private Replacement(Range<Integer> replaceRange, String replacementString) {
+ this.replaceRange = checkNotNull(replaceRange, "Null replaceRange");
+ this.replacementString = checkNotNull(replacementString, "Null replacementString");
+ }
+
+ /** The range of characters in the original source to replace. */
+ public Range<Integer> getReplaceRange() {
+ return replaceRange;
+ }
+
+ /** The string to replace the range of characters with. */
+ public String getReplacementString() {
+ return replacementString;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof Replacement) {
+ Replacement that = (Replacement) o;
+ return replaceRange.equals(that.getReplaceRange())
+ && replacementString.equals(that.getReplacementString());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(replaceRange, replacementString);
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/SnippetFormatter.java b/core/src/main/java/com/google/googlejavaformat/java/SnippetFormatter.java
new file mode 100644
index 0000000..8d426b6
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/SnippetFormatter.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Formats a subset of a compilation unit. */
+public class SnippetFormatter {
+
+ /** The kind of snippet to format. */
+ public enum SnippetKind {
+ COMPILATION_UNIT,
+ CLASS_BODY_DECLARATIONS,
+ STATEMENTS,
+ EXPRESSION
+ }
+
+ private class SnippetWrapper {
+ int offset;
+ final StringBuilder contents = new StringBuilder();
+
+ public SnippetWrapper append(String str) {
+ contents.append(str);
+ return this;
+ }
+
+ public SnippetWrapper appendSource(String source) {
+ this.offset = contents.length();
+ contents.append(source);
+ return this;
+ }
+
+ public void closeBraces(int initialIndent) {
+ for (int i = initialIndent; --i >= 0; ) {
+ contents.append("\n").append(createIndentationString(i)).append("}");
+ }
+ }
+ }
+
+ private static final int INDENTATION_SIZE = 2;
+ private final Formatter formatter = new Formatter();
+ private static final CharMatcher NOT_WHITESPACE = CharMatcher.whitespace().negate();
+
+ public String createIndentationString(int indentationLevel) {
+ Preconditions.checkArgument(
+ indentationLevel >= 0,
+ "Indentation level cannot be less than zero. Given: %s",
+ indentationLevel);
+ int spaces = indentationLevel * INDENTATION_SIZE;
+ StringBuilder buf = new StringBuilder(spaces);
+ for (int i = 0; i < spaces; i++) {
+ buf.append(' ');
+ }
+ return buf.toString();
+ }
+
+ private static Range<Integer> offsetRange(Range<Integer> range, int offset) {
+ range = range.canonical(DiscreteDomain.integers());
+ return Range.closedOpen(range.lowerEndpoint() + offset, range.upperEndpoint() + offset);
+ }
+
+ private static List<Range<Integer>> offsetRanges(List<Range<Integer>> ranges, int offset) {
+ List<Range<Integer>> result = new ArrayList<>();
+ for (Range<Integer> range : ranges) {
+ result.add(offsetRange(range, offset));
+ }
+ return result;
+ }
+
+ /** Runs the Google Java formatter on the given source, with only the given ranges specified. */
+ public ImmutableList<Replacement> format(
+ SnippetKind kind,
+ String source,
+ List<Range<Integer>> ranges,
+ int initialIndent,
+ boolean includeComments)
+ throws FormatterException {
+ RangeSet<Integer> rangeSet = TreeRangeSet.create();
+ for (Range<Integer> range : ranges) {
+ rangeSet.add(range);
+ }
+ if (includeComments) {
+ if (kind != SnippetKind.COMPILATION_UNIT) {
+ throw new IllegalArgumentException(
+ "comment formatting is only supported for compilation units");
+ }
+ return formatter.getFormatReplacements(source, ranges);
+ }
+ SnippetWrapper wrapper = snippetWrapper(kind, source, initialIndent);
+ ranges = offsetRanges(ranges, wrapper.offset);
+
+ String replacement = formatter.formatSource(wrapper.contents.toString(), ranges);
+ replacement =
+ replacement.substring(
+ wrapper.offset,
+ replacement.length() - (wrapper.contents.length() - wrapper.offset - source.length()));
+
+ return toReplacements(source, replacement).stream()
+ .filter(r -> rangeSet.encloses(r.getReplaceRange()))
+ .collect(toImmutableList());
+ }
+
+ /**
+ * Generates {@code Replacement}s rewriting {@code source} to {@code replacement}, under the
+ * assumption that they differ in whitespace alone.
+ */
+ private static List<Replacement> toReplacements(String source, String replacement) {
+ if (!NOT_WHITESPACE.retainFrom(source).equals(NOT_WHITESPACE.retainFrom(replacement))) {
+ throw new IllegalArgumentException(
+ "source = \"" + source + "\", replacement = \"" + replacement + "\"");
+ }
+ /*
+ * In the past we seemed to have problems touching non-whitespace text in the formatter, even
+ * just replacing some code with itself. Retrospective attempts to reproduce this have failed,
+ * but this may be an issue for future changes.
+ */
+ List<Replacement> replacements = new ArrayList<>();
+ int i = NOT_WHITESPACE.indexIn(source);
+ int j = NOT_WHITESPACE.indexIn(replacement);
+ if (i != 0 || j != 0) {
+ replacements.add(Replacement.create(0, i, replacement.substring(0, j)));
+ }
+ while (i != -1 && j != -1) {
+ int i2 = NOT_WHITESPACE.indexIn(source, i + 1);
+ int j2 = NOT_WHITESPACE.indexIn(replacement, j + 1);
+ if (i2 == -1 || j2 == -1) {
+ break;
+ }
+ if ((i2 - i) != (j2 - j)
+ || !source.substring(i + 1, i2).equals(replacement.substring(j + 1, j2))) {
+ replacements.add(Replacement.create(i + 1, i2, replacement.substring(j + 1, j2)));
+ }
+ i = i2;
+ j = j2;
+ }
+ return replacements;
+ }
+
+ private SnippetWrapper snippetWrapper(SnippetKind kind, String source, int initialIndent) {
+ /*
+ * Synthesize a dummy class around the code snippet provided by Eclipse. The dummy class is
+ * correctly formatted -- the blocks use correct indentation, etc.
+ */
+ switch (kind) {
+ case COMPILATION_UNIT:
+ {
+ SnippetWrapper wrapper = new SnippetWrapper();
+ for (int i = 1; i <= initialIndent; i++) {
+ wrapper.append("class Dummy {\n").append(createIndentationString(i));
+ }
+ wrapper.appendSource(source);
+ wrapper.closeBraces(initialIndent);
+ return wrapper;
+ }
+ case CLASS_BODY_DECLARATIONS:
+ {
+ SnippetWrapper wrapper = new SnippetWrapper();
+ for (int i = 1; i <= initialIndent; i++) {
+ wrapper.append("class Dummy {\n").append(createIndentationString(i));
+ }
+ wrapper.appendSource(source);
+ wrapper.closeBraces(initialIndent);
+ return wrapper;
+ }
+ case STATEMENTS:
+ {
+ SnippetWrapper wrapper = new SnippetWrapper();
+ wrapper.append("class Dummy {\n").append(createIndentationString(1));
+ for (int i = 2; i <= initialIndent; i++) {
+ wrapper.append("{\n").append(createIndentationString(i));
+ }
+ wrapper.appendSource(source);
+ wrapper.closeBraces(initialIndent);
+ return wrapper;
+ }
+ case EXPRESSION:
+ {
+ SnippetWrapper wrapper = new SnippetWrapper();
+ wrapper.append("class Dummy {\n").append(createIndentationString(1));
+ for (int i = 2; i <= initialIndent; i++) {
+ wrapper.append("{\n").append(createIndentationString(i));
+ }
+ wrapper.append("Object o = ");
+ wrapper.appendSource(source);
+ wrapper.append(";");
+ wrapper.closeBraces(initialIndent);
+ return wrapper;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown snippet kind: " + kind);
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/StringWrapper.java b/core/src/main/java/com/google/googlejavaformat/java/StringWrapper.java
new file mode 100644
index 0000000..e41bb66
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/StringWrapper.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.collect.Iterables.getLast;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.joining;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Strings;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Range;
+import com.google.common.collect.TreeRangeMap;
+import com.google.googlejavaformat.Newlines;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.parser.JavacParser;
+import com.sun.tools.javac.parser.ParserFactory;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Log;
+import com.sun.tools.javac.util.Options;
+import com.sun.tools.javac.util.Position;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardLocation;
+
+/** Wraps string literals that exceed the column limit. */
+public final class StringWrapper {
+ /** Reflows long string literals in the given Java source code. */
+ public static String wrap(String input, Formatter formatter) throws FormatterException {
+ return StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, input, formatter);
+ }
+
+ /**
+ * Reflows string literals in the given Java source code that extend past the given column limit.
+ */
+ static String wrap(final int columnLimit, String input, Formatter formatter)
+ throws FormatterException {
+ if (!longLines(columnLimit, input)) {
+ // fast path
+ return input;
+ }
+
+ TreeRangeMap<Integer, String> replacements = getReflowReplacements(columnLimit, input);
+ String firstPass = formatter.formatSource(input, replacements.asMapOfRanges().keySet());
+
+ if (!firstPass.equals(input)) {
+ // If formatting the replacement ranges resulted in a change, recalculate the replacements on
+ // the updated input.
+ input = firstPass;
+ replacements = getReflowReplacements(columnLimit, input);
+ }
+
+ String result = applyReplacements(input, replacements);
+
+ {
+ // We really don't want bugs in this pass to change the behaviour of programs we're
+ // formatting, so check that the pretty-printed AST is the same before and after reformatting.
+ String expected = parse(input, /* allowStringFolding= */ true).toString();
+ String actual = parse(result, /* allowStringFolding= */ true).toString();
+ if (!expected.equals(actual)) {
+ throw new FormatterException(
+ String.format(
+ "Something has gone terribly wrong. Please file a bug: "
+ + "https://github.com/google/google-java-format/issues/new"
+ + "\n\n=== Actual: ===\n%s\n=== Expected: ===\n%s\n",
+ actual, expected));
+ }
+ }
+
+ return result;
+ }
+
+ private static TreeRangeMap<Integer, String> getReflowReplacements(
+ int columnLimit, final String input) throws FormatterException {
+ JCTree.JCCompilationUnit unit = parse(input, /* allowStringFolding= */ false);
+ String separator = Newlines.guessLineSeparator(input);
+
+ // Paths to string literals that extend past the column limit.
+ List<TreePath> toFix = new ArrayList<>();
+ final Position.LineMap lineMap = unit.getLineMap();
+ new TreePathScanner<Void, Void>() {
+ @Override
+ public Void visitLiteral(LiteralTree literalTree, Void aVoid) {
+ if (literalTree.getKind() != Kind.STRING_LITERAL) {
+ return null;
+ }
+ Tree parent = getCurrentPath().getParentPath().getLeaf();
+ if (parent instanceof MemberSelectTree
+ && ((MemberSelectTree) parent).getExpression().equals(literalTree)) {
+ return null;
+ }
+ int endPosition = getEndPosition(unit, literalTree);
+ int lineEnd = endPosition;
+ while (Newlines.hasNewlineAt(input, lineEnd) == -1) {
+ lineEnd++;
+ }
+ if (lineMap.getColumnNumber(lineEnd) - 1 <= columnLimit) {
+ return null;
+ }
+ toFix.add(getCurrentPath());
+ return null;
+ }
+ }.scan(new TreePath(unit), null);
+
+ TreeRangeMap<Integer, String> replacements = TreeRangeMap.create();
+ for (TreePath path : toFix) {
+ // Find the outermost contiguous enclosing concatenation expression
+ TreePath enclosing = path;
+ while (enclosing.getParentPath().getLeaf().getKind() == Tree.Kind.PLUS) {
+ enclosing = enclosing.getParentPath();
+ }
+ // Is the literal being wrapped the first in a chain of concatenation expressions?
+ // i.e. `ONE + TWO + THREE`
+ // We need this information to handle continuation indents.
+ AtomicBoolean first = new AtomicBoolean(false);
+ // Finds the set of string literals in the concat expression that includes the one that needs
+ // to be wrapped.
+ List<Tree> flat = flatten(input, unit, path, enclosing, first);
+ // Zero-indexed start column
+ int startColumn = lineMap.getColumnNumber(getStartPosition(flat.get(0))) - 1;
+
+ // Handling leaving trailing non-string tokens at the end of the literal,
+ // e.g. the trailing `);` in `foo("...");`.
+ int end = getEndPosition(unit, getLast(flat));
+ int lineEnd = end;
+ while (Newlines.hasNewlineAt(input, lineEnd) == -1) {
+ lineEnd++;
+ }
+ int trailing = lineEnd - end;
+
+ // Get the original source text of the string literals, excluding `"` and `+`.
+ ImmutableList<String> components = stringComponents(input, unit, flat);
+ replacements.put(
+ Range.closedOpen(getStartPosition(flat.get(0)), getEndPosition(unit, getLast(flat))),
+ reflow(separator, columnLimit, startColumn, trailing, components, first.get()));
+ }
+ return replacements;
+ }
+
+ /**
+ * Returns the source text of the given string literal trees, excluding the leading and trailing
+ * double-quotes and the `+` operator.
+ */
+ private static ImmutableList<String> stringComponents(
+ String input, JCTree.JCCompilationUnit unit, List<Tree> flat) {
+ ImmutableList.Builder<String> result = ImmutableList.builder();
+ StringBuilder piece = new StringBuilder();
+ for (Tree tree : flat) {
+ // adjust for leading and trailing double quotes
+ String text = input.substring(getStartPosition(tree) + 1, getEndPosition(unit, tree) - 1);
+ int start = 0;
+ for (int idx = 0; idx < text.length(); idx++) {
+ if (CharMatcher.whitespace().matches(text.charAt(idx))) {
+ // continue below
+ } else if (hasEscapedWhitespaceAt(text, idx) != -1) {
+ // continue below
+ } else if (hasEscapedNewlineAt(text, idx) != -1) {
+ int length;
+ while ((length = hasEscapedNewlineAt(text, idx)) != -1) {
+ idx += length;
+ }
+ } else {
+ continue;
+ }
+ piece.append(text, start, idx);
+ result.add(piece.toString());
+ piece = new StringBuilder();
+ start = idx;
+ }
+ if (piece.length() > 0) {
+ result.add(piece.toString());
+ piece = new StringBuilder();
+ }
+ if (start < text.length()) {
+ piece.append(text, start, text.length());
+ }
+ }
+ if (piece.length() > 0) {
+ result.add(piece.toString());
+ }
+ return result.build();
+ }
+
+ static int hasEscapedWhitespaceAt(String input, int idx) {
+ return Stream.of("\\t")
+ .mapToInt(x -> input.startsWith(x, idx) ? x.length() : -1)
+ .filter(x -> x != -1)
+ .findFirst()
+ .orElse(-1);
+ }
+
+ static int hasEscapedNewlineAt(String input, int idx) {
+ return Stream.of("\\r\\n", "\\r", "\\n")
+ .mapToInt(x -> input.startsWith(x, idx) ? x.length() : -1)
+ .filter(x -> x != -1)
+ .findFirst()
+ .orElse(-1);
+ }
+
+ /**
+ * Reflows the given source text, trying to split on word boundaries.
+ *
+ * @param separator the line separator
+ * @param columnLimit the number of columns to wrap at
+ * @param startColumn the column position of the beginning of the original text
+ * @param trailing extra space to leave after the last line
+ * @param components the text to reflow
+ * @param first0 true if the text includes the beginning of its enclosing concat chain, i.e. a
+ */
+ private static String reflow(
+ String separator,
+ int columnLimit,
+ int startColumn,
+ int trailing,
+ ImmutableList<String> components,
+ boolean first0) {
+ // We have space between the start column and the limit to output the first line.
+ // Reserve two spaces for the quotes.
+ int width = columnLimit - startColumn - 2;
+ Deque<String> input = new ArrayDeque<>(components);
+ List<String> lines = new ArrayList<>();
+ boolean first = first0;
+ while (!input.isEmpty()) {
+ int length = 0;
+ List<String> line = new ArrayList<>();
+ if (input.stream().mapToInt(x -> x.length()).sum() <= width) {
+ width -= trailing;
+ }
+ while (!input.isEmpty() && (length <= 4 || (length + input.peekFirst().length()) < width)) {
+ String text = input.removeFirst();
+ line.add(text);
+ length += text.length();
+ if (text.endsWith("\\n") || text.endsWith("\\r")) {
+ break;
+ }
+ }
+ if (line.isEmpty()) {
+ line.add(input.removeFirst());
+ }
+ // add the split line to the output, and process whatever's left
+ lines.add(String.join("", line));
+ if (first) {
+ width -= 6; // subsequent lines have a four-space continuation indent and a `+ `
+ first = false;
+ }
+ }
+
+ return lines.stream()
+ .collect(
+ joining(
+ "\"" + separator + Strings.repeat(" ", startColumn + (first0 ? 4 : -2)) + "+ \"",
+ "\"",
+ "\""));
+ }
+
+ /**
+ * Flattens the given binary expression tree, and extracts the subset that contains the given path
+ * and any adjacent nodes that are also string literals.
+ */
+ private static List<Tree> flatten(
+ String input,
+ JCTree.JCCompilationUnit unit,
+ TreePath path,
+ TreePath parent,
+ AtomicBoolean firstInChain) {
+ List<Tree> flat = new ArrayList<>();
+
+ // flatten the expression tree with a pre-order traversal
+ ArrayDeque<Tree> todo = new ArrayDeque<>();
+ todo.add(parent.getLeaf());
+ while (!todo.isEmpty()) {
+ Tree first = todo.removeFirst();
+ if (first.getKind() == Tree.Kind.PLUS) {
+ BinaryTree bt = (BinaryTree) first;
+ todo.addFirst(bt.getRightOperand());
+ todo.addFirst(bt.getLeftOperand());
+ } else {
+ flat.add(first);
+ }
+ }
+
+ int idx = flat.indexOf(path.getLeaf());
+ Verify.verify(idx != -1);
+
+ // walk outwards from the leaf for adjacent string literals to also reflow
+ int startIdx = idx;
+ int endIdx = idx + 1;
+ while (startIdx > 0
+ && flat.get(startIdx - 1).getKind() == Tree.Kind.STRING_LITERAL
+ && noComments(input, unit, flat.get(startIdx - 1), flat.get(startIdx))) {
+ startIdx--;
+ }
+ while (endIdx < flat.size()
+ && flat.get(endIdx).getKind() == Tree.Kind.STRING_LITERAL
+ && noComments(input, unit, flat.get(endIdx - 1), flat.get(endIdx))) {
+ endIdx++;
+ }
+
+ firstInChain.set(startIdx == 0);
+ return ImmutableList.copyOf(flat.subList(startIdx, endIdx));
+ }
+
+ private static boolean noComments(
+ String input, JCTree.JCCompilationUnit unit, Tree one, Tree two) {
+ return STRING_CONCAT_DELIMITER.matchesAllOf(
+ input.subSequence(getEndPosition(unit, one), getStartPosition(two)));
+ }
+
+ public static final CharMatcher STRING_CONCAT_DELIMITER =
+ CharMatcher.whitespace().or(CharMatcher.anyOf("\"+"));
+
+ private static int getEndPosition(JCTree.JCCompilationUnit unit, Tree tree) {
+ return ((JCTree) tree).getEndPosition(unit.endPositions);
+ }
+
+ private static int getStartPosition(Tree tree) {
+ return ((JCTree) tree).getStartPosition();
+ }
+
+ /** Returns true if any lines in the given Java source exceed the column limit. */
+ private static boolean longLines(int columnLimit, String input) {
+ // TODO(cushon): consider adding Newlines.lineIterable?
+ Iterator<String> it = Newlines.lineIterator(input);
+ while (it.hasNext()) {
+ String line = it.next();
+ if (line.length() > columnLimit) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Parses the given Java source. */
+ private static JCTree.JCCompilationUnit parse(String source, boolean allowStringFolding)
+ throws FormatterException {
+ DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
+ Context context = new Context();
+ context.put(DiagnosticListener.class, diagnostics);
+ Options.instance(context).put("--enable-preview", "true");
+ Options.instance(context).put("allowStringFolding", Boolean.toString(allowStringFolding));
+ JCTree.JCCompilationUnit unit;
+ JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8);
+ try {
+ fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ SimpleJavaFileObject sjfo =
+ new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) {
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ return source;
+ }
+ };
+ Log.instance(context).useSource(sjfo);
+ ParserFactory parserFactory = ParserFactory.instance(context);
+ JavacParser parser =
+ parserFactory.newParser(
+ source, /*keepDocComments=*/ true, /*keepEndPos=*/ true, /*keepLineMap=*/ true);
+ unit = parser.parseCompilationUnit();
+ unit.sourcefile = sjfo;
+ Iterable<Diagnostic<? extends JavaFileObject>> errorDiagnostics =
+ Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic);
+ if (!Iterables.isEmpty(errorDiagnostics)) {
+ // error handling is done during formatting
+ throw FormatterException.fromJavacDiagnostics(errorDiagnostics);
+ }
+ return unit;
+ }
+
+ /** Applies replacements to the given string. */
+ private static String applyReplacements(
+ String javaInput, TreeRangeMap<Integer, String> replacementMap) throws FormatterException {
+ // process in descending order so the replacement ranges aren't perturbed if any replacements
+ // differ in size from the input
+ Map<Range<Integer>, String> ranges = replacementMap.asDescendingMapOfRanges();
+ if (ranges.isEmpty()) {
+ return javaInput;
+ }
+ StringBuilder sb = new StringBuilder(javaInput);
+ for (Map.Entry<Range<Integer>, String> entry : ranges.entrySet()) {
+ Range<Integer> range = entry.getKey();
+ sb.replace(range.lowerEndpoint(), range.upperEndpoint(), entry.getValue());
+ }
+ return sb.toString();
+ }
+
+ private StringWrapper() {}
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/Trees.java b/core/src/main/java/com/google/googlejavaformat/java/Trees.java
new file mode 100644
index 0000000..397daca
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/Trees.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.Pretty;
+import com.sun.tools.javac.tree.TreeInfo;
+import java.io.IOError;
+import java.io.IOException;
+import javax.lang.model.element.Name;
+
+/** Utilities for working with {@link Tree}s. */
+class Trees {
+ /** Returns the length of the source for the node. */
+ static int getLength(Tree tree, TreePath path) {
+ return getEndPosition(tree, path) - getStartPosition(tree);
+ }
+
+ /** Returns the source start position of the node. */
+ static int getStartPosition(Tree expression) {
+ return ((JCTree) expression).getStartPosition();
+ }
+
+ /** Returns the source end position of the node. */
+ static int getEndPosition(Tree expression, TreePath path) {
+ return ((JCTree) expression)
+ .getEndPosition(((JCTree.JCCompilationUnit) path.getCompilationUnit()).endPositions);
+ }
+
+ /** Returns the source text for the node. */
+ static String getSourceForNode(Tree node, TreePath path) {
+ CharSequence source;
+ try {
+ source = path.getCompilationUnit().getSourceFile().getCharContent(false);
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ return source.subSequence(getStartPosition(node), getEndPosition(node, path)).toString();
+ }
+
+ /** Returns the simple name of a (possibly qualified) method invocation expression. */
+ static Name getMethodName(MethodInvocationTree methodInvocation) {
+ ExpressionTree select = methodInvocation.getMethodSelect();
+ return select instanceof MemberSelectTree
+ ? ((MemberSelectTree) select).getIdentifier()
+ : ((IdentifierTree) select).getName();
+ }
+
+ /** Returns the receiver of a qualified method invocation expression, or {@code null}. */
+ static ExpressionTree getMethodReceiver(MethodInvocationTree methodInvocation) {
+ ExpressionTree select = methodInvocation.getMethodSelect();
+ return select instanceof MemberSelectTree ? ((MemberSelectTree) select).getExpression() : null;
+ }
+
+ /** Returns the string name of an operator, including assignment and compound assignment. */
+ static String operatorName(ExpressionTree expression) {
+ JCTree.Tag tag = ((JCTree) expression).getTag();
+ if (tag == JCTree.Tag.ASSIGN) {
+ return "=";
+ }
+ boolean assignOp = expression instanceof CompoundAssignmentTree;
+ if (assignOp) {
+ tag = tag.noAssignOp();
+ }
+ String name = new Pretty(/*writer*/ null, /*sourceOutput*/ true).operatorName(tag);
+ return assignOp ? name + "=" : name;
+ }
+
+ /** Returns the precedence of an expression's operator. */
+ static int precedence(ExpressionTree expression) {
+ return TreeInfo.opPrec(((JCTree) expression).getTag());
+ }
+
+ /**
+ * Returns the enclosing type declaration (class, enum, interface, or annotation) for the given
+ * path.
+ */
+ static ClassTree getEnclosingTypeDeclaration(TreePath path) {
+ for (; path != null; path = path.getParentPath()) {
+ switch (path.getLeaf().getKind()) {
+ case CLASS:
+ case ENUM:
+ case INTERFACE:
+ case ANNOTATED_TYPE:
+ return (ClassTree) path.getLeaf();
+ default:
+ break;
+ }
+ }
+ throw new AssertionError();
+ }
+
+ /** Skips a single parenthesized tree. */
+ static ExpressionTree skipParen(ExpressionTree node) {
+ return ((ParenthesizedTree) node).getExpression();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/TypeNameClassifier.java b/core/src/main/java/com/google/googlejavaformat/java/TypeNameClassifier.java
new file mode 100644
index 0000000..4e871a6
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/TypeNameClassifier.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import com.google.common.base.Verify;
+import java.util.List;
+import java.util.Optional;
+
+/** Heuristics for classifying qualified names as types. */
+public final class TypeNameClassifier {
+
+ private TypeNameClassifier() {}
+
+ /** A state machine for classifying qualified names. */
+ private enum TyParseState {
+
+ /** The start state. */
+ START(false) {
+ @Override
+ public TyParseState next(JavaCaseFormat n) {
+ switch (n) {
+ case UPPERCASE:
+ // if we see an UpperCamel later, assume this was a class
+ // e.g. com.google.FOO.Bar
+ return TyParseState.AMBIGUOUS;
+ case LOWER_CAMEL:
+ return TyParseState.REJECT;
+ case LOWERCASE:
+ // could be a package
+ return TyParseState.START;
+ case UPPER_CAMEL:
+ return TyParseState.TYPE;
+ }
+ throw new AssertionError();
+ }
+ },
+
+ /** The current prefix is a type. */
+ TYPE(true) {
+ @Override
+ public TyParseState next(JavaCaseFormat n) {
+ switch (n) {
+ case UPPERCASE:
+ case LOWER_CAMEL:
+ case LOWERCASE:
+ return TyParseState.FIRST_STATIC_MEMBER;
+ case UPPER_CAMEL:
+ return TyParseState.TYPE;
+ }
+ throw new AssertionError();
+ }
+ },
+
+ /** The current prefix is a type, followed by a single static member access. */
+ FIRST_STATIC_MEMBER(true) {
+ @Override
+ public TyParseState next(JavaCaseFormat n) {
+ return TyParseState.REJECT;
+ }
+ },
+
+ /** Anything not represented by one of the other states. */
+ REJECT(false) {
+ @Override
+ public TyParseState next(JavaCaseFormat n) {
+ return TyParseState.REJECT;
+ }
+ },
+
+ /** An ambiguous type prefix. */
+ AMBIGUOUS(false) {
+ @Override
+ public TyParseState next(JavaCaseFormat n) {
+ switch (n) {
+ case UPPERCASE:
+ return AMBIGUOUS;
+ case LOWER_CAMEL:
+ case LOWERCASE:
+ return TyParseState.REJECT;
+ case UPPER_CAMEL:
+ return TyParseState.TYPE;
+ }
+ throw new AssertionError();
+ }
+ };
+
+ private final boolean isSingleUnit;
+
+ TyParseState(boolean isSingleUnit) {
+ this.isSingleUnit = isSingleUnit;
+ }
+
+ public boolean isSingleUnit() {
+ return isSingleUnit;
+ }
+
+ /** Transition function. */
+ public abstract TyParseState next(JavaCaseFormat n);
+ }
+
+ /**
+ * Returns the end index (inclusive) of the longest prefix that matches the naming conventions of
+ * a type or static field access, or -1 if no such prefix was found.
+ *
+ * <p>Examples:
+ *
+ * <ul>
+ * <li>ClassName
+ * <li>ClassName.staticMemberName
+ * <li>com.google.ClassName.InnerClass.staticMemberName
+ * </ul>
+ */
+ static Optional<Integer> typePrefixLength(List<String> nameParts) {
+ TyParseState state = TyParseState.START;
+ Optional<Integer> typeLength = Optional.empty();
+ for (int i = 0; i < nameParts.size(); i++) {
+ state = state.next(JavaCaseFormat.from(nameParts.get(i)));
+ if (state == TyParseState.REJECT) {
+ break;
+ }
+ if (state.isSingleUnit()) {
+ typeLength = Optional.of(i);
+ }
+ }
+ return typeLength;
+ }
+
+ /** Case formats used in Java identifiers. */
+ public enum JavaCaseFormat {
+ UPPERCASE,
+ LOWERCASE,
+ UPPER_CAMEL,
+ LOWER_CAMEL;
+
+ /** Classifies an identifier's case format. */
+ static JavaCaseFormat from(String name) {
+ Verify.verify(!name.isEmpty());
+ boolean firstUppercase = false;
+ boolean hasUppercase = false;
+ boolean hasLowercase = false;
+ boolean first = true;
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+ if (!Character.isAlphabetic(c)) {
+ continue;
+ }
+ if (first) {
+ firstUppercase = Character.isUpperCase(c);
+ first = false;
+ }
+ hasUppercase |= Character.isUpperCase(c);
+ hasLowercase |= Character.isLowerCase(c);
+ }
+ if (firstUppercase) {
+ return hasLowercase ? UPPER_CAMEL : UPPERCASE;
+ } else {
+ return hasUppercase ? LOWER_CAMEL : LOWERCASE;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/UsageException.java b/core/src/main/java/com/google/googlejavaformat/java/UsageException.java
new file mode 100644
index 0000000..82c0843
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/UsageException.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Joiner;
+
+/** Checked exception class for formatter command-line usage errors. */
+final class UsageException extends Exception {
+
+ private static final Joiner NEWLINE_JOINER = Joiner.on(System.lineSeparator());
+
+ private static final String[] DOCS_LINK = {
+ "https://github.com/google/google-java-format",
+ };
+
+ private static final String[] USAGE = {
+ "",
+ "Usage: google-java-format [options] file(s)",
+ "",
+ "Options:",
+ " -i, -r, -replace, --replace",
+ " Send formatted output back to files, not stdout.",
+ " -",
+ " Format stdin -> stdout",
+ " --assume-filename, -assume-filename",
+ " File name to use for diagnostics when formatting standard input (default is <stdin>).",
+ " --aosp, -aosp, -a",
+ " Use AOSP style instead of Google Style (4-space indentation).",
+ " --fix-imports-only",
+ " Fix import order and remove any unused imports, but do no other formatting.",
+ " --skip-sorting-imports",
+ " Do not fix the import order. Unused imports will still be removed.",
+ " --skip-removing-unused-imports",
+ " Do not remove unused imports. Imports will still be sorted.",
+ " . --skip-reflowing-long-strings",
+ " Do not reflow string literals that exceed the column limit.",
+ " . --skip-javadoc-formatting",
+ " Do not reformat javadoc.",
+ " --dry-run, -n",
+ " Prints the paths of the files whose contents would change if the formatter were run"
+ + " normally.",
+ " --set-exit-if-changed",
+ " Return exit code 1 if there are any formatting changes.",
+ " --lines, -lines, --line, -line",
+ " Line range(s) to format, like 5:10 (1-based; default is all).",
+ " --offset, -offset",
+ " Character offset to format (0-based; default is all).",
+ " --length, -length",
+ " Character length to format.",
+ " --help, -help, -h",
+ " Print this usage statement.",
+ " --version, -version, -v",
+ " Print the version.",
+ " @<filename>",
+ " Read options and filenames from file.",
+ "",
+ };
+
+ private static final String[] ADDITIONAL_USAGE = {
+ "If -i is given with -, the result is sent to stdout.",
+ "The --lines, --offset, and --length flags may be given more than once.",
+ "The --offset and --length flags must be given an equal number of times.",
+ "If --lines, --offset, or --length are given, only one file (or -) may be given."
+ };
+
+ UsageException() {
+ super(buildMessage(null));
+ }
+
+ UsageException(String message) {
+ super(buildMessage(checkNotNull(message)));
+ }
+
+ private static String buildMessage(String message) {
+ StringBuilder builder = new StringBuilder();
+ if (message != null) {
+ builder.append(message).append('\n');
+ }
+ appendLines(builder, USAGE);
+ appendLines(builder, ADDITIONAL_USAGE);
+ appendLines(builder, new String[] {""});
+ appendLine(builder, Main.versionString());
+ appendLines(builder, DOCS_LINK);
+ return builder.toString();
+ }
+
+ private static void appendLine(StringBuilder builder, String line) {
+ builder.append(line).append(System.lineSeparator());
+ }
+
+ private static void appendLines(StringBuilder builder, String[] lines) {
+ NEWLINE_JOINER.appendTo(builder, lines).append(System.lineSeparator());
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/filer/FormattingFiler.java b/core/src/main/java/com/google/googlejavaformat/java/filer/FormattingFiler.java
new file mode 100644
index 0000000..d38d84e
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/filer/FormattingFiler.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java.filer;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.googlejavaformat.java.Formatter;
+import java.io.IOException;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.Messager;
+import javax.lang.model.element.Element;
+import javax.tools.FileObject;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A decorating {@link Filer} implementation which formats Java source files with a {@link
+ * Formatter}.
+ */
+public final class FormattingFiler implements Filer {
+
+ private final Filer delegate;
+ // TODO(ronshapiro): consider allowing users to create their own Formatter instance
+ private final Formatter formatter = new Formatter();
+ private final Messager messager;
+
+ /** @param delegate filer to decorate */
+ public FormattingFiler(Filer delegate) {
+ this(delegate, null);
+ }
+
+ /**
+ * Create a new {@link FormattingFiler}. An optional {@link Messager} may be specified to make
+ * logs more visible.
+ *
+ * @param delegate filer to decorate
+ * @param messager to log warnings to
+ */
+ public FormattingFiler(Filer delegate, @Nullable Messager messager) {
+ this.delegate = checkNotNull(delegate);
+ this.messager = messager;
+ }
+
+ @Override
+ public JavaFileObject createSourceFile(CharSequence name, Element... originatingElements)
+ throws IOException {
+ return new FormattingJavaFileObject(
+ delegate.createSourceFile(name, originatingElements), formatter, messager);
+ }
+
+ @Override
+ public JavaFileObject createClassFile(CharSequence name, Element... originatingElements)
+ throws IOException {
+ return delegate.createClassFile(name, originatingElements);
+ }
+
+ @Override
+ public FileObject createResource(
+ JavaFileManager.Location location,
+ CharSequence pkg,
+ CharSequence relativeName,
+ Element... originatingElements)
+ throws IOException {
+ return delegate.createResource(location, pkg, relativeName, originatingElements);
+ }
+
+ @Override
+ public FileObject getResource(
+ JavaFileManager.Location location, CharSequence pkg, CharSequence relativeName)
+ throws IOException {
+ return delegate.getResource(location, pkg, relativeName);
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/filer/FormattingJavaFileObject.java b/core/src/main/java/com/google/googlejavaformat/java/filer/FormattingJavaFileObject.java
new file mode 100644
index 0000000..c8ae807
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/filer/FormattingJavaFileObject.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java.filer;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.io.CharSink;
+import com.google.common.io.CharSource;
+import com.google.googlejavaformat.java.Formatter;
+import com.google.googlejavaformat.java.FormatterException;
+import java.io.IOException;
+import java.io.Writer;
+import javax.annotation.processing.Messager;
+import javax.tools.Diagnostic;
+import javax.tools.ForwardingJavaFileObject;
+import javax.tools.JavaFileObject;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** A {@link JavaFileObject} decorator which {@linkplain Formatter formats} source code. */
+final class FormattingJavaFileObject extends ForwardingJavaFileObject<JavaFileObject> {
+ /** A rough estimate of the average file size: 80 chars per line, 500 lines. */
+ private static final int DEFAULT_FILE_SIZE = 80 * 500;
+
+ private final Formatter formatter;
+ private final Messager messager;
+
+ /**
+ * Create a new {@link FormattingJavaFileObject}.
+ *
+ * @param delegate {@link JavaFileObject} to decorate
+ * @param messager to log messages with.
+ */
+ FormattingJavaFileObject(
+ JavaFileObject delegate, Formatter formatter, @Nullable Messager messager) {
+ super(checkNotNull(delegate));
+ this.formatter = checkNotNull(formatter);
+ this.messager = messager;
+ }
+
+ @Override
+ public Writer openWriter() throws IOException {
+ final StringBuilder stringBuilder = new StringBuilder(DEFAULT_FILE_SIZE);
+ return new Writer() {
+ @Override
+ public void write(char[] chars, int start, int end) throws IOException {
+ stringBuilder.append(chars, start, end - start);
+ }
+
+ @Override
+ public void write(String string) throws IOException {
+ stringBuilder.append(string);
+ }
+
+ @Override
+ public void flush() throws IOException {}
+
+ @Override
+ public void close() throws IOException {
+ try {
+ formatter.formatSource(
+ CharSource.wrap(stringBuilder),
+ new CharSink() {
+ @Override
+ public Writer openStream() throws IOException {
+ return fileObject.openWriter();
+ }
+ });
+ } catch (FormatterException e) {
+ // An exception will happen when the code being formatted has an error. It's better to
+ // log the exception and emit unformatted code so the developer can view the code which
+ // caused a problem.
+ try (Writer writer = fileObject.openWriter()) {
+ writer.append(stringBuilder.toString());
+ }
+ if (messager != null) {
+ messager.printMessage(Diagnostic.Kind.NOTE, "Error formatting " + getName());
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java
new file mode 100644
index 0000000..28a1103
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * 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.java.java14;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.MoreCollectors.toOptional;
+
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.googlejavaformat.Op;
+import com.google.googlejavaformat.OpsBuilder;
+import com.google.googlejavaformat.java.JavaInputAstVisitor;
+import com.sun.source.tree.BindingPatternTree;
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.SwitchExpressionTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.YieldTree;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.TreeInfo;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Extends {@link JavaInputAstVisitor} with support for AST nodes that were added or modified for
+ * Java 14.
+ */
+public class Java14InputAstVisitor extends JavaInputAstVisitor {
+
+ public Java14InputAstVisitor(OpsBuilder builder, int indentMultiplier) {
+ super(builder, indentMultiplier);
+ }
+
+ @Override
+ public Void visitBindingPattern(BindingPatternTree node, Void unused) {
+ sync(node);
+ scan(node.getType(), null);
+ builder.breakOp(" ");
+ visit(node.getBinding());
+ return null;
+ }
+
+ @Override
+ public Void visitYield(YieldTree node, Void aVoid) {
+ sync(node);
+ return super.visitYield(node, aVoid);
+ }
+
+ @Override
+ public Void visitSwitchExpression(SwitchExpressionTree node, Void aVoid) {
+ sync(node);
+ visitSwitch(node.getExpression(), node.getCases());
+ return null;
+ }
+
+ @Override
+ public Void visitClass(ClassTree tree, Void unused) {
+ switch (tree.getKind()) {
+ case ANNOTATION_TYPE:
+ visitAnnotationType(tree);
+ break;
+ case CLASS:
+ case INTERFACE:
+ visitClassDeclaration(tree);
+ break;
+ case ENUM:
+ visitEnumDeclaration(tree);
+ break;
+ case RECORD:
+ visitRecordDeclaration(tree);
+ break;
+ default:
+ throw new AssertionError(tree.getKind());
+ }
+ return null;
+ }
+
+ public void visitRecordDeclaration(ClassTree node) {
+ sync(node);
+ List<Op> breaks =
+ visitModifiers(
+ node.getModifiers(),
+ Direction.VERTICAL,
+ /* declarationAnnotationBreak= */ Optional.empty());
+ Verify.verify(node.getExtendsClause() == null);
+ boolean hasSuperInterfaceTypes = !node.getImplementsClause().isEmpty();
+ builder.addAll(breaks);
+ token("record");
+ builder.space();
+ visit(node.getSimpleName());
+ if (!node.getTypeParameters().isEmpty()) {
+ token("<");
+ }
+ builder.open(plusFour);
+ {
+ if (!node.getTypeParameters().isEmpty()) {
+ typeParametersRest(node.getTypeParameters(), hasSuperInterfaceTypes ? plusFour : ZERO);
+ }
+ ImmutableList<JCVariableDecl> parameters =
+ compactRecordConstructor(node)
+ .map(m -> ImmutableList.copyOf(m.getParameters()))
+ .orElseGet(() -> recordVariables(node));
+ token("(");
+ if (!parameters.isEmpty()) {
+ // Break before args.
+ builder.breakToFill("");
+ }
+ // record headers can't declare receiver parameters
+ visitFormals(/* receiver= */ Optional.empty(), parameters);
+ token(")");
+ if (hasSuperInterfaceTypes) {
+ builder.breakToFill(" ");
+ builder.open(node.getImplementsClause().size() > 1 ? plusFour : ZERO);
+ token("implements");
+ builder.space();
+ boolean first = true;
+ for (Tree superInterfaceType : node.getImplementsClause()) {
+ if (!first) {
+ token(",");
+ builder.breakOp(" ");
+ }
+ scan(superInterfaceType, null);
+ first = false;
+ }
+ builder.close();
+ }
+ }
+ builder.close();
+ if (node.getMembers() == null) {
+ token(";");
+ } else {
+ List<Tree> members =
+ node.getMembers().stream()
+ .filter(t -> (TreeInfo.flags((JCTree) t) & Flags.GENERATED_MEMBER) == 0)
+ .collect(toImmutableList());
+ addBodyDeclarations(members, BracesOrNot.YES, FirstDeclarationsOrNot.YES);
+ }
+ dropEmptyDeclarations();
+ }
+
+ private static Optional<JCMethodDecl> compactRecordConstructor(ClassTree node) {
+ return node.getMembers().stream()
+ .filter(JCMethodDecl.class::isInstance)
+ .map(JCMethodDecl.class::cast)
+ .filter(m -> (m.mods.flags & COMPACT_RECORD_CONSTRUCTOR) == COMPACT_RECORD_CONSTRUCTOR)
+ .collect(toOptional());
+ }
+
+ private static ImmutableList<JCVariableDecl> recordVariables(ClassTree node) {
+ return node.getMembers().stream()
+ .filter(JCVariableDecl.class::isInstance)
+ .map(JCVariableDecl.class::cast)
+ .filter(m -> (m.mods.flags & RECORD) == RECORD)
+ .collect(toImmutableList());
+ }
+
+ @Override
+ public Void visitInstanceOf(InstanceOfTree node, Void unused) {
+ sync(node);
+ builder.open(plusFour);
+ scan(node.getExpression(), null);
+ builder.breakOp(" ");
+ builder.open(ZERO);
+ token("instanceof");
+ builder.breakOp(" ");
+ if (node.getPattern() != null) {
+ scan(node.getPattern(), null);
+ } else {
+ scan(node.getType(), null);
+ }
+ builder.close();
+ builder.close();
+ return null;
+ }
+
+ @Override
+ public Void visitCase(CaseTree node, Void unused) {
+ sync(node);
+ markForPartialFormat();
+ builder.forcedBreak();
+ if (node.getExpressions().isEmpty()) {
+ token("default", plusTwo);
+ } else {
+ token("case", plusTwo);
+ builder.space();
+ boolean first = true;
+ for (ExpressionTree expression : node.getExpressions()) {
+ if (!first) {
+ token(",");
+ builder.space();
+ }
+ scan(expression, null);
+ first = false;
+ }
+ }
+ switch (node.getCaseKind()) {
+ case STATEMENT:
+ token(":");
+ builder.open(plusTwo);
+ visitStatements(node.getStatements());
+ builder.close();
+ break;
+ case RULE:
+ builder.space();
+ token("-");
+ token(">");
+ builder.space();
+ scan(node.getBody(), null);
+ token(";");
+ break;
+ default:
+ throw new AssertionError(node.getCaseKind());
+ }
+ return null;
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/javadoc/CharStream.java b/core/src/main/java/com/google/googlejavaformat/java/javadoc/CharStream.java
new file mode 100644
index 0000000..8fbd49f
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/javadoc/CharStream.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java.javadoc;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * String reader designed for use from the lexer. Callers invoke the {@link #tryConsume tryConsume*}
+ * methods to specify what characters they expect and then {@link #readAndResetRecorded} to retrieve
+ * and consume the matched characters. This is a slightly odd API -- why not just return the matched
+ * characters from tryConsume? -- but it is convenient for the lexer.
+ */
+final class CharStream {
+ String remaining;
+ int toConsume;
+
+ CharStream(String input) {
+ this.remaining = checkNotNull(input);
+ }
+
+ boolean tryConsume(String expected) {
+ if (!remaining.startsWith(expected)) {
+ return false;
+ }
+ toConsume = expected.length();
+ return true;
+ }
+
+ /*
+ * @param pattern the pattern to search for, which must be anchored to match only at position 0
+ */
+ boolean tryConsumeRegex(Pattern pattern) {
+ Matcher matcher = pattern.matcher(remaining);
+ if (!matcher.find()) {
+ return false;
+ }
+ checkArgument(matcher.start() == 0);
+ toConsume = matcher.end();
+ return true;
+ }
+
+ String readAndResetRecorded() {
+ String result = remaining.substring(0, toConsume);
+ remaining = remaining.substring(toConsume);
+ toConsume = 0; // TODO(cpovirk): Set this to a bogus value here and in the constructor.
+ return result;
+ }
+
+ boolean isExhausted() {
+ return remaining.isEmpty();
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java b/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java
new file mode 100644
index 0000000..5addc67
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java.javadoc;
+
+import static com.google.googlejavaformat.java.javadoc.JavadocLexer.lex;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.BR_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.PARAGRAPH_OPEN_TAG;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+import static java.util.regex.Pattern.compile;
+
+import com.google.common.collect.ImmutableList;
+import com.google.googlejavaformat.java.javadoc.JavadocLexer.LexException;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Entry point for formatting Javadoc.
+ *
+ * <p>This stateless class reads tokens from the stateful lexer and translates them to "requests"
+ * and "writes" to the stateful writer. It also munges tokens into "standardized" forms. Finally, it
+ * performs postprocessing to convert the written Javadoc to a one-liner if possible or to leave a
+ * single blank line if it's empty.
+ */
+public final class JavadocFormatter {
+
+ static final int MAX_LINE_LENGTH = 100;
+
+ /**
+ * Formats the given Javadoc comment, which must start with ∕✱✱ and end with ✱∕. The output will
+ * start and end with the same characters.
+ */
+ public static String formatJavadoc(String input, int blockIndent) {
+ ImmutableList<Token> tokens;
+ try {
+ tokens = lex(input);
+ } catch (LexException e) {
+ return input;
+ }
+ String result = render(tokens, blockIndent);
+ return makeSingleLineIfPossible(blockIndent, result);
+ }
+
+ private static String render(List<Token> input, int blockIndent) {
+ JavadocWriter output = new JavadocWriter(blockIndent);
+ for (Token token : input) {
+ switch (token.getType()) {
+ case BEGIN_JAVADOC:
+ output.writeBeginJavadoc();
+ break;
+ case END_JAVADOC:
+ output.writeEndJavadoc();
+ return output.toString();
+ case FOOTER_JAVADOC_TAG_START:
+ output.writeFooterJavadocTagStart(token);
+ break;
+ case LIST_OPEN_TAG:
+ output.writeListOpen(token);
+ break;
+ case LIST_CLOSE_TAG:
+ output.writeListClose(token);
+ break;
+ case LIST_ITEM_OPEN_TAG:
+ output.writeListItemOpen(token);
+ break;
+ case HEADER_OPEN_TAG:
+ output.writeHeaderOpen(token);
+ break;
+ case HEADER_CLOSE_TAG:
+ output.writeHeaderClose(token);
+ break;
+ case PARAGRAPH_OPEN_TAG:
+ output.writeParagraphOpen(standardizePToken(token));
+ break;
+ case BLOCKQUOTE_OPEN_TAG:
+ case BLOCKQUOTE_CLOSE_TAG:
+ output.writeBlockquoteOpenOrClose(token);
+ break;
+ case PRE_OPEN_TAG:
+ output.writePreOpen(token);
+ break;
+ case PRE_CLOSE_TAG:
+ output.writePreClose(token);
+ break;
+ case CODE_OPEN_TAG:
+ output.writeCodeOpen(token);
+ break;
+ case CODE_CLOSE_TAG:
+ output.writeCodeClose(token);
+ break;
+ case TABLE_OPEN_TAG:
+ output.writeTableOpen(token);
+ break;
+ case TABLE_CLOSE_TAG:
+ output.writeTableClose(token);
+ break;
+ case MOE_BEGIN_STRIP_COMMENT:
+ output.requestMoeBeginStripComment(token);
+ break;
+ case MOE_END_STRIP_COMMENT:
+ output.writeMoeEndStripComment(token);
+ break;
+ case HTML_COMMENT:
+ output.writeHtmlComment(token);
+ break;
+ case BR_TAG:
+ output.writeBr(standardizeBrToken(token));
+ break;
+ case WHITESPACE:
+ output.requestWhitespace();
+ break;
+ case FORCED_NEWLINE:
+ output.writeLineBreakNoAutoIndent();
+ break;
+ case LITERAL:
+ output.writeLiteral(token);
+ break;
+ case PARAGRAPH_CLOSE_TAG:
+ case LIST_ITEM_CLOSE_TAG:
+ case OPTIONAL_LINE_BREAK:
+ break;
+ default:
+ throw new AssertionError(token.getType());
+ }
+ }
+ throw new AssertionError();
+ }
+
+ /*
+ * TODO(cpovirk): Is this really the right location for the standardize* methods? Maybe the lexer
+ * should include them as part of its own postprocessing? Or even the writer could make sense.
+ */
+
+ private static Token standardizeBrToken(Token token) {
+ return standardize(token, STANDARD_BR_TOKEN);
+ }
+
+ private static Token standardizePToken(Token token) {
+ return standardize(token, STANDARD_P_TOKEN);
+ }
+
+ private static Token standardize(Token token, Token standardToken) {
+ return SIMPLE_TAG_PATTERN.matcher(token.getValue()).matches() ? standardToken : token;
+ }
+
+ private static final Token STANDARD_BR_TOKEN = new Token(BR_TAG, "<br>");
+ private static final Token STANDARD_P_TOKEN = new Token(PARAGRAPH_OPEN_TAG, "<p>");
+ private static final Pattern SIMPLE_TAG_PATTERN = compile("^<\\w+\\s*/?\\s*>", CASE_INSENSITIVE);
+
+ private static final Pattern ONE_CONTENT_LINE_PATTERN = compile(" */[*][*]\n *[*] (.*)\n *[*]/");
+
+ /**
+ * Returns the given string or a one-line version of it (e.g., "∕✱✱ Tests for foos. ✱∕") if it
+ * fits on one line.
+ */
+ private static String makeSingleLineIfPossible(int blockIndent, String input) {
+ int oneLinerContentLength = MAX_LINE_LENGTH - "/** */".length() - blockIndent;
+ Matcher matcher = ONE_CONTENT_LINE_PATTERN.matcher(input);
+ if (matcher.matches() && matcher.group(1).isEmpty()) {
+ return "/** */";
+ } else if (matcher.matches() && matcher.group(1).length() <= oneLinerContentLength) {
+ return "/** " + matcher.group(1) + " */";
+ }
+ return input;
+ }
+
+ private JavadocFormatter() {}
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java b/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java
new file mode 100644
index 0000000..108d4a7
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java.javadoc;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Verify.verify;
+import static com.google.common.collect.Iterators.peekingIterator;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.BEGIN_JAVADOC;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.BLOCKQUOTE_CLOSE_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.BLOCKQUOTE_OPEN_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.BR_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.CODE_CLOSE_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.CODE_OPEN_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.END_JAVADOC;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.FOOTER_JAVADOC_TAG_START;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.FORCED_NEWLINE;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.HEADER_CLOSE_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.HEADER_OPEN_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.HTML_COMMENT;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.LIST_CLOSE_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.LIST_ITEM_CLOSE_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.LIST_ITEM_OPEN_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.LIST_OPEN_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.LITERAL;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.MOE_BEGIN_STRIP_COMMENT;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.MOE_END_STRIP_COMMENT;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.OPTIONAL_LINE_BREAK;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.PARAGRAPH_CLOSE_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.PARAGRAPH_OPEN_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.PRE_CLOSE_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.PRE_OPEN_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.TABLE_CLOSE_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.TABLE_OPEN_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.WHITESPACE;
+import static java.lang.String.format;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+import static java.util.regex.Pattern.DOTALL;
+import static java.util.regex.Pattern.compile;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.PeekingIterator;
+import com.google.googlejavaformat.java.javadoc.Token.Type;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/** Lexer for the Javadoc formatter. */
+final class JavadocLexer {
+ /** Takes a Javadoc comment, including ∕✱✱ and ✱∕, and returns tokens, including ∕✱✱ and ✱∕. */
+ static ImmutableList<Token> lex(String input) throws LexException {
+ /*
+ * TODO(cpovirk): In theory, we should interpret Unicode escapes (yet output them in their
+ * original form). This would mean mean everything from an encoded ∕✱✱ to an encoded <pre> tag,
+ * so we'll probably never bother.
+ */
+ input = stripJavadocBeginAndEnd(input);
+ input = normalizeLineEndings(input);
+ return new JavadocLexer(new CharStream(input)).generateTokens();
+ }
+
+ /** The lexer crashes on windows line endings, so for now just normalize to `\n`. */
+ // TODO(cushon): use the platform line separator for output
+ private static String normalizeLineEndings(String input) {
+ return NON_UNIX_LINE_ENDING.matcher(input).replaceAll("\n");
+ }
+
+ private static final Pattern NON_UNIX_LINE_ENDING = Pattern.compile("\r\n?");
+
+ private static String stripJavadocBeginAndEnd(String input) {
+ /*
+ * We do this ahead of time so that the main part of the lexer need not say things like
+ * "(?![*]/)" to avoid accidentally swallowing ✱∕ when consuming a newline.
+ */
+ checkArgument(input.startsWith("/**"), "Missing /**: %s", input);
+ checkArgument(input.endsWith("*/") && input.length() > 4, "Missing */: %s", input);
+ return input.substring("/**".length(), input.length() - "*/".length());
+ }
+
+ private final CharStream input;
+ private final NestingCounter braceDepth = new NestingCounter();
+ private final NestingCounter preDepth = new NestingCounter();
+ private final NestingCounter codeDepth = new NestingCounter();
+ private final NestingCounter tableDepth = new NestingCounter();
+ private boolean somethingSinceNewline;
+
+ private JavadocLexer(CharStream input) {
+ this.input = checkNotNull(input);
+ }
+
+ private ImmutableList<Token> generateTokens() throws LexException {
+ ImmutableList.Builder<Token> tokens = ImmutableList.builder();
+
+ Token token = new Token(BEGIN_JAVADOC, "/**");
+ tokens.add(token);
+
+ while (!input.isExhausted()) {
+ token = readToken();
+ tokens.add(token);
+ }
+
+ checkMatchingTags();
+
+ token = new Token(END_JAVADOC, "*/");
+ tokens.add(token);
+
+ ImmutableList<Token> result = tokens.build();
+ result = joinAdjacentLiteralsAndAdjacentWhitespace(result);
+ result = inferParagraphTags(result);
+ result = optionalizeSpacesAfterLinks(result);
+ result = deindentPreCodeBlocks(result);
+ return result;
+ }
+
+ private Token readToken() throws LexException {
+ Type type = consumeToken();
+ String value = input.readAndResetRecorded();
+ return new Token(type, value);
+ }
+
+ private Type consumeToken() throws LexException {
+ boolean preserveExistingFormatting = preserveExistingFormatting();
+
+ if (input.tryConsumeRegex(NEWLINE_PATTERN)) {
+ somethingSinceNewline = false;
+ return preserveExistingFormatting ? FORCED_NEWLINE : WHITESPACE;
+ } else if (input.tryConsume(" ") || input.tryConsume("\t")) {
+ // TODO(cpovirk): How about weird whitespace chars? Ideally we'd distinguish breaking vs. not.
+ // Returning LITERAL here prevent us from breaking a <pre> line. For more info, see LITERAL.
+ return preserveExistingFormatting ? LITERAL : WHITESPACE;
+ }
+
+ /*
+ * TODO(cpovirk): Maybe try to detect things like "{@code\n@GwtCompatible}" that aren't intended
+ * as tags. But in the most likely case, in which that happens inside <pre>{@code, we have no
+ * great options for fixing it.
+ * https://github.com/google/google-java-format/issues/7#issuecomment-197383926
+ */
+ if (!somethingSinceNewline && input.tryConsumeRegex(FOOTER_TAG_PATTERN)) {
+ checkMatchingTags();
+ somethingSinceNewline = true;
+ return FOOTER_JAVADOC_TAG_START;
+ }
+ somethingSinceNewline = true;
+
+ if (input.tryConsumeRegex(INLINE_TAG_OPEN_PATTERN)) {
+ braceDepth.increment();
+ return LITERAL;
+ } else if (input.tryConsume("{")) {
+ braceDepth.incrementIfPositive();
+ return LITERAL;
+ } else if (input.tryConsume("}")) {
+ braceDepth.decrementIfPositive();
+ return LITERAL;
+ }
+
+ // Inside an inline tag, don't do any HTML interpretation.
+ if (braceDepth.isPositive()) {
+ verify(input.tryConsumeRegex(LITERAL_PATTERN));
+ return LITERAL;
+ }
+
+ if (input.tryConsumeRegex(PRE_OPEN_PATTERN)) {
+ preDepth.increment();
+ return preserveExistingFormatting ? LITERAL : PRE_OPEN_TAG;
+ } else if (input.tryConsumeRegex(PRE_CLOSE_PATTERN)) {
+ preDepth.decrementIfPositive();
+ return preserveExistingFormatting() ? LITERAL : PRE_CLOSE_TAG;
+ }
+
+ if (input.tryConsumeRegex(CODE_OPEN_PATTERN)) {
+ codeDepth.increment();
+ return preserveExistingFormatting ? LITERAL : CODE_OPEN_TAG;
+ } else if (input.tryConsumeRegex(CODE_CLOSE_PATTERN)) {
+ codeDepth.decrementIfPositive();
+ return preserveExistingFormatting() ? LITERAL : CODE_CLOSE_TAG;
+ }
+
+ if (input.tryConsumeRegex(TABLE_OPEN_PATTERN)) {
+ tableDepth.increment();
+ return preserveExistingFormatting ? LITERAL : TABLE_OPEN_TAG;
+ } else if (input.tryConsumeRegex(TABLE_CLOSE_PATTERN)) {
+ tableDepth.decrementIfPositive();
+ return preserveExistingFormatting() ? LITERAL : TABLE_CLOSE_TAG;
+ }
+
+ if (preserveExistingFormatting) {
+ verify(input.tryConsumeRegex(LITERAL_PATTERN));
+ return LITERAL;
+ }
+
+ if (input.tryConsumeRegex(PARAGRAPH_OPEN_PATTERN)) {
+ return PARAGRAPH_OPEN_TAG;
+ } else if (input.tryConsumeRegex(PARAGRAPH_CLOSE_PATTERN)) {
+ return PARAGRAPH_CLOSE_TAG;
+ } else if (input.tryConsumeRegex(LIST_OPEN_PATTERN)) {
+ return LIST_OPEN_TAG;
+ } else if (input.tryConsumeRegex(LIST_CLOSE_PATTERN)) {
+ return LIST_CLOSE_TAG;
+ } else if (input.tryConsumeRegex(LIST_ITEM_OPEN_PATTERN)) {
+ return LIST_ITEM_OPEN_TAG;
+ } else if (input.tryConsumeRegex(LIST_ITEM_CLOSE_PATTERN)) {
+ return LIST_ITEM_CLOSE_TAG;
+ } else if (input.tryConsumeRegex(BLOCKQUOTE_OPEN_PATTERN)) {
+ return BLOCKQUOTE_OPEN_TAG;
+ } else if (input.tryConsumeRegex(BLOCKQUOTE_CLOSE_PATTERN)) {
+ return BLOCKQUOTE_CLOSE_TAG;
+ } else if (input.tryConsumeRegex(HEADER_OPEN_PATTERN)) {
+ return HEADER_OPEN_TAG;
+ } else if (input.tryConsumeRegex(HEADER_CLOSE_PATTERN)) {
+ return HEADER_CLOSE_TAG;
+ } else if (input.tryConsumeRegex(BR_PATTERN)) {
+ return BR_TAG;
+ } else if (input.tryConsumeRegex(MOE_BEGIN_STRIP_COMMENT_PATTERN)) {
+ return MOE_BEGIN_STRIP_COMMENT;
+ } else if (input.tryConsumeRegex(MOE_END_STRIP_COMMENT_PATTERN)) {
+ return MOE_END_STRIP_COMMENT;
+ } else if (input.tryConsumeRegex(HTML_COMMENT_PATTERN)) {
+ return HTML_COMMENT;
+ } else if (input.tryConsumeRegex(LITERAL_PATTERN)) {
+ return LITERAL;
+ }
+ throw new AssertionError();
+ }
+
+ private boolean preserveExistingFormatting() {
+ return preDepth.isPositive() || tableDepth.isPositive() || codeDepth.isPositive();
+ }
+
+ private void checkMatchingTags() throws LexException {
+ if (braceDepth.isPositive()
+ || preDepth.isPositive()
+ || tableDepth.isPositive()
+ || codeDepth.isPositive()) {
+ throw new LexException();
+ }
+ }
+
+ /**
+ * Join together adjacent literal tokens, and join together adjacent whitespace tokens.
+ *
+ * <p>For literal tokens, this means something like {@code ["<b>", "foo", "</b>"] =>
+ * ["<b>foo</b>"]}. See {@link #LITERAL_PATTERN} for discussion of why those tokens are separate
+ * to begin with.
+ *
+ * <p>Whitespace tokens are treated analogously. We don't really "want" to join whitespace tokens,
+ * but in the course of joining literals, we incidentally join whitespace, too. We do take
+ * advantage of the joining later on: It simplifies {@link #inferParagraphTags}.
+ *
+ * <p>Note that we do <i>not</i> merge a literal token and a whitespace token together.
+ */
+ private static ImmutableList<Token> joinAdjacentLiteralsAndAdjacentWhitespace(List<Token> input) {
+ /*
+ * Note: Our final token is always END_JAVADOC. This saves us some trouble:
+ *
+ * - Our inner while() doesn't need a hasNext() check.
+ *
+ * - We don't need to check for leftover accumulated literals after we exit the loop.
+ */
+ ImmutableList.Builder<Token> output = ImmutableList.builder();
+ StringBuilder accumulated = new StringBuilder();
+
+ for (PeekingIterator<Token> tokens = peekingIterator(input.iterator()); tokens.hasNext(); ) {
+ if (tokens.peek().getType() == LITERAL) {
+ accumulated.append(tokens.peek().getValue());
+ tokens.next();
+ continue;
+ }
+
+ /*
+ * IF we have accumulated some literals to join together (say, "foo<b>bar</b>"), and IF we'll
+ * next see whitespace followed by a "@" literal, we need to join that together with the
+ * previous literals. That ensures that we won't insert a line break before the "@," turning
+ * it into a tag.
+ */
+
+ if (accumulated.length() == 0) {
+ output.add(tokens.peek());
+ tokens.next();
+ continue;
+ }
+
+ StringBuilder seenWhitespace = new StringBuilder();
+ while (tokens.peek().getType() == WHITESPACE) {
+ seenWhitespace.append(tokens.next().getValue());
+ }
+
+ if (tokens.peek().getType() == LITERAL && tokens.peek().getValue().startsWith("@")) {
+ // OK, we're in the case described above.
+ accumulated.append(" ");
+ accumulated.append(tokens.peek().getValue());
+ tokens.next();
+ continue;
+ }
+
+ output.add(new Token(LITERAL, accumulated.toString()));
+ accumulated.setLength(0);
+
+ if (seenWhitespace.length() > 0) {
+ output.add(new Token(WHITESPACE, seenWhitespace.toString()));
+ }
+
+ // We have another token coming, possibly of type OTHER. Leave it for the next iteration.
+ }
+
+ /*
+ * TODO(cpovirk): Another case where we could try to join tokens is if a line ends with
+ * /[^ -]-/, as in "non-\nblocking."
+ */
+ return output.build();
+ }
+
+ /**
+ * Where the input has two consecutive line breaks between literals, insert a {@code <p>} tag
+ * between the literals.
+ *
+ * <p>This method must be called after {@link #joinAdjacentLiteralsAndAdjacentWhitespace}, as it
+ * assumes that adjacent whitespace tokens have already been joined.
+ */
+ private static ImmutableList<Token> inferParagraphTags(List<Token> input) {
+ ImmutableList.Builder<Token> output = ImmutableList.builder();
+
+ for (PeekingIterator<Token> tokens = peekingIterator(input.iterator()); tokens.hasNext(); ) {
+ if (tokens.peek().getType() == LITERAL) {
+ output.add(tokens.next());
+
+ if (tokens.peek().getType() == WHITESPACE
+ && hasMultipleNewlines(tokens.peek().getValue())) {
+ output.add(tokens.next());
+
+ if (tokens.peek().getType() == LITERAL) {
+ output.add(new Token(PARAGRAPH_OPEN_TAG, "<p>"));
+ }
+ }
+ } else {
+ // TODO(cpovirk): Or just `continue` from the <p> case and move this out of the `else`?
+ output.add(tokens.next());
+ }
+ }
+
+ return output.build();
+
+ /*
+ * Note: We do not want to insert <p> tags inside <pre>. Fortunately, the formatter gets that
+ * right without special effort on our part. The reason: Line breaks inside a <pre> section are
+ * of type FORCED_NEWLINE rather than WHITESPACE.
+ */
+ }
+
+ /**
+ * Replaces whitespace after a {@code href=...>} token with an "optional link break." This allows
+ * us to output either {@code <a href=foo>foo</a>} or {@code <a href=foo>\nfoo</a>}, depending on
+ * how much space we have left on the line.
+ *
+ * <p>This method must be called after {@link #joinAdjacentLiteralsAndAdjacentWhitespace}, as it
+ * assumes that adjacent whitespace tokens have already been joined.
+ */
+ private static ImmutableList<Token> optionalizeSpacesAfterLinks(List<Token> input) {
+ ImmutableList.Builder<Token> output = ImmutableList.builder();
+
+ for (PeekingIterator<Token> tokens = peekingIterator(input.iterator()); tokens.hasNext(); ) {
+ if (tokens.peek().getType() == LITERAL && tokens.peek().getValue().matches("^href=[^>]*>")) {
+ output.add(tokens.next());
+
+ if (tokens.peek().getType() == WHITESPACE) {
+ output.add(new Token(OPTIONAL_LINE_BREAK, tokens.next().getValue()));
+ }
+ } else {
+ output.add(tokens.next());
+ }
+ }
+
+ return output.build();
+
+ /*
+ * Note: We do not want to insert <p> tags inside <pre>. Fortunately, the formatter gets that
+ * right without special effort on our part. The reason: Line breaks inside a <pre> section are
+ * of type FORCED_NEWLINE rather than WHITESPACE.
+ */
+ }
+
+ /**
+ * Adjust indentation inside `<pre>{@code` blocks.
+ *
+ * <p>Also trim leading and trailing blank lines, and move the trailing `}` to its own line.
+ */
+ private static ImmutableList<Token> deindentPreCodeBlocks(List<Token> input) {
+ ImmutableList.Builder<Token> output = ImmutableList.builder();
+ for (PeekingIterator<Token> tokens = peekingIterator(input.iterator()); tokens.hasNext(); ) {
+ if (tokens.peek().getType() != PRE_OPEN_TAG) {
+ output.add(tokens.next());
+ continue;
+ }
+
+ output.add(tokens.next());
+ List<Token> initialNewlines = new ArrayList<>();
+ while (tokens.hasNext() && tokens.peek().getType() == FORCED_NEWLINE) {
+ initialNewlines.add(tokens.next());
+ }
+ if (tokens.peek().getType() != LITERAL
+ || !tokens.peek().getValue().matches("[ \t]*[{]@code")) {
+ output.addAll(initialNewlines);
+ output.add(tokens.next());
+ continue;
+ }
+
+ deindentPreCodeBlock(output, tokens);
+ }
+ return output.build();
+ }
+
+ private static void deindentPreCodeBlock(
+ ImmutableList.Builder<Token> output, PeekingIterator<Token> tokens) {
+ Deque<Token> saved = new ArrayDeque<>();
+ output.add(new Token(LITERAL, tokens.next().getValue().trim()));
+ while (tokens.hasNext() && tokens.peek().getType() != PRE_CLOSE_TAG) {
+ Token token = tokens.next();
+ saved.addLast(token);
+ }
+ while (!saved.isEmpty() && saved.peekFirst().getType() == FORCED_NEWLINE) {
+ saved.removeFirst();
+ }
+ while (!saved.isEmpty() && saved.peekLast().getType() == FORCED_NEWLINE) {
+ saved.removeLast();
+ }
+ if (saved.isEmpty()) {
+ return;
+ }
+
+ // move the trailing `}` to its own line
+ Token last = saved.peekLast();
+ boolean trailingBrace = false;
+ if (last.getType() == LITERAL && last.getValue().endsWith("}")) {
+ saved.removeLast();
+ if (last.length() > 1) {
+ saved.addLast(
+ new Token(LITERAL, last.getValue().substring(0, last.getValue().length() - 1)));
+ saved.addLast(new Token(FORCED_NEWLINE, null));
+ }
+ trailingBrace = true;
+ }
+
+ int trim = -1;
+ for (Token token : saved) {
+ if (token.getType() == LITERAL) {
+ int idx = CharMatcher.isNot(' ').indexIn(token.getValue());
+ if (idx != -1 && (trim == -1 || idx < trim)) {
+ trim = idx;
+ }
+ }
+ }
+
+ output.add(new Token(FORCED_NEWLINE, "\n"));
+ for (Token token : saved) {
+ if (token.getType() == LITERAL) {
+ output.add(
+ new Token(
+ LITERAL,
+ trim > 0 && token.length() > trim
+ ? token.getValue().substring(trim)
+ : token.getValue()));
+ } else {
+ output.add(token);
+ }
+ }
+
+ if (trailingBrace) {
+ output.add(new Token(LITERAL, "}"));
+ } else {
+ output.add(new Token(FORCED_NEWLINE, "\n"));
+ }
+ }
+
+ private static final CharMatcher NEWLINE = CharMatcher.is('\n');
+
+ private static boolean hasMultipleNewlines(String s) {
+ return NEWLINE.countIn(s) > 1;
+ }
+
+ /*
+ * This also eats any trailing whitespace. We would be smart enough to ignore that, anyway --
+ * except in the case of <pre>/<table>, inside which we otherwise leave whitespace intact.
+ *
+ * We'd remove the trailing whitespace later on (in JavaCommentsHelper.rewrite), but I feel safer
+ * stripping it now: It otherwise might confuse our line-length count, which we use for wrapping.
+ */
+ private static final Pattern NEWLINE_PATTERN = compile("^[ \t]*\n[ \t]*[*]?[ \t]?");
+
+ // We ensure elsewhere that we match this only at the beginning of a line.
+ // Only match tags that start with a lowercase letter, to avoid false matches on unescaped
+ // annotations inside code blocks.
+ // Match "@param <T>" specially in case the <T> is a <P> or other HTML tag we treat specially.
+ private static final Pattern FOOTER_TAG_PATTERN = compile("^@(param\\s+<\\w+>|[a-z]\\w*)");
+ private static final Pattern MOE_BEGIN_STRIP_COMMENT_PATTERN =
+ compile("^<!--\\s*MOE:begin_intracomment_strip\\s*-->");
+ private static final Pattern MOE_END_STRIP_COMMENT_PATTERN =
+ compile("^<!--\\s*MOE:end_intracomment_strip\\s*-->");
+ private static final Pattern HTML_COMMENT_PATTERN = fullCommentPattern();
+ private static final Pattern PRE_OPEN_PATTERN = openTagPattern("pre");
+ private static final Pattern PRE_CLOSE_PATTERN = closeTagPattern("pre");
+ private static final Pattern CODE_OPEN_PATTERN = openTagPattern("code");
+ private static final Pattern CODE_CLOSE_PATTERN = closeTagPattern("code");
+ private static final Pattern TABLE_OPEN_PATTERN = openTagPattern("table");
+ private static final Pattern TABLE_CLOSE_PATTERN = closeTagPattern("table");
+ private static final Pattern LIST_OPEN_PATTERN = openTagPattern("ul|ol|dl");
+ private static final Pattern LIST_CLOSE_PATTERN = closeTagPattern("ul|ol|dl");
+ private static final Pattern LIST_ITEM_OPEN_PATTERN = openTagPattern("li|dt|dd");
+ private static final Pattern LIST_ITEM_CLOSE_PATTERN = closeTagPattern("li|dt|dd");
+ private static final Pattern HEADER_OPEN_PATTERN = openTagPattern("h[1-6]");
+ private static final Pattern HEADER_CLOSE_PATTERN = closeTagPattern("h[1-6]");
+ private static final Pattern PARAGRAPH_OPEN_PATTERN = openTagPattern("p");
+ private static final Pattern PARAGRAPH_CLOSE_PATTERN = closeTagPattern("p");
+ private static final Pattern BLOCKQUOTE_OPEN_PATTERN = openTagPattern("blockquote");
+ private static final Pattern BLOCKQUOTE_CLOSE_PATTERN = closeTagPattern("blockquote");
+ private static final Pattern BR_PATTERN = openTagPattern("br");
+ private static final Pattern INLINE_TAG_OPEN_PATTERN = compile("^[{]@\\w*");
+ /*
+ * We exclude < so that we don't swallow following HTML tags. This lets us fix up "foo<p>" (~400
+ * hits in Google-internal code). We will join unnecessarily split "words" (like "foo<b>bar</b>")
+ * in a later step. There's a similar story for braces. I'm not sure I actually need to exclude @
+ * or *. TODO(cpovirk): Try removing them.
+ *
+ * Thanks to the "rejoin" step in joinAdjacentLiteralsAndAdjacentWhitespace(), we could get away
+ * with matching only one character here. That would eliminate the need for the regex entirely.
+ * That might be faster or slower than what we do now.
+ */
+ private static final Pattern LITERAL_PATTERN = compile("^.[^ \t\n@<{}*]*", DOTALL);
+
+ private static Pattern fullCommentPattern() {
+ return compile("^<!--.*?-->", DOTALL);
+ }
+
+ private static Pattern openTagPattern(String namePattern) {
+ return compile(format("^<(?:%s)\\b[^>]*>", namePattern), CASE_INSENSITIVE);
+ }
+
+ private static Pattern closeTagPattern(String namePattern) {
+ return compile(format("^</(?:%s)\\b[^>]*>", namePattern), CASE_INSENSITIVE);
+ }
+
+ static class LexException extends Exception {}
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocWriter.java b/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocWriter.java
new file mode 100644
index 0000000..c2431c4
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocWriter.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java.javadoc;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Sets.immutableEnumSet;
+import static com.google.googlejavaformat.java.javadoc.JavadocWriter.AutoIndent.AUTO_INDENT;
+import static com.google.googlejavaformat.java.javadoc.JavadocWriter.AutoIndent.NO_AUTO_INDENT;
+import static com.google.googlejavaformat.java.javadoc.JavadocWriter.RequestedWhitespace.BLANK_LINE;
+import static com.google.googlejavaformat.java.javadoc.JavadocWriter.RequestedWhitespace.NEWLINE;
+import static com.google.googlejavaformat.java.javadoc.JavadocWriter.RequestedWhitespace.NONE;
+import static com.google.googlejavaformat.java.javadoc.JavadocWriter.RequestedWhitespace.WHITESPACE;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.HEADER_OPEN_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.LIST_ITEM_OPEN_TAG;
+import static com.google.googlejavaformat.java.javadoc.Token.Type.PARAGRAPH_OPEN_TAG;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Ordering;
+import com.google.googlejavaformat.java.javadoc.Token.Type;
+
+/**
+ * Stateful object that accepts "requests" and "writes," producing formatted Javadoc.
+ *
+ * <p>Our Javadoc formatter doesn't ever generate a parse tree, only a stream of tokens, so the
+ * writer must compute and store the answer to questions like "How many levels of nested HTML list
+ * are we inside?"
+ */
+final class JavadocWriter {
+ private final int blockIndent;
+ private final StringBuilder output = new StringBuilder();
+ /**
+ * Whether we are inside an {@code <li>} element, excluding the case in which the {@code <li>}
+ * contains a {@code <ul>} or {@code <ol>} that we are also inside -- unless of course we're
+ * inside an {@code <li>} element in that inner list :)
+ */
+ private boolean continuingListItemOfInnermostList;
+
+ private boolean continuingFooterTag;
+ private final NestingCounter continuingListItemCount = new NestingCounter();
+ private final NestingCounter continuingListCount = new NestingCounter();
+ private final NestingCounter postWriteModifiedContinuingListCount = new NestingCounter();
+ private int remainingOnLine;
+ private boolean atStartOfLine;
+ private RequestedWhitespace requestedWhitespace = NONE;
+ private Token requestedMoeBeginStripComment;
+ private int indentForMoeEndStripComment;
+ private boolean wroteAnythingSignificant;
+
+ JavadocWriter(int blockIndent) {
+ this.blockIndent = blockIndent;
+ }
+
+ /**
+ * Requests whitespace between the previously written token and the next written token. The
+ * request may be honored, or it may be overridden by a request for "more significant" whitespace,
+ * like a newline.
+ */
+ void requestWhitespace() {
+ requestWhitespace(WHITESPACE);
+ }
+
+ void requestMoeBeginStripComment(Token token) {
+ // We queue this up so that we can put it after any requested whitespace.
+ requestedMoeBeginStripComment = checkNotNull(token);
+ }
+
+ void writeBeginJavadoc() {
+ /*
+ * JavaCommentsHelper will make sure this is indented right. But it seems sensible enough that,
+ * if our input starts with ∕✱✱, so too does our output.
+ */
+ output.append("/**");
+ writeNewline();
+ }
+
+ void writeEndJavadoc() {
+ output.append("\n");
+ appendSpaces(blockIndent + 1);
+ output.append("*/");
+ }
+
+ void writeFooterJavadocTagStart(Token token) {
+ // Close any unclosed lists (e.g., <li> without <ul>).
+ // TODO(cpovirk): Actually generate </ul>, etc.?
+ /*
+ * TODO(cpovirk): Also generate </pre> and </table> if appropriate. This is necessary for
+ * idempotency in broken Javadoc. (We don't necessarily need that, but full idempotency may be a
+ * nice goal, especially if it helps us use a fuzzer to test.) Unfortunately, the writer doesn't
+ * currently know which of those tags are open.
+ */
+ continuingListItemOfInnermostList = false;
+ continuingListItemCount.reset();
+ continuingListCount.reset();
+ /*
+ * There's probably no need for this, since its only effect is to disable blank lines in some
+ * cases -- and we're doing that already in the footer.
+ */
+ postWriteModifiedContinuingListCount.reset();
+
+ if (!wroteAnythingSignificant) {
+ // Javadoc consists solely of tags. This is frowned upon in general but OK for @Overrides.
+ } else if (!continuingFooterTag) {
+ // First footer tag after a body tag.
+ requestBlankLine();
+ } else {
+ // Subsequent footer tag.
+ continuingFooterTag = false;
+ requestNewline();
+ }
+ writeToken(token);
+ continuingFooterTag = true;
+ }
+
+ void writeListOpen(Token token) {
+ requestBlankLine();
+
+ writeToken(token);
+ continuingListItemOfInnermostList = false;
+ continuingListCount.increment();
+ postWriteModifiedContinuingListCount.increment();
+
+ requestNewline();
+ }
+
+ void writeListClose(Token token) {
+ requestNewline();
+
+ continuingListItemCount.decrementIfPositive();
+ continuingListCount.decrementIfPositive();
+ writeToken(token);
+ postWriteModifiedContinuingListCount.decrementIfPositive();
+
+ requestBlankLine();
+ }
+
+ void writeListItemOpen(Token token) {
+ requestNewline();
+
+ if (continuingListItemOfInnermostList) {
+ continuingListItemOfInnermostList = false;
+ continuingListItemCount.decrementIfPositive();
+ }
+ writeToken(token);
+ continuingListItemOfInnermostList = true;
+ continuingListItemCount.increment();
+ }
+
+ void writeHeaderOpen(Token token) {
+ requestBlankLine();
+
+ writeToken(token);
+ }
+
+ void writeHeaderClose(Token token) {
+ writeToken(token);
+
+ requestBlankLine();
+ }
+
+ void writeParagraphOpen(Token token) {
+ if (!wroteAnythingSignificant) {
+ /*
+ * The user included an initial <p> tag. Ignore it, and don't request a blank line before the
+ * next token.
+ */
+ return;
+ }
+
+ requestBlankLine();
+
+ writeToken(token);
+ }
+
+ void writeBlockquoteOpenOrClose(Token token) {
+ requestBlankLine();
+
+ writeToken(token);
+
+ requestBlankLine();
+ }
+
+ void writePreOpen(Token token) {
+ requestBlankLine();
+
+ writeToken(token);
+ }
+
+ void writePreClose(Token token) {
+ writeToken(token);
+
+ requestBlankLine();
+ }
+
+ void writeCodeOpen(Token token) {
+ writeToken(token);
+ }
+
+ void writeCodeClose(Token token) {
+ writeToken(token);
+ }
+
+ void writeTableOpen(Token token) {
+ requestBlankLine();
+
+ writeToken(token);
+ }
+
+ void writeTableClose(Token token) {
+ writeToken(token);
+
+ requestBlankLine();
+ }
+
+ void writeMoeEndStripComment(Token token) {
+ writeLineBreakNoAutoIndent();
+ appendSpaces(indentForMoeEndStripComment);
+
+ // Or maybe just "output.append(token.getValue())?" I'm kind of surprised this is so easy.
+ writeToken(token);
+
+ requestNewline();
+ }
+
+ void writeHtmlComment(Token token) {
+ requestNewline();
+
+ writeToken(token);
+
+ requestNewline();
+ }
+
+ void writeBr(Token token) {
+ writeToken(token);
+
+ requestNewline();
+ }
+
+ void writeLineBreakNoAutoIndent() {
+ writeNewline(NO_AUTO_INDENT);
+ }
+
+ void writeLiteral(Token token) {
+ writeToken(token);
+ }
+
+ @Override
+ public String toString() {
+ return output.toString();
+ }
+
+ private void requestBlankLine() {
+ requestWhitespace(BLANK_LINE);
+ }
+
+ private void requestNewline() {
+ requestWhitespace(NEWLINE);
+ }
+
+ private void requestWhitespace(RequestedWhitespace requestedWhitespace) {
+ this.requestedWhitespace =
+ Ordering.natural().max(requestedWhitespace, this.requestedWhitespace);
+ }
+
+ /**
+ * The kind of whitespace that has been requested between the previous and next tokens. The order
+ * of the values is significant: It goes from lowest priority to highest. For example, if the
+ * previous token requests {@link #BLANK_LINE} after it but the next token requests only {@link
+ * #NEWLINE} before it, we insert {@link #BLANK_LINE}.
+ */
+ enum RequestedWhitespace {
+ NONE,
+ WHITESPACE,
+ NEWLINE,
+ BLANK_LINE,
+ ;
+ }
+
+ private void writeToken(Token token) {
+ if (requestedMoeBeginStripComment != null) {
+ requestNewline();
+ }
+
+ if (requestedWhitespace == BLANK_LINE
+ && (postWriteModifiedContinuingListCount.isPositive() || continuingFooterTag)) {
+ /*
+ * We don't write blank lines inside lists or footer tags, even in cases where we otherwise
+ * would (e.g., before a <p> tag). Justification: We don't write blank lines _between_ list
+ * items or footer tags, so it would be strange to write blank lines _within_ one. Of course,
+ * an alternative approach would be to go ahead and write blank lines between items/tags,
+ * either always or only in the case that an item contains a blank line.
+ */
+ requestedWhitespace = NEWLINE;
+ }
+
+ if (requestedWhitespace == BLANK_LINE) {
+ writeBlankLine();
+ requestedWhitespace = NONE;
+ } else if (requestedWhitespace == NEWLINE) {
+ writeNewline();
+ requestedWhitespace = NONE;
+ }
+ boolean needWhitespace = (requestedWhitespace == WHITESPACE);
+
+ /*
+ * Write a newline if necessary to respect the line limit. (But if we're at the beginning of the
+ * line, a newline won't help. Or it might help but only by separating "<p>veryverylongword,"
+ * which goes against our style.)
+ */
+ if (!atStartOfLine && token.length() + (needWhitespace ? 1 : 0) > remainingOnLine) {
+ writeNewline();
+ }
+ if (!atStartOfLine && needWhitespace) {
+ output.append(" ");
+ remainingOnLine--;
+ }
+
+ if (requestedMoeBeginStripComment != null) {
+ output.append(requestedMoeBeginStripComment.getValue());
+ requestedMoeBeginStripComment = null;
+ indentForMoeEndStripComment = innerIndent();
+ requestNewline();
+ writeToken(token);
+ return;
+ }
+
+ output.append(token.getValue());
+
+ if (!START_OF_LINE_TOKENS.contains(token.getType())) {
+ atStartOfLine = false;
+ }
+
+ /*
+ * TODO(cpovirk): We really want the number of "characters," not chars. Figure out what the
+ * right way of measuring that is (grapheme count (with BreakIterator?)? sum of widths of all
+ * graphemes? I don't think that our style guide is specific about this.). Moreover, I am
+ * probably brushing other problems with surrogates, etc. under the table. Hopefully I mostly
+ * get away with it by joining all non-space, non-tab characters together.
+ *
+ * Possibly the "width" question has no right answer:
+ * http://denisbider.blogspot.com/2015/09/when-monospace-fonts-arent-unicode.html
+ */
+ remainingOnLine -= token.length();
+ requestedWhitespace = NONE;
+ wroteAnythingSignificant = true;
+ }
+
+ private void writeBlankLine() {
+ output.append("\n");
+ appendSpaces(blockIndent + 1);
+ output.append("*");
+ writeNewline();
+ }
+
+ private void writeNewline() {
+ writeNewline(AUTO_INDENT);
+ }
+
+ private void writeNewline(AutoIndent autoIndent) {
+ output.append("\n");
+ appendSpaces(blockIndent + 1);
+ output.append("*");
+ appendSpaces(1);
+ remainingOnLine = JavadocFormatter.MAX_LINE_LENGTH - blockIndent - 3;
+ if (autoIndent == AUTO_INDENT) {
+ appendSpaces(innerIndent());
+ remainingOnLine -= innerIndent();
+ }
+ atStartOfLine = true;
+ }
+
+ enum AutoIndent {
+ AUTO_INDENT,
+ NO_AUTO_INDENT
+ }
+
+ private int innerIndent() {
+ int innerIndent = continuingListItemCount.value() * 4 + continuingListCount.value() * 2;
+ if (continuingFooterTag) {
+ innerIndent += 4;
+ }
+ return innerIndent;
+ }
+
+ // If this is a hotspot, keep a String of many spaces around, and call append(string, start, end).
+ private void appendSpaces(int count) {
+ output.append(Strings.repeat(" ", count));
+ }
+
+ /**
+ * Tokens that are always pinned to the following token. For example, {@code <p>} in {@code <p>Foo
+ * bar} (never {@code <p> Foo bar} or {@code <p>\nFoo bar}).
+ *
+ * <p>This is not the only kind of "pinning" that we do: See also the joining of LITERAL tokens
+ * done by the lexer. The special pinning here is necessary because these tokens are not of type
+ * LITERAL (because they require other special handling).
+ */
+ private static final ImmutableSet<Type> START_OF_LINE_TOKENS =
+ immutableEnumSet(LIST_ITEM_OPEN_TAG, PARAGRAPH_OPEN_TAG, HEADER_OPEN_TAG);
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/javadoc/NestingCounter.java b/core/src/main/java/com/google/googlejavaformat/java/javadoc/NestingCounter.java
new file mode 100644
index 0000000..43e7125
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/javadoc/NestingCounter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java.javadoc;
+
+/** Mutable integer for tracking the level of nesting. */
+final class NestingCounter {
+ private int value;
+
+ int value() {
+ return value;
+ }
+
+ void increment() {
+ value++;
+ }
+
+ void incrementIfPositive() {
+ if (value > 0) {
+ value++;
+ }
+ }
+
+ void decrementIfPositive() {
+ if (value > 0) {
+ value--;
+ }
+ }
+
+ boolean isPositive() {
+ return value > 0;
+ }
+
+ void reset() {
+ value = 0;
+ }
+}
diff --git a/core/src/main/java/com/google/googlejavaformat/java/javadoc/Token.java b/core/src/main/java/com/google/googlejavaformat/java/javadoc/Token.java
new file mode 100644
index 0000000..d617824
--- /dev/null
+++ b/core/src/main/java/com/google/googlejavaformat/java/javadoc/Token.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java.javadoc;
+
+/**
+ * Javadoc token. Our idea of what constitutes a token is often larger or smaller than what you'd
+ * naturally expect. The decision is usually pragmatic rather than theoretical. Most of the details
+ * are in {@link JavadocLexer}.
+ */
+final class Token {
+ /**
+ * Javadoc token type.
+ *
+ * <p>The general idea is that every token that requires special handling (extra line breaks,
+ * indentation, forcing or forbidding whitespace) from {@link JavadocWriter} gets its own type.
+ * But I haven't been super careful about it, so I'd imagine that we could merge or remove some of
+ * these if we wanted. (For example, PARAGRAPH_CLOSE_TAG and LIST_ITEM_CLOSE_TAG could share a
+ * common IGNORABLE token type. But their corresponding OPEN tags exist, so I've kept the CLOSE
+ * tags.)
+ *
+ * <p>Note, though, that tokens of the same type may still have been handled differently by {@link
+ * JavadocLexer} when it created them. For example, LITERAL is used for both plain text and inline
+ * tags, even though the two affect the lexer's state differently.
+ */
+ enum Type {
+ /** ∕✱✱ */
+ BEGIN_JAVADOC,
+ /** ✱∕ */
+ END_JAVADOC,
+ /** The {@code @foo} that begins a block Javadoc tag like {@code @throws}. */
+ FOOTER_JAVADOC_TAG_START,
+ LIST_OPEN_TAG,
+ LIST_CLOSE_TAG,
+ LIST_ITEM_OPEN_TAG,
+ LIST_ITEM_CLOSE_TAG,
+ HEADER_OPEN_TAG,
+ HEADER_CLOSE_TAG,
+ PARAGRAPH_OPEN_TAG,
+ PARAGRAPH_CLOSE_TAG,
+ // TODO(cpovirk): Support <div> (probably identically to <blockquote>).
+ BLOCKQUOTE_OPEN_TAG,
+ BLOCKQUOTE_CLOSE_TAG,
+ PRE_OPEN_TAG,
+ PRE_CLOSE_TAG,
+ CODE_OPEN_TAG,
+ CODE_CLOSE_TAG,
+ TABLE_OPEN_TAG,
+ TABLE_CLOSE_TAG,
+ /** {@code <!-- MOE:begin_intracomment_strip -->} */
+ MOE_BEGIN_STRIP_COMMENT,
+ /** {@code <!-- MOE:end_intracomment_strip -->} */
+ MOE_END_STRIP_COMMENT,
+ HTML_COMMENT,
+ // TODO(cpovirk): Support <hr> (probably a blank line before and after).
+ BR_TAG,
+ /**
+ * Whitespace that is not in a {@code <pre>} or {@code <table>} section. Whitespace includes
+ * leading newlines, asterisks, and tabs and spaces. In the output, it is translated to newlines
+ * (with leading spaces and asterisks) or spaces.
+ */
+ WHITESPACE,
+ /**
+ * A newline in a {@code <pre>} or {@code <table>} section. We preserve user formatting in these
+ * sections, including newlines.
+ */
+ FORCED_NEWLINE,
+ /**
+ * Token that permits but does not force a line break. The way that we accomplish this is
+ * somewhat indirect: As far as {@link JavadocWriter} is concerned, this token is meaningless.
+ * But its mere existence prevents {@link JavadocLexer} from joining two {@link #LITERAL} tokens
+ * that would otherwise be adjacent. Since this token is not real whitespace, the writer may end
+ * up writing the literals together with no space between, just as if they'd been joined.
+ * However, if they don't fit together on the line, the writer will write the first one, start a
+ * new line, and write the second. Hence, the token acts as an optional line break.
+ */
+ OPTIONAL_LINE_BREAK,
+ /**
+ * Anything else: {@code foo}, {@code <b>}, {@code {@code foo}} etc. {@link JavadocLexer}
+ * sometimes creates adjacent literal tokens, which it then merges into a single, larger literal
+ * token before returning its output.
+ *
+ * <p>This also includes whitespace in a {@code <pre>} or {@code <table>} section. We preserve
+ * user formatting in these sections, including arbitrary numbers of spaces. By treating such
+ * whitespace as a literal, we can merge it with adjacent literals, preventing us from
+ * autowrapping inside these sections -- and doing so naively, to boot. The wrapped line would
+ * have no indentation after "* " or, possibly worse, it might begin with an arbitrary amount of
+ * whitespace that didn't fit on the previous line. Of course, by doing this, we're potentially
+ * creating lines of more than 100 characters. But it seems fair to call in the humans to
+ * resolve such problems.
+ */
+ LITERAL,
+ ;
+ }
+
+ private final Type type;
+ private final String value;
+
+ Token(Type type, String value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ Type getType() {
+ return type;
+ }
+
+ String getValue() {
+ return value;
+ }
+
+ int length() {
+ return value.length();
+ }
+
+ @Override
+ public String toString() {
+ return "\n" + getType() + ": " + getValue();
+ }
+}
diff --git a/core/src/main/scripts/google-java-format.el b/core/src/main/scripts/google-java-format.el
new file mode 100644
index 0000000..f9e8d2a
--- /dev/null
+++ b/core/src/main/scripts/google-java-format.el
@@ -0,0 +1,105 @@
+;;; google-java-format.el --- Format code with google-java-format -*- lexical-binding: t; -*-
+;;
+;; Copyright 2015 Google, Inc. All Rights Reserved.
+;;
+;; Package-Requires: ((emacs "24"))
+;;
+;; 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.
+
+;; Keywords: tools, Java
+
+;;; Commentary:
+
+;; This package allows a user to filter code through
+;; google-java-format, fixing its formatting.
+
+;; To use it, ensure the directory of this file is in your `load-path'
+;; and add
+;;
+;; (require 'google-java-format)
+;;
+;; to your .emacs configuration.
+
+;; You may also want to bind `google-java-format-region' to a key:
+;;
+;; (global-set-key [C-M-tab] #'google-java-format-region)
+
+;;; Code:
+
+(defgroup google-java-format nil
+ "Format code using google-java-format."
+ :group 'tools)
+
+(defcustom google-java-format-executable
+ "/usr/bin/google-java-format"
+ "Location of the google-java-format executable.
+
+A string containing the name or the full path of the executable."
+ :group 'google-java-format
+ :type '(file :must-match t :match #'file-executable-p)
+ :risky t)
+
+;;;###autoload
+(defun google-java-format-region (start end)
+ "Use google-java-format to format the code between START and END.
+If called interactively, uses the region, if there is one. If
+there is no region, then formats the current line."
+ (interactive
+ (if (use-region-p)
+ (list (region-beginning) (region-end))
+ (list (point) (1+ (point)))))
+ (let ((cursor (point))
+ (temp-buffer (generate-new-buffer " *google-java-format-temp*"))
+ (stderr-file (make-temp-file "google-java-format")))
+ (unwind-protect
+ (let ((status (call-process-region
+ ;; Note that emacs character positions are 1-indexed,
+ ;; and google-java-format is 0-indexed, so we have to
+ ;; subtract 1 from START to line it up correctly.
+ (point-min) (point-max)
+ google-java-format-executable
+ nil (list temp-buffer stderr-file) t
+ "--offset" (number-to-string (1- start))
+ "--length" (number-to-string (- end start))
+ "-"))
+ (stderr
+ (with-temp-buffer
+ (insert-file-contents stderr-file)
+ (when (> (point-max) (point-min))
+ (insert ": "))
+ (buffer-substring-no-properties
+ (point-min) (line-end-position)))))
+ (cond
+ ((stringp status)
+ (error "google-java-format killed by signal %s%s" status stderr))
+ ((not (zerop status))
+ (error "google-java-format failed with code %d%s" status stderr))
+ (t (message "google-java-format succeeded%s" stderr)
+ (delete-region (point-min) (point-max))
+ (insert-buffer-substring temp-buffer)
+ (goto-char cursor))))
+ (delete-file stderr-file)
+ (when (buffer-name temp-buffer) (kill-buffer temp-buffer)))))
+
+;;;###autoload
+(defun google-java-format-buffer ()
+ "Use google-java-format to format the current buffer."
+ (interactive)
+ (google-java-format-region (point-min) (point-max)))
+
+;;;###autoload
+(defalias 'google-java-format 'google-java-format-region)
+
+(provide 'google-java-format)
+
+;;; google-java-format.el ends here
diff --git a/core/src/test/java/com/google/googlejavaformat/NewlinesTest.java b/core/src/test/java/com/google/googlejavaformat/NewlinesTest.java
new file mode 100644
index 0000000..4b7dab7
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/NewlinesTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** {@link Newlines}Test */
+@RunWith(JUnit4.class)
+public class NewlinesTest {
+ @Test
+ public void offsets() {
+ assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\nbar\n")))
+ .containsExactly(0, 4, 8);
+ assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\nbar"))).containsExactly(0, 4);
+
+ assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\rbar\r")))
+ .containsExactly(0, 4, 8);
+ assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\rbar"))).containsExactly(0, 4);
+
+ assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\r\nbar\r\n")))
+ .containsExactly(0, 5, 10);
+ assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\r\nbar")))
+ .containsExactly(0, 5);
+ }
+
+ @Test
+ public void lines() {
+ assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\nbar\n")))
+ .containsExactly("foo\n", "bar\n");
+ assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\nbar")))
+ .containsExactly("foo\n", "bar");
+
+ assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\rbar\r")))
+ .containsExactly("foo\r", "bar\r");
+ assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\rbar")))
+ .containsExactly("foo\r", "bar");
+
+ assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\r\nbar\r\n")))
+ .containsExactly("foo\r\n", "bar\r\n");
+ assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\r\nbar")))
+ .containsExactly("foo\r\n", "bar");
+ }
+
+ @Test
+ public void terminalOffset() {
+ Iterator<Integer> it = Newlines.lineOffsetIterator("foo\nbar\n");
+ it.next();
+ it.next();
+ it.next();
+ try {
+ it.next();
+ fail();
+ } catch (NoSuchElementException e) {
+ // expected
+ }
+
+ it = Newlines.lineOffsetIterator("foo\nbar");
+ it.next();
+ it.next();
+ try {
+ it.next();
+ fail();
+ } catch (NoSuchElementException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void terminalLine() {
+ Iterator<String> it = Newlines.lineIterator("foo\nbar\n");
+ it.next();
+ it.next();
+ try {
+ it.next();
+ fail();
+ } catch (NoSuchElementException e) {
+ // expected
+ }
+
+ it = Newlines.lineIterator("foo\nbar");
+ it.next();
+ it.next();
+ try {
+ it.next();
+ fail();
+ } catch (NoSuchElementException e) {
+ // expected
+ }
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/ArrayDimensionTest.java b/core/src/test/java/com/google/googlejavaformat/java/ArrayDimensionTest.java
new file mode 100644
index 0000000..3da9834
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/ArrayDimensionTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for array dimension handling, especially mixed array notation and type annotations on
+ * dimensions.
+ */
+@RunWith(Parameterized.class)
+public class ArrayDimensionTest {
+ @Parameters
+ public static Iterable<Object[]> parameters() {
+ String[] inputs = {
+ // mixed array notation multi-variable declarations
+ "int a[], b @B [], c @B [][] @C [];",
+ "int a @A [], b @B [], c @B [] @C [];",
+ "int a[] @A [], b @B [], c @B [] @C [];",
+ "int a, b @B [], c @B [] @C [];",
+ "int @A [] a, b @B [], c @B [] @C [];",
+ "int @A [] a = {}, b @B [] = {{}}, c @B [] @C [] = {{{}}};",
+ // mixed array notation methods
+ "int[][][][][] argh()[] @A @B [] @C @D [][] {}",
+ "int[][] @T [] @U [] @V @W [][][] argh() @A @B [] @C @D [] {}",
+ "int e1() @A [] {}",
+ "int f1()[] @A [] {}",
+ "int g1() @A [] @B [] {}",
+ "int h1() @A @B [] @C @B [] {}",
+ "int[] e2() @A [] {}",
+ "int @X [] f2()[] @A [] {}",
+ "int[] g2() @A [] @B [] {}",
+ "int @X [] h2() @A @B [] @C @B [] {}",
+ "@X int[] e3() @A [] {}",
+ "@X int @Y [] f3()[] @A [] {}",
+ "@X int @Y [] g3() @A [] @B [] {}",
+ "@X int[] h3() @A @B [] @C @B [] {}",
+ // mixed array notation single-variable declarations
+ "int[] e2() @A [] {}",
+ "int @I [] f2()[] @A [] {}",
+ "int[] @J [] g2() @A [] @B [] {}",
+ "int @I [] @J [] h2() @A @B [] @C @B [] {}",
+ "int a1[];",
+ "int b1 @A [];",
+ "int c1[] @A [];",
+ "int d1 @A [] @B [];",
+ "int[] a2[];",
+ "int @A [] b2 @B [];",
+ "int[] c2[] @A [];",
+ "int @A [] d2 @B [] @C [];",
+ // array dimension expressions
+ "int[][] a0 = new @A int[0];",
+ "int[][] a1 = new int @A [0] @B [];",
+ "int[][] a2 = new int[0] @A [] @B [];",
+ "int[][] a4 = new int[0] @A [][] @B [];",
+ // nested array type uses
+ "List<int @A [] @B []> xs;",
+ "List<int[] @A [][] @B []> xs;",
+ };
+ return Iterables.transform(Arrays.asList(inputs), input -> new Object[] {input});
+ }
+
+ private final String input;
+
+ public ArrayDimensionTest(String input) {
+ this.input = input;
+ }
+
+ @Test
+ public void format() throws Exception {
+ String source = "class T {" + input + "}";
+ String formatted = new Formatter().formatSource(source);
+ String statement =
+ formatted.substring("class T {".length(), formatted.length() - "}\n".length());
+ // ignore line breaks after declaration-style annotations
+ statement =
+ Joiner.on(' ').join(Splitter.on('\n').omitEmptyStrings().trimResults().split(statement));
+ assertThat(statement).isEqualTo(input);
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/CommandLineFlagsTest.java b/core/src/test/java/com/google/googlejavaformat/java/CommandLineFlagsTest.java
new file mode 100644
index 0000000..e5fbc9f
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/CommandLineFlagsTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for command-line flags.
+ */
+@RunWith(JUnit4.class)
+public class CommandLineFlagsTest {
+
+ // TODO(eaftan): Disallow passing both -lines and -offset/-length, like clang-format.
+
+ @Test
+ public void formatInPlaceRequiresAtLeastOneFile() throws UsageException {
+ try {
+ Main.processArgs("-i");
+ fail();
+ } catch (UsageException e) {
+ // expected
+ }
+
+ try {
+ Main.processArgs("-i", "-");
+ fail();
+ } catch (UsageException e) {
+ // expected
+ }
+
+ Main.processArgs("-i", "Foo.java");
+ Main.processArgs("-i", "Foo.java", "Bar.java");
+ }
+
+ @Test
+ public void formatASubsetRequiresExactlyOneFile() throws UsageException {
+ Main.processArgs("-lines", "10", "Foo.java");
+
+ try {
+ Main.processArgs("-lines", "10");
+ fail();
+ } catch (UsageException e) {
+ // expected
+ }
+
+ try {
+ Main.processArgs("-lines", "10", "Foo.java", "Bar.java");
+ fail();
+ } catch (UsageException e) {
+ // expected
+ }
+
+ Main.processArgs("-offset", "10", "-length", "10", "Foo.java");
+
+ try {
+ Main.processArgs("-offset", "10", "-length", "10");
+ fail();
+ } catch (UsageException e) {
+ // expected
+ }
+
+ try {
+ Main.processArgs("-offset", "10", "-length", "10", "Foo.java", "Bar.java");
+ fail();
+ } catch (UsageException e) {
+ // expected
+ }
+ }
+
+ // TODO(eaftan): clang-format allows a single offset with no length, which means to format
+ // up to the end of the file. We should match that behavior.
+ @Test
+ public void numberOfOffsetsMustMatchNumberOfLengths() throws UsageException {
+ Main.processArgs("-offset", "10", "-length", "20", "Foo.java");
+
+ try {
+ Main.processArgs("-offset", "10", "-length", "20", "-offset", "50", "Foo.java");
+ fail();
+ } catch (UsageException e) {
+ // expected
+ }
+
+ try {
+ Main.processArgs("-offset", "10", "-length", "20", "-length", "50", "Foo.java");
+ fail();
+ } catch (UsageException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void noFilesToFormatRequiresEitherHelpOrVersion() throws UsageException {
+ Main.processArgs("-version");
+
+ Main.processArgs("-help");
+
+ try {
+ Main.processArgs();
+ fail();
+ } catch (UsageException e) {
+ // expected
+ }
+
+ try {
+ Main.processArgs("-aosp");
+ fail();
+ } catch (UsageException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void stdinAndFiles() {
+ try {
+ Main.processArgs("-", "A.java");
+ fail();
+ } catch (UsageException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .contains("cannot format from standard input and files simultaneously");
+ }
+ }
+
+ @Test
+ public void inPlaceStdin() {
+ try {
+ Main.processArgs("-i", "-");
+ fail();
+ } catch (UsageException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .contains("in-place formatting was requested but no files were provided");
+ }
+ }
+
+ @Test
+ public void inPlaceDryRun() {
+ try {
+ Main.processArgs("-i", "-n", "A.java");
+ fail();
+ } catch (UsageException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .contains("cannot use --dry-run and --in-place at the same time");
+ }
+ }
+
+ @Test
+ public void assumeFileNameOnlyWorksWithStdin() {
+ try {
+ Main.processArgs("--assume-filename=Foo.java", "Foo.java");
+ fail();
+ } catch (UsageException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .contains("--assume-filename is only supported when formatting standard input");
+ }
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/CommandLineOptionsParserTest.java b/core/src/test/java/com/google/googlejavaformat/java/CommandLineOptionsParserTest.java
new file mode 100644
index 0000000..8d71f4d
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/CommandLineOptionsParserTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Range;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** {@link CommandLineOptionsParser}Test */
+@RunWith(JUnit4.class)
+public class CommandLineOptionsParserTest {
+
+ @Rule public TemporaryFolder testFolder = new TemporaryFolder();
+
+ @Test
+ public void defaults() {
+ CommandLineOptions options = CommandLineOptionsParser.parse(Collections.<String>emptyList());
+ assertThat(options.files()).isEmpty();
+ assertThat(options.stdin()).isFalse();
+ assertThat(options.aosp()).isFalse();
+ assertThat(options.help()).isFalse();
+ assertThat(options.lengths()).isEmpty();
+ assertThat(options.lines().asRanges()).isEmpty();
+ assertThat(options.offsets()).isEmpty();
+ assertThat(options.inPlace()).isFalse();
+ assertThat(options.version()).isFalse();
+ assertThat(options.sortImports()).isTrue();
+ assertThat(options.removeUnusedImports()).isTrue();
+ assertThat(options.dryRun()).isFalse();
+ assertThat(options.setExitIfChanged()).isFalse();
+ assertThat(options.reflowLongStrings()).isTrue();
+ assertThat(options.formatJavadoc()).isTrue();
+ }
+
+ @Test
+ public void hello() {
+ CommandLineOptions options =
+ CommandLineOptionsParser.parse(
+ Arrays.asList("-lines=1:10,20:30", "-i", "Hello.java", "Goodbye.java"));
+ assertThat(options.lines().asRanges())
+ .containsExactly(Range.closedOpen(0, 10), Range.closedOpen(19, 30));
+ assertThat(options.inPlace()).isTrue();
+ assertThat(options.files()).containsExactly("Hello.java", "Goodbye.java");
+ }
+
+ @Test
+ public void stdin() {
+ assertThat(CommandLineOptionsParser.parse(Arrays.asList("-")).stdin()).isTrue();
+ }
+
+ @Test
+ public void aosp() {
+ assertThat(CommandLineOptionsParser.parse(Arrays.asList("-aosp")).aosp()).isTrue();
+ }
+
+ @Test
+ public void help() {
+ assertThat(CommandLineOptionsParser.parse(Arrays.asList("-help")).help()).isTrue();
+ }
+
+ @Test
+ public void lengths() {
+ assertThat(
+ CommandLineOptionsParser.parse(Arrays.asList("-length", "1", "--length", "2"))
+ .lengths())
+ .containsExactly(1, 2);
+ }
+
+ @Test
+ public void lines() {
+ assertThat(
+ CommandLineOptionsParser.parse(
+ Arrays.asList("--lines", "1:2", "-lines=4:5", "--line", "7:8", "-line=10:11"))
+ .lines()
+ .asRanges())
+ .containsExactly(
+ Range.closedOpen(0, 2),
+ Range.closedOpen(3, 5),
+ Range.closedOpen(6, 8),
+ Range.closedOpen(9, 11));
+ }
+
+ @Test
+ public void offset() {
+ assertThat(
+ CommandLineOptionsParser.parse(Arrays.asList("-offset", "1", "--offset", "2"))
+ .offsets())
+ .containsExactly(1, 2);
+ }
+
+ @Test
+ public void inPlace() {
+ assertThat(CommandLineOptionsParser.parse(Arrays.asList("-i", "A.java")).inPlace()).isTrue();
+ }
+
+ @Test
+ public void version() {
+ assertThat(CommandLineOptionsParser.parse(Arrays.asList("-v")).version()).isTrue();
+ }
+
+ @Test
+ public void skipSortingImports() {
+ assertThat(
+ CommandLineOptionsParser.parse(Arrays.asList("--skip-sorting-imports")).sortImports())
+ .isFalse();
+ }
+
+ @Test
+ public void skipRemovingUnusedImports() {
+ assertThat(
+ CommandLineOptionsParser.parse(Arrays.asList("--skip-removing-unused-imports"))
+ .removeUnusedImports())
+ .isFalse();
+ }
+
+ @Test
+ public void dryRun() {
+ assertThat(CommandLineOptionsParser.parse(Arrays.asList("--dry-run")).dryRun()).isTrue();
+ assertThat(CommandLineOptionsParser.parse(Arrays.asList("-n")).dryRun()).isTrue();
+ }
+
+ @Test
+ public void setExitIfChanged() {
+ assertThat(
+ CommandLineOptionsParser.parse(Arrays.asList("--set-exit-if-changed"))
+ .setExitIfChanged())
+ .isTrue();
+ }
+
+ // TODO(cushon): consider handling this in the parser and reporting a more detailed error
+ @Test
+ public void illegalLines() {
+ try {
+ CommandLineOptionsParser.parse(Arrays.asList("-lines=1:1", "-lines=1:1"));
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).contains("overlap");
+ }
+ }
+
+ @Test
+ public void paramsFile() throws IOException {
+ Path outer = testFolder.newFile("outer").toPath();
+ Path exit = testFolder.newFile("exit").toPath();
+ Path nested = testFolder.newFile("nested").toPath();
+
+ String[] args = {"--dry-run", "@" + exit, "L", "@" + outer, "Q"};
+
+ Files.write(exit, "--set-exit-if-changed".getBytes(UTF_8));
+ Files.write(outer, ("M\n@" + nested.toAbsolutePath() + "\nP").getBytes(UTF_8));
+ Files.write(nested, "ℕ\n\n \n@@O\n".getBytes(UTF_8));
+
+ CommandLineOptions options = CommandLineOptionsParser.parse(Arrays.asList(args));
+ assertThat(options.files()).containsExactly("L", "M", "ℕ", "@O", "P", "Q");
+ }
+
+ @Test
+ public void assumeFilename() {
+ assertThat(
+ CommandLineOptionsParser.parse(Arrays.asList("--assume-filename", "Foo.java"))
+ .assumeFilename())
+ .hasValue("Foo.java");
+ assertThat(CommandLineOptionsParser.parse(Arrays.asList("Foo.java")).assumeFilename())
+ .isEmpty();
+ }
+
+ @Test
+ public void skipReflowLongStrings() {
+ assertThat(
+ CommandLineOptionsParser.parse(Arrays.asList("--skip-reflowing-long-strings"))
+ .reflowLongStrings())
+ .isFalse();
+ }
+
+ @Test
+ public void skipJavadocFormatting() {
+ assertThat(
+ CommandLineOptionsParser.parse(Arrays.asList("--skip-javadoc-formatting"))
+ .formatJavadoc())
+ .isFalse();
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/DiagnosticTest.java b/core/src/test/java/com/google/googlejavaformat/java/DiagnosticTest.java
new file mode 100644
index 0000000..0b81ba6
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/DiagnosticTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Joiner;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Locale;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for error reporting. */
+@RunWith(JUnit4.class)
+public class DiagnosticTest {
+ @Rule public TemporaryFolder testFolder = new TemporaryFolder();
+
+ private Locale backupLocale;
+
+ @Before
+ public void setUpLocale() throws Exception {
+ backupLocale = Locale.getDefault();
+ Locale.setDefault(Locale.ROOT);
+ }
+
+ @After
+ public void restoreLocale() throws Exception {
+ Locale.setDefault(backupLocale);
+ }
+
+ @Test
+ public void parseError() throws Exception {
+ String input =
+ Joiner.on('\n')
+ .join(
+ "public class InvalidSyntax {",
+ " private static NumPrinter {",
+ " public static void print(int n) {",
+ " System.out.printf(\"%d%n\", n);",
+ " }",
+ " }",
+ "",
+ " public static void main(String[] args) {",
+ " NumPrinter.print(args.length);",
+ " }",
+ "}");
+
+ StringWriter stdout = new StringWriter();
+ StringWriter stderr = new StringWriter();
+ Main main = new Main(new PrintWriter(stdout, true), new PrintWriter(stderr, true), System.in);
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("InvalidSyntax.java");
+ Files.write(path, input.getBytes(UTF_8));
+
+ int result = main.format(path.toString());
+ assertThat(stdout.toString()).isEmpty();
+ assertThat(stderr.toString()).contains("InvalidSyntax.java:2:29: error: <identifier> expected");
+ assertThat(result).isEqualTo(1);
+ }
+
+ @Test
+ public void lexError() throws Exception {
+ String input = "\\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuu00not-actually-a-unicode-escape-sequence";
+
+ StringWriter stdout = new StringWriter();
+ StringWriter stderr = new StringWriter();
+ Main main = new Main(new PrintWriter(stdout, true), new PrintWriter(stderr, true), System.in);
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("InvalidSyntax.java");
+ Files.write(path, input.getBytes(UTF_8));
+
+ int result = main.format(path.toString());
+ assertThat(stdout.toString()).isEmpty();
+ assertThat(stderr.toString())
+ .contains("InvalidSyntax.java:1:35: error: illegal unicode escape");
+ assertThat(result).isEqualTo(1);
+ }
+
+ @Test
+ public void oneFileParseError() throws Exception {
+ String one = "class One {\n";
+ String two = "class Two {}\n";
+
+ StringWriter stdout = new StringWriter();
+ StringWriter stderr = new StringWriter();
+ Main main = new Main(new PrintWriter(stdout, true), new PrintWriter(stderr, true), System.in);
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path pathOne = tmpdir.resolve("One.java");
+ Files.write(pathOne, one.getBytes(UTF_8));
+
+ Path pathTwo = tmpdir.resolve("Two.java");
+ Files.write(pathTwo, two.getBytes(UTF_8));
+
+ int result = main.format(pathOne.toString(), pathTwo.toString());
+ assertThat(stdout.toString()).isEqualTo(two);
+ assertThat(stderr.toString()).contains("One.java:1:13: error: reached end of file");
+ assertThat(result).isEqualTo(1);
+ }
+
+ @Test
+ public void oneFileParseErrorReplace() throws Exception {
+ String one = "class One {}}\n";
+ String two = "class Two {\n}\n";
+
+ StringWriter stdout = new StringWriter();
+ StringWriter stderr = new StringWriter();
+ Main main = new Main(new PrintWriter(stdout, true), new PrintWriter(stderr, true), System.in);
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path pathOne = tmpdir.resolve("One.java");
+ Files.write(pathOne, one.getBytes(UTF_8));
+
+ Path pathTwo = tmpdir.resolve("Two.java");
+ Files.write(pathTwo, two.getBytes(UTF_8));
+
+ int result = main.format("-i", pathOne.toString(), pathTwo.toString());
+ assertThat(stdout.toString()).isEmpty();
+ assertThat(stderr.toString()).contains("One.java:1:14: error: class, interface");
+ assertThat(result).isEqualTo(1);
+ // don't edit files with parse errors
+ assertThat(Files.readAllLines(pathOne, UTF_8)).containsExactly("class One {}}");
+ assertThat(Files.readAllLines(pathTwo, UTF_8)).containsExactly("class Two {}");
+ }
+
+ @Test
+ public void parseError2() throws FormatterException, IOException, UsageException {
+ String input = "class Foo { void f() {\n g() } }";
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("A.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {path.toString()};
+ int exitCode = main.format(args);
+
+ assertThat(exitCode).isEqualTo(1);
+ assertThat(err.toString()).contains("A.java:2:6: error: ';' expected");
+ }
+
+ @Test
+ public void parseErrorStdin() throws FormatterException, IOException, UsageException {
+ String input = "class Foo { void f() {\n g() } }";
+
+ InputStream inStream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), inStream);
+ String[] args = {"-"};
+ int exitCode = main.format(args);
+
+ assertThat(exitCode).isEqualTo(1);
+ assertThat(err.toString()).contains("<stdin>:2:6: error: ';' expected");
+ }
+
+ @Test
+ public void lexError2() throws FormatterException, IOException, UsageException {
+ String input = "class Foo { void f() {\n g('foo'); } }";
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("A.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {path.toString()};
+ int exitCode = main.format(args);
+
+ assertThat(exitCode).isEqualTo(1);
+ assertThat(err.toString()).contains("A.java:2:5: error: unclosed character literal");
+ }
+
+ @Test
+ public void lexErrorStdin() throws FormatterException, IOException, UsageException {
+ String input = "class Foo { void f() {\n g('foo'); } }";
+ InputStream inStream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), inStream);
+ String[] args = {"-"};
+ int exitCode = main.format(args);
+
+ assertThat(exitCode).isEqualTo(1);
+ assertThat(err.toString()).contains("<stdin>:2:5: error: unclosed character literal");
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java
new file mode 100644
index 0000000..289ea1b
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION;
+import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
+import static com.google.common.io.Files.getFileExtension;
+import static com.google.common.io.Files.getNameWithoutExtension;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.CharStreams;
+import com.google.common.reflect.ClassPath;
+import com.google.common.reflect.ClassPath.ResourceInfo;
+import com.google.googlejavaformat.Newlines;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Integration test for google-java-format. */
+@RunWith(Parameterized.class)
+public class FormatterIntegrationTest {
+
+ private static final ImmutableSet<String> JAVA14_TESTS = ImmutableSet.of("java14");
+
+ @Parameters(name = "{index}: {0}")
+ public static Iterable<Object[]> data() throws IOException {
+ Path testDataPath = Paths.get("com/google/googlejavaformat/java/testdata");
+ ClassLoader classLoader = FormatterIntegrationTest.class.getClassLoader();
+ Map<String, String> inputs = new TreeMap<>();
+ Map<String, String> outputs = new TreeMap<>();
+ for (ResourceInfo resourceInfo : ClassPath.from(classLoader).getResources()) {
+ String resourceName = resourceInfo.getResourceName();
+ Path resourceNamePath = Paths.get(resourceName);
+ if (resourceNamePath.startsWith(testDataPath)) {
+ Path subPath = testDataPath.relativize(resourceNamePath);
+ assertEquals("bad testdata file names", 1, subPath.getNameCount());
+ String baseName = getNameWithoutExtension(subPath.getFileName().toString());
+ String extension = getFileExtension(subPath.getFileName().toString());
+ String contents;
+ try (InputStream stream =
+ FormatterIntegrationTest.class.getClassLoader().getResourceAsStream(resourceName)) {
+ contents = CharStreams.toString(new InputStreamReader(stream, UTF_8));
+ }
+ switch (extension) {
+ case "input":
+ inputs.put(baseName, contents);
+ break;
+ case "output":
+ outputs.put(baseName, contents);
+ break;
+ default:
+ }
+ }
+ }
+ List<Object[]> testInputs = new ArrayList<>();
+ assertEquals("unmatched inputs and outputs", inputs.size(), outputs.size());
+ for (Map.Entry<String, String> entry : inputs.entrySet()) {
+ String fileName = entry.getKey();
+ String input = inputs.get(fileName);
+ assertTrue("unmatched input", outputs.containsKey(fileName));
+ String expectedOutput = outputs.get(fileName);
+ if (JAVA14_TESTS.contains(fileName) && getMajor() < 14) {
+ continue;
+ }
+ testInputs.add(new Object[] {fileName, input, expectedOutput});
+ }
+ return testInputs;
+ }
+
+ private static int getMajor() {
+ try {
+ Method versionMethod = Runtime.class.getMethod("version");
+ Object version = versionMethod.invoke(null);
+ return (int) version.getClass().getMethod("major").invoke(version);
+ } catch (Exception e) {
+ // continue below
+ }
+ int version = (int) Double.parseDouble(JAVA_CLASS_VERSION.value());
+ if (49 <= version && version <= 52) {
+ return version - (49 - 5);
+ }
+ throw new IllegalStateException("Unknown Java version: " + JAVA_SPECIFICATION_VERSION.value());
+ }
+
+ private final String name;
+ private final String input;
+ private final String expected;
+ private final String separator;
+
+ public FormatterIntegrationTest(String name, String input, String expected) {
+ this.name = name;
+ this.input = input;
+ this.expected = expected;
+ this.separator = Newlines.getLineEnding(expected);
+ }
+
+ @Test
+ public void format() {
+ try {
+ String output = new Formatter().formatSource(input);
+ assertEquals("bad output for " + name, expected, output);
+ } catch (FormatterException e) {
+ fail(String.format("Formatter crashed on %s: %s", name, e.getMessage()));
+ }
+ }
+
+ @Test
+ public void idempotentLF() {
+ try {
+ String mangled = expected.replace(separator, "\n");
+ String output = new Formatter().formatSource(mangled);
+ assertEquals("bad output for " + name, mangled, output);
+ } catch (FormatterException e) {
+ fail(String.format("Formatter crashed on %s: %s", name, e.getMessage()));
+ }
+ }
+
+ @Test
+ public void idempotentCR() throws IOException {
+ try {
+ String mangled = expected.replace(separator, "\r");
+ String output = new Formatter().formatSource(mangled);
+ assertEquals("bad output for " + name, mangled, output);
+ } catch (FormatterException e) {
+ fail(String.format("Formatter crashed on %s: %s", name, e.getMessage()));
+ }
+ }
+
+ @Test
+ public void idempotentCRLF() {
+ try {
+ String mangled = expected.replace(separator, "\r\n");
+ String output = new Formatter().formatSource(mangled);
+ assertEquals("bad output for " + name, mangled, output);
+ } catch (FormatterException e) {
+ fail(String.format("Formatter crashed on %s: %s", name, e.getMessage()));
+ }
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/FormatterTest.java b/core/src/test/java/com/google/googlejavaformat/java/FormatterTest.java
new file mode 100644
index 0000000..3f6e974
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterTest.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Joiner;
+import com.google.common.io.CharStreams;
+import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration test for google-java-format. */
+@RunWith(JUnit4.class)
+public final class FormatterTest {
+
+ @Rule public TemporaryFolder testFolder = new TemporaryFolder();
+
+ @Test
+ public void testFormatAosp() throws Exception {
+ // don't forget to misspell "long", or you will be mystified for a while
+ String input =
+ "class A{void b(){while(true){weCanBeCertainThatThisWillEndUpGettingWrapped("
+ + "because, it, is, just, so, very, very, very, very, looong);}}}";
+ String expectedOutput =
+ Joiner.on("\n")
+ .join(
+ "class A {",
+ " void b() {",
+ " while (true) {",
+ " weCanBeCertainThatThisWillEndUpGettingWrapped(",
+ " because, it, is, just, so, very, very, very, very, looong);",
+ " }",
+ " }",
+ "}",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("A.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"--aosp", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void testFormatNonJavaFiles() throws Exception {
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+
+ // should succeed because non-Java files are skipped
+ assertThat(main.format("foo.go")).isEqualTo(0);
+ assertThat(err.toString()).contains("Skipping non-Java file: " + "foo.go");
+
+ // format still fails on missing files
+ assertThat(main.format("Foo.java")).isEqualTo(1);
+ assertThat(err.toString()).contains("Foo.java: could not read file: ");
+ }
+
+ @Test
+ public void testFormatStdinStdoutWithDashFlag() throws Exception {
+ String input = "class Foo{\n" + "void f\n" + "() {\n" + "}\n" + "}\n";
+ String expectedOutput = "class Foo {\n" + " void f() {}\n" + "}\n";
+
+ InputStream in = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ InputStream oldIn = System.in;
+ System.setIn(in);
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ assertThat(main.format("-")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+
+ System.setIn(oldIn);
+ }
+
+ @Test
+ public void testFormatLengthUpToEOF() throws Exception {
+ String input = "class Foo{\n" + "void f\n" + "() {\n" + "}\n" + "}\n\n\n\n\n\n";
+ String expectedOutput = "class Foo {\n" + " void f() {}\n" + "}\n";
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"--offset", "0", "--length", String.valueOf(input.length()), path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void testFormatLengthOutOfRange() throws Exception {
+ String input = "class Foo{}\n";
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"--offset", "0", "--length", "9999", path.toString()};
+ assertThat(main.format(args)).isEqualTo(1);
+ assertThat(err.toString())
+ .contains("error: invalid length 9999, offset + length (9999) is outside the file");
+ }
+
+ @Test
+ public void blankInClassBody() throws FormatterException {
+ String input = "package test;\nclass T {\n\n}\n";
+ String output = new Formatter().formatSource(input);
+ String expect = "package test;\n\nclass T {}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void blankInClassBodyNoTrailing() throws FormatterException {
+ String input = "package test;\nclass T {\n\n}";
+ String output = new Formatter().formatSource(input);
+ String expect = "package test;\n\nclass T {}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void docCommentTrailingBlank() throws FormatterException {
+ String input = "class T {\n/** asd */\n\nint x;\n}";
+ String output = new Formatter().formatSource(input);
+ String expect = "class T {\n /** asd */\n int x;\n}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void blockCommentInteriorTrailingBlank() throws FormatterException {
+ String input = "class T {\n/*\n* asd \n* fgh\n*/ \n\nint x;\n}";
+ String output = new Formatter().formatSource(input);
+ String expect = "class T {\n /*\n * asd\n * fgh\n */\n\n int x;\n}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void blockCommentTrailingBlank() throws FormatterException {
+ String input = "class T {\n/* asd */ \n\nint x;\n}";
+ String output = new Formatter().formatSource(input);
+ String expect = "class T {\n /* asd */\n\n int x;\n}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void lineCommentTrailingBlank() throws FormatterException {
+ String input = "class T {\n// asd \n\nint x;\n}";
+ String output = new Formatter().formatSource(input);
+ String expect = "class T {\n // asd\n\n int x;\n}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void lineCommentTrailingThinSpace() throws FormatterException {
+ // The Unicode thin space is matched by CharMatcher.whitespace() but not trim().
+ String input = "class T {\n // asd\u2009\n}\n";
+ String output = new Formatter().formatSource(input);
+ String expect = "class T {\n // asd\n}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void noBlankAfterLineCommentWithInteriorBlankLine() throws FormatterException {
+ String input = "class T {\n// asd \n\n// dsa \nint x;\n}";
+ String output = new Formatter().formatSource(input);
+ String expect = "class T {\n // asd\n\n // dsa\n int x;\n}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void badConstructor() throws FormatterException {
+ String input = "class X { Y() {} }";
+ String output = new Formatter().formatSource(input);
+ String expect = "class X {\n Y() {}\n}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void voidMethod() throws FormatterException {
+ String input = "class X { void Y() {} }";
+ String output = new Formatter().formatSource(input);
+ String expect = "class X {\n void Y() {}\n}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ private static final String UNORDERED_IMPORTS =
+ Joiner.on('\n')
+ .join(
+ "import com.google.common.base.Preconditions;",
+ "",
+ "import static org.junit.Assert.fail;",
+ "import static com.google.truth.Truth.assertThat;",
+ "",
+ "import org.junit.runners.JUnit4;",
+ "import org.junit.runner.RunWith;",
+ "",
+ "import java.util.List;",
+ "",
+ "import javax.annotations.Nullable;");
+
+ @Test
+ public void importsNotReorderedByDefault() throws FormatterException {
+ String input =
+ "package com.google.example;\n" + UNORDERED_IMPORTS + "\npublic class ExampleTest {}\n";
+ String output = new Formatter().formatSource(input);
+ String expect =
+ "package com.google.example;\n\n" + UNORDERED_IMPORTS + "\n\npublic class ExampleTest {}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void importsFixedIfRequested() throws FormatterException {
+ String input =
+ "package com.google.example;\n"
+ + UNORDERED_IMPORTS
+ + "\npublic class ExampleTest {\n"
+ + " @Nullable List<?> xs;\n"
+ + "}\n";
+ String output = new Formatter().formatSourceAndFixImports(input);
+ String expect =
+ "package com.google.example;\n\n"
+ + "import java.util.List;\n"
+ + "import javax.annotations.Nullable;\n\n"
+ + "public class ExampleTest {\n"
+ + " @Nullable List<?> xs;\n"
+ + "}\n";
+ assertThat(output).isEqualTo(expect);
+ }
+
+ @Test
+ public void importOrderingWithoutFormatting() throws IOException, UsageException {
+ importOrdering(
+ "--fix-imports-only", "com/google/googlejavaformat/java/testimports/A.imports-only");
+ }
+
+ @Test
+ public void importOrderingAndFormatting() throws IOException, UsageException {
+ importOrdering(null, "com/google/googlejavaformat/java/testimports/A.imports-and-formatting");
+ }
+
+ @Test
+ public void formattingWithoutImportOrdering() throws IOException, UsageException {
+ importOrdering(
+ "--skip-sorting-imports",
+ "com/google/googlejavaformat/java/testimports/A.formatting-and-unused-import-removal");
+ }
+
+ @Test
+ public void formattingWithoutRemovingUnusedImports() throws IOException, UsageException {
+ importOrdering(
+ "--skip-removing-unused-imports",
+ "com/google/googlejavaformat/java/testimports/A.formatting-and-import-sorting");
+ }
+
+ private void importOrdering(String sortArg, String outputResourceName)
+ throws IOException, UsageException {
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+
+ String inputResourceName = "com/google/googlejavaformat/java/testimports/A.input";
+ String input = getResource(inputResourceName);
+ String expectedOutput = getResource(outputResourceName);
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args =
+ sortArg != null
+ ? new String[] {sortArg, "-i", path.toString()}
+ : new String[] {"-i", path.toString()};
+ main.format(args);
+
+ assertThat(err.toString()).isEmpty();
+ assertThat(out.toString()).isEmpty();
+ String output = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ private String getResource(String resourceName) throws IOException {
+ try (InputStream stream = getClass().getClassLoader().getResourceAsStream(resourceName)) {
+ assertWithMessage("Missing resource: " + resourceName).that(stream).isNotNull();
+ return CharStreams.toString(new InputStreamReader(stream, StandardCharsets.UTF_8));
+ }
+ }
+
+ // regression test for google-java-format#47
+ @Test
+ public void testTrailingCommentWithoutTerminalNewline() throws Exception {
+ assertThat(new Formatter().formatSource("/*\n * my comment */"))
+ .isEqualTo("/*\n * my comment */\n");
+ }
+
+ @Test
+ public void testEmptyArray() throws Exception {
+ assertThat(new Formatter().formatSource("class T { int x[] = {,}; }"))
+ .isEqualTo("class T {\n int x[] = {,};\n}\n");
+ }
+
+ @Test
+ public void stringEscapeLength() throws Exception {
+ assertThat(new Formatter().formatSource("class T {{ f(\"\\\"\"); }}"))
+ .isEqualTo("class T {\n {\n f(\"\\\"\");\n }\n}\n");
+ }
+
+ @Test
+ public void wrapLineComment() throws Exception {
+ assertThat(
+ new Formatter()
+ .formatSource(
+ "class T {\n"
+ + " public static void main(String[] args) { // one long incredibly"
+ + " unbroken sentence moving from topic to topic so that no-one had a"
+ + " chance to interrupt;\n"
+ + " }\n"
+ + "}\n"))
+ .isEqualTo(
+ "class T {\n"
+ + " public static void main(\n"
+ + " String[]\n"
+ + " args) { // one long incredibly unbroken sentence moving"
+ + " from topic to topic so that no-one\n"
+ + " // had a chance to interrupt;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test
+ public void onlyWrapLineCommentOnWhitespace() throws Exception {
+ assertThat(
+ new Formatter()
+ .formatSource(
+ "class T {\n"
+ + " public static void main(String[] args) { // one_long_incredibly"
+ + "_unbroken_sentence_moving_from_topic_to_topic_so_that_no-one_had_a"
+ + "_chance_to_interrupt;\n"
+ + " }\n"
+ + "}\n"))
+ .isEqualTo(
+ "class T {\n"
+ + " public static void main(\n"
+ + " String[]\n"
+ + " args) { // one_long_incredibly"
+ + "_unbroken_sentence_moving_from_topic_to_topic_so_that_no-one_had_a"
+ + "_chance_to_interrupt;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test
+ public void onlyWrapLineCommentOnWhitespace_noLeadingWhitespace() throws Exception {
+ assertThat(
+ new Formatter()
+ .formatSource(
+ "class T {\n"
+ + " public static void main(String[] args) { //one_long_incredibly"
+ + "_unbroken_sentence_moving_from_topic_to_topic_so_that_no-one_had_a"
+ + "_chance_to_interrupt;\n"
+ + " }\n"
+ + "}\n"))
+ .isEqualTo(
+ "class T {\n"
+ + " public static void main(\n"
+ + " String[]\n"
+ + " args) { // one_long_incredibly"
+ + "_unbroken_sentence_moving_from_topic_to_topic_so_that_no-one_had_a"
+ + "_chance_to_interrupt;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test
+ public void throwsFormatterException() throws Exception {
+ try {
+ new Formatter().formatSourceAndFixImports("package foo; public class {");
+ fail();
+ } catch (FormatterException expected) {
+ }
+ }
+
+ @Test
+ public void blankLinesImportComment() throws FormatterException {
+ String withBlank =
+ "package p;\n"
+ + "\n"
+ + "/** test */\n"
+ + "\n"
+ + "import a.A;\n"
+ + "\n"
+ + "class T {\n"
+ + " A a;\n"
+ + "}\n";
+ String withoutBlank =
+ "package p;\n"
+ + "\n"
+ + "/** test */\n"
+ + "import a.A;\n"
+ + "\n"
+ + "class T {\n"
+ + " A a;\n"
+ + "}\n";
+
+ // Formatting deletes the blank line between the "javadoc" and the first import.
+ assertThat(new Formatter().formatSource(withBlank)).isEqualTo(withoutBlank);
+ assertThat(new Formatter().formatSourceAndFixImports(withBlank)).isEqualTo(withoutBlank);
+ assertThat(new Formatter().formatSource(withoutBlank)).isEqualTo(withoutBlank);
+ assertThat(new Formatter().formatSourceAndFixImports(withoutBlank)).isEqualTo(withoutBlank);
+
+ // Just fixing imports preserves whitespace around imports.
+ assertThat(RemoveUnusedImports.removeUnusedImports(withBlank)).isEqualTo(withBlank);
+ assertThat(ImportOrderer.reorderImports(withBlank, Style.GOOGLE)).isEqualTo(withBlank);
+ assertThat(RemoveUnusedImports.removeUnusedImports(withoutBlank)).isEqualTo(withoutBlank);
+ assertThat(ImportOrderer.reorderImports(withoutBlank, Style.GOOGLE)).isEqualTo(withoutBlank);
+ }
+
+ @Test
+ public void dontWrapMoeLineComments() throws Exception {
+ assertThat(
+ new Formatter()
+ .formatSource(
+ "class T {\n"
+ + " // MOE: one long incredibly"
+ + " unbroken sentence moving from topic to topic so that no-one had a"
+ + " chance to interrupt;\n"
+ + "}\n"))
+ .isEqualTo(
+ "class T {\n"
+ + " // MOE: one long incredibly"
+ + " unbroken sentence moving from topic to topic so that no-one had a"
+ + " chance to interrupt;\n"
+ + "}\n");
+ }
+
+ @Test
+ public void removeTrailingTabsInComments() throws Exception {
+ assertThat(
+ new Formatter()
+ .formatSource(
+ "class Foo {\n"
+ + " void f() {\n"
+ + " int x = 0; // comment\t\t\t\n"
+ + " return;\n"
+ + " }\n"
+ + "}\n"))
+ .isEqualTo(
+ "class Foo {\n"
+ + " void f() {\n"
+ + " int x = 0; // comment\n"
+ + " return;\n"
+ + " }\n"
+ + "}\n");
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/ImportOrdererTest.java b/core/src/test/java/com/google/googlejavaformat/java/ImportOrdererTest.java
new file mode 100644
index 0000000..5a6b1f9
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/ImportOrdererTest.java
@@ -0,0 +1,843 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests for {@link ImportOrderer}. */
+@RunWith(Enclosed.class)
+public class ImportOrdererTest {
+
+ /** Tests for import ordering in Google style. */
+ @RunWith(Parameterized.class)
+ public static class GoogleStyle {
+
+ private final String input;
+ private final String reordered;
+
+ public GoogleStyle(String input, String reordered) {
+ this.input = input;
+ this.reordered = reordered;
+ }
+
+ @Parameters(name = "{index}: {0}")
+ public static Collection<Object[]> parameters() {
+ // Inputs are provided as three-dimensional arrays. Each element of the outer array is a test
+ // case. It consists of two arrays of lines. The first array of lines is the test input, and
+ // the second one is the expected output. If the second array has a single element starting
+ // with !! then it is expected that ImportOrderer will throw a FormatterException with that
+ // message.
+ //
+ // If a line ends with \ then we remove the \ and don't append a \n. That allows us to check
+ // some parsing edge cases.
+ String[][][] inputsOutputs = {
+ {
+ // Empty input produces empty output.
+ {}, //
+ {}
+ },
+ {
+ {
+ "foo", "bar",
+ },
+ {
+ "foo", "bar",
+ },
+ },
+ {
+ {
+ "package foo;", //
+ "",
+ "import com.google.first.Bar;",
+ "",
+ "public class Blim {}",
+ },
+ {
+ "package foo;", //
+ "",
+ "import com.google.first.Bar;",
+ "",
+ "public class Blim {}",
+ },
+ },
+ {
+ {
+ "package foo;",
+ "",
+ "import com.google.first.Bar;",
+ "import com.google.second.Foo;",
+ "",
+ "public class Blim {}",
+ },
+ {
+ "package foo;",
+ "",
+ "import com.google.first.Bar;",
+ "import com.google.second.Foo;",
+ "",
+ "public class Blim {}",
+ },
+ },
+ {
+ {
+ "package foo;",
+ "",
+ "import com.google.second.Foo;",
+ "import com.google.first.Bar;",
+ "",
+ "public class Blim {}",
+ },
+ {
+ "package foo;",
+ "",
+ "import com.google.first.Bar;",
+ "import com.google.second.Foo;",
+ "",
+ "public class Blim {}",
+ },
+ },
+ {
+ {
+ "import java.util.Collection;",
+ "// BUG: diagnostic contains",
+ "import java.util.List;",
+ "",
+ "class B74235047 {}"
+ },
+ {
+ "import java.util.Collection;",
+ "// BUG: diagnostic contains",
+ "import java.util.List;",
+ "",
+ "class B74235047 {}"
+ }
+ },
+ {
+ {
+ "import java.util.Set;",
+ "import java.util.Collection;",
+ "// BUG: diagnostic contains",
+ "import java.util.List;",
+ "",
+ "class B74235047 {}"
+ },
+ {
+ "import java.util.Collection;",
+ "// BUG: diagnostic contains",
+ "import java.util.List;",
+ "import java.util.Set;",
+ "",
+ "class B74235047 {}"
+ }
+ },
+ {
+ {
+ "import java.util.List;",
+ "// BUG: diagnostic contains",
+ "import java.util.Set;",
+ "import java.util.Collection;",
+ "",
+ "class B74235047 {}"
+ },
+ {
+ "import java.util.Collection;",
+ "import java.util.List;",
+ "// BUG: diagnostic contains",
+ "import java.util.Set;",
+ "",
+ "class B74235047 {}"
+ }
+ },
+ {
+ {
+ "// BEGIN-STRIP",
+ "import com.google.testing.testsize.MediumTest;",
+ "import com.google.testing.testsize.MediumTestAttribute;",
+ "// END-STRIP",
+ "",
+ "class B74235047 {}"
+ },
+ {
+ "// BEGIN-STRIP",
+ "import com.google.testing.testsize.MediumTest;",
+ "import com.google.testing.testsize.MediumTestAttribute;",
+ "// END-STRIP",
+ "",
+ "class B74235047 {}"
+ }
+ },
+ {
+ {
+ "import com.google.testing.testsize.MediumTest; // Keep this import",
+ "import com.google.testing.testsize.MediumTestAttribute; // Keep this import",
+ "",
+ "class B74235047 {}"
+ },
+ {
+ "import com.google.testing.testsize.MediumTest; // Keep this import",
+ "import com.google.testing.testsize.MediumTestAttribute; // Keep this import",
+ "",
+ "class B74235047 {}"
+ }
+ },
+ {
+ {
+ "import java.util.Set;",
+ "import java.util.List;",
+ "",
+ "// This comment doesn't get moved because of the blank line.",
+ "",
+ "class B74235047 {}"
+ },
+ {
+ "import java.util.List;",
+ "import java.util.Set;",
+ "",
+ "// This comment doesn't get moved because of the blank line.",
+ "",
+ "class B74235047 {}"
+ }
+ },
+ {
+ {
+ "import b.B;",
+ "// MOE: end_strip",
+ "import c.C;",
+ "// MOE: begin_strip",
+ "import a.A;",
+ "",
+ "class B74235047 {}"
+ },
+ {
+ "import a.A;",
+ "import b.B;",
+ "// MOE: end_strip",
+ "import c.C;",
+ "// MOE: begin_strip",
+ "",
+ "class B74235047 {}"
+ }
+ },
+ {
+ {
+ // Check double semicolons
+ "package foo;",
+ "",
+ "import com.google.second.Foo;;",
+ "import com.google.first.Bar;;",
+ "",
+ "public class Blim {}",
+ },
+ {
+ "package foo;",
+ "",
+ "import com.google.first.Bar;",
+ "import com.google.second.Foo;",
+ "",
+ "public class Blim {}",
+ },
+ },
+ {
+ {
+ "package foo;",
+ "",
+ "import com.google.second.Foo;",
+ "import com.google.first.Bar;",
+ "import com.google.second.Foo;",
+ "import com.google.first.Bar;",
+ "",
+ "public class Blim {}",
+ },
+ {
+ "package foo;",
+ "",
+ "import com.google.first.Bar;",
+ "import com.google.second.Foo;",
+ "",
+ "public class Blim {}",
+ },
+ },
+ {
+ // Google style frowns on wildcard imports, but we handle them all the same.
+ {
+ "package foo;",
+ "",
+ "import com.google.second.*;",
+ "import com.google.first.Bar;",
+ "import com.google.first.*;",
+ "",
+ "public class Blim {}",
+ },
+ {
+ "package foo;",
+ "",
+ "import com.google.first.*;",
+ "import com.google.first.Bar;",
+ "import com.google.second.*;",
+ "",
+ "public class Blim {}",
+ },
+ },
+ {
+ {
+ "package com.google.example;",
+ "",
+ "import com.google.common.base.Preconditions;",
+ "",
+ "import org.junit.runner.RunWith;",
+ "import org.junit.runners.JUnit4;",
+ "",
+ "import java.util.List;",
+ "",
+ "import javax.annotations.Nullable;",
+ "",
+ "import static org.junit.Assert.fail;",
+ "import static com.google.truth.Truth.assertThat;",
+ "",
+ "@RunWith(JUnit4.class)",
+ "public class SomeTest {}",
+ },
+ {
+ "package com.google.example;",
+ "",
+ "import static com.google.truth.Truth.assertThat;",
+ "import static org.junit.Assert.fail;",
+ "",
+ "import com.google.common.base.Preconditions;",
+ "import java.util.List;",
+ "import javax.annotations.Nullable;",
+ "import org.junit.runner.RunWith;",
+ "import org.junit.runners.JUnit4;",
+ "",
+ "@RunWith(JUnit4.class)",
+ "public class SomeTest {}",
+ },
+ },
+
+ // we unindent imports, if we reorder them
+ {
+ {
+ " import com.foo.Second;", //
+ " import com.foo.First;",
+ " public class Foo {}",
+ },
+ {
+ "import com.foo.First;", //
+ "import com.foo.Second;",
+ "",
+ "public class Foo {}",
+ }
+ },
+
+ // Error cases
+ {
+ {
+ "package com.google.example;", //
+ "",
+ "import\\", // \\ means there is no newline here.
+ },
+ {
+ "!!Unexpected token after import: ",
+ }
+ },
+ {
+ {
+ "package com.google.example;", //
+ "",
+ "import",
+ },
+ {
+ "!!Unexpected token after import: \n",
+ }
+ },
+ {
+ {
+ "package com.google.example;", //
+ "",
+ "import foo\\",
+ },
+ {
+ "!!Expected ; after import",
+ }
+ },
+ {
+ {
+ "package com.google.example;", //
+ "",
+ "import foo.\\",
+ },
+ {
+ "!!Could not parse imported name, at: ",
+ }
+ },
+ {
+ {
+ "import com.foo.Second;",
+ "import com.foo.First;",
+ "/* we don't support block comments",
+ " between imports either */",
+ "import com.foo.Third;",
+ },
+ {
+ "!!Imports not contiguous (perhaps a comment separates them?)",
+ }
+ },
+ {
+ {
+ "import com.foo.Second; /* no block comments after imports */", //
+ "import com.foo.First;",
+ },
+ {
+ "!!Imports not contiguous (perhaps a comment separates them?)",
+ }
+ },
+ {
+ {
+ "import com.foo.Second;",
+ "import com.foo.First;",
+ "/* but we're not fooled by comments that look like imports:",
+ "import com.foo.Third;",
+ "*/",
+ },
+ {
+ "import com.foo.First;",
+ "import com.foo.Second;",
+ "",
+ "/* but we're not fooled by comments that look like imports:",
+ "import com.foo.Third;",
+ "*/",
+ }
+ },
+ {
+ {
+ "import com . foo . Second ;", // syntactically valid, but we don't support it
+ "import com.foo.First;",
+ },
+ {
+ "!!Expected ; after import",
+ }
+ },
+ {
+ {
+ "import com.abc.@;", //
+ "import com.abc.@@;",
+ },
+ {
+ "!!Could not parse imported name, at: @",
+ }
+ },
+ {
+ {
+ "import com.abc.3;", // digits not syntactically valid
+ "import com.abc.2;",
+ },
+ {
+ // .3 is a single token (a floating-point constant)
+ "!!Expected ; after import",
+ }
+ },
+ {
+ {
+ "import com.foo.Second", // missing semicolon
+ "import com.foo.First;",
+ },
+ {
+ "!!Expected ; after import",
+ }
+ },
+ {
+ {
+ "import com.foo.Second; import com.foo.First;", "class Test {}",
+ },
+ {
+ "import com.foo.First;", //
+ "import com.foo.Second;",
+ "",
+ "class Test {}",
+ }
+ },
+ {
+ {
+ "import com.foo.Second; import com.foo.First; class Test {}",
+ },
+ {
+ "import com.foo.First;", //
+ "import com.foo.Second;",
+ "",
+ "class Test {}",
+ }
+ },
+ {
+ {
+ "package p;", //
+ "",
+ "/** test */",
+ "",
+ "import a.A;",
+ "",
+ "/** test */",
+ "",
+ "class Test {}",
+ },
+ {
+ "package p;", //
+ "",
+ "/** test */",
+ "",
+ "import a.A;",
+ "",
+ "/** test */",
+ "",
+ "class Test {}",
+ }
+ },
+ {
+ {
+ "package p; import a.A; class Test {}",
+ },
+ {
+ "package p;", //
+ "",
+ "import a.A;",
+ "",
+ "class Test {}",
+ }
+ },
+ };
+
+ ImmutableList.Builder<Object[]> builder = ImmutableList.builder();
+ Arrays.stream(inputsOutputs).forEach(input -> builder.add(createRow(input)));
+ return builder.build();
+ }
+
+ @Test
+ public void reorder() throws FormatterException {
+ try {
+ String output = ImportOrderer.reorderImports(input, Style.GOOGLE);
+ assertWithMessage("Expected exception").that(reordered).doesNotMatch("^!!");
+ assertWithMessage(input).that(output).isEqualTo(reordered);
+ } catch (FormatterException e) {
+ if (!reordered.startsWith("!!")) {
+ throw e;
+ }
+ assertThat(reordered).endsWith("\n");
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("error: " + reordered.substring(2, reordered.length() - 1));
+ }
+ }
+ }
+
+ /** Tests for import ordering in AOSP style. */
+ @RunWith(Parameterized.class)
+ public static class AospStyle {
+
+ private final String input;
+ private final String reordered;
+
+ public AospStyle(String input, String reordered) {
+ this.input = input;
+ this.reordered = reordered;
+ }
+
+ @Parameters(name = "{index}: {0}")
+ public static Collection<Object[]> parameters() {
+ // Inputs are provided as three-dimensional arrays. Each element of the outer array is a test
+ // case. It consists of two arrays of lines. The first array of lines is the test input, and
+ // the second one is the expected output. If the second array has a single element starting
+ // with !! then it is expected that ImportOrderer will throw a FormatterException with that
+ // message.
+ //
+ // If a line ends with \ then we remove the \ and don't append a \n. That allows us to check
+ // some parsing edge cases.
+ String[][][] inputsOutputs = {
+ // Capital letter before lowercase
+ {
+ {
+ "package foo;",
+ "",
+ "import android.abC.Bar;",
+ "import android.abc.Bar;",
+ "public class Blim {}",
+ },
+ {
+ "package foo;",
+ "",
+ "import android.abC.Bar;",
+ "import android.abc.Bar;",
+ "",
+ "public class Blim {}",
+ }
+ },
+ // Blank line between "com.android" and "com.anythingelse"
+ {
+ {
+ "package foo;",
+ "",
+ "import com.android.Bar;",
+ "import com.google.Bar;",
+ "public class Blim {}",
+ },
+ {
+ "package foo;",
+ "",
+ "import com.android.Bar;",
+ "",
+ "import com.google.Bar;",
+ "",
+ "public class Blim {}",
+ }
+ },
+ // Rough ordering -- statics, android, third party, then java, with blank lines between
+ // major groupings
+ {
+ {
+ "package foo;",
+ "",
+ "import static net.Bar.baz;",
+ "import static org.junit.Bar.baz;",
+ "import static com.google.Bar.baz;",
+ "import static java.lang.Bar.baz;",
+ "import static junit.Bar.baz;",
+ "import static javax.annotation.Bar.baz;",
+ "import static android.Bar.baz;",
+ "import net.Bar;",
+ "import org.junit.Bar;",
+ "import com.google.Bar;",
+ "import java.lang.Bar;",
+ "import junit.Bar;",
+ "import javax.annotation.Bar;",
+ "import android.Bar;",
+ "public class Blim {}",
+ },
+ {
+ "package foo;",
+ "",
+ "import static android.Bar.baz;",
+ "",
+ "import static com.google.Bar.baz;",
+ "",
+ "import static junit.Bar.baz;",
+ "",
+ "import static net.Bar.baz;",
+ "",
+ "import static org.junit.Bar.baz;",
+ "",
+ "import static java.lang.Bar.baz;",
+ "",
+ "import static javax.annotation.Bar.baz;",
+ "",
+ "import android.Bar;",
+ "",
+ "import com.google.Bar;",
+ "",
+ "import junit.Bar;",
+ "",
+ "import net.Bar;",
+ "",
+ "import org.junit.Bar;",
+ "",
+ "import java.lang.Bar;",
+ "",
+ "import javax.annotation.Bar;",
+ "",
+ "public class Blim {}",
+ }
+ },
+ {
+ {
+ "package foo;",
+ "",
+ "import static java.first.Bar.baz;",
+ "import static com.second.Bar.baz;",
+ "import com.first.Bar;",
+ "import static android.second.Bar.baz;",
+ "import dalvik.first.Bar;",
+ "import static dalvik.first.Bar.baz;",
+ "import static androidx.second.Bar.baz;",
+ "import java.second.Bar;",
+ "import static com.android.second.Bar.baz;",
+ "import static net.first.Bar.baz;",
+ "import gov.second.Bar;",
+ "import junit.second.Bar;",
+ "import static libcore.second.Bar.baz;",
+ "import static java.second.Bar.baz;",
+ "import static net.second.Bar.baz;",
+ "import static org.first.Bar.baz;",
+ "import static dalvik.second.Bar.baz;",
+ "import javax.first.Bar;",
+ "import static javax.second.Bar.baz;",
+ "import android.first.Bar;",
+ "import android.second.Bar;",
+ "import static javax.first.Bar.baz;",
+ "import androidx.first.Bar;",
+ "import static androidx.first.Bar.baz;",
+ "import androidx.second.Bar;",
+ "import com.android.first.Bar;",
+ "import gov.first.Bar;",
+ "import com.android.second.Bar;",
+ "import dalvik.second.Bar;",
+ "import static org.second.Bar.baz;",
+ "import net.first.Bar;",
+ "import libcore.second.Bar;",
+ "import static android.first.Bar.baz;",
+ "import com.second.Bar;",
+ "import static gov.second.Bar.baz;",
+ "import static gov.first.Bar.baz;",
+ "import static junit.first.Bar.baz;",
+ "import libcore.first.Bar;",
+ "import junit.first.Bar;",
+ "import javax.second.Bar;",
+ "import static libcore.first.Bar.baz;",
+ "import net.second.Bar;",
+ "import static com.first.Bar.baz;",
+ "import org.second.Bar;",
+ "import static junit.second.Bar.baz;",
+ "import java.first.Bar;",
+ "import org.first.Bar;",
+ "import static com.android.first.Bar.baz;",
+ "public class Blim {}",
+ },
+ {
+ "package foo;", //
+ "",
+ "import static android.first.Bar.baz;",
+ "import static android.second.Bar.baz;",
+ "",
+ "import static androidx.first.Bar.baz;",
+ "import static androidx.second.Bar.baz;",
+ "",
+ "import static com.android.first.Bar.baz;",
+ "import static com.android.second.Bar.baz;",
+ "",
+ "import static dalvik.first.Bar.baz;",
+ "import static dalvik.second.Bar.baz;",
+ "",
+ "import static libcore.first.Bar.baz;",
+ "import static libcore.second.Bar.baz;",
+ "",
+ "import static com.first.Bar.baz;",
+ "import static com.second.Bar.baz;",
+ "",
+ "import static gov.first.Bar.baz;",
+ "import static gov.second.Bar.baz;",
+ "",
+ "import static junit.first.Bar.baz;",
+ "import static junit.second.Bar.baz;",
+ "",
+ "import static net.first.Bar.baz;",
+ "import static net.second.Bar.baz;",
+ "",
+ "import static org.first.Bar.baz;",
+ "import static org.second.Bar.baz;",
+ "",
+ "import static java.first.Bar.baz;",
+ "import static java.second.Bar.baz;",
+ "",
+ "import static javax.first.Bar.baz;",
+ "import static javax.second.Bar.baz;",
+ "",
+ "import android.first.Bar;",
+ "import android.second.Bar;",
+ "",
+ "import androidx.first.Bar;",
+ "import androidx.second.Bar;",
+ "",
+ "import com.android.first.Bar;",
+ "import com.android.second.Bar;",
+ "",
+ "import dalvik.first.Bar;",
+ "import dalvik.second.Bar;",
+ "",
+ "import libcore.first.Bar;",
+ "import libcore.second.Bar;",
+ "",
+ "import com.first.Bar;",
+ "import com.second.Bar;",
+ "",
+ "import gov.first.Bar;",
+ "import gov.second.Bar;",
+ "",
+ "import junit.first.Bar;",
+ "import junit.second.Bar;",
+ "",
+ "import net.first.Bar;",
+ "import net.second.Bar;",
+ "",
+ "import org.first.Bar;",
+ "import org.second.Bar;",
+ "",
+ "import java.first.Bar;",
+ "import java.second.Bar;",
+ "",
+ "import javax.first.Bar;",
+ "import javax.second.Bar;",
+ "",
+ "public class Blim {}",
+ },
+ },
+ };
+ ImmutableList.Builder<Object[]> builder = ImmutableList.builder();
+ Arrays.stream(inputsOutputs).forEach(input -> builder.add(createRow(input)));
+ return builder.build();
+ }
+
+ @Test
+ public void reorder() throws FormatterException {
+ try {
+ String output = ImportOrderer.reorderImports(input, Style.AOSP);
+ assertWithMessage("Expected exception").that(reordered).doesNotMatch("^!!");
+ assertWithMessage(input).that(output).isEqualTo(reordered);
+ } catch (FormatterException e) {
+ if (!reordered.startsWith("!!")) {
+ throw e;
+ }
+ assertThat(reordered).endsWith("\n");
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("error: " + reordered.substring(2, reordered.length() - 1));
+ }
+ }
+ }
+
+ private static Object[] createRow(String[][] inputAndOutput) {
+ assertThat(inputAndOutput).hasLength(2);
+ String[] input = inputAndOutput[0];
+ String[] output = inputAndOutput[1];
+ if (output.length == 0) {
+ output = input;
+ }
+ Object[] row = {
+ Joiner.on('\n').join(input) + '\n', //
+ Joiner.on('\n').join(output) + '\n',
+ };
+ // If a line ends with \ then we remove the \ and don't append a \n. That allows us to check
+ // some parsing edge cases.
+ row[0] = ((String) row[0]).replace("\\\n", "");
+ return row;
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java b/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java
new file mode 100644
index 0000000..4116997
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java
@@ -0,0 +1,1418 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Joiner;
+import com.google.common.io.ByteStreams;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests formatting javadoc. */
+@RunWith(JUnit4.class)
+public final class JavadocFormattingTest {
+
+ private final Formatter formatter = new Formatter();
+
+ @Test
+ public void notJavadoc() {
+ String[] input = {
+ "/**/", //
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**/", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void empty() {
+ String[] input = {
+ "/***/", //
+ "class Test {}",
+ };
+ String[] expected = {
+ "/***/", "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void emptyMultipleLines() {
+ String[] input = {
+ "/**", //
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** */", "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void simple() {
+ String[] input = {
+ "/** */", //
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** */", "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void commentMostlyUntouched() {
+ // This test isn't necessarily what we'd want to do, but it's what we do now, and it's OK-ish.
+ String[] input = {
+ "/**",
+ " * Foo.",
+ " *",
+ " * <!--",
+ "*abc",
+ " * def ",
+ " * </tr>",
+ " *-->bar",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * Foo.",
+ " * <!--",
+ " *abc",
+ " * def",
+ " * </tr>",
+ " *-->",
+ " * bar",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void moeComments() {
+ String[] input = {
+ "/**",
+ " * Deatomizes the given user.",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " * See go/deatomizer-v5 for the design doc.",
+ " * <!-- MOE:end_intracomment_strip -->",
+ " * To reatomize, call {@link reatomize}.",
+ " *",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " * <p>This method is used in the Google teleporter.",
+ " *",
+ " * <p>Yes, we have a teleporter.",
+ " * <!-- MOE:end_intracomment_strip -->",
+ " *",
+ " * @param user the person to teleport.",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " * Users must sign go/deatomize-waiver ahead of time.",
+ " * <!-- MOE:end_intracomment_strip -->",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " * @deprecated Sometimes turns the user into a goat.",
+ " * <!-- MOE:end_intracomment_strip -->",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * Deatomizes the given user.",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " * See go/deatomizer-v5 for the design doc.",
+ " * <!-- MOE:end_intracomment_strip -->",
+ " * To reatomize, call {@link reatomize}.",
+ " *",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " * <p>This method is used in the Google teleporter.",
+ " *",
+ " * <p>Yes, we have a teleporter.",
+ " * <!-- MOE:end_intracomment_strip -->",
+ " *",
+ " * @param user the person to teleport.",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " * Users must sign go/deatomize-waiver ahead of time.",
+ " * <!-- MOE:end_intracomment_strip -->",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " * @deprecated Sometimes turns the user into a goat.",
+ " * <!-- MOE:end_intracomment_strip -->",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void moeCommentBeginOnlyInMiddleOfDoc() {
+ // We don't really care what happens here so long as we don't explode.
+ String[] input = {
+ "/**", //
+ " * Foo.",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " * Bar.",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * Foo.",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " * Bar.",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void moeCommentBeginOnlyAtEndOfDoc() {
+ // We don't really care what happens here so long as we don't explode.
+ // TODO(cpovirk): OK, maybe try to leave it in....
+ String[] input = {
+ "/**", //
+ " * Foo.",
+ " * <!-- MOE:begin_intracomment_strip -->",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** Foo. */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void moeCommentEndOnly() {
+ // We don't really care what happens here so long as we don't explode.
+ String[] input = {
+ "/**", //
+ " * Foo.",
+ " * <!-- MOE:end_intracomment_strip -->",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * Foo.",
+ " * <!-- MOE:end_intracomment_strip -->",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void tableMostlyUntouched() {
+ String[] input = {
+ "/**",
+ " * Foo.",
+ " *",
+ " * <table>",
+ "*<tr><td>a<td>b</tr>",
+ " * <tr>",
+ " * <td>A",
+ " * <td>B",
+ " * </tr>",
+ " *</table>",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * Foo.",
+ " *",
+ " * <table>",
+ " * <tr><td>a<td>b</tr>",
+ " * <tr>",
+ " * <td>A",
+ " * <td>B",
+ " * </tr>",
+ " * </table>",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void preMostlyUntouched() {
+ /*
+ * Arguably we shouldn't insert the space between "*" and "4," since doing so changes the
+ * rendered HTML output (by inserting a space there). However, inserting a space between "*" and
+ * "</pre>" (which has no impact on the rendered HTML AFAIK) is a good thing, and inserting
+ * similar spaces in the case of <table> is good, too. And if "* 4" breaks a user's intended
+ * formatting, that user can fix it up, just as that user would have to fix up the "*4"
+ * style violation. The main downside to "* 4" is that the user might not notice that we made
+ * the change at all. (We've also slightly complicated NEWLINE_PATTERN and writeNewline to
+ * accommodate it.)
+ */
+ String[] input = {
+ "/**", //
+ " * Example:",
+ " *",
+ " * <pre>",
+ "* 1 2<br> 3 ",
+ " *4 5 6",
+ "7 8",
+ " *</pre>",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * Example:",
+ " *",
+ " * <pre>",
+ " * 1 2<br> 3",
+ " * 4 5 6",
+ " * 7 8",
+ " * </pre>",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void preCodeExample() {
+ // We should figure out whether we want a newline or blank line before <pre> or not.
+ String[] input = {
+ "/**",
+ " * Example:",
+ " *",
+ " * <pre> {@code",
+ " *",
+ " * Abc.def(foo, 7, true); // blah}</pre>",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * Example:",
+ " *",
+ " * <pre>{@code",
+ " * Abc.def(foo, 7, true); // blah",
+ " * }</pre>",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void preNotWrapped() {
+ String[] input = {
+ "/**",
+ " * Example:",
+ " *",
+ " * <pre>",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012 "
+ + "456789012 45678901",
+ " * </pre>",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * Example:",
+ " *",
+ " * <pre>",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012 "
+ + "456789012 45678901",
+ " * </pre>",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void javaCodeInPre() {
+ String[] input = {
+ "/**",
+ " * Example:",
+ " *",
+ " *<pre>",
+ " * aaaaa | a | +",
+ " * \"bbbb | b | \"",
+ " *</pre>",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * Example:",
+ " *",
+ " * <pre>",
+ " * aaaaa | a | +",
+ " * \"bbbb | b | \"",
+ " * </pre>",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void joinLines() {
+ String[] input = {
+ "/**", //
+ " * foo",
+ " * bar",
+ " * baz",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** foo bar baz */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void oneLinerIs100() {
+ String[] input = {
+ "/**",
+ " * 567890123 567890123 567890123 567890123 567890123 567890123 567890123 567890123 "
+ + "567890123 567",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** 567890123 567890123 567890123 567890123 567890123 567890123 567890123 567890123 "
+ + "567890123 567 */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void oneLinerWouldBe101() {
+ String[] input = {
+ "/**",
+ " * 567890123 567890123 567890123 567890123 567890123 567890123 567890123 567890123 "
+ + "567890123 5678",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * 567890123 567890123 567890123 567890123 567890123 567890123 567890123 567890123 "
+ + "567890123 5678",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void multilineWrap() {
+ String[] input = {
+ "/**",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012 "
+ + "456789012 45678901",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012 "
+ + "456789012",
+ " * 45678901",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void tooLong() {
+ String[] input = {
+ "/**",
+ " * abc",
+ " *",
+ " * <p>789012345678901234567890123456789012345678901234567890123456789012345678901234567"
+ + "8901234567890123456",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * abc",
+ " *",
+ " * <p>789012345678901234567890123456789012345678901234567890123456789012345678901234567"
+ + "8901234567890123456",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void joinedTokens() {
+ /*
+ * Originally, 4, <b>, and 8901 are separate tokens. Test that we join them (and thus don't
+ * split them across lines).
+ */
+ String[] input = {
+ "/**",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012 "
+ + "456789012 4<b>8901",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012 "
+ + "456789012",
+ " * 4<b>8901",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void joinedAtSign() {
+ /*
+ * The last 456789012 would fit on the first line with the others. But putting it there would
+ * mean the next line would start with @5678901, which would then be interpreted as a tag.
+ */
+ String[] input = {
+ "/**",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012 "
+ + "456789012 @5678901",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012",
+ " * 456789012 @5678901",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void joinedMultipleAtSign() {
+ // This is the same as above except that it tests multiple consecutive @... tokens.
+ String[] input = {
+ "/**",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012 "
+ + "@56789012 @5678901",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012",
+ " * 456789012 @56789012 @5678901",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void noAsterisk() {
+ String[] input = {
+ "/**", //
+ " abc<p>def",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * abc",
+ " *",
+ " * <p>def",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void significantAsterisks() {
+ String[] input = {
+ "/** *", //
+ " * *",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** * * */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void links() {
+ String[] input = {
+ "/**",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012 "
+ + "456789012 4567 <a",
+ " * href=foo>foo</a>.",
+ " *",
+ " * <p>789012 456789012 456789012 456789012 456789012 456789012 456789012 456789 "
+ + "<a href=foo>",
+ " * foo</a>.",
+ " *",
+ " * <p>789012 456789012 456789012 456789012 456789012 456789012 456789012 4567890 "
+ + "<a href=foo>",
+ " * foo</a>.",
+ " *",
+ " * <p><a href=foo>",
+ " * foo</a>.",
+ " *",
+ " * <p>foo <a href=bar>",
+ " * bar</a>.",
+ " *",
+ " * <p>foo-<a href=bar>",
+ " * bar</a>.",
+ " *",
+ " * <p>foo<a href=bar>",
+ " * bar</a>.",
+ " *",
+ " * <p><a href=foo>foo</a> bar.",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * 456789012 456789012 456789012 456789012 456789012 456789012 456789012 456789012 "
+ + "456789012 4567 <a",
+ " * href=foo>foo</a>.",
+ " *",
+ " * <p>789012 456789012 456789012 456789012 456789012 456789012 456789012 456789 "
+ + "<a href=foo>foo</a>.",
+ " *",
+ " * <p>789012 456789012 456789012 456789012 456789012 456789012 456789012 4567890 "
+ + "<a href=foo>",
+ " * foo</a>.",
+ " *",
+ " * <p><a href=foo>foo</a>.",
+ " *",
+ " * <p>foo <a href=bar>bar</a>.",
+ " *",
+ " * <p>foo-<a href=bar>bar</a>.",
+ " *",
+ /*
+ * In this next case, we've removed a space from the output. Fortunately, the depot doesn't
+ * appear to contain any occurrences of this pattern. And if it does, the better fix is to
+ * insert a space before <a href> rather than after.
+ */
+ " * <p>foo<a href=bar>bar</a>.",
+ " *",
+ " * <p><a href=foo>foo</a> bar.",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void heading() {
+ String[] input = {
+ "/**", //
+ " * abc<h1>def</h1>ghi",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * abc",
+ " *",
+ " * <h1>def</h1>",
+ " *",
+ " * ghi",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void blockquote() {
+ String[] input = {
+ "/**", //
+ " * abc<blockquote><p>def</blockquote>ghi",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * abc",
+ " *",
+ " * <blockquote>",
+ " *",
+ " * <p>def",
+ " *",
+ " * </blockquote>",
+ " *",
+ " * ghi",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void lists() {
+ String[] input = {
+ "/**", //
+ "* hi",
+ "*",
+ "* <ul>",
+ "* <li>",
+ "* <ul>",
+ "* <li>a</li>",
+ "* </ul>",
+ "* </li>",
+ "* </ul>",
+ "*/",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * hi",
+ " *",
+ " * <ul>",
+ " * <li>",
+ " * <ul>",
+ " * <li>a",
+ " * </ul>",
+ " * </ul>",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void lists2() {
+ String[] input = {
+ "/**", //
+ " * Foo.",
+ " *",
+ " * <ul><li>1<ul><li>1a<li>1b</ul>more 1<p>still more 1<li>2</ul>",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * Foo.",
+ " *",
+ " * <ul>",
+ " * <li>1",
+ " * <ul>",
+ " * <li>1a",
+ " * <li>1b",
+ " * </ul>",
+ " * more 1",
+ " * <p>still more 1",
+ " * <li>2",
+ " * </ul>",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void closeInnerListStillNewline() {
+ String[] input = {
+ "/**", //
+ " * Foo.",
+ " *",
+ " * <ul><li><ul><li>a</ul>b</ul>",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * Foo.",
+ " *",
+ " * <ul>",
+ " * <li>",
+ " * <ul>",
+ " * <li>a",
+ " * </ul>",
+ " * b",
+ " * </ul>",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void listItemWrap() {
+ String[] input = {
+ "/**", //
+ " * Foo.",
+ " *",
+ " * <ul><li>234567890 234567890 234567890 234567890 234567890 234567890 234567890 234567890"
+ + " 234567890 234567890</ul>",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * Foo.",
+ " *",
+ " * <ul>",
+ " * <li>234567890 234567890 234567890 234567890 234567890 234567890 234567890 234567890"
+ + " 234567890",
+ " * 234567890",
+ " * </ul>",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void unclosedList() {
+ String[] input = {
+ "/**", //
+ " * Foo.",
+ " *",
+ " * <ul><li>1",
+ " * @return blah",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * Foo.",
+ " *",
+ " * <ul>",
+ " * <li>1",
+ " *",
+ " * @return blah",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void br() {
+ String[] input = {
+ "/**", //
+ " * abc<br>def",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * abc<br>",
+ " * def",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void brSpaceBug() {
+ // TODO(b/28983091): Remove the space before <br> here.
+ String[] input = {
+ "/**", //
+ " * abc <br>def",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * abc <br>",
+ " * def",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void brAtSignBug() {
+ /*
+ * This is a bug -- more of a "spec" bug than an implementation bug, and hard to fix.
+ * Fortunately, some very quick searching didn't turn up any instances in the Google codebase.
+ */
+ String[] input = {
+ "/**", //
+ " * abc<br>@foo ",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * abc<br>",
+ " * @foo", // interpreted as a block tag now!
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void unicodeCharacterCountArguableBug() {
+ /*
+ * We might prefer for multi-char characters like 𝄞 to be treated as taking up one column (or
+ * perhaps for all characters to be treated based on their width in monospace fonts). But
+ * currently we just count chars.
+ */
+ String[] input = {
+ "/**",
+ " * 456789𝄞12 456789𝄞12 456789𝄞12 456789𝄞12 456789𝄞12 456789𝄞12 456789𝄞12 456789𝄞12 "
+ + "456789𝄞12 456789𝄞",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * 456789𝄞12 456789𝄞12 456789𝄞12 456789𝄞12 456789𝄞12 456789𝄞12 456789𝄞12 456789𝄞12",
+ " * 456789𝄞12 456789𝄞",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void blankLineBeforeParams() {
+ String[] input = {
+ "/**", //
+ " * hello world",
+ " * @param this is a param",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * hello world",
+ " *",
+ " * @param this is a param",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void onlyParams() {
+ String[] input = {
+ "/**", //
+ " *",
+ " *",
+ " * @param this is a param",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** @param this is a param */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void paramsContinuationIndented() {
+ String[] input = {
+ "/**", //
+ " * hello world",
+ " *",
+ " * @param foo 567890123 567890123 567890123 567890123 567890123 567890123 567890123"
+ + " 567890123 567890123",
+ " * @param bar another",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * hello world",
+ " *",
+ " * @param foo 567890123 567890123 567890123 567890123 567890123 567890123 567890123"
+ + " 567890123",
+ " * 567890123",
+ " * @param bar another",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void paramsOtherIndents() {
+ String[] input = {
+ "/**", //
+ " * hello world",
+ " *",
+ " * @param foo a<p>b<ul><li>a<ul><li>x</ul></ul>",
+ " * @param bar another",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * hello world",
+ " *",
+ " * @param foo a",
+ " * <p>b",
+ " * <ul>",
+ " * <li>a",
+ " * <ul>",
+ " * <li>x",
+ " * </ul>",
+ " * </ul>",
+ " *", // TODO(cpovirk): Ideally we would probably eliminate this.
+ " * @param bar another",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void paragraphTag() {
+ String[] input = {
+ "class Test {",
+ " /**",
+ " * hello<p>world",
+ " */",
+ " void f() {}",
+ "",
+ " /**",
+ " * hello",
+ " * <p>",
+ " * world",
+ " */",
+ " void f() {}",
+ "}",
+ };
+ String[] expected = {
+ "class Test {",
+ " /**",
+ " * hello",
+ " *",
+ " * <p>world",
+ " */",
+ " void f() {}",
+ "",
+ " /**",
+ " * hello",
+ " *",
+ " * <p>world",
+ " */",
+ " void f() {}",
+ "}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void xhtmlParagraphTag() {
+ String[] input = {
+ "class Test {",
+ " /**",
+ " * hello<p/>world",
+ " */",
+ " void f() {}",
+ "",
+ "}",
+ };
+ String[] expected = {
+ "class Test {",
+ " /**",
+ " * hello",
+ " *",
+ " * <p>world",
+ " */",
+ " void f() {}",
+ "}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void removeInitialParagraphTag() {
+ String[] input = {
+ "/**", //
+ " * <p>hello<p>world",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * hello",
+ " *",
+ " * <p>world",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void inferParagraphTags() {
+ String[] input = {
+ "/**",
+ " *",
+ " *",
+ " * foo",
+ " * foo",
+ " *",
+ " *",
+ " * foo",
+ " *",
+ " * bar",
+ " *",
+ " * <pre>",
+ " *",
+ " * baz",
+ " *",
+ " * </pre>",
+ " *",
+ " * <ul>",
+ " * <li>foo",
+ " *",
+ " * bar",
+ " * </ul>",
+ " *",
+ " *",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * foo foo",
+ " *",
+ " * <p>foo",
+ " *",
+ " * <p>bar",
+ " *",
+ " * <pre>",
+ " *",
+ " * baz",
+ " *",
+ " * </pre>",
+ " *",
+ " * <ul>",
+ " * <li>foo",
+ " * <p>bar",
+ " * </ul>",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void paragraphTagNewlines() throws Exception {
+ String input =
+ new String(
+ ByteStreams.toByteArray(getClass().getResourceAsStream("testjavadoc/B28750242.input")),
+ UTF_8);
+ String expected =
+ new String(
+ ByteStreams.toByteArray(getClass().getResourceAsStream("testjavadoc/B28750242.output")),
+ UTF_8);
+ String output = formatter.formatSource(input);
+ assertThat(output).isEqualTo(expected);
+ }
+
+ @Test
+ public void listItemSpaces() throws Exception {
+ String input =
+ new String(
+ ByteStreams.toByteArray(getClass().getResourceAsStream("testjavadoc/B31404367.input")),
+ UTF_8);
+ String expected =
+ new String(
+ ByteStreams.toByteArray(getClass().getResourceAsStream("testjavadoc/B31404367.output")),
+ UTF_8);
+ String output = formatter.formatSource(input);
+ assertThat(output).isEqualTo(expected);
+ }
+
+ @Test
+ public void htmlTagsInCode() {
+ String[] input = {
+ "/** abc {@code {} <p> <li> <pre> <table>} def */", //
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** abc {@code {} <p> <li> <pre> <table>} def */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void loneBraceDoesNotStartInlineTag() {
+ String[] input = {
+ "/** { <p> } */", //
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * {",
+ " *",
+ " * <p>}",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void unicodeEscapesNotReplaced() {
+ // Test that we don't replace them with their interpretations.
+ String[] input = {
+ "/** foo \\u0000 bar \\u6c34 baz */", //
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** foo \\u0000 bar \\u6c34 baz */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void unicodeEscapesNotInterpretedBug() {
+ /*
+ * In theory, \u003C should be treated exactly like <, and so too should the escaped versions of
+ * @, *, and other special chars. We don't recognize that, though, so we don't put what is
+ * effectively "<p>" on a new line.
+ */
+ String[] input = {
+ "/** a\\u003Cp>b */", //
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** a\\u003Cp>b */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void trailingLink() {
+ // Eclipse's parser seems to want to discard the line break after {@link}. Test that we see it.
+ String[] input = {
+ "/**", //
+ " * abc {@link Foo}",
+ " * def",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** abc {@link Foo} def */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void codeInCode() {
+ // Eclipse's parser seems to get confused at the second {@code}. Test that we handle it.
+ String[] input = {
+ "/** abc {@code {@code foo}} def */", //
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** abc {@code {@code foo}} def */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void quotedTextSplitAcrossLinks() {
+ /*
+ * This demonstrates one of multiple reasons that we can't hand the Javadoc *content* to
+ * Eclipse's lexer as if it were Java code.
+ */
+ String[] input = {
+ "/**", //
+ " * abc \"foo",
+ " * bar baz\" def",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** abc \"foo bar baz\" def */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void standardizeTags() {
+ String[] input = {
+ "/**",
+ " * foo",
+ " *",
+ " * <P>bar",
+ " *",
+ " * <p class=clazz>baz<BR>",
+ " * baz",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**",
+ " * foo",
+ " *",
+ " * <p>bar",
+ " *",
+ " * <p class=clazz>baz<br>",
+ " * baz",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void removeCloseTags() {
+ String[] input = {
+ "/**", //
+ " * foo</p>",
+ " *",
+ " * <p>bar</p>",
+ " */",
+ "class Test {}",
+ };
+ String[] expected = {
+ "/**", //
+ " * foo",
+ " *",
+ " * <p>bar",
+ " */",
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void javadocFullSentences() {
+ String[] input = {
+ "/** In our application, bats are often found hanging from the ceiling, especially on"
+ + " Wednesdays. Sometimes sick bats have issues where their claws do not close entirely."
+ + " This class provides a nice, grippable surface for them to cling to. */",
+ "class Grippable {}",
+ };
+ String[] expected = {
+ "/**",
+ " * In our application, bats are often found hanging from the ceiling, especially on"
+ + " Wednesdays.",
+ " * Sometimes sick bats have issues where their claws do not close entirely. This class"
+ + " provides a",
+ " * nice, grippable surface for them to cling to.",
+ " */",
+ "class Grippable {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void javadocSentenceFragment() {
+ String[] input = {
+ "/** Provides a comfy, grippable surface for sick bats with claw-closing problems, which are"
+ + " sometimes found hanging from the ceiling on Wednesdays. */",
+ "class Grippable {}",
+ };
+ String[] expected = {
+ "/**",
+ " * Provides a comfy, grippable surface for sick bats with claw-closing problems, which are"
+ + " sometimes",
+ " * found hanging from the ceiling on Wednesdays.",
+ " */",
+ "class Grippable {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ @Test
+ public void javadocCanEndAnywhere() {
+ String[] input = {
+ "/** foo <pre*/", //
+ "class Test {}",
+ };
+ String[] expected = {
+ "/** foo <pre */", //
+ "class Test {}",
+ };
+ doFormatTest(input, expected);
+ }
+
+ private void doFormatTest(String[] input, String[] expected) {
+ try {
+ String actual = formatter.formatSource(Joiner.on('\n').join(input));
+ assertThat(actual).isEqualTo(Joiner.on('\n').join(expected) + "\n");
+ } catch (FormatterException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Test
+ public void windowsLineSeparator() throws FormatterException {
+ String[] input = {
+ "/**", " * hello", " *", " * <p>world", " */", "class Test {}",
+ };
+ for (String separator : Arrays.asList("\r", "\r\n")) {
+ String actual = formatter.formatSource(Joiner.on(separator).join(input));
+ assertThat(actual).isEqualTo(Joiner.on(separator).join(input) + separator);
+ }
+ }
+
+ @Test
+ public void u2028LineSeparator() {
+ String[] input = {
+ "public class Foo {",
+ " /**\u2028",
+ " * Set and enable something.",
+ " */",
+ " public void setSomething() {}",
+ "}",
+ };
+ String[] expected = {
+ "public class Foo {",
+ " /**",
+ " * \u2028 Set and enable something.",
+ " */",
+ " public void setSomething() {}",
+ "}",
+ };
+ doFormatTest(input, expected);
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/LineRangesToCharRangesTest.java b/core/src/test/java/com/google/googlejavaformat/java/LineRangesToCharRangesTest.java
new file mode 100644
index 0000000..ca3ad7c
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/LineRangesToCharRangesTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link Formatter#lineRangesToCharRanges} */
+@RunWith(JUnit4.class)
+public class LineRangesToCharRangesTest {
+
+ @SafeVarargs
+ final Set<Range<Integer>> getCharRanges(String input, Range<Integer>... ranges) {
+ RangeSet<Integer> rangeSet = TreeRangeSet.create();
+ for (Range<Integer> range : ranges) {
+ rangeSet.add(range);
+ }
+ return Formatter.lineRangesToCharRanges(input, rangeSet).asRanges();
+ }
+
+ @Test
+ public void emptyLineRanges() throws Exception {
+ assertThat(getCharRanges("", Range.closedOpen(0, 1))).isEmpty();
+ }
+
+ @Test
+ public void lineRanges() throws Exception {
+ assertThat(getCharRanges("_\n_\n_\n", Range.closedOpen(0, 1)))
+ .containsExactly(Range.closedOpen(0, 1));
+ assertThat(getCharRanges("_\n_\n_\n", Range.closedOpen(1, 2)))
+ .containsExactly(Range.closedOpen(2, 3));
+ assertThat(getCharRanges("_\n_\n_\n", Range.closedOpen(2, 3)))
+ .containsExactly(Range.closedOpen(4, 5));
+ assertThat(getCharRanges("_\n_\n_\n", Range.closedOpen(3, 4))).isEmpty();
+ }
+
+ @Test
+ public void blankLineRange() throws Exception {
+ assertThat(getCharRanges("hello\n\nworld", Range.closedOpen(0, 1)))
+ .containsExactly(Range.closedOpen(0, 5));
+ assertThat(getCharRanges("hello\n\nworld", Range.closedOpen(1, 2))).isEmpty();
+ assertThat(getCharRanges("hello\n\nworld", Range.closedOpen(2, 3)))
+ .containsExactly(Range.closedOpen(7, 12));
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/MainTest.java b/core/src/test/java/com/google/googlejavaformat/java/MainTest.java
new file mode 100644
index 0000000..613d391
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/MainTest.java
@@ -0,0 +1,598 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ProcessBuilder.Redirect;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.EnumSet;
+import java.util.Locale;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link Main}. */
+@RunWith(JUnit4.class)
+public class MainTest {
+
+ @Rule public TemporaryFolder testFolder = new TemporaryFolder();
+
+ // PrintWriter instances used below are hard-coded to use system-default line separator.
+ private final Joiner joiner = Joiner.on(System.lineSeparator());
+
+ @Test
+ public void testUsageOutput() {
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+
+ try {
+ main.format("--help");
+ throw new AssertionError("Expected UsageException to be thrown");
+ } catch (UsageException e) {
+
+ String usage = e.getMessage();
+
+ // Check that doc links are included.
+ assertThat(usage).contains("https://github.com/google/google-java-format");
+ assertThat(usage).contains("Usage: google-java-format");
+
+ // Sanity check that a flag and description is in included.
+ assertThat(usage).contains("--length");
+ assertThat(usage).contains("Character length to format.");
+
+ // Check that some of the additional text is included.
+ assertThat(usage).contains("the result is sent to stdout");
+ }
+ }
+
+ @Test
+ public void version() throws UsageException {
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ assertThat(main.format("-version")).isEqualTo(0);
+ assertThat(err.toString()).contains("google-java-format: Version ");
+ }
+
+ @Test
+ public void preserveOriginalFile() throws Exception {
+ Path path = testFolder.newFile("Test.java").toPath();
+ Files.write(path, "class Test {}\n".getBytes(UTF_8));
+ try {
+ Files.setPosixFilePermissions(path, EnumSet.of(PosixFilePermission.OWNER_READ));
+ } catch (UnsupportedOperationException e) {
+ return;
+ }
+ Main main =
+ new Main(
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out, UTF_8)), true),
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true),
+ System.in);
+ int errorCode = main.format("-replace", path.toAbsolutePath().toString());
+ assertWithMessage("Error Code").that(errorCode).isEqualTo(0);
+ }
+
+ @Test
+ public void testMain() throws Exception {
+ Process process =
+ new ProcessBuilder(
+ ImmutableList.of(
+ Paths.get(System.getProperty("java.home")).resolve("bin/java").toString(),
+ "-cp",
+ System.getProperty("java.class.path"),
+ Main.class.getName()))
+ .redirectError(Redirect.PIPE)
+ .redirectOutput(Redirect.PIPE)
+ .start();
+ process.waitFor();
+ String err = new String(ByteStreams.toByteArray(process.getErrorStream()), UTF_8);
+ assertThat(err).contains("Usage: google-java-format");
+ assertThat(process.exitValue()).isEqualTo(0);
+ }
+
+ // end to end javadoc formatting test
+ @Test
+ public void javadoc() throws Exception {
+ String[] input = {
+ "/**",
+ " * graph",
+ " *",
+ " * graph",
+ " *",
+ " * @param foo lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
+ + " eiusmod tempor incididunt ut labore et dolore magna aliqua",
+ " */",
+ "class Test {",
+ " /**",
+ " * creates entropy",
+ " */",
+ " public static void main(String... args) {}",
+ "}",
+ };
+ String[] expected = {
+ "/**",
+ " * graph",
+ " *",
+ " * <p>graph",
+ " *",
+ " * @param foo lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
+ + " eiusmod tempor",
+ " * incididunt ut labore et dolore magna aliqua",
+ " */",
+ "class Test {",
+ " /** creates entropy */",
+ " public static void main(String... args) {}",
+ "}",
+ "",
+ };
+ InputStream in = new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8));
+ StringWriter out = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true),
+ in);
+ assertThat(main.format("-")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(joiner.join(expected));
+ }
+
+ // end to end import fixing test
+ @Test
+ public void imports() throws Exception {
+ String[] input = {
+ "import java.util.LinkedList;",
+ "import java.util.List;",
+ "import java.util.ArrayList;",
+ "class Test {",
+ " /**",
+ " * May be an {@link ArrayList}.",
+ " */",
+ " public static List<String> names;",
+ "}",
+ };
+ String[] expected = {
+ "import java.util.ArrayList;",
+ "import java.util.List;",
+ "",
+ "class Test {",
+ " /**",
+ " * May be an {@link ArrayList}.",
+ " */",
+ " public static List<String> names;",
+ "}",
+ };
+ InputStream in = new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8));
+ StringWriter out = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true),
+ in);
+ assertThat(main.format("-", "--fix-imports-only")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(joiner.join(expected));
+ }
+
+ @Test
+ public void optimizeImportsDoesNotLeaveEmptyLines() throws Exception {
+ String[] input = {
+ "package abc;",
+ "",
+ "import java.util.LinkedList;",
+ "import java.util.List;",
+ "import java.util.ArrayList;",
+ "",
+ "import static java.nio.charset.StandardCharsets.UTF_8;",
+ "",
+ "import java.util.EnumSet;",
+ "",
+ "class Test ",
+ "extends ArrayList {",
+ "}"
+ };
+ String[] expected = {
+ "package abc;", //
+ "",
+ "import java.util.ArrayList;",
+ "",
+ "class Test extends ArrayList {}",
+ ""
+ };
+
+ // pre-check expectation with local formatter instance
+ String optimized = new Formatter().formatSourceAndFixImports(joiner.join(input));
+ assertThat(optimized).isEqualTo(joiner.join(expected));
+
+ InputStream in = new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8));
+ StringWriter out = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true),
+ in);
+ assertThat(main.format("-")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(joiner.join(expected));
+ }
+
+ // test that -lines handling works with import removal
+ @Test
+ public void importRemovalLines() throws Exception {
+ String[] input = {
+ "import java.util.ArrayList;",
+ "import java.util.List;",
+ "class Test {",
+ "ArrayList<String> a = new ArrayList<>();",
+ "ArrayList<String> b = new ArrayList<>();",
+ "}",
+ };
+ String[] expected = {
+ "import java.util.ArrayList;",
+ "",
+ "class Test {",
+ " ArrayList<String> a = new ArrayList<>();",
+ "ArrayList<String> b = new ArrayList<>();",
+ "}",
+ };
+ StringWriter out = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true),
+ new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8)));
+ assertThat(main.format("-", "-lines", "4")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(joiner.join(expected));
+ }
+
+ // test that errors are reported on the right line when imports are removed
+ @Test
+ public void importRemoveErrorParseError() throws Exception {
+ Locale backupLocale = Locale.getDefault();
+ try {
+ Locale.setDefault(Locale.ROOT);
+
+ String[] input = {
+ "import java.util.ArrayList;", //
+ "import java.util.List;",
+ "class Test {",
+ "}}",
+ };
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(err, true),
+ new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8)));
+ assertThat(main.format("-")).isEqualTo(1);
+ assertThat(err.toString()).contains("<stdin>:4:3: error: class, interface");
+
+ } finally {
+ Locale.setDefault(backupLocale);
+ }
+ }
+
+ @Test
+ public void packageInfo() throws Exception {
+ String[] input = {
+ "@CheckReturnValue",
+ "@ParametersAreNonnullByDefault",
+ "package com.google.common.labs.base;",
+ "",
+ "import javax.annotation.CheckReturnValue;",
+ "import javax.annotation.ParametersAreNonnullByDefault;",
+ "",
+ };
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(err, true),
+ new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8)));
+ assertThat(main.format("-")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(joiner.join(input));
+ }
+
+ @Test
+ public void newline() throws Exception {
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(err, true),
+ new ByteArrayInputStream("class T {}\n\t".getBytes(UTF_8)));
+ assertThat(main.format("-")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo("class T {}\n");
+ }
+
+ @Test
+ public void dryRunStdinUnchanged() throws Exception {
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(err, true),
+ new ByteArrayInputStream("class Test {}\n".getBytes(UTF_8)));
+ assertThat(main.format("-n", "-")).isEqualTo(0);
+ assertThat(out.toString()).isEmpty();
+ assertThat(err.toString()).isEmpty();
+ }
+
+ @Test
+ public void dryRunStdinChanged() throws Exception {
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ String input = "class Test {\n}\n";
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(err, true),
+ new ByteArrayInputStream(input.getBytes(UTF_8)));
+ assertThat(main.format("-n", "-")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo("<stdin>" + System.lineSeparator());
+ assertThat(err.toString()).isEmpty();
+ }
+
+ @Test
+ public void dryRunFiles() throws Exception {
+ Path a = testFolder.newFile("A.java").toPath();
+ Path b = testFolder.newFile("B.java").toPath();
+ Path c = testFolder.newFile("C.java").toPath();
+ Files.write(a, "class A {}\n".getBytes(UTF_8));
+ Files.write(b, "class B {\n}\n".getBytes(UTF_8));
+ Files.write(c, "class C {\n}\n".getBytes(UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ int exitCode =
+ main.format(
+ "-n",
+ a.toAbsolutePath().toAbsolutePath().toString(),
+ b.toAbsolutePath().toString(),
+ c.toAbsolutePath().toString());
+
+ assertThat(exitCode).isEqualTo(0);
+
+ assertThat(out.toString())
+ .isEqualTo(
+ b.toAbsolutePath().toString()
+ + System.lineSeparator()
+ + c.toAbsolutePath().toString()
+ + System.lineSeparator());
+ assertThat(err.toString()).isEmpty();
+ }
+
+ @Test
+ public void keepGoingWhenFilesDontExist() throws Exception {
+ Path a = testFolder.newFile("A.java").toPath();
+ Path b = testFolder.newFile("B.java").toPath();
+ File cFile = testFolder.newFile("C.java");
+ Path c = cFile.toPath();
+ cFile.delete();
+
+ Files.write(a, "class A{}\n".getBytes(UTF_8));
+ Files.write(b, "class B{}\n".getBytes(UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+
+ int exitCode =
+ main.format(
+ "",
+ a.toAbsolutePath().toString(),
+ c.toAbsolutePath().toString(),
+ b.toAbsolutePath().toString());
+
+ // Formatter returns failure if a file was not present.
+ assertThat(exitCode).isEqualTo(1);
+
+ // Present files were correctly formatted.
+ assertThat(out.toString()).isEqualTo("class A {}\nclass B {}\n");
+
+ // File not found still showed error.
+ assertThat(err.toString()).isNotEmpty();
+ }
+
+ @Test
+ public void exitIfChangedStdin() throws Exception {
+ Path path = testFolder.newFile("Test.java").toPath();
+ Files.write(path, "class Test {\n}\n".getBytes(UTF_8));
+ Process process =
+ new ProcessBuilder(
+ ImmutableList.of(
+ Paths.get(System.getProperty("java.home")).resolve("bin/java").toString(),
+ "-cp",
+ System.getProperty("java.class.path"),
+ Main.class.getName(),
+ "-n",
+ "--set-exit-if-changed",
+ "-"))
+ .redirectInput(path.toFile())
+ .redirectError(Redirect.PIPE)
+ .redirectOutput(Redirect.PIPE)
+ .start();
+ process.waitFor();
+ String err = new String(ByteStreams.toByteArray(process.getErrorStream()), UTF_8);
+ String out = new String(ByteStreams.toByteArray(process.getInputStream()), UTF_8);
+ assertThat(err).isEmpty();
+ assertThat(out).isEqualTo("<stdin>" + System.lineSeparator());
+ assertThat(process.exitValue()).isEqualTo(1);
+ }
+
+ @Test
+ public void exitIfChangedFiles() throws Exception {
+ Path path = testFolder.newFile("Test.java").toPath();
+ Files.write(path, "class Test {\n}\n".getBytes(UTF_8));
+ Process process =
+ new ProcessBuilder(
+ ImmutableList.of(
+ Paths.get(System.getProperty("java.home")).resolve("bin/java").toString(),
+ "-cp",
+ System.getProperty("java.class.path"),
+ Main.class.getName(),
+ "-n",
+ "--set-exit-if-changed",
+ path.toAbsolutePath().toString()))
+ .redirectError(Redirect.PIPE)
+ .redirectOutput(Redirect.PIPE)
+ .start();
+ process.waitFor();
+ String err = new String(ByteStreams.toByteArray(process.getErrorStream()), UTF_8);
+ String out = new String(ByteStreams.toByteArray(process.getInputStream()), UTF_8);
+ assertThat(err).isEmpty();
+ assertThat(out).isEqualTo(path.toAbsolutePath().toString() + System.lineSeparator());
+ assertThat(process.exitValue()).isEqualTo(1);
+ }
+
+ @Test
+ public void assumeFilename_error() throws Exception {
+ String[] input = {
+ "class Test {}}",
+ };
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(err, true),
+ new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8)));
+ assertThat(main.format("--assume-filename=Foo.java", "-")).isEqualTo(1);
+ assertThat(err.toString()).contains("Foo.java:1:15: error: class, interface");
+ }
+
+ @Test
+ public void assumeFilename_dryRun() throws Exception {
+ String[] input = {
+ "class Test {", //
+ "}",
+ };
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(err, true),
+ new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8)));
+ assertThat(main.format("--dry-run", "--assume-filename=Foo.java", "-")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo("Foo.java" + System.lineSeparator());
+ }
+
+ @Test
+ public void reflowLongStrings() throws Exception {
+ String[] input = {
+ "class T {", //
+ " String s = \"one long incredibly unbroken sentence moving from topic to topic so that no"
+ + " one had a chance to interrupt\";",
+ "}"
+ };
+ String[] expected = {
+ "class T {",
+ " String s =",
+ " \"one long incredibly unbroken sentence moving from topic to topic so that no one had"
+ + " a\"",
+ " + \" chance to interrupt\";",
+ "}",
+ "",
+ };
+ InputStream in = new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8));
+ StringWriter out = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true),
+ in);
+ assertThat(main.format("-")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(joiner.join(expected));
+ }
+
+ @Test
+ public void noReflowLongStrings() throws Exception {
+ String[] input = {
+ "class T {", //
+ " String s = \"one long incredibly unbroken sentence moving from topic to topic so that no"
+ + " one had a chance to interrupt\";",
+ "}"
+ };
+ String[] expected = {
+ "class T {",
+ " String s =",
+ " \"one long incredibly unbroken sentence moving from topic to topic so that no one had"
+ + " a chance to interrupt\";",
+ "}",
+ "",
+ };
+ InputStream in = new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8));
+ StringWriter out = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true),
+ in);
+ assertThat(main.format("--skip-reflowing-long-strings", "-")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(joiner.join(expected));
+ }
+
+ @Test
+ public void noFormatJavadoc() throws Exception {
+ String[] input = {
+ "/**",
+ " * graph",
+ " *",
+ " * graph",
+ " *",
+ " * @param foo lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
+ + " eiusmod tempor incididunt ut labore et dolore magna aliqua",
+ " */",
+ "class Test {",
+ " /**",
+ " * creates entropy",
+ " */",
+ " public static void main(String... args) {}",
+ "}",
+ "",
+ };
+ InputStream in = new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8));
+ StringWriter out = new StringWriter();
+ Main main =
+ new Main(
+ new PrintWriter(out, true),
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true),
+ in);
+ assertThat(main.format("--skip-javadoc-formatting", "-")).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(joiner.join(input));
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/ModifierOrdererTest.java b/core/src/test/java/com/google/googlejavaformat/java/ModifierOrdererTest.java
new file mode 100644
index 0000000..1f8fc17
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/ModifierOrdererTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Range;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** {@link ModifierOrderer}Test */
+@RunWith(JUnit4.class)
+public class ModifierOrdererTest {
+
+ @Test
+ public void simple() throws FormatterException {
+ assertThat(ModifierOrderer.reorderModifiers("static abstract class InnerClass {}").getText())
+ .isEqualTo("abstract static class InnerClass {}");
+ }
+
+ @Test
+ public void comment() throws FormatterException {
+ assertThat(ModifierOrderer.reorderModifiers("static/*1*/abstract/*2*/public").getText())
+ .isEqualTo("public/*1*/abstract/*2*/static");
+ }
+
+ @Test
+ public void everything() throws FormatterException {
+ assertThat(
+ ModifierOrderer.reorderModifiers(
+ "strictfp native synchronized volatile transient final static abstract"
+ + " private protected public")
+ .getText())
+ .isEqualTo(
+ "public protected private abstract static final transient volatile synchronized"
+ + " native strictfp");
+ }
+
+ @Test
+ public void everythingIncludingDefault() throws FormatterException {
+ assertThat(
+ ModifierOrderer.reorderModifiers(
+ "strictfp native synchronized volatile transient final static default abstract"
+ + " private protected public")
+ .getText())
+ .isEqualTo(
+ "public protected private abstract default static final transient volatile synchronized"
+ + " native strictfp");
+ }
+
+ @Test
+ public void subRange() throws FormatterException {
+ String[] lines = {
+ "class Test {", //
+ " static public int a;",
+ " static public int b;",
+ "}",
+ };
+ String input = Joiner.on('\n').join(lines);
+ String substring = "static public int a";
+ int start = input.indexOf(substring);
+ int end = start + substring.length();
+ String output =
+ ModifierOrderer.reorderModifiers(
+ new JavaInput(input), Arrays.asList(Range.closedOpen(start, end)))
+ .getText();
+ assertThat(output).contains("public static int a;");
+ assertThat(output).contains("static public int b;");
+ }
+
+ @Test
+ public void whitespace() throws FormatterException {
+ String[] lines = {
+ "class Test {", //
+ " static",
+ " public int a;",
+ "}",
+ };
+ String input = Joiner.on('\n').join(lines);
+ String substring = "static public int a";
+ int start = input.indexOf(substring);
+ int end = start + substring.length();
+ String output =
+ ModifierOrderer.reorderModifiers(
+ new JavaInput(input), Arrays.asList(Range.closedOpen(start, end)))
+ .getText();
+ assertThat(output).contains("public\n static int a;");
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/PartialFormattingTest.java b/core/src/test/java/com/google/googlejavaformat/java/PartialFormattingTest.java
new file mode 100644
index 0000000..57d55d7
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/PartialFormattingTest.java
@@ -0,0 +1,1802 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ObjectArrays;
+import com.google.common.collect.Range;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests formatting parts of files. */
+@RunWith(Parameterized.class)
+public final class PartialFormattingTest {
+
+ @Parameters
+ public static Iterable<Object[]> parameters() {
+ return ImmutableList.copyOf(new Object[][] {{"\n"}, {"\r"}, {"\r\n"}});
+ }
+
+ @Rule public TemporaryFolder testFolder = new TemporaryFolder();
+
+ private final String newline;
+
+ public PartialFormattingTest(String newline) {
+ this.newline = newline;
+ }
+
+ String lines(String... args) {
+ return Joiner.on(newline).join(args);
+ }
+
+ @Test
+ public void testGetFormatReplacements0() throws Exception {
+ String input =
+ lines(
+ /* line 0 character 0 */ "class Foo{",
+ /* line 1 character 11 */ "void f",
+ /* line 2 character 18 */ "() {",
+ /* line 3 character 23 */ "}",
+ /* line 4 character 25 */ "}",
+ "");
+ String expectedOutput =
+ lines(
+
+ /* line 0 character 0 */ "class Foo{",
+ /* line 1 character 11 */ " void f() {}",
+ /* line 2 character 25 */ "}",
+ "");
+ // Claim to have modified the parentheses.
+ int start = input.indexOf("() {");
+ String output = doGetFormatReplacements(input, start, start + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void testGetFormatReplacements1() throws Exception {
+ String input =
+ lines(
+ /* line 0 character 0 */ "class Foo{",
+ /* line 1 character 11 */ "void f",
+ /* line 2 character 18 */ "() {",
+ /* line 3 character 23 */ "}",
+ /* line 4 character 25 */ "}",
+ "");
+ String expectedOutput =
+ lines(
+ /* line 0 character 0 */ "class Foo{",
+ /* line 1 character 11 */ " void f() {}",
+ /* line 2 character 25 */ "}",
+ "");
+ // Claim to have modified everything after the parentheses.
+ String output = doGetFormatReplacements(input, 20, 21);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void expandToStatement() throws Exception {
+ String input =
+ lines(
+ "class Foo {{",
+ "ImmutableList<Integer> ids = ImmutableList.builder()",
+ ".add(1)",
+ ".add(2)",
+ ".add(3)",
+ ".build();",
+ "}}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Foo {{",
+ " ImmutableList<Integer> ids ="
+ + " ImmutableList.builder().add(1).add(2).add(3).build();",
+ "}}",
+ "");
+ int idx = input.indexOf("add(2)");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void expandToMethodSignature() throws Exception {
+ String input =
+ lines(
+ "class Foo {",
+ "void ",
+ " m() ",
+ " {",
+ "ImmutableList<Integer> ids = ImmutableList.builder()",
+ ".add(1)",
+ ".add(2)",
+ ".build();",
+ "}}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Foo {",
+ " void m() {",
+ "ImmutableList<Integer> ids = ImmutableList.builder()",
+ ".add(1)",
+ ".add(2)",
+ ".build();",
+ "}}",
+ "");
+ int idx = input.indexOf("void");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void expandToClassSignature() throws Exception {
+ String input =
+ lines(
+ "class",
+ "Foo<XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,"
+ + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,"
+ + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX> {",
+ "void ",
+ " m() ",
+ " {",
+ "}}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Foo<",
+ " XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,",
+ " XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,",
+ " XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX> {",
+ "void ",
+ " m() ",
+ " {",
+ "}}",
+ "");
+ int idx = input.indexOf("class");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void ignoreNeighbouringStatements() throws Exception {
+ String input =
+ lines(
+ "class Test {",
+ "int ",
+ " xxx ",
+ " = 1;",
+ "int ",
+ " yyy ",
+ " = 1;",
+ "int ",
+ " zzz ",
+ " = 1;",
+ "}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Test {",
+ "int ",
+ " xxx ",
+ " = 1;",
+ " int yyy = 1;",
+ "int ",
+ " zzz ",
+ " = 1;",
+ "}",
+ "");
+ int idx = input.indexOf("yyy");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void insertLeadingNewlines() throws Exception {
+ String input =
+ lines(
+ "class Test { int xxx = 1; int yyy = 1; int zzz = 1; }", //
+ "");
+ String expectedOutput =
+ lines(
+ "class Test { int xxx = 1;", //
+ " int yyy = 1;",
+ " int zzz = 1; }",
+ "");
+ int idx = input.indexOf("yyy");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void insertLeadingNewlines2() throws Exception {
+ String input =
+ lines(
+ "class Test { int xxx = 1;", //
+ "",
+ " int yyy = 1; int zzz = 1; }");
+ String expectedOutput =
+ lines(
+ "class Test { int xxx = 1;", //
+ "", //
+ " int yyy = 1;",
+ " int zzz = 1; }");
+ int idx = input.indexOf("yyy");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void insertTrailingNewlines() throws Exception {
+ String input =
+ lines(
+ "class Test { int xxx = 1;", //
+ " int yyy = 1; int zzz = 1; }");
+ String expectedOutput =
+ lines(
+ "class Test { int xxx = 1;", //
+ " int yyy = 1;",
+ " int zzz = 1; }");
+ int idx = input.indexOf("yyy");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void rejoinMethodSignatureLines() throws Exception {
+ String input =
+ lines(
+ "class Test { void zzz", //
+ "() { int x; } }");
+ String expectedOutput =
+ lines(
+ "class Test {", //
+ " void zzz() {",
+ " int x; } }");
+ int idx = input.indexOf("zzz");
+ String output = doGetFormatReplacements(input, idx, idx);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void formatTrailingBrace() throws Exception {
+ String input =
+ lines(
+ "class Test { void f() { return; } }", //
+ "");
+ String expectedOutput =
+ lines(
+ "class Test { void f() { return;", //
+ " }",
+ "}",
+ "");
+ int idx = input.indexOf("}");
+ String output = doGetFormatReplacements(input, idx, idx);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void formatTrailingBraceEmptyMethodBody() throws Exception {
+ String input =
+ lines(
+ "class Test { void f() {} }", //
+ "");
+ String expectedOutput =
+ lines(
+ "class Test {", //
+ " void f() {}",
+ "}",
+ "");
+ int idx = input.indexOf("}");
+ String output = doGetFormatReplacements(input, idx, idx);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void formatTrailingBraceEmptyClassBody() throws Exception {
+ String input =
+ lines(
+ "class Test { int x; }", //
+ "");
+ String expectedOutput =
+ lines(
+ "class Test { int x;", //
+ "}",
+ "");
+ int idx = input.indexOf("}");
+ String output = doGetFormatReplacements(input, idx, idx);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void formatTrailingBraceEmptyClassBody2() throws Exception {
+ String input =
+ lines(
+ "class Test {", //
+ "}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Test {}", //
+ "");
+ int idx = input.indexOf("}");
+ String output = doGetFormatReplacements(input, idx, idx);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void onlyPackage() throws Exception {
+ String input =
+ lines(
+ "package", //
+ "test",
+ ";",
+ "class Test {}",
+ "");
+ String expectedOutput =
+ lines(
+ "package test;", //
+ "",
+ "class Test {}",
+ "");
+ int idx = input.indexOf("test");
+ String output = doGetFormatReplacements(input, idx, idx);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ private static String doGetFormatReplacements(String input, int characterILo, int characterIHi)
+ throws Exception {
+ return new Formatter()
+ .formatSource(input, ImmutableList.of(Range.closedOpen(characterILo, characterIHi + 1)));
+ }
+
+ @Test
+ public void testLength() throws Exception {
+ String input =
+ lines(
+ "class Foo{", //
+ "int xxx;",
+ "int yyy;",
+ "int zzz;",
+ "}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Foo{", //
+ "int xxx;",
+ " int yyy;",
+ "int zzz;",
+ "}",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "3", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void testLengthRange() throws Exception {
+ String input =
+ lines(
+ "class Foo{", //
+ "int xxx;",
+ "int yyy;",
+ "int zzz;",
+ "}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Foo{", //
+ "int xxx;",
+ " int yyy;",
+ " int zzz;",
+ "}",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "3:4", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void statementAndComments() throws Exception {
+ String input =
+ lines(
+ "public class MyTest {",
+ "{",
+ "// asd",
+ "int x = 1;",
+ "// asd",
+ "int y = 2;",
+ "// asd",
+ "int z = 3;",
+ "// asd",
+ "}",
+ "}",
+ "",
+ "");
+ String expectedOutput =
+ lines(
+ "public class MyTest {",
+ "{",
+ "// asd",
+ "int x = 1;",
+ " // asd",
+ " int y = 2;",
+ "// asd",
+ "int z = 3;",
+ "// asd",
+ "}",
+ "}",
+ "",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "5", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void statementAndComments2() throws Exception {
+ String input =
+ lines(
+ "public class MyTest {",
+ "{",
+ "// asd",
+ "int x = 1;",
+ "// asd",
+ "int y = 2;",
+ "// asd",
+ "int z = 3;",
+ "// asd",
+ "}",
+ "}",
+ "",
+ "");
+ String expectedOutput =
+ lines(
+ "public class MyTest {",
+ "{",
+ "// asd",
+ "int x = 1;",
+ " // asd",
+ " int y = 2;",
+ "// asd",
+ "int z = 3;",
+ "// asd",
+ "}",
+ "}",
+ "",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "6", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void statementAndComments3() throws Exception {
+ String input =
+ lines(
+ "public class MyTest {",
+ "{",
+ "// asd",
+ "int x = 1;",
+ "// asd",
+ "int y = 2;",
+ "// asd",
+ "int z = 3;",
+ "// asd",
+ "}",
+ "}",
+ "",
+ "");
+ String expectedOutput =
+ lines(
+ "public class MyTest {",
+ "{",
+ "// asd",
+ "int x = 1;",
+ "// asd",
+ "int y = 2;",
+ " // asd",
+ " int z = 3;",
+ "// asd",
+ "}",
+ "}",
+ "",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "7", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void blankAndComment() throws Exception {
+ String input =
+ lines(
+ "public class MyTest {",
+ " public void testListDefinitions() throws Exception {",
+ " definitionService.insert(createDefinition(1));",
+ " definitionService.insert(createIncrementalDefinition(2));",
+ " definitionService.insert(createDefinition(3));",
+ " definitionService.insert(createIncrementalDefinition(4));",
+ "",
+ " // No maxResults",
+ " assertThat(achievementFirstPartyHelper.listDefinitionsByApplication(",
+ " STUB_GAIA_ID, STUB_APPLICATION_ID, Optional.<Integer>absent(),",
+ " "
+ + " Optional.<String>absent()).getAchievements()).containsExactly(createExpectedDefinition(1),"
+ + " createIncrementalExpectedDefinition(2), createExpectedDefinition(3),"
+ + " createIncrementalExpectedDefinition(4)).inOrder();",
+ " }",
+ "}",
+ "",
+ "");
+ String expectedOutput =
+ lines(
+ "public class MyTest {",
+ " public void testListDefinitions() throws Exception {",
+ " definitionService.insert(createDefinition(1));",
+ " definitionService.insert(createIncrementalDefinition(2));",
+ " definitionService.insert(createDefinition(3));",
+ " definitionService.insert(createIncrementalDefinition(4));",
+ "",
+ " // No maxResults",
+ " assertThat(",
+ " achievementFirstPartyHelper",
+ " .listDefinitionsByApplication(",
+ " STUB_GAIA_ID,",
+ " STUB_APPLICATION_ID,",
+ " Optional.<Integer>absent(),",
+ " Optional.<String>absent())",
+ " .getAchievements())",
+ " .containsExactly(",
+ " createExpectedDefinition(1),",
+ " createIncrementalExpectedDefinition(2),",
+ " createExpectedDefinition(3),",
+ " createIncrementalExpectedDefinition(4))",
+ " .inOrder();",
+ " }",
+ "}",
+ "",
+ "");
+
+ String toFormat =
+ lines(
+ " assertThat(achievementFirstPartyHelper.listDefinitionsByApplication(", //
+ "");
+ int idx = input.indexOf(toFormat);
+ String output = doGetFormatReplacements(input, idx, idx + toFormat.length());
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void emptyFile() throws Exception {
+ new Formatter().formatSource("");
+ new Formatter()
+ .formatSource(
+ lines(
+ "", //
+ ""),
+ ImmutableList.of(Range.closedOpen(0, 1)));
+ }
+
+ @Test
+ public void testGetFormatReplacementRanges() throws Exception {
+ String input =
+ lines(
+ /* line 0 character 0 */ "class Foo{",
+ /* line 1 character 11 */ "void f",
+ /* line 2 character 18 */ "() {",
+ /* line 3 character 23 */ "}",
+ /* line 4 character 25 */ "}",
+ "");
+ // Claim to have modified the parentheses.
+ int start = input.indexOf("() {");
+ ImmutableList<Replacement> ranges =
+ new Formatter()
+ .getFormatReplacements(input, ImmutableList.of(Range.closedOpen(start, start + 1)));
+ assertThat(ranges).hasSize(1);
+ Replacement replacement = ranges.get(0);
+ assertThat(replacement.getReplacementString())
+ .isEqualTo(
+ lines(
+ "", //
+ " void f() {}"));
+ int replaceFrom = input.indexOf("void f") - newline.length();
+ assertThat(replacement.getReplaceRange().lowerEndpoint()).isEqualTo(replaceFrom);
+ }
+
+ @Test
+ public void noTokensOnLine() throws Exception {
+ String input =
+ lines(
+ " package com.google.googlejavaformat.java;",
+ "/*",
+ " * Copyright 2015 Google Inc.",
+ " *",
+ " * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not",
+ " * 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 distribute",
+ " * is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY",
+ " * or implied. See the License for the specific language governing permissions an",
+ " * the License.",
+ " */",
+ "",
+ "import com.google.googlejavaformat.FormatterDiagnostic;",
+ "import java.util.List;",
+ "",
+ "/** Checked exception class for formatter errors. */",
+ "public final class FormatterException extends Exception {",
+ "",
+ " FormatterException(String message) {",
+ " super(message);",
+ " }",
+ "",
+ " /**",
+ " * @param errors",
+ " */",
+ " public FormatterException(List<FormatterDiagnostic> errors) {",
+ " // TODO(cushon): Auto-generated constructor stub",
+ " }",
+ "}");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("FormatterException.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "3:4", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(input);
+ }
+
+ @Test
+ public void nestedStatement1() throws Exception {
+ String input =
+ lines(
+ "public class MyTest {{",
+ "int x = ",
+ " 1;",
+ "int y = new Runnable() {",
+ " void run() {",
+ " System.err.println(42);",
+ " }",
+ "};",
+ "int z = ",
+ " 1;",
+ "}}",
+ "",
+ "");
+ String expectedOutput =
+ lines(
+ "public class MyTest {{",
+ "int x = ",
+ " 1;",
+ " int y =",
+ " new Runnable() {",
+ " void run() {",
+ " System.err.println(42);",
+ " }",
+ " };",
+ "int z = ",
+ " 1;",
+ "}}",
+ "",
+ "");
+
+ String toFormat = "Runnable";
+ int idx = input.indexOf(toFormat);
+ String output = doGetFormatReplacements(input, idx, idx + toFormat.length());
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void nestedStatement2() throws Exception {
+ String input =
+ lines(
+ "public class MyTest {",
+ "int x = ",
+ " 1;",
+ "int y = new Runnable() {",
+ " void run() {",
+ " System.err.println(42);",
+ " }",
+ "};",
+ "int z = ",
+ " 1;",
+ "}",
+ "",
+ "");
+ String expectedOutput =
+ lines(
+ "public class MyTest {",
+ "int x = ",
+ " 1;",
+ " int y =",
+ " new Runnable() {",
+ " void run() {",
+ " System.err.println(42);",
+ " }",
+ " };",
+ "int z = ",
+ " 1;",
+ "}",
+ "",
+ "");
+
+ String toFormat = "Runnable";
+ int idx = input.indexOf(toFormat);
+ String output = doGetFormatReplacements(input, idx, idx + toFormat.length());
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void blankLine() throws Exception {
+ String input =
+ lines(
+ "public class MyTest {", //
+ "int x = 1;",
+ "",
+ "int y = 1;",
+ "}",
+ "",
+ "");
+ String expectedOutput = input;
+
+ testFormatLine(input, expectedOutput, 3);
+ }
+
+ @Test
+ public void lineWithIdentifier() throws Exception {
+ String input =
+ lines(
+ "public class MyTest {", //
+ "int",
+ "y",
+ "= 1;",
+ "}",
+ "",
+ "");
+ String expectedOutput =
+ lines(
+ "public class MyTest {", //
+ " int y = 1;",
+ "}",
+ "",
+ "");
+
+ testFormatLine(input, expectedOutput, 3);
+ }
+
+ // formatted region expands to include entire comment
+ @Test
+ public void lineInsideComment() throws Exception {
+ String input =
+ lines(
+ "public class MyTest {",
+ "/* This is a",
+ " poorly indented",
+ " comment*/",
+ "int x;",
+ "}",
+ "",
+ "");
+ String expectedOutput =
+ lines(
+ "public class MyTest {",
+ " /* This is a",
+ " poorly indented",
+ " comment*/",
+ " int x;",
+ "}",
+ "",
+ "");
+
+ testFormatLine(input, expectedOutput, 3);
+ }
+
+ @Test
+ public void testReplacementsSorted() throws Exception {
+ String input =
+ lines(
+ "class Test {",
+ "int a = 1;",
+ "int b = 2;",
+ "int c = 3;",
+ "int d = 4;",
+ "int e = 5;",
+ "}");
+ List<Range<Integer>> ranges = new ArrayList<>();
+ for (int i = 1; i <= 5; i += 2) {
+ int idx = input.indexOf(String.valueOf(i));
+ ranges.add(Range.closedOpen(idx, idx + 1));
+ }
+
+ ImmutableList<Replacement> replacements = new Formatter().getFormatReplacements(input, ranges);
+
+ // expect replacements in ascending order, by start position
+ List<Integer> startPositions = new ArrayList<>();
+ for (Replacement replacement : replacements) {
+ startPositions.add(replacement.getReplaceRange().lowerEndpoint());
+ }
+ assertThat(startPositions).hasSize(3);
+ assertThat(startPositions).isInStrictOrder();
+ }
+
+ @Test
+ public void testReplacementsSorted_DescendingInput() throws Exception {
+ String input =
+ lines(
+ "class Test {",
+ "int a = 1;",
+ "int b = 2;",
+ "int c = 3;",
+ "int d = 4;",
+ "int e = 5;",
+ "}");
+ List<Range<Integer>> ranges = new ArrayList<>();
+ for (int i = 5; i >= 1; i -= 2) {
+ int idx = input.indexOf(String.valueOf(i));
+ ranges.add(Range.closedOpen(idx, idx + 1));
+ }
+
+ ImmutableList<Replacement> replacements = new Formatter().getFormatReplacements(input, ranges);
+
+ // expect replacements in ascending order, by start position
+ List<Integer> startPositions = new ArrayList<>();
+ for (Replacement replacement : replacements) {
+ startPositions.add(replacement.getReplaceRange().lowerEndpoint());
+ }
+ assertThat(startPositions).hasSize(3);
+ assertThat(startPositions).isInStrictOrder();
+ }
+
+ private void testFormatLine(String input, String expectedOutput, int i) throws Exception {
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", Integer.toString(i), path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void lineWithTrailingComment() throws Exception {
+ String input =
+ lines(
+ "class Foo{", //
+ "int xxx; // asd",
+ "}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Foo{", //
+ " int xxx; // asd",
+ "}",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "2", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ // Nested statements are OK as long as they're nested inside "block-like" constructs.
+ @Test
+ public void nestedStatement_allowPartial() throws Exception {
+ String input =
+ lines(
+ "public class MyTest {{",
+ "if (true) {",
+ "if (true) {",
+ "System.err.println(\"Hello\");",
+ "} else {",
+ "System.err.println(\"Goodbye\");",
+ "}",
+ "}",
+ "}}",
+ "");
+ String expectedOutput =
+ lines(
+ "public class MyTest {{",
+ "if (true) {",
+ "if (true) {",
+ " System.err.println(\"Hello\");",
+ "} else {",
+ "System.err.println(\"Goodbye\");",
+ "}",
+ "}",
+ "}}",
+ "");
+
+ String toFormat = "Hello";
+ int idx = input.indexOf(toFormat);
+ String output = doGetFormatReplacements(input, idx, idx + toFormat.length());
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ // regression test for b/b22196513
+ @Test
+ public void noTrailingWhitespace() throws Exception {
+ String input =
+ lines(
+ "", //
+ "class Test {",
+ " {",
+ " {",
+ " {",
+ " }",
+ " }",
+ "}}",
+ "");
+ String expected =
+ lines(
+ "", //
+ "class Test {",
+ " {",
+ " {",
+ " {",
+ " }",
+ " }",
+ " }",
+ "}",
+ "");
+ int start = input.indexOf(newline + "}}");
+ ImmutableList<Range<Integer>> ranges =
+ ImmutableList.of(Range.closedOpen(start, start + newline.length() + 2));
+ String output = new Formatter().formatSource(input, ranges);
+ assertEquals("bad output", expected, output);
+ }
+
+ // regression test for b/b22196513
+ @Test
+ public void trailingNonBreakingWhitespace() throws Exception {
+ String input =
+ lines(
+ "", //
+ "class Test {",
+ " {",
+ " int x;int y;",
+ " }",
+ "}",
+ "");
+ String expected =
+ lines(
+ "", //
+ "class Test {",
+ " {",
+ " int x;",
+ " int y;",
+ " }",
+ "}",
+ "");
+ String match = "int x;";
+ int start = input.indexOf(match);
+ int end = start + match.length();
+ ImmutableList<Range<Integer>> ranges = ImmutableList.of(Range.closedOpen(start, end));
+ String output = new Formatter().formatSource(input, ranges);
+ assertEquals("bad output", expected, output);
+ }
+
+ @Test
+ public void outOfRangeStartLine() throws Exception {
+ String input =
+ lines(
+ "class Foo {", //
+ "int x = 1;",
+ "}");
+ String expectedOutput =
+ lines(
+ "class Foo {", //
+ " int x = 1;",
+ "}",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "-1:3", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void outOfRangeEndLine() throws Exception {
+ String input =
+ lines(
+ "class Foo {", //
+ "int x = 1;",
+ "}");
+ String expectedOutput =
+ lines(
+ "class Foo {", //
+ " int x = 1;",
+ "}",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "1:5", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void testOutOfRangeLines() throws Exception {
+ String input =
+ lines(
+ "class Foo {", //
+ "}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Foo {}", //
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines=23:27", "-lines=31:35", "-lines=52:63", "-lines=1:1", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void testEmptyFirstLine() throws Exception {
+ String input =
+ lines(
+ "", //
+ "",
+ "class Foo {",
+ "}",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines=1:1", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ }
+
+ @Test
+ public void testEmptyLastLine() throws Exception {
+ String input =
+ lines(
+ "class Foo {", //
+ "}",
+ "",
+ "");
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines=5:5", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ }
+
+ // Regression test for b/22872933
+ // Don't extend partial formatting ranges across switch cases.
+ @Test
+ public void switchCase() throws Exception {
+ String input =
+ lines(
+ "class Test {",
+ " {",
+ " switch (foo) {",
+ " case FOO:",
+ " f();",
+ " break;",
+ " case BAR:",
+ " g();",
+ " break;",
+ " }",
+ " }",
+ "}");
+ String expectedOutput =
+ lines(
+ "class Test {",
+ " {",
+ " switch (foo) {",
+ " case FOO:",
+ " f();",
+ " break;",
+ " case BAR:", // we deliberately only format the first case
+ " g();",
+ " break;",
+ " }",
+ " }",
+ "}");
+
+ int idx = input.indexOf("f()");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ // regression test for b/23349153
+ @Test
+ public void emptyStatement() throws Exception {
+ String input =
+ lines(
+ "class Test {{", //
+ "Object o = f();;",
+ "}}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Test {{", //
+ " Object o = f();",
+ " ;",
+ "}}",
+ "");
+ int idx = input.indexOf("Object o");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void preserveTrailingWhitespaceAfterNewline() throws Exception {
+ String input =
+ lines(
+ "class Test {{", //
+ "Object o = f(); ",
+ " int x;",
+ "}}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Test {{", //
+ " Object o = f();",
+ " int x;",
+ "}}",
+ "");
+ int idx = input.indexOf("Object o");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void trailingWhitespace() throws Exception {
+ String input =
+ lines(
+ "class Test {{", //
+ "Object o = f(); ",
+ " ;",
+ "}}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Test {{", //
+ " Object o = f();",
+ " ;",
+ "}}",
+ "");
+ int idx = input.indexOf("Object o");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ // Regression test for b/18479811
+ @Test
+ public void onNewline() throws Exception {
+
+ String line1 = "for (Integer x : Arrays.asList(1, 2, 3)) {";
+ String line2 = "System.err.println(x);";
+ String input =
+ lines(
+ "class Test {{", //
+ line1,
+ line2,
+ "}}}",
+ "");
+
+ int startOffset = input.indexOf(line1);
+ int length = 1;
+
+ String expectedFormatLine1 =
+ lines(
+ "class Test {{",
+ " for (Integer x : Arrays.asList(1, 2, 3)) {",
+ "System.err.println(x);",
+ "}}}",
+ "");
+
+ for (; length <= line1.length() + newline.length(); length++) {
+ Range<Integer> range = Range.closedOpen(startOffset, startOffset + length);
+ String output = new Formatter().formatSource(input, ImmutableList.of(range));
+ assertEquals("bad output", expectedFormatLine1, output);
+ }
+
+ String expectedFormatLine1And2 =
+ lines(
+ "class Test {{",
+ " for (Integer x : Arrays.asList(1, 2, 3)) {",
+ " System.err.println(x);",
+ "}}}",
+ "");
+
+ for (; length <= line1.length() + line2.length() + 2 * newline.length(); length++) {
+ Range<Integer> range = Range.closedOpen(startOffset, startOffset + length);
+ String output = new Formatter().formatSource(input, ImmutableList.of(range));
+ assertEquals("bad output", expectedFormatLine1And2, output);
+ }
+ }
+
+ @Test
+ public void afterNewline() throws Exception {
+
+ String line1 = "for (Integer x : Arrays.asList(1, 2, 3)) {";
+ String line2 = " System.err.println(x);";
+ String input =
+ lines(
+ "class Test {{", //
+ line1,
+ line2,
+ "}}}",
+ "");
+
+ String expectedFormatLine1 =
+ lines(
+ "class Test {{", //
+ " for (Integer x : Arrays.asList(1, 2, 3)) {", //
+ line2,
+ "}}}",
+ "");
+
+ String expectedFormatLine2 =
+ lines(
+ "class Test {{", //
+ line1,
+ " System.err.println(x);",
+ "}}}",
+ "");
+
+ int line2Start = input.indexOf(line2);
+ int nonWhitespaceLine2Start = input.indexOf("System.err");
+ int start;
+ // formatting a range that touches non-whitespace characters in line2 should format line2
+ for (start = nonWhitespaceLine2Start; start > (line2Start - newline.length()); start--) {
+ Range<Integer> range = Range.closedOpen(start, nonWhitespaceLine2Start + newline.length());
+ String output = new Formatter().formatSource(input, ImmutableList.of(range));
+ assertThat(output).isEqualTo(expectedFormatLine2);
+ }
+ // formatting a range that touches whitespace characters between line1 and line2 should
+ // not result in any formatting
+ assertThat(input.substring(start, start + newline.length())).isEqualTo(newline);
+ int line1End = input.indexOf(line1) + line1.length();
+ for (; start >= line1End; start--) {
+ Range<Integer> range = Range.closedOpen(start, line2Start);
+ String output = new Formatter().formatSource(input, ImmutableList.of(range));
+ assertThat(output).isEqualTo(input);
+ }
+ // formatting a range that touches non-whitespace characters in line1 should format line1
+ assertThat(input.substring(start + 1, start + 1 + newline.length())).isEqualTo(newline);
+ int line1Start = input.indexOf(line1);
+ for (; start >= line1Start; start--) {
+ Range<Integer> range = Range.closedOpen(start, line2Start);
+ String output = new Formatter().formatSource(input, ImmutableList.of(range));
+ assertThat(output).isEqualTo(expectedFormatLine1);
+ }
+ }
+
+ @Test
+ public void commentBeforeBadConstructor() throws Exception {
+ String[] lines = {
+ "class D {", //
+ " /** */",
+ " F() {}",
+ "}",
+ };
+ String output = new Formatter().formatSource(lines(lines));
+ String[] expected = {
+ "class D {", //
+ " /** */",
+ " F() {}",
+ "}",
+ "",
+ };
+ assertThat(output).isEqualTo(lines(expected));
+ }
+
+ @Test
+ public void partialEnum() throws Exception {
+ String[] input = {
+ "enum E {", //
+ "ONE,",
+ "TWO,",
+ "THREE;",
+ "}",
+ };
+ String[] expected = {
+ "enum E {", //
+ "ONE,",
+ " TWO,",
+ "THREE;",
+ "}",
+ };
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, lines(input).getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "3", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(lines(expected));
+ }
+
+ @Test
+ public void partialModifierOrder() throws Exception {
+ String[] input = {
+ "class T {", //
+ "final private int a = 0;",
+ "final private int b = 0;",
+ "final private int c = 0;",
+ "}",
+ };
+ String[] expected = {
+ "class T {", //
+ "final private int a = 0;",
+ " private final int b = 0;",
+ "final private int c = 0;",
+ "}",
+ };
+
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, lines(input).getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ String[] args = {"-lines", "3", path.toString()};
+ assertThat(main.format(args)).isEqualTo(0);
+ assertThat(out.toString()).isEqualTo(lines(expected));
+ }
+
+ @Test
+ public void endOfLine() throws Exception {
+ String[] input = {
+ "class foo {",
+ " foo(",
+ " int aaaaaaaaaaaaaaa,",
+ " int ccccccccccccc) {",
+ " int a = 0;",
+ " int c = 0;",
+ " }",
+ "}",
+ };
+ String[] expected = {
+ "class foo {",
+ " foo(int aaaaaaaaaaaaaaa, int ccccccccccccc) {",
+ " int a = 0;",
+ " int c = 0;",
+ " }",
+ "}",
+ };
+ String in = lines(input);
+ // request partial formatting of the end of the first parameter
+ int start = in.indexOf(lines(",", " int ccccccccccccc"));
+ assertThat(in.substring(start, start + 1)).isEqualTo(",");
+
+ assertThat(new Formatter().formatSource(in, ImmutableList.of(Range.closedOpen(start, start))))
+ .isEqualTo(lines(expected));
+
+ assertThat(formatMain(lines(input), "-offset", String.valueOf(start), "-length", "0"))
+ .isEqualTo(lines(expected));
+ }
+
+ private String formatMain(String input, String... args) throws Exception {
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Test.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ assertThat(main.format(ObjectArrays.concat(args, path.toString()))).isEqualTo(0);
+ return out.toString();
+ }
+
+ // formatting the newlinea after a statement is a no-op
+ @Test
+ public void endOfLineStatement() throws Exception {
+ String[] input = {
+ "class foo {{", //
+ " int a = 0; ",
+ " int c = 0;",
+ "}}",
+ };
+ String[] expected = {
+ "class foo {{", //
+ " int a = 0;",
+ " int c = 0;",
+ "}}",
+ };
+ String in = lines(input);
+ int idx = in.indexOf(';');
+ assertThat(new Formatter().formatSource(in, ImmutableList.of(Range.closedOpen(idx, idx))))
+ .isEqualTo(lines(expected));
+ }
+
+ // formatting trailing whitespace at the end of the line doesn't format the line on either side
+ @Test
+ public void endOfLineStatementNewline() throws Exception {
+ String[] input = {
+ "class foo {{", //
+ " int a = 0; ",
+ " int c = 0;",
+ "}}",
+ };
+ String in = lines(input);
+ int idx = in.indexOf(';');
+ assertThat(
+ new Formatter().formatSource(in, ImmutableList.of(Range.closedOpen(idx + 1, idx + 1))))
+ .isEqualTo(in);
+ }
+
+ @Test
+ public void importNewlines() throws Exception {
+ String input =
+ lines(
+ "package p;",
+ "import java.util.ArrayList;",
+ "class Foo {",
+ " ArrayList<String> xs = new ArrayList<>();",
+ "}",
+ "");
+ String expectedOutput =
+ lines(
+ "package p;",
+ "",
+ "import java.util.ArrayList;",
+ "",
+ "class Foo {",
+ " ArrayList<String> xs = new ArrayList<>();",
+ "}",
+ "");
+
+ String output = runFormatter(input, new String[] {"-lines", "2"});
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void b36458607() throws Exception {
+ String input =
+ lines(
+ "// copyright",
+ "",
+ "package p;",
+ "import static c.g.I.c;",
+ "",
+ "/** */",
+ "class Foo {{ c(); }}",
+ "");
+ String expectedOutput =
+ lines(
+ "// copyright",
+ "",
+ "package p;",
+ "",
+ "import static c.g.I.c;",
+ "",
+ "/** */",
+ "class Foo {{ c(); }}",
+ "");
+
+ String output = runFormatter(input, new String[] {"-lines", "4"});
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void b32159971() throws Exception {
+ String input =
+ lines(
+ "", //
+ "",
+ "package p;",
+ "class X {}",
+ "");
+ String expectedOutput =
+ lines(
+ "package p;", //
+ "",
+ "class X {}",
+ "");
+
+ String output = runFormatter(input, new String[] {"-lines", "3"});
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void b21668189() throws Exception {
+ String input =
+ lines(
+ "class Foo {", //
+ " {",
+ " int x = 1;",
+ " ",
+ " int y = 2;",
+ " }",
+ "}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Foo {", //
+ " {",
+ " int x = 1;",
+ "",
+ " int y = 2;",
+ " }",
+ "}",
+ "");
+
+ String output = runFormatter(input, new String[] {"-lines", "4:5"});
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ private String runFormatter(String input, String[] args) throws IOException, UsageException {
+ Path tmpdir = testFolder.newFolder().toPath();
+ Path path = tmpdir.resolve("Foo.java");
+ Files.write(path, input.getBytes(StandardCharsets.UTF_8));
+
+ StringWriter out = new StringWriter();
+ StringWriter err = new StringWriter();
+
+ Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in);
+ assertThat(main.format(ObjectArrays.concat(args, path.toString()))).isEqualTo(0);
+ return out.toString();
+ }
+
+ @Test
+ public void trailing() throws Exception {
+ String input =
+ lines(
+ "package foo.bar.baz;",
+ "",
+ "public class B {",
+ " public void f() {",
+ " int a = 7 +4;",
+ " int b = 7 +4;",
+ " int c = 7 +4;",
+ " int d = 7 +4;",
+ "",
+ " int e = 7 +4;",
+ " }",
+ "}");
+ String expected =
+ lines(
+ "package foo.bar.baz;",
+ "",
+ "public class B {",
+ " public void f() {",
+ " int a = 7 +4;",
+ " int b = 7 +4;",
+ " int c = 7 + 4;",
+ " int d = 7 +4;",
+ "",
+ " int e = 7 +4;",
+ " }",
+ "}");
+ String actual =
+ new Formatter().formatSource(input, ImmutableList.of(rangeOf(input, "int c = 7 +4")));
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ private Range<Integer> rangeOf(String input, String needle) {
+ int idx = input.indexOf(needle);
+ return Range.closedOpen(idx, idx + needle.length());
+ }
+
+ @Test
+ public void importJavadocNewlines() throws Exception {
+ String input =
+ lines(
+ "package p;",
+ "import java.util.ArrayList;",
+ "/** */",
+ "class Foo {",
+ " ArrayList<String> xs = new ArrayList<>();",
+ "}",
+ "");
+ String expectedOutput =
+ lines(
+ "package p;",
+ "",
+ "import java.util.ArrayList;",
+ "",
+ "/** */",
+ "class Foo {",
+ " ArrayList<String> xs = new ArrayList<>();",
+ "}",
+ "");
+
+ String output = runFormatter(input, new String[] {"-lines", "2"});
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void nestedSwitchCase() throws Exception {
+ String input =
+ lines(
+ "class Test {",
+ " {",
+ " switch (foo) {",
+ " case FOO:",
+ " f();",
+ " break;",
+ " case BAR:",
+ " switch (bar) {",
+ " case BAZ:",
+ " h();",
+ " break;",
+ " case BOZ:",
+ " i();",
+ " break;",
+ " }",
+ " }",
+ " }",
+ "}");
+ String expectedOutput =
+ lines(
+ "class Test {",
+ " {",
+ " switch (foo) {",
+ " case FOO:",
+ " f();",
+ " break;",
+ " case BAR:",
+ " switch (bar) {",
+ " case BAZ:",
+ " h();",
+ " break;",
+ " case BOZ:",
+ " i();",
+ " break;",
+ " }",
+ " }",
+ " }",
+ "}");
+
+ int idx = input.indexOf("h()");
+ String output = doGetFormatReplacements(input, idx, idx + 1);
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+
+ @Test
+ public void b117602702() throws Exception {
+ String input =
+ lines(
+ "class Foo {", //
+ "private Foo () {};",
+ "}",
+ "");
+ String expectedOutput =
+ lines(
+ "class Foo {", //
+ " private Foo() {}",
+ " ;",
+ "}",
+ "");
+
+ String output = runFormatter(input, new String[] {"-offset", "13", "-length", "1"});
+ assertThat(output).isEqualTo(expectedOutput);
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/RemoveUnusedImportsTest.java b/core/src/test/java/com/google/googlejavaformat/java/RemoveUnusedImportsTest.java
new file mode 100644
index 0000000..1965feb
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/RemoveUnusedImportsTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.googlejavaformat.java.RemoveUnusedImports.removeUnusedImports;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** {@link RemoveUnusedImports}Test */
+@RunWith(Parameterized.class)
+public class RemoveUnusedImportsTest {
+ @Parameters(name = "{index}: {0}")
+ public static Collection<Object[]> parameters() {
+ String[][][] inputsOutputs = {
+ {
+ {
+ "import java.util.List;",
+ "import java.util.ArrayList;",
+ "",
+ "class Test {",
+ " /** could be an {@link ArrayList} */",
+ " List<String> xs;",
+ "}",
+ },
+ {
+ "import java.util.List;",
+ "import java.util.ArrayList;",
+ "",
+ "class Test {",
+ " /** could be an {@link ArrayList} */",
+ " List<String> xs;",
+ "}",
+ },
+ },
+ {
+ {
+ "import java.util.ArrayList;", //
+ "import java.util.Collection;",
+ "/** {@link ArrayList#add} {@link Collection#remove(Object)} */",
+ "class Test {}",
+ },
+ {
+ "import java.util.ArrayList;", //
+ "import java.util.Collection;",
+ "/** {@link ArrayList#add} {@link Collection#remove(Object)} */",
+ "class Test {}",
+ },
+ },
+ {
+ {
+ "import a.A;",
+ "import a.B;",
+ "import a.C;",
+ "class Test {",
+ " /** a",
+ " * {@link A} */",
+ " void f() {}",
+ "}",
+ },
+ {
+ "import a.A;", //
+ "class Test {",
+ " /** a",
+ " * {@link A} */",
+ " void f() {}",
+ "}",
+ },
+ },
+ {
+ {
+ "import a.A;import a.B;", //
+ "import a.C; // hello",
+ "class Test {",
+ " B b;",
+ "}",
+ },
+ {
+ "import a.B;", //
+ "// hello",
+ "class Test {",
+ " B b;",
+ "}",
+ },
+ },
+ {
+ {
+ "import a.A;",
+ "import b.B;",
+ "import c.C;",
+ "import d.D;",
+ "import e.E;",
+ "import f.F;",
+ "import g.G;",
+ "import h.H;",
+ "/**",
+ " * {@link A} {@linkplain B} {@value D#FOO}",
+ " *",
+ " * @exception E",
+ " * @throws F",
+ " * @see C",
+ " * @see H#foo",
+ " * @see <a href=\"whatever\">",
+ " */",
+ "class Test {",
+ "}",
+ },
+ {
+ "import a.A;",
+ "import b.B;",
+ "import c.C;",
+ "import d.D;",
+ "import e.E;",
+ "import f.F;",
+ "import h.H;",
+ "/**",
+ " * {@link A} {@linkplain B} {@value D#FOO}",
+ " *",
+ " * @exception E",
+ " * @throws F",
+ " * @see C",
+ " * @see H#foo",
+ " * @see <a href=\"whatever\">",
+ " */",
+ "class Test {",
+ "}",
+ },
+ },
+ {
+ {
+ "import java.util.Map;",
+ "/** {@link Map.Entry#containsKey(Object)} } */",
+ "class Test {}",
+ },
+ {
+ "import java.util.Map;",
+ "/** {@link Map.Entry#containsKey(Object)} } */",
+ "class Test {}",
+ },
+ },
+ {
+ {
+ "/** {@link #containsKey(Object)} } */", //
+ "class Test {}",
+ },
+ {
+ "/** {@link #containsKey(Object)} } */", //
+ "class Test {}",
+ },
+ },
+ {
+ {
+ "import java.util.*;", //
+ "class Test {",
+ " List<String> xs;",
+ "}",
+ },
+ {
+ "import java.util.*;", //
+ "class Test {",
+ " List<String> xs;",
+ "}",
+ },
+ },
+ {
+ {
+ "package com.foo;",
+ "import static com.foo.Outer.A;",
+ "import com.foo.*;",
+ "import com.foo.B;",
+ "import com.bar.C;",
+ "class Test {",
+ " A a;",
+ " B b;",
+ " C c;",
+ "}",
+ },
+ {
+ "package com.foo;",
+ "import static com.foo.Outer.A;",
+ "import com.bar.C;",
+ "class Test {",
+ " A a;",
+ " B b;",
+ " C c;",
+ "}",
+ }
+ },
+ {
+ {
+ "import java.util.Map;", //
+ "import java.util.Map.Entry;",
+ "/** {@link #foo(Map.Entry[])} */",
+ "public class Test {}",
+ },
+ {
+ "import java.util.Map;", //
+ "/** {@link #foo(Map.Entry[])} */",
+ "public class Test {}",
+ },
+ },
+ {
+ {
+ "import java.util.List;",
+ "import java.util.Collection;",
+ "/** {@link java.util.List#containsAll(Collection)} */",
+ "public class Test {}",
+ },
+ {
+ "import java.util.Collection;",
+ "/** {@link java.util.List#containsAll(Collection)} */",
+ "public class Test {}",
+ },
+ },
+ {
+ {
+ "package p;",
+ "import java.lang.Foo;",
+ "import java.lang.Foo.Bar;",
+ "import p.Baz;",
+ "import p.Baz.Bork;",
+ "public class Test implements Foo, Bar, Baz, Bork {}",
+ },
+ {
+ "package p;",
+ "import java.lang.Foo.Bar;",
+ "import p.Baz.Bork;",
+ "public class Test implements Foo, Bar, Baz, Bork {}",
+ },
+ },
+ {
+ {
+ "import java.lang.Foo;", //
+ "interface Test { private static void foo() {} }",
+ },
+ {
+ "interface Test { private static void foo() {} }",
+ },
+ },
+ };
+ ImmutableList.Builder<Object[]> builder = ImmutableList.builder();
+ for (String[][] inputAndOutput : inputsOutputs) {
+ assertThat(inputAndOutput.length).isEqualTo(2);
+ String[] input = inputAndOutput[0];
+ String[] output = inputAndOutput[1];
+ String[] parameters = {
+ Joiner.on('\n').join(input) + '\n', Joiner.on('\n').join(output) + '\n',
+ };
+ builder.add(parameters);
+ }
+ return builder.build();
+ }
+
+ private final String input;
+ private final String expected;
+
+ public RemoveUnusedImportsTest(String input, String expected) {
+ this.input = input;
+ this.expected = expected;
+ }
+
+ @Test
+ public void removeUnused() throws FormatterException {
+ assertThat(removeUnusedImports(input)).isEqualTo(expected);
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/SnippetFormatterTest.java b/core/src/test/java/com/google/googlejavaformat/java/SnippetFormatterTest.java
new file mode 100644
index 0000000..f763dd5
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/SnippetFormatterTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Range;
+import com.google.googlejavaformat.java.SnippetFormatter.SnippetKind;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** {@link SnippetFormatter}Test */
+@RunWith(JUnit4.class)
+public class SnippetFormatterTest {
+ @Test
+ public void expression() throws FormatterException {
+ String input = "x\n=42";
+ List<Replacement> replacements =
+ new SnippetFormatter()
+ .format(
+ SnippetKind.EXPRESSION,
+ input,
+ ImmutableList.of(Range.closedOpen(0, input.length())),
+ 4,
+ false);
+ assertThat(replacements)
+ .containsExactly(Replacement.create(1, 2, " "), Replacement.create(3, 3, " "));
+ }
+
+ @Test
+ public void statement() throws FormatterException {
+ String input = "int x\n=42;";
+ List<Replacement> replacements =
+ new SnippetFormatter()
+ .format(
+ SnippetKind.STATEMENTS,
+ input,
+ ImmutableList.of(Range.closedOpen(0, input.length())),
+ 4,
+ false);
+ assertThat(replacements)
+ .containsExactly(Replacement.create(5, 6, " "), Replacement.create(7, 7, " "));
+ }
+
+ @Test
+ public void classMember() throws FormatterException {
+ String input = "void f() {\n}";
+ List<Replacement> replacements =
+ new SnippetFormatter()
+ .format(
+ SnippetKind.CLASS_BODY_DECLARATIONS,
+ input,
+ ImmutableList.of(Range.closedOpen(0, input.length())),
+ 4,
+ false);
+ assertThat(replacements).containsExactly(Replacement.create(10, 11, ""));
+ }
+
+ @Test
+ public void compilation() throws FormatterException {
+ String input = "/** a\nb*/\nclass Test {\n}";
+ List<Replacement> replacements =
+ new SnippetFormatter()
+ .format(
+ SnippetKind.COMPILATION_UNIT,
+ input,
+ ImmutableList.of(Range.closedOpen(input.indexOf("class"), input.length())),
+ 4,
+ false);
+ assertThat(replacements).containsExactly(Replacement.create(22, 23, ""));
+ }
+
+ @Test
+ public void compilationWithComments() throws FormatterException {
+ String input = "/** a\nb*/\nclass Test {\n}";
+ List<Replacement> replacements =
+ new SnippetFormatter()
+ .format(
+ SnippetKind.COMPILATION_UNIT,
+ input,
+ ImmutableList.of(Range.closedOpen(0, input.length())),
+ 4,
+ true);
+ assertThat(replacements)
+ .containsExactly(Replacement.create(0, 24, "/** a b */\nclass Test {}\n"));
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/StringWrapperIntegrationTest.java b/core/src/test/java/com/google/googlejavaformat/java/StringWrapperIntegrationTest.java
new file mode 100644
index 0000000..89c94ea
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/StringWrapperIntegrationTest.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** {@link StringWrapper}IntegrationTest */
+@RunWith(Parameterized.class)
+public class StringWrapperIntegrationTest {
+
+ @Parameters
+ public static Collection<Object[]> parameters() {
+ String[][][] inputsAndOutputs = {
+ {
+ {
+ "class T {", //
+ " String s =",
+ " \"one long incredibly unbroken sentence\"",
+ " + \" moving from topic to topic\"",
+ " + \" so that no-one had a chance to\"",
+ " + \" interrupt\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"one long incredibly unbroken\"",
+ " + \" sentence moving from\"",
+ " + \" topic to topic so that\"",
+ " + \" no-one had a chance to\"",
+ " + \" interrupt\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s =",
+ " \"one long incredibly unbroken\"",
+ " + \" sentence moving from topic to topic so that\"",
+ " + \" no-one had a chance to\"",
+ " + \" interrupt\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"one long incredibly unbroken\"",
+ " + \" sentence moving from\"",
+ " + \" topic to topic so that\"",
+ " + \" no-one had a chance to\"",
+ " + \" interrupt\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s =",
+ " \"one long incredibly unbroken\"",
+ " + \" sentence moving from topic to topic\"",
+ " + \" so that no-one had a chance to interr\"",
+ " + \"upt\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"one long incredibly unbroken\"",
+ " + \" sentence moving from\"",
+ " + \" topic to topic so that\"",
+ " + \" no-one had a chance to\"",
+ " + \" interrupt\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s = \"one long incredibly unbroken sentence moving from topic to topic so that"
+ + " no-one had a chance to interrupt\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"one long incredibly unbroken\"",
+ " + \" sentence moving from\"",
+ " + \" topic to topic so that\"",
+ " + \" no-one had a chance to\"",
+ " + \" interrupt\";",
+ "}",
+ },
+ },
+ {
+ {
+ "class T {", //
+ " String s =",
+ " \"one long incredibly unbroken sentence\"",
+ " + \" moving from topic to topic\"",
+ " + 42",
+ " + \" so that no-one had a chance to interr\"",
+ " + \"upt\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"one long incredibly unbroken\"",
+ " + \" sentence moving from\"",
+ " + \" topic to topic\"",
+ " + 42",
+ " + \" so that no-one had a\"",
+ " + \" chance to interrupt\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s ="
+ + " \"onelongincrediblyunbrokensentencemovingfromtopictotopicsothatnoonehadachanceto"
+ + " interrupt\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " "
+ + " \"onelongincrediblyunbrokensentencemovingfromtopictotopicsothatnoonehadachanceto\"",
+ " + \" interrupt\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s = \"\\n\\none\\nlong\\nincredibly\\nunbroken\\nsentence\\nmoving\\nfrom\\n"
+ + " topic\\nto\\n topic\\nso\\nthat\\nno-one\\nhad\\na\\nchance\\nto\\ninterrupt\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"\\n\\n\"",
+ " + \"one\\n\"",
+ " + \"long\\n\"",
+ " + \"incredibly\\n\"",
+ " + \"unbroken\\n\"",
+ " + \"sentence\\n\"",
+ " + \"moving\\n\"",
+ " + \"from\\n\"",
+ " + \" topic\\n\"",
+ " + \"to\\n\"",
+ " + \" topic\\n\"",
+ " + \"so\\n\"",
+ " + \"that\\n\"",
+ " + \"no-one\\n\"",
+ " + \"had\\n\"",
+ " + \"a\\n\"",
+ " + \"chance\\n\"",
+ " + \"to\\n\"",
+ " + \"interrupt\";",
+ "}",
+ },
+ },
+ {
+ {
+ "class T {", //
+ " String s = \"\\n\\n\\none\\n\\nlong\\n\\nincredibly\\n\\nunbroken\\n\\nsentence\\n\\n"
+ + "moving\\n\\nfrom\\n\\n topic\\n\\nto\\n\\n topic\\n\\nso\\n\\nthat\\n\\nno-one"
+ + "\\n\\nhad\\n\\na\\n\\nchance\\n\\nto\\n\\ninterrupt\\n\\n\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"\\n\\n\\n\"",
+ " + \"one\\n\\n\"",
+ " + \"long\\n\\n\"",
+ " + \"incredibly\\n\\n\"",
+ " + \"unbroken\\n\\n\"",
+ " + \"sentence\\n\\n\"",
+ " + \"moving\\n\\n\"",
+ " + \"from\\n\\n\"",
+ " + \" topic\\n\\n\"",
+ " + \"to\\n\\n\"",
+ " + \" topic\\n\\n\"",
+ " + \"so\\n\\n\"",
+ " + \"that\\n\\n\"",
+ " + \"no-one\\n\\n\"",
+ " + \"had\\n\\n\"",
+ " + \"a\\n\\n\"",
+ " + \"chance\\n\\n\"",
+ " + \"to\\n\\n\"",
+ " + \"interrupt\\n\\n\";",
+ "}",
+ },
+ },
+ {
+ {
+ "class T {", //
+ " String s = \"onelongincrediblyunbrokensenten\\tcemovingfromtopictotopicsothatnoonehada"
+ + "chance tointerrupt\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"onelongincrediblyunbrokensenten\"",
+ " + \"\\tcemovingfromtopictotopicsothatnoonehadachance\"",
+ " + \" tointerrupt\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s = \"onelongincrediblyunbrokensentencemovingfromtopictotopicsothatnoonehada"
+ + "chancetointerrupt_____________________)_\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"onelongincrediblyunbrokensentencemovingfromtopictotopicsothatnoonehada"
+ + "chancetointerrupt_____________________)_\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s = \"onelongincrediblyunbrokensentencemovingfromtopictotopicsot atnoonehada"
+ + "chancetointerrupt______________________\";;",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"onelongincrediblyunbrokensentencemovingfromtopictotopicsot\"",
+ " + \" atnoonehadachancetointerrupt______________________\";",
+ " ;",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s = \"__ onelongincrediblyunbrokensentencemovingfromtopictotopicsothatnoonehada"
+ + "chanceto interrupt\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"__"
+ + " onelongincrediblyunbrokensentencemovingfromtopictotopicsothatnoonehadachanceto\"",
+ " + \" interrupt\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s =",
+ " \"one long incredibly unbroken sentence\"",
+ " // comment",
+ " + \" moving from topic to topic\"",
+ " // comment",
+ " + \" so that no-one had a chance to\"",
+ " // comment",
+ " + \" interrupt\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"one long incredibly unbroken\"",
+ " + \" sentence\"",
+ " // comment",
+ " + \" moving from topic to\"",
+ " + \" topic\"",
+ " // comment",
+ " + \" so that no-one had a\"",
+ " + \" chance to\"",
+ " // comment",
+ " + \" interrupt\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s =",
+ " \"aaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbb ccccccccccccccccccccccccccc"
+ + " dddddddddddddddddd\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"aaaaaaaaaaaaaaaaaaaaaaaa\"",
+ " + \" bbbbbbbbbbbbbbbbbb\"",
+ " + \" ccccccccccccccccccccccccccc\"",
+ " + \" dddddddddddddddddd\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " String s =",
+ " \"aaaaaaaaaaaaaaaaaaaaaaaa \"",
+ " + \"bbbbbbbbbbbbbbbbbb \"",
+ " + \"ccccccccccccccccccccccccccc \"",
+ " + \"dddddddddddddddddd\";",
+ "}"
+ },
+ {
+ "class T {",
+ " String s =",
+ " \"aaaaaaaaaaaaaaaaaaaaaaaa\"",
+ " + \" bbbbbbbbbbbbbbbbbb\"",
+ " + \" ccccccccccccccccccccccccccc\"",
+ " + \" dddddddddddddddddd\";",
+ "}",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " byte[] bytes =",
+ " \"one long incredibly unbroken sentence moving from topic to topic so that no-one"
+ + " had a chance to interrupt\".getBytes();",
+ "}"
+ },
+ {
+ "class T {", //
+ " byte[] bytes =",
+ " \"one long incredibly unbroken sentence moving from topic to topic so that no-one"
+ + " had a chance to interrupt\"",
+ " .getBytes();",
+ "}"
+ },
+ },
+ };
+ return Arrays.stream(inputsAndOutputs)
+ .map(
+ inputAndOutput -> {
+ assertThat(inputAndOutput).hasLength(2);
+ return new String[] {
+ Joiner.on('\n').join(inputAndOutput[0]) + '\n', //
+ Joiner.on('\n').join(inputAndOutput[1]) + '\n',
+ };
+ })
+ .collect(toImmutableList());
+ }
+
+ private final Formatter formatter = new Formatter();
+
+ private final String input;
+ private final String output;
+
+ public StringWrapperIntegrationTest(String input, String output) {
+ this.input = input;
+ this.output = output;
+ }
+
+ @Test
+ public void test() throws Exception {
+ assertThat(StringWrapper.wrap(40, formatter.formatSource(input), formatter)).isEqualTo(output);
+ }
+
+ @Test
+ public void testCR() throws Exception {
+ assertThat(StringWrapper.wrap(40, formatter.formatSource(input.replace("\n", "\r")), formatter))
+ .isEqualTo(output.replace("\n", "\r"));
+ }
+
+ @Test
+ public void testCRLF() throws Exception {
+ assertThat(
+ StringWrapper.wrap(40, formatter.formatSource(input.replace("\n", "\r\n")), formatter))
+ .isEqualTo(output.replace("\n", "\r\n"));
+ }
+
+ @Test
+ public void idempotent() throws Exception {
+ String wrap = StringWrapper.wrap(40, formatter.formatSource(input), formatter);
+ assertThat(wrap).isEqualTo(formatter.formatSource(wrap));
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/StringWrapperTest.java b/core/src/test/java/com/google/googlejavaformat/java/StringWrapperTest.java
new file mode 100644
index 0000000..99e1b2f
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/StringWrapperTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** {@link StringWrapper}Test */
+@RunWith(JUnit4.class)
+public class StringWrapperTest {
+ @Test
+ public void testAwkwardLineEndWrapping() throws Exception {
+ String input =
+ lines(
+ "class T {",
+ // This is a wide line, but has to be split in code because of 100-char limit.
+ " String s = someMethodWithQuiteALongNameThatWillGetUsUpCloseToTheColumnLimit() "
+ + "+ \"foo bar foo bar foo bar\";",
+ "",
+ " String someMethodWithQuiteALongNameThatWillGetUsUpCloseToTheColumnLimit() {",
+ " return null;",
+ " }",
+ "}");
+ String output =
+ lines(
+ "class T {",
+ " String s =",
+ " someMethodWithQuiteALongNameThatWillGetUsUpCloseToTheColumnLimit()",
+ " + \"foo bar foo bar foo bar\";",
+ "",
+ " String someMethodWithQuiteALongNameThatWillGetUsUpCloseToTheColumnLimit() {",
+ " return null;",
+ " }",
+ "}");
+
+ assertThat(StringWrapper.wrap(100, input, new Formatter())).isEqualTo(output);
+ }
+
+ private static String lines(String... line) {
+ return Joiner.on('\n').join(line) + '\n';
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/TypeNameClassifierTest.java b/core/src/test/java/com/google/googlejavaformat/java/TypeNameClassifierTest.java
new file mode 100644
index 0000000..9d1e00a
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/TypeNameClassifierTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.java;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+
+import com.google.common.base.Splitter;
+import com.google.googlejavaformat.java.TypeNameClassifier.JavaCaseFormat;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** {@link TypeNameClassifier}Test */
+@RunWith(JUnit4.class)
+public final class TypeNameClassifierTest {
+ @Test
+ public void caseFormat() throws Exception {
+ assertThat(JavaCaseFormat.from("CONST")).isEqualTo(JavaCaseFormat.UPPERCASE);
+ assertThat(JavaCaseFormat.from("TypeName")).isEqualTo(JavaCaseFormat.UPPER_CAMEL);
+ assertThat(JavaCaseFormat.from("fieldName")).isEqualTo(JavaCaseFormat.LOWER_CAMEL);
+ assertThat(JavaCaseFormat.from("com")).isEqualTo(JavaCaseFormat.LOWERCASE);
+
+ assertThat(JavaCaseFormat.from("CONST_$")).isEqualTo(JavaCaseFormat.UPPERCASE);
+ assertThat(JavaCaseFormat.from("TypeName_$")).isEqualTo(JavaCaseFormat.UPPER_CAMEL);
+ assertThat(JavaCaseFormat.from("fieldName_$")).isEqualTo(JavaCaseFormat.LOWER_CAMEL);
+ assertThat(JavaCaseFormat.from("com_$")).isEqualTo(JavaCaseFormat.LOWERCASE);
+
+ assertThat(JavaCaseFormat.from("A_$")).isEqualTo(JavaCaseFormat.UPPERCASE);
+ assertThat(JavaCaseFormat.from("a_$")).isEqualTo(JavaCaseFormat.LOWERCASE);
+ assertThat(JavaCaseFormat.from("_")).isEqualTo(JavaCaseFormat.LOWERCASE);
+ assertThat(JavaCaseFormat.from("_A")).isEqualTo(JavaCaseFormat.UPPERCASE);
+ }
+
+ private static Optional<Integer> getPrefix(String qualifiedName) {
+ return TypeNameClassifier.typePrefixLength(Splitter.on('.').splitToList(qualifiedName));
+ }
+
+ @Test
+ public void typePrefixLength() {
+ assertThat(getPrefix("fieldName")).isEmpty();
+ assertThat(getPrefix("CONST")).isEmpty();
+ assertThat(getPrefix("ClassName")).hasValue(0);
+ assertThat(getPrefix("com.ClassName")).hasValue(1);
+ assertThat(getPrefix("ClassName.foo")).hasValue(1);
+ assertThat(getPrefix("com.ClassName.foo")).hasValue(2);
+ assertThat(getPrefix("ClassName.foo.bar")).hasValue(1);
+ assertThat(getPrefix("com.ClassName.foo.bar")).hasValue(2);
+ assertThat(getPrefix("ClassName.CONST")).hasValue(1);
+ assertThat(getPrefix("ClassName.varName")).hasValue(1);
+ assertThat(getPrefix("ClassName.Inner.varName")).hasValue(2);
+ }
+
+ @Test
+ public void ambiguousClass() {
+ assertThat(getPrefix("com.google.security.acl.proto2api.ACL.Entry.newBuilder")).hasValue(7);
+ // A human would probably identify this as "class-shaped", but just looking
+ // at the case we have to assume it could be something like `field1.field2.CONST`.
+ assertThat(getPrefix("com.google.security.acl.proto2api.ACL.newBuilder")).isEmpty();
+ }
+}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/filer/FormattingFilerTest.java b/core/src/test/java/com/google/googlejavaformat/java/filer/FormattingFilerTest.java
new file mode 100644
index 0000000..4fef207
--- /dev/null
+++ b/core/src/test/java/com/google/googlejavaformat/java/filer/FormattingFilerTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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.java.filer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.testing.compile.CompilationRule;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.Messager;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.tools.FileObject;
+import javax.tools.JavaFileManager.Location;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link com.google.googlejavaformat.java.filer.FormattingFiler}. */
+@RunWith(JUnit4.class)
+public class FormattingFilerTest {
+
+ @Rule public CompilationRule compilationRule = new CompilationRule();
+
+ @Test
+ public void invalidSyntaxDoesNotThrowError() throws IOException {
+ List<String> logMessages = new ArrayList<>();
+ Messager messager =
+ new Messager() {
+ @Override
+ public void printMessage(javax.tools.Diagnostic.Kind kind, CharSequence msg) {
+ logMessages.add(kind.toString() + ";" + msg);
+ }
+
+ @Override
+ public void printMessage(javax.tools.Diagnostic.Kind kind, CharSequence msg, Element e) {}
+
+ @Override
+ public void printMessage(
+ javax.tools.Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a) {}
+
+ @Override
+ public void printMessage(
+ javax.tools.Diagnostic.Kind kind,
+ CharSequence msg,
+ Element e,
+ AnnotationMirror a,
+ AnnotationValue v) {}
+ };
+
+ String file = Joiner.on('\n').join("package foo;", "public class Bar {");
+ FormattingFiler formattingFiler = new FormattingFiler(new FakeFiler(), messager);
+ Writer writer = formattingFiler.createSourceFile("foo.Bar").openWriter();
+ writer.write(file);
+ writer.close();
+
+ assertThat(logMessages).containsExactly("NOTE;Error formatting foo.Bar");
+ }
+
+ @Test
+ public void formatsFile() throws IOException {
+ FormattingFiler formattingFiler = new FormattingFiler(new FakeFiler());
+ JavaFileObject sourceFile = formattingFiler.createSourceFile("foo.Bar");
+ try (Writer writer = sourceFile.openWriter()) {
+ writer.write("package foo;class Bar{private String baz;\n\n\n\n}");
+ }
+
+ assertThat(sourceFile.getCharContent(false).toString())
+ .isEqualTo(
+ Joiner.on('\n')
+ .join(
+ "package foo;",
+ "",
+ "class Bar {",
+ " private String baz;",
+ "}",
+ "")); // trailing newline
+ }
+
+ private static class FakeFiler implements Filer {
+ @Override
+ public JavaFileObject createSourceFile(CharSequence name, Element... originatingElements)
+ throws IOException {
+ return new ObservingJavaFileObject(name.toString(), Kind.SOURCE);
+ }
+
+ @Override
+ public JavaFileObject createClassFile(CharSequence name, Element... originatingElements)
+ throws IOException {
+ return new ObservingJavaFileObject(name.toString(), Kind.CLASS);
+ }
+
+ @Override
+ public FileObject createResource(
+ Location location,
+ CharSequence pkg,
+ CharSequence relativeName,
+ Element... originatingElements)
+ throws IOException {
+ return new ObservingJavaFileObject(pkg.toString() + relativeName, Kind.OTHER);
+ }
+
+ @Override
+ public FileObject getResource(Location location, CharSequence pkg, CharSequence relativeName)
+ throws IOException {
+ return new ObservingJavaFileObject(pkg.toString() + relativeName, Kind.OTHER);
+ }
+ }
+
+ private static class ObservingJavaFileObject extends SimpleJavaFileObject {
+ private final StringWriter output = new StringWriter();
+
+ ObservingJavaFileObject(String name, Kind kind) {
+ super(URI.create(name), kind);
+ }
+
+ @Override
+ public Writer openWriter() throws IOException {
+ return output;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return output.toString();
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/A.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/A.input
new file mode 100644
index 0000000..81d13aa
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/A.input
@@ -0,0 +1,84 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for AbstractTypeDeclarations, AnnotationTypeDeclarations, AnnotationTypeMemberDeclarations,
+ * Annotations, AnonymousClassDeclarations, ArrayAccesses, ArrayCreations, ArrayInitializers,
+ * ArrayTypes, AssertStatements, and Assignments.
+ */
+class A {
+ public @interface X {
+ int x();
+
+ int y() default
+ 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1;
+ }
+
+ @X(x = 1)
+ private @interface Y {}
+
+ // TODO(jdd): Add annotation declaration with empty body.
+
+ @X(x = 1)
+ @Y
+ protected @interface Z {}
+
+ // TODO(jdd): Include type annotations once we can include a higher language level.
+
+ int[] array1 = new int[5];
+ int[] array2 =
+ new int[] {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31
+ };
+ int[] array3 = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31
+ };
+ int[][] array4 = {
+ {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19},
+ {20, 21}, {22, 23}
+ };
+ int[][][] arrayWithLongName =
+ new int
+ [0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ [0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ [];
+
+ A a1 =
+ new A() {
+ int x =
+ array1[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ + array2[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ + array3[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ + array4[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0][
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0];
+ };
+
+ void f(int something) {
+ assert 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ == 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1;
+ assert 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ == 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ : "that was certainly unexpected!";
+ arrayWithLongName[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0] =
+ arrayWithLongName[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0];
+ something =
+ 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2
+ + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2
+ + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/A.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/A.output
new file mode 100644
index 0000000..3eff456
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/A.output
@@ -0,0 +1,84 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for AbstractTypeDeclarations, AnnotationTypeDeclarations, AnnotationTypeMemberDeclarations,
+ * Annotations, AnonymousClassDeclarations, ArrayAccesses, ArrayCreations, ArrayInitializers,
+ * ArrayTypes, AssertStatements, and Assignments.
+ */
+class A {
+ public @interface X {
+ int x();
+
+ int y() default
+ 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1;
+ }
+
+ @X(x = 1)
+ private @interface Y {}
+
+ // TODO(jdd): Add annotation declaration with empty body.
+
+ @X(x = 1)
+ @Y
+ protected @interface Z {}
+
+ // TODO(jdd): Include type annotations once we can include a higher language level.
+
+ int[] array1 = new int[5];
+ int[] array2 =
+ new int[] {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31
+ };
+ int[] array3 = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31
+ };
+ int[][] array4 = {
+ {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19},
+ {20, 21}, {22, 23}
+ };
+ int[][][] arrayWithLongName =
+ new int
+ [0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ [0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ [];
+
+ A a1 =
+ new A() {
+ int x =
+ array1[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ + array2[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ + array3[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ + array4[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0][
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0];
+ };
+
+ void f(int something) {
+ assert 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ == 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1;
+ assert 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ == 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ : "that was certainly unexpected!";
+ arrayWithLongName[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0] =
+ arrayWithLongName[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0];
+ something =
+ 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2
+ + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2
+ + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B.input
new file mode 100644
index 0000000..321f721
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B.input
@@ -0,0 +1,18 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for Blocks, BodyDeclarations, BooleanLiterals, and BreakStatements. */
+class B {
+ int x;
+ private int y;
+ public int z;
+
+ void f() {
+ LABEL:
+ while (true != false) {
+ if (false == true)
+ break;
+ if (false == false)
+ break LABEL;
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B.output
new file mode 100644
index 0000000..6eb0119
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B.output
@@ -0,0 +1,16 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for Blocks, BodyDeclarations, BooleanLiterals, and BreakStatements. */
+class B {
+ int x;
+ private int y;
+ public int z;
+
+ void f() {
+ LABEL:
+ while (true != false) {
+ if (false == true) break;
+ if (false == false) break LABEL;
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B112853497.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B112853497.input
new file mode 100644
index 0000000..e38d0b3
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B112853497.input
@@ -0,0 +1,12 @@
+class B112853497 {
+ {
+ XxxxxxxxxxXxxx xxxXxxxx =
+ xxxXxxxx
+ .xxxxXxXxxxx(xxxxXxxx, XXXXXX_XXXX_XXXXXXXX_XX_XXXXXXXX)
+ .toBuilder()
+ .xxxXxxxxXxxxXx(xxxxxXx.xxxXxxxxXxxxXx())
+ .xxxXxxxxXxxxXx(xxxxxXx.xxxXxxxxXxxxXx())
+ .xxxXxxxxx(xxxxxx.xxxXxxxxxXxxx())
+ .xxxxx();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B112853497.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B112853497.output
new file mode 100644
index 0000000..f5079e1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B112853497.output
@@ -0,0 +1,10 @@
+class B112853497 {
+ {
+ XxxxxxxxxxXxxx xxxXxxxx =
+ xxxXxxxx.xxxxXxXxxxx(xxxxXxxx, XXXXXX_XXXX_XXXXXXXX_XX_XXXXXXXX).toBuilder()
+ .xxxXxxxxXxxxXx(xxxxxXx.xxxXxxxxXxxxXx())
+ .xxxXxxxxXxxxXx(xxxxxXx.xxxXxxxxXxxxXx())
+ .xxxXxxxxx(xxxxxx.xxxXxxxxxXxxx())
+ .xxxxx();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B124394008.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B124394008.input
new file mode 100644
index 0000000..0b51407
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B124394008.input
@@ -0,0 +1,8 @@
+class B124394008 {
+ {
+ LocalReviewProto.Builder newReview =
+ readProfileResponse.flatMap(authorExtractor::extract)
+ .map(luReviewsExtractor::toLocalReviewProto)
+ .orElse(LocalReviewProto.getDefaultInstance()).toBuilder();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B124394008.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B124394008.output
new file mode 100644
index 0000000..176b46a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B124394008.output
@@ -0,0 +1,10 @@
+class B124394008 {
+ {
+ LocalReviewProto.Builder newReview =
+ readProfileResponse
+ .flatMap(authorExtractor::extract)
+ .map(luReviewsExtractor::toLocalReviewProto)
+ .orElse(LocalReviewProto.getDefaultInstance())
+ .toBuilder();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B126411718.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B126411718.input
new file mode 100644
index 0000000..c5cedba
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B126411718.input
@@ -0,0 +1,10 @@
+class B126411718 {
+ {
+ logger
+ .atDebug()
+ .log(
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(),
+ deletedPersistentNames);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B126411718.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B126411718.output
new file mode 100644
index 0000000..3f51285
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B126411718.output
@@ -0,0 +1,7 @@
+class B126411718 {
+ {
+ logger.atDebug().log(
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(), deletedPersistentNames);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B142553964.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B142553964.input
new file mode 100644
index 0000000..fb49cc6
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B142553964.input
@@ -0,0 +1,8 @@
+/*************************************************************************************************************************************************************
+ * Copyright
+ *
+ * Some
+ *
+ * Company
+ *************************************************************************************************************************************************************/
+class T {}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B142553964.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B142553964.output
new file mode 100644
index 0000000..c00d84e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B142553964.output
@@ -0,0 +1,8 @@
+/*************************************************************************************************************************************************************
+ * Copyright
+ *
+ * Some
+ *
+ * Company
+ *************************************************************************************************************************************************************/
+class T {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B154342628.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B154342628.input
new file mode 100644
index 0000000..dbde8f7
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B154342628.input
@@ -0,0 +1,8 @@
+class B154342628 {
+ void f() {
+ var var = 42;
+ return writtenVariables.stream()
+ .filter(var -> deletedVariableIds.contains(var.getId()))
+ .collect(toImmutableList());
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B154342628.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B154342628.output
new file mode 100644
index 0000000..dbde8f7
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B154342628.output
@@ -0,0 +1,8 @@
+class B154342628 {
+ void f() {
+ var var = 42;
+ return writtenVariables.stream()
+ .filter(var -> deletedVariableIds.contains(var.getId()))
+ .collect(toImmutableList());
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B18479811.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B18479811.input
new file mode 100644
index 0000000..f805f67
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B18479811.input
@@ -0,0 +1,15 @@
+class Test {
+ public void testPrimitiveFields() {
+ ConverterVerifier.verifyFieldsAreAnnotatedWithToPojo(
+ Contact.class, DomainContact.class,
+ new ImmutableList.Builder<String>().add(
+ "address1", "address2", "assistant", "assistantPhone", "birthDate", "committed",
+ "createdBy", "createdDate", "deleted", "department", "description",
+ "domainObjId", "email1", "email2", "emailOptOut", "firstName", "lastName",
+ "leadSource", "level", "modifiedBy", "modifiedDate",
+ "phoneFax", "phoneHome", "phoneMobile", "phoneOther", "phoneWork",
+ "salutation", "title"
+
+ ).build());
+ }
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B18479811.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B18479811.output
new file mode 100644
index 0000000..04f392c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B18479811.output
@@ -0,0 +1,38 @@
+class Test {
+ public void testPrimitiveFields() {
+ ConverterVerifier.verifyFieldsAreAnnotatedWithToPojo(
+ Contact.class,
+ DomainContact.class,
+ new ImmutableList.Builder<String>()
+ .add(
+ "address1",
+ "address2",
+ "assistant",
+ "assistantPhone",
+ "birthDate",
+ "committed",
+ "createdBy",
+ "createdDate",
+ "deleted",
+ "department",
+ "description",
+ "domainObjId",
+ "email1",
+ "email2",
+ "emailOptOut",
+ "firstName",
+ "lastName",
+ "leadSource",
+ "level",
+ "modifiedBy",
+ "modifiedDate",
+ "phoneFax",
+ "phoneHome",
+ "phoneMobile",
+ "phoneOther",
+ "phoneWork",
+ "salutation",
+ "title")
+ .build());
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950219.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950219.input
new file mode 100644
index 0000000..b4dd884
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950219.input
@@ -0,0 +1,8 @@
+class B19950219 {
+ @Override
+ String normalizeWord(String word) {
+ return Ascii.toLowerCase(word);
+ }
+ @Override
+ String convert(CaseFormat format, String s) {
+ }}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950219.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950219.output
new file mode 100644
index 0000000..c00a09c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950219.output
@@ -0,0 +1,9 @@
+class B19950219 {
+ @Override
+ String normalizeWord(String word) {
+ return Ascii.toLowerCase(word);
+ }
+
+ @Override
+ String convert(CaseFormat format, String s) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950468.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950468.input
new file mode 100644
index 0000000..9b4a8e2
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950468.input
@@ -0,0 +1,5 @@
+class B19950468 {
+ int code() {}
+
+ // comment
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950468.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950468.output
new file mode 100644
index 0000000..9b4a8e2
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950468.output
@@ -0,0 +1,5 @@
+class B19950468 {
+ int code() {}
+
+ // comment
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950815.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950815.input
new file mode 100644
index 0000000..369b8d1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950815.input
@@ -0,0 +1,12 @@
+class B19950815 {
+ void m() {
+ checkArgument(
+ truncationLength >= 0, "maxLength (%s) must be >= length of the truncation indicator (%s)",
+ maxLength, truncationIndicator.length());
+ }
+
+ private String finishCollapseFrom(
+ CharSequence sequence, int start, int end, char replacement, StringBuilder builder,
+ boolean inMatchingGroup) {
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950815.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950815.output
new file mode 100644
index 0000000..108a51e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19950815.output
@@ -0,0 +1,17 @@
+class B19950815 {
+ void m() {
+ checkArgument(
+ truncationLength >= 0,
+ "maxLength (%s) must be >= length of the truncation indicator (%s)",
+ maxLength,
+ truncationIndicator.length());
+ }
+
+ private String finishCollapseFrom(
+ CharSequence sequence,
+ int start,
+ int end,
+ char replacement,
+ StringBuilder builder,
+ boolean inMatchingGroup) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19996259.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19996259.input
new file mode 100644
index 0000000..a029186
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19996259.input
@@ -0,0 +1,16 @@
+class B19996259 {
+ void g( // line comment
+ int x) {}
+
+ void //
+ g //
+ ( //
+ int //
+ x //
+ , //
+ int //
+ y //
+ ) //
+ { //
+ } //
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19996259.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19996259.output
new file mode 100644
index 0000000..f0584c2
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B19996259.output
@@ -0,0 +1,16 @@
+class B19996259 {
+ void g( // line comment
+ int x) {}
+
+ void //
+ g //
+ ( //
+ int //
+ x //
+ , //
+ int //
+ y //
+ ) //
+ { //
+ } //
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128174.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128174.input
new file mode 100644
index 0000000..a4ae333
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128174.input
@@ -0,0 +1,8 @@
+class B20128174 {
+
+ enum Suit { DIAMONDS, HEARTS, CLUBS, SPADES };
+
+ enum Suit { DIAMONDS, HEARTS, CLUBS, SPADES; };
+
+ enum Suit { DIAMONDS, HEARTS, CLUBS, SPADES,; };
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128174.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128174.output
new file mode 100644
index 0000000..7550737
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128174.output
@@ -0,0 +1,24 @@
+class B20128174 {
+
+ enum Suit {
+ DIAMONDS,
+ HEARTS,
+ CLUBS,
+ SPADES
+ };
+
+ enum Suit {
+ DIAMONDS,
+ HEARTS,
+ CLUBS,
+ SPADES;
+ };
+
+ enum Suit {
+ DIAMONDS,
+ HEARTS,
+ CLUBS,
+ SPADES,
+ ;
+ };
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128588.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128588.input
new file mode 100644
index 0000000..74ed3db
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128588.input
@@ -0,0 +1,84 @@
+@Foo @Bar @Baz package edu.oswego.cs.dl.util.concurrent;
+
+@Foo @Bar @Baz class Test {
+
+ @Foo @Bar @Baz Object f() {
+ }
+
+ @Foo @Bar @Baz public Object f() {
+ }
+
+ @Foo @Bar @Baz void f() {
+ }
+
+ @Foo @Bar @Baz static Object field;
+
+ static @Foo @Bar @Baz Object field;
+
+ @Foo @Bar @Baz Object field;
+
+ @Foo(xs=42) @Bar @Baz Object field;
+
+ {
+ @Foo @Bar @Baz final Object var;
+
+ final @Foo @Bar @Baz Object var;
+
+ @Foo @Bar @Baz Object var;
+
+ @Foo(xs=42) @Bar @Baz Object var;
+ }
+
+ void f(
+ @Foo @Bar @Baz final Object var,
+ final @Foo @Bar @Baz Object var,
+ @Foo @Bar @Baz Object var,
+ @Foo(xs=42) @Bar @Baz Object var) {}
+
+ <@TA T extends @TA Object> @TA T f(List<? extends @TA T> a, List<? super @TA T> b) throws @TA Exception {}
+
+ @FooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @BarXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @BazXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX int x;
+
+@FooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @BarXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @BazXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX int x;
+
+ @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated Object var;
+
+ @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated Object var;
+
+ @Deprecated(x=42) @Deprecated @Deprecated @Deprecated @Deprecated Object var;
+
+ @Deprecated(
+ x = 42,
+ y = {
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ })
+ Object var;
+
+ @Deprecated(
+ y = {
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ })
+ Object var;
+}
+
+@Frozzle({
+ @Mirror(in = edu.oswego.cs.dl.util.concurrent.F.class, methods = "foo"),
+ @Mirror(in = edu.oswego.cs.dl.util.concurrent.F.class, methods = "foo"),
+ @Mirror(in = edu.oswego.cs.dl.util.concurrent.F.class, enable = false, methods = {
+ "foo", "foo", "foo", "foo", "foo",
+ "foo", "foo", "foo", "foo", "foo",
+ "foo", "foo", "foo", "foo", "foo",
+ }),
+ @Mirror(
+ in = edu.oswego.cs.dl.util.concurrent.F.class,
+ enable = false,
+ methods = {"foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo"}),
+ @Mirror(in = edu.oswego.cs.dl.util.concurrent.F.class, methods = "foo"),
+})
+class C {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128588.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128588.output
new file mode 100644
index 0000000..273fa79
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128588.output
@@ -0,0 +1,137 @@
+@Foo
+@Bar
+@Baz
+package edu.oswego.cs.dl.util.concurrent;
+
+@Foo
+@Bar
+@Baz
+class Test {
+
+ @Foo
+ @Bar
+ @Baz
+ Object f() {}
+
+ @Foo
+ @Bar
+ @Baz
+ public Object f() {}
+
+ @Foo
+ @Bar
+ @Baz
+ void f() {}
+
+ @Foo @Bar @Baz static Object field;
+
+ static @Foo @Bar @Baz Object field;
+
+ @Foo @Bar @Baz Object field;
+
+ @Foo(xs = 42)
+ @Bar
+ @Baz
+ Object field;
+
+ {
+ @Foo
+ @Bar
+ @Baz
+ final Object var;
+
+ final @Foo @Bar @Baz Object var;
+
+ @Foo
+ @Bar
+ @Baz
+ Object var;
+
+ @Foo(xs = 42)
+ @Bar
+ @Baz
+ Object var;
+ }
+
+ void f(
+ @Foo @Bar @Baz final Object var,
+ final @Foo @Bar @Baz Object var,
+ @Foo @Bar @Baz Object var,
+ @Foo(xs = 42) @Bar @Baz Object var) {}
+
+ <@TA T extends @TA Object> @TA T f(List<? extends @TA T> a, List<? super @TA T> b)
+ throws @TA Exception {}
+
+ @FooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ @BarXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ @BazXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ int x;
+
+ @FooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ @BarXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ @BazXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ int x;
+
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ Object var;
+
+ @Deprecated @Deprecated @Deprecated @Deprecated @Deprecated Object var;
+
+ @Deprecated(x = 42)
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ @Deprecated
+ Object var;
+
+ @Deprecated(
+ x = 42,
+ y = {
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ })
+ Object var;
+
+ @Deprecated(
+ y = {
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ })
+ Object var;
+}
+
+@Frozzle({
+ @Mirror(in = edu.oswego.cs.dl.util.concurrent.F.class, methods = "foo"),
+ @Mirror(in = edu.oswego.cs.dl.util.concurrent.F.class, methods = "foo"),
+ @Mirror(
+ in = edu.oswego.cs.dl.util.concurrent.F.class,
+ enable = false,
+ methods = {
+ "foo", "foo", "foo", "foo", "foo",
+ "foo", "foo", "foo", "foo", "foo",
+ "foo", "foo", "foo", "foo", "foo",
+ }),
+ @Mirror(
+ in = edu.oswego.cs.dl.util.concurrent.F.class,
+ enable = false,
+ methods = {"foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo"}),
+ @Mirror(in = edu.oswego.cs.dl.util.concurrent.F.class, methods = "foo"),
+})
+class C {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128760.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128760.input
new file mode 100644
index 0000000..30d17f4
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128760.input
@@ -0,0 +1,107 @@
+class B20128760 {
+ void f() {
+ x =
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ XxxxxxxxxXxx.<Xxxxxx, Xxxxxx>xxxxxxx()
+ .xxx(XxxxxxxxxXxxXxxx.Xxxxx.XXXXX, "Xxxxx xxxxxxxxx xxxxx")
+ .xxx(
+ XxxxxxxxxXxxXxxx.Xxxxx.XXXXXxXXXXX,
+ XxxxXxxxxxxxxx.xxxXxxxxxxxxXxxxxXxxxxXxXxxxxx(Xxxxxxxxxx.xxxXxxxXxxxx(x)))
+ .xxxxx();
+
+ xxxxxxXxxx(
+ xxx
+ .xxxXxXxxxxxxx(
+ new XxxxxxxxxXxxxxxxx.Xxxxxxx()
+ .xxxxXxxxXxx(
+ Xxxxx.xxxXxxxxXxxx(
+ new XxxxXx(
+ xxxxxxxxx1.xxxXxxxXx().xxxXxxxx()
+ + xxxxxxxxx2.xxxXxxxXx().xxxXxxxx())))
+ .xxxxx())
+ .xxxXxxxxxx())
+ .xxxxxxxxXxxxxxx();
+
+ XXxxxx<Xxxx, XxxxxxxXxxxxxxxxxxXxxxxxxxx> xxxxxxxxxXxxXxxXxXxxxxxxxXx =
+ XX
+ .xxxxXxxXxxx2(
+ "XxxxXxxxxxxxXxxxXxxxXxxxxxxxXxxxXxxXxxxxxxXxxxxxxxx",
+ xxxxxxxxXxxxXxXxxxxxxxXx,
+ xxxxxxxxXxxxXxxxxXxxxXxXxxxxxxxXx)
+ .xxxxxxxxXx(
+ "XxxxxxxxXxxxxxxxxXxx",
+ xxxxxxxxXxxxxxxxxXxxXx,
+ XX.xxxxxXx(XX.xxxxx(), XX.xxxxxx(XxxxxxxXxxxxxxxxxxXxxxxxxxx.xxxxx)));
+
+ xxXxxxxxxxXxxxXxxx =
+ (XxxxXxxx)
+ xxxxxxxxXxxxxxxxxx.xxx()
+ .xxxxxXxxxxxx
+ .xxxXxxxxxxx(xxxxxxxxXxxxxxxxxx.xxx().xxxxXxxxx.xxxXxxxxxxXxxx() - 1)
+ .xxxXxxx()
+ .xxxxXxxxXxXx(X.xx.xxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ xxxxxxXxxx(
+ xxxxxxxxxx
+ .xxxXxxxXxxxxXxxxxxxxxxxXxxxXxxxXxxxxXxxx(XXXxXXXXxXX, XXXXXXxXXXxXXXXxXX, XXXXXxXX)
+ .xxxxxxxxxxxXxxxXxxxXxxxxx())
+ .xxxxxxxxXxxxxxx(
+ xxxxXxxxxxxxxxxXxxxXxxxXxxxx(xxxx1Xxxx, Xxxxx.XXxXXX),
+ xxxxXxxxxxxxxxxXxxxXxxxXxxxx(xxxx2Xxxx, Xxxxx.XXxXXX));
+
+ xxxx(
+ xxxx(
+ xxxx(
+ xxxx(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx))));
+
+ assertThat(
+ main
+ .format("--offset", "0", "--length", String.valueOf(input.length()), path.toString()))
+ .isEqualTo(0);
+ }
+
+ private static class GenerateDashboardRowFn
+ extends
+ XX.XxXx<
+ Xxxx<
+ Xxxx,
+ Xxxxx2.XxxXxxxXxxxxxxxxxx<XxxxxxxxXxxx, Xxxx<XxxxxxxxXxxx, XxxxxxxXxxxxxxxx>>>,
+ Xxxx<Xxxx, XxxxxxxXxxxxxxxxxxXxxxxxxxx>> {}
+
+ public class XxxxxxxXxxxxxxxXxxxXxxxxXxxxxxx<X>
+ extends
+ XxxxxxxXxxxxxxxXxxxXxxxxXxxxxxx<
+ XxxxxxxXxxxxxxxXxxxXxxxxXxxxxxx<X>, XxxxXxxxxxxXxxxxxxxx<? extends Xxxxxxxx<X>>> {}
+
+ public class Foo
+ extends
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}
+
+ public class Foo
+ extends
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ implements
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}
+
+ public class Foo
+ extends
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ implements
+ Xxxxxxxxxxxxx<Xxxxxxxxxxxxxxxxxxxxxxxxxxxxx, Xxxxxxxxxxxxxxxxxxxxxxxxxxxxx, Xxxxxxxxxxxxxxxxxxxxxxxxxxxxx>,
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}
+
+ {
+ Stream<ItemKey> itemIdsStream = stream(members)
+ .flatMap(m -> m.getFieldValues()
+ .entrySet()
+ .stream()
+ .filter(fv -> itemLinkFieldIds.contains(fv.getKey()))
+ .flatMap(fv -> FieldDTO.deserializeStringToListOfStrings(fv.getValue())
+ .stream()
+ .map(id -> new ItemKey(fieldsById.get(fv.getKey()).getItemTypeId(), id))));
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128760.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128760.output
new file mode 100644
index 0000000..b5b27fa
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128760.output
@@ -0,0 +1,105 @@
+class B20128760 {
+ void f() {
+ x =
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ XxxxxxxxxXxx.<Xxxxxx, Xxxxxx>xxxxxxx()
+ .xxx(XxxxxxxxxXxxXxxx.Xxxxx.XXXXX, "Xxxxx xxxxxxxxx xxxxx")
+ .xxx(
+ XxxxxxxxxXxxXxxx.Xxxxx.XXXXXxXXXXX,
+ XxxxXxxxxxxxxx.xxxXxxxxxxxxXxxxxXxxxxXxXxxxxx(Xxxxxxxxxx.xxxXxxxXxxxx(x)))
+ .xxxxx();
+
+ xxxxxxXxxx(
+ xxx.xxxXxXxxxxxxx(
+ new XxxxxxxxxXxxxxxxx.Xxxxxxx()
+ .xxxxXxxxXxx(
+ Xxxxx.xxxXxxxxXxxx(
+ new XxxxXx(
+ xxxxxxxxx1.xxxXxxxXx().xxxXxxxx()
+ + xxxxxxxxx2.xxxXxxxXx().xxxXxxxx())))
+ .xxxxx())
+ .xxxXxxxxxx())
+ .xxxxxxxxXxxxxxx();
+
+ XXxxxx<Xxxx, XxxxxxxXxxxxxxxxxxXxxxxxxxx> xxxxxxxxxXxxXxxXxXxxxxxxxXx =
+ XX.xxxxXxxXxxx2(
+ "XxxxXxxxxxxxXxxxXxxxXxxxxxxxXxxxXxxXxxxxxxXxxxxxxxx",
+ xxxxxxxxXxxxXxXxxxxxxxXx,
+ xxxxxxxxXxxxXxxxxXxxxXxXxxxxxxxXx)
+ .xxxxxxxxXx(
+ "XxxxxxxxXxxxxxxxxXxx",
+ xxxxxxxxXxxxxxxxxXxxXx,
+ XX.xxxxxXx(XX.xxxxx(), XX.xxxxxx(XxxxxxxXxxxxxxxxxxXxxxxxxxx.xxxxx)));
+
+ xxXxxxxxxxXxxxXxxx =
+ (XxxxXxxx)
+ xxxxxxxxXxxxxxxxxx
+ .xxx()
+ .xxxxxXxxxxxx
+ .xxxXxxxxxxx(xxxxxxxxXxxxxxxxxx.xxx().xxxxXxxxx.xxxXxxxxxxXxxx() - 1)
+ .xxxXxxx()
+ .xxxxXxxxXxXx(X.xx.xxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ xxxxxxXxxx(
+ xxxxxxxxxx
+ .xxxXxxxXxxxxXxxxxxxxxxxXxxxXxxxXxxxxXxxx(XXXxXXXXxXX, XXXXXXxXXXxXXXXxXX, XXXXXxXX)
+ .xxxxxxxxxxxXxxxXxxxXxxxxx())
+ .xxxxxxxxXxxxxxx(
+ xxxxXxxxxxxxxxxXxxxXxxxXxxxx(xxxx1Xxxx, Xxxxx.XXxXXX),
+ xxxxXxxxxxxxxxxXxxxXxxxXxxxx(xxxx2Xxxx, Xxxxx.XXxXXX));
+
+ xxxx(
+ xxxx(
+ xxxx(
+ xxxx(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx))));
+
+ assertThat(
+ main.format(
+ "--offset", "0", "--length", String.valueOf(input.length()), path.toString()))
+ .isEqualTo(0);
+ }
+
+ private static class GenerateDashboardRowFn
+ extends XX.XxXx<
+ Xxxx<Xxxx, Xxxxx2.XxxXxxxXxxxxxxxxxx<XxxxxxxxXxxx, Xxxx<XxxxxxxxXxxx, XxxxxxxXxxxxxxxx>>>,
+ Xxxx<Xxxx, XxxxxxxXxxxxxxxxxxXxxxxxxxx>> {}
+
+ public class XxxxxxxXxxxxxxxXxxxXxxxxXxxxxxx<X>
+ extends XxxxxxxXxxxxxxxXxxxXxxxxXxxxxxx<
+ XxxxxxxXxxxxxxxXxxxXxxxxXxxxxxx<X>, XxxxXxxxxxxXxxxxxxxx<? extends Xxxxxxxx<X>>> {}
+
+ public class Foo
+ extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}
+
+ public class Foo
+ extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ implements Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}
+
+ public class Foo
+ extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ implements Xxxxxxxxxxxxx<
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxx>,
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}
+
+ {
+ Stream<ItemKey> itemIdsStream =
+ stream(members)
+ .flatMap(
+ m ->
+ m.getFieldValues().entrySet().stream()
+ .filter(fv -> itemLinkFieldIds.contains(fv.getKey()))
+ .flatMap(
+ fv ->
+ FieldDTO.deserializeStringToListOfStrings(fv.getValue()).stream()
+ .map(
+ id ->
+ new ItemKey(
+ fieldsById.get(fv.getKey()).getItemTypeId(), id))));
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128921.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128921.input
new file mode 100644
index 0000000..5d3a9ee
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128921.input
@@ -0,0 +1,10 @@
+public class B20128921 {
+
+ <T> void m(Iterable<T> ax) {
+
+ for (T a : ax) {
+
+ System.err.println(x);
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128921.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128921.output
new file mode 100644
index 0000000..5d3a9ee
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20128921.output
@@ -0,0 +1,10 @@
+public class B20128921 {
+
+ <T> void m(Iterable<T> ax) {
+
+ for (T a : ax) {
+
+ System.err.println(x);
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20341001.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20341001.input
new file mode 100644
index 0000000..504ba77
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20341001.input
@@ -0,0 +1,54 @@
+public class B20341001 {
+ int[] xs = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ };
+ int[] xs = {
+ Foo.CONSTxx,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ };
+ int[] xs = {
+ Foo_CONST,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ };
+ int[] xs = {
+ "foo",
+ "0123456",
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ };
+ int[] xs = {
+ "foo",
+ "01234567",
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ };
+ {
+ return ImmutableList.of(
+ item1,
+ item2,
+ item3,
+ item4,
+ item5,
+ item6,
+ item7,
+ item8,
+ item9,
+ item10,
+ item11,
+ item12,
+ item13);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20341001.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20341001.output
new file mode 100644
index 0000000..09a8f1b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20341001.output
@@ -0,0 +1,303 @@
+public class B20341001 {
+ int[] xs = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1
+ };
+ int[] xs = {
+ Foo.CONSTxx,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ };
+ int[] xs = {
+ Foo_CONST, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1
+ };
+ int[] xs = {
+ "foo", "0123456", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ };
+ int[] xs = {
+ "foo",
+ "01234567",
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ };
+
+ {
+ return ImmutableList.of(
+ item1, item2, item3, item4, item5, item6, item7, item8, item9, item10, item11, item12,
+ item13);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20524742.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20524742.input
new file mode 100644
index 0000000..7ea5a29
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20524742.input
@@ -0,0 +1,3 @@
+class Test {{
+ int x;
+}}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20524742.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20524742.output
new file mode 100644
index 0000000..bbd5dc3
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20524742.output
@@ -0,0 +1,5 @@
+class Test {
+ {
+ int x;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20529113.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20529113.input
new file mode 100644
index 0000000..8f65948
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20529113.input
@@ -0,0 +1,12 @@
+@BugPattern(category = GUAVA, severity = NOT_A_PROBLEM, maturity = EXPERIMENTAL)
+@BugPattern(
+ name = "AsyncFunctionReturnsImmediate", summary = SIMPLIFY,
+ category = GUAVA, severity = NOT_A_PROBLEM, maturity = EXPERIMENTAL)
+@BugPattern(
+ name = "AsyncFunctionReturnsImmediate", summary = SIMPLIFY,
+ explanation =
+ "If an AsyncFunction always returns immediateFuture() and never throws, it can "
+ + "be replaced with a Function.",
+ category = GUAVA, severity = NOT_A_PROBLEM, maturity = EXPERIMENTAL)
+class Test {
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20529113.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20529113.output
new file mode 100644
index 0000000..de0c052
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20529113.output
@@ -0,0 +1,17 @@
+@BugPattern(category = GUAVA, severity = NOT_A_PROBLEM, maturity = EXPERIMENTAL)
+@BugPattern(
+ name = "AsyncFunctionReturnsImmediate",
+ summary = SIMPLIFY,
+ category = GUAVA,
+ severity = NOT_A_PROBLEM,
+ maturity = EXPERIMENTAL)
+@BugPattern(
+ name = "AsyncFunctionReturnsImmediate",
+ summary = SIMPLIFY,
+ explanation =
+ "If an AsyncFunction always returns immediateFuture() and never throws, it can "
+ + "be replaced with a Function.",
+ category = GUAVA,
+ severity = NOT_A_PROBLEM,
+ maturity = EXPERIMENTAL)
+class Test {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20531711.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20531711.input
new file mode 100644
index 0000000..01f410f
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20531711.input
@@ -0,0 +1,16 @@
+class B20531711 {
+ void m() {
+ when(mLoginHelper.getAllAccounts()).thenReturn(
+ new Account[] {
+ new Account("test1", "test1"),
+ new Account("test2", "test2"),
+ });
+
+ when(mLoginHelper.getAllAccounts()).thenReturn(
+ new Account[] {
+ new Account("test1", "test1"),
+ new Account("test2", "test2"),
+// asd
+ });
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20531711.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20531711.output
new file mode 100644
index 0000000..806f4bd
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20531711.output
@@ -0,0 +1,16 @@
+class B20531711 {
+ void m() {
+ when(mLoginHelper.getAllAccounts())
+ .thenReturn(
+ new Account[] {
+ new Account("test1", "test1"), new Account("test2", "test2"),
+ });
+
+ when(mLoginHelper.getAllAccounts())
+ .thenReturn(
+ new Account[] {
+ new Account("test1", "test1"), new Account("test2", "test2"),
+ // asd
+ });
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20535125.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20535125.input
new file mode 100644
index 0000000..30c232b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20535125.input
@@ -0,0 +1,146 @@
+package com.google.googlejavaformat.java.test;
+
+class Test {
+ void m() {
+ if (!metadata.ignoreOutputTransformations()
+ && Producers.isListenableFutureMapKey(outputKey)) {
+ ImmutableList<ProducerNode<?>> nodes = createMapNodes((ProducerNode) node);
+ checkCollectionNodesAgainstWhitelist(nodes, whitelist);
+ return nodes;
+
+ } else if (!metadata.ignoreOutputTransformations()
+ && Producers.isListenableFutureListKey(outputKey)) {
+ ImmutableList<ProducerNode<?>> nodes = createListNodes((ProducerNode) node);
+ return nodes;
+ }
+
+ Set<Short> shorts = new HashSet<>();
+ for (short i = 0; i < 99; ++i) {
+
+ shorts.add(i);
+ shorts.remove(i - 1);
+
+ }
+
+ short i = 0;
+ do {
+
+ shorts.add(i);
+ shorts.remove(i - 1);
+ i++;
+
+ } while (i < 99);
+
+ if (true) {
+
+ System.err.println("Hi");
+
+ }
+
+ if (true) {
+
+ System.err.println("Hi");
+
+ } else {
+
+ System.err.println("Hi");
+
+ }
+
+ if (true) {
+
+ System.err.println("Hi");
+
+ } else if (true) {
+
+ System.err.println("Hi");
+
+ } else {
+
+ System.err.println("Hi");
+
+ }
+
+ try {
+
+ throw new Exception();
+
+ } catch (Exception e) {
+
+ System.err.println("Hi");
+
+ }
+
+ try {
+
+ throw new Exception();
+
+ } finally {
+
+ System.err.println("Hi");
+
+ }
+
+ try {
+
+ throw new Exception();
+
+ } catch (Exception e) {
+
+ System.err.println("Hi");
+
+ } finally {
+
+ System.err.println("Hi");
+
+ }
+
+ try {
+
+ throw new Exception();
+
+ } catch (Exception e) {
+
+ System.err.println("Hi");
+
+ } catch (Exception e) {
+
+ System.err.println("Hi");
+
+ } finally {
+
+ System.err.println("Hi");
+
+ }
+
+ try (Lock l = lock.lock()) {}
+
+ try (Lock l = lock.lock()) {} finally {}
+
+ for (;;) {}
+
+ while (true) {}
+
+ do {} while (true);
+
+ if (true) {}
+
+ if (true) {} else if (true) {}
+
+ if (true) {} else if (true) {} else if (true) {}
+
+ if (true) {} else if (true) {} else {}
+
+ if (true) {} else if (true) {} else if (true) {} else {}
+
+ try {} catch (Exception e) {} catch (Exception e) {} finally {}
+
+ try {} catch (Exception e) {} finally {}
+
+ try {} catch (Exception e) {} catch (Exception e) {}
+
+ try {} catch (Exception e) {}
+
+ }
+
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20535125.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20535125.output
new file mode 100644
index 0000000..950f4eb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20535125.output
@@ -0,0 +1,161 @@
+package com.google.googlejavaformat.java.test;
+
+class Test {
+ void m() {
+ if (!metadata.ignoreOutputTransformations() && Producers.isListenableFutureMapKey(outputKey)) {
+ ImmutableList<ProducerNode<?>> nodes = createMapNodes((ProducerNode) node);
+ checkCollectionNodesAgainstWhitelist(nodes, whitelist);
+ return nodes;
+
+ } else if (!metadata.ignoreOutputTransformations()
+ && Producers.isListenableFutureListKey(outputKey)) {
+ ImmutableList<ProducerNode<?>> nodes = createListNodes((ProducerNode) node);
+ return nodes;
+ }
+
+ Set<Short> shorts = new HashSet<>();
+ for (short i = 0; i < 99; ++i) {
+
+ shorts.add(i);
+ shorts.remove(i - 1);
+ }
+
+ short i = 0;
+ do {
+
+ shorts.add(i);
+ shorts.remove(i - 1);
+ i++;
+
+ } while (i < 99);
+
+ if (true) {
+
+ System.err.println("Hi");
+ }
+
+ if (true) {
+
+ System.err.println("Hi");
+
+ } else {
+
+ System.err.println("Hi");
+ }
+
+ if (true) {
+
+ System.err.println("Hi");
+
+ } else if (true) {
+
+ System.err.println("Hi");
+
+ } else {
+
+ System.err.println("Hi");
+ }
+
+ try {
+
+ throw new Exception();
+
+ } catch (Exception e) {
+
+ System.err.println("Hi");
+ }
+
+ try {
+
+ throw new Exception();
+
+ } finally {
+
+ System.err.println("Hi");
+ }
+
+ try {
+
+ throw new Exception();
+
+ } catch (Exception e) {
+
+ System.err.println("Hi");
+
+ } finally {
+
+ System.err.println("Hi");
+ }
+
+ try {
+
+ throw new Exception();
+
+ } catch (Exception e) {
+
+ System.err.println("Hi");
+
+ } catch (Exception e) {
+
+ System.err.println("Hi");
+
+ } finally {
+
+ System.err.println("Hi");
+ }
+
+ try (Lock l = lock.lock()) {}
+
+ try (Lock l = lock.lock()) {
+ } finally {
+ }
+
+ for (; ; ) {}
+
+ while (true) {}
+
+ do {} while (true);
+
+ if (true) {}
+
+ if (true) {
+ } else if (true) {
+ }
+
+ if (true) {
+ } else if (true) {
+ } else if (true) {
+ }
+
+ if (true) {
+ } else if (true) {
+ } else {
+ }
+
+ if (true) {
+ } else if (true) {
+ } else if (true) {
+ } else {
+ }
+
+ try {
+ } catch (Exception e) {
+ } catch (Exception e) {
+ } finally {
+ }
+
+ try {
+ } catch (Exception e) {
+ } finally {
+ }
+
+ try {
+ } catch (Exception e) {
+ } catch (Exception e) {
+ }
+
+ try {
+ } catch (Exception e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20567842.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20567842.input
new file mode 100644
index 0000000..5a7b3c5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20567842.input
@@ -0,0 +1,10 @@
+public class B20567842 {
+ // don't try to wrap the rhs as '{1, 2, 3}', go to block-like initializer style:
+ int[] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = {1, 2, 3};
+
+ // force block style, but not one-per-line for trailing ',':
+ int[] x = {a.b, true ? 1 : 2, CONST,};
+
+ // don't format one-per-line here:
+ int[] x = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20567842.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20567842.output
new file mode 100644
index 0000000..ec2a4df
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20567842.output
@@ -0,0 +1,17 @@
+public class B20567842 {
+ // don't try to wrap the rhs as '{1, 2, 3}', go to block-like initializer style:
+ int[] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = {
+ 1, 2, 3
+ };
+
+ // force block style, but not one-per-line for trailing ',':
+ int[] x = {
+ a.b, true ? 1 : 2, CONST,
+ };
+
+ // don't format one-per-line here:
+ int[] x = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1
+ };
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20569245.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20569245.input
new file mode 100644
index 0000000..fcdf5ed
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20569245.input
@@ -0,0 +1,35 @@
+class B20569245 {
+ void m() {
+ if (true) System.err.println("asd");
+
+ if (true) System.err.println("asd");
+ else if (true) System.err.println("asd");
+ else System.err.println("asd");
+
+ if (true) System.err.println("asd");
+ else if (true) System.err.println("asd");
+
+ if (true) System.err.println("asd");
+ else System.err.println("asd");
+
+ if (true)
+ System.err.println("asd");
+
+ if (true)
+ System.err.println("asd");
+ else if (true)
+ System.err.println("asd");
+ else
+ System.err.println("asd");
+
+ if (true)
+ System.err.println("asd");
+ else if (true)
+ System.err.println("asd");
+
+ if (true)
+ System.err.println("asd");
+ else
+ System.err.println("asd");
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20569245.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20569245.output
new file mode 100644
index 0000000..d21176c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20569245.output
@@ -0,0 +1,27 @@
+class B20569245 {
+ void m() {
+ if (true) System.err.println("asd");
+
+ if (true) System.err.println("asd");
+ else if (true) System.err.println("asd");
+ else System.err.println("asd");
+
+ if (true) System.err.println("asd");
+ else if (true) System.err.println("asd");
+
+ if (true) System.err.println("asd");
+ else System.err.println("asd");
+
+ if (true) System.err.println("asd");
+
+ if (true) System.err.println("asd");
+ else if (true) System.err.println("asd");
+ else System.err.println("asd");
+
+ if (true) System.err.println("asd");
+ else if (true) System.err.println("asd");
+
+ if (true) System.err.println("asd");
+ else System.err.println("asd");
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20577626.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20577626.input
new file mode 100644
index 0000000..b14efeb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20577626.input
@@ -0,0 +1,18 @@
+class B20577626 {
+ private @Mock
+ GsaConfigFlags mGsaConfig;
+
+ @Foo
+ @Bar
+ private @Mock
+ GsaConfigFlags mGsaConfig;
+
+ @Foo
+ abstract @Bar
+ void m() {}
+
+ @Foo
+ @Baz
+ abstract @Bar
+ void m() {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20577626.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20577626.output
new file mode 100644
index 0000000..bc3df14
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20577626.output
@@ -0,0 +1,12 @@
+class B20577626 {
+ private @Mock GsaConfigFlags mGsaConfig;
+
+ @Foo @Bar private @Mock GsaConfigFlags mGsaConfig;
+
+ @Foo
+ abstract @Bar void m() {}
+
+ @Foo
+ @Baz
+ abstract @Bar void m() {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20578077.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20578077.input
new file mode 100644
index 0000000..11b12ed
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20578077.input
@@ -0,0 +1,13 @@
+class B20578077 {
+ {
+ new IteratorTester<Integer>(
+ 4, MODIFIABLE, newArrayList(1, 2), IteratorTester.KnownOrder.KNOWN_ORDER) {
+ @Override
+ protected Iterator<Integer> newTargetIterator() {
+ Iterator<Integer> iterator = Lists.newArrayList(1, 2).iterator();
+ return new IteratorWithSunJavaBug6529795<Integer>(iterator);
+ }
+ }
+ .test();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20578077.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20578077.output
new file mode 100644
index 0000000..d5b545e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20578077.output
@@ -0,0 +1,12 @@
+class B20578077 {
+ {
+ new IteratorTester<Integer>(
+ 4, MODIFIABLE, newArrayList(1, 2), IteratorTester.KnownOrder.KNOWN_ORDER) {
+ @Override
+ protected Iterator<Integer> newTargetIterator() {
+ Iterator<Integer> iterator = Lists.newArrayList(1, 2).iterator();
+ return new IteratorWithSunJavaBug6529795<Integer>(iterator);
+ }
+ }.test();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20580212.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20580212.input
new file mode 100644
index 0000000..c203e3b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20580212.input
@@ -0,0 +1,16 @@
+class B20580212 {
+ void m() {
+ GroupExpansionReply mockIsgReply =
+ buildRecipientListSubGroupReply(
+ RECIPIENT1,
+ alternatives /* isgExpansions */,
+ alternativesDeltas /* isgExpansionsScoreDeltas */,
+ false /* withRecipient */,
+ 0 /* recipientScoreDelta */);
+
+ try {
+ } catch (IllegalStateException e) { /* expected */ }
+ }
+
+ static class ThrowsAtEndException extends RuntimeException { /* nothing */ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20580212.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20580212.output
new file mode 100644
index 0000000..df3f684
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20580212.output
@@ -0,0 +1,20 @@
+class B20580212 {
+ void m() {
+ GroupExpansionReply mockIsgReply =
+ buildRecipientListSubGroupReply(
+ RECIPIENT1,
+ alternatives /* isgExpansions */,
+ alternativesDeltas /* isgExpansionsScoreDeltas */,
+ false /* withRecipient */,
+ 0 /* recipientScoreDelta */);
+
+ try {
+ } catch (IllegalStateException e) {
+ /* expected */
+ }
+ }
+
+ static class ThrowsAtEndException extends RuntimeException {
+ /* nothing */
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20701054.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20701054.input
new file mode 100644
index 0000000..957c2df
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20701054.input
@@ -0,0 +1,185 @@
+class B20701054 {
+ void m() {
+ ImmutableList<String> x = ImmutableList.builder().add(1).build();
+ OptionalBinder.<ASD>newOptionalBinder(binder(), InputWhitelist.class).setBinding().to(
+ AllInputWhitelist.class);
+
+ Foo z = Foo.INSTANCE.field;
+ Foo z = Foo.INSTANCE.field.field;
+ Foo z = Foo.INSTANCE.field.field.field;
+ Foo z = Foo.INSTANCE.field.field.field.field;
+ Foo z = Foo.INSTANCE.field.field.field.field.field;
+
+ ImmutableList<String> x = ImmutableList.INSTANCE.add(1).build();
+ ImmutableList<String> x = ImmutableList.INSTANCE.add(1).add(2).build();
+ ImmutableList<String> x = ImmutableList.INSTANCE.add(1).add(2).add(3).build();
+ ImmutableList<String> x = ImmutableList.INSTANCE.add(1).add(2).add(3).add(4).build();
+
+ ImmutableList<String> x = ImmutableList.builder().add(1).build();
+ ImmutableList<String> x = ImmutableList.builder().add(1).add(2).build();
+ ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).build();
+ ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).add(4).build();
+ ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).add(4).add(5).build();
+ ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).add(4).add(5).add(6).build();
+
+ ImmutableList<String> x =
+ new ImmutableList.Builder<>()
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .build();
+
+ ImmutableList<String> x =
+ ImmutableList.new Builder<>()
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .build();
+
+ System.err.println(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ Class.my.contrived.example.function(
+ veryLongArgumentxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ PTable<Long, List<PageSpeedUrlResult>> latestResults =
+ FJ.joinOneShot2(accountSummaryProvider.get(), results).parallelDo(
+ "extractPageSpeedUrls", new ExtractPageSpeedUrlsFn(false));
+
+ PTable<Long, Long> adImpressionsByAccount =
+ impressionExtractor.getImpressionBreakdownByAccountId().parallelDo(
+ "rekeyAdImpressionsByAccountId", new ExtractTotalImpressionsFn());
+
+ PTable<String, CrawlError> crawlerErrorsByCode =
+ crawlReportSource.read("readCrawlReportTable").parallelDo(
+ "reKeyErrorsByPropertyCode", new RekeyErrorsByPropertyCodeFn());
+
+ if (ImmutableList.builder().add(1).add(2).add(3)) {
+ }
+
+ if (ImmutableList.builder()
+ .add(1)
+ .add(2)
+ .add(3)
+ .add(4)) {}
+
+ if (value.fst.name.toString().equals("value")) {
+ }
+
+ analysis().analyze(compilationUnit, context, configuration, new DescriptionListener() {
+ @Override
+ public void onDescribed(Description description) {
+ listener.onDescribed(description.filterFixes(new Predicate<Fix>() {
+ @Override
+ public boolean apply(Fix fix) {
+ return compiles(fix, (JCCompilationUnit) compilationUnit, context);
+ }
+ }));
+ }
+ });
+
+ (test ? xxx : yyy).zzz(xxx);
+
+ (test ? xxx : yyy)
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ (test
+ ? xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ : yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy)
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ zzzzzzzzzzzzzzzzzzzz.zzzzzzzzzzzzzzzzzzzzzzzzzzz.zzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ field.field.field.field.field.field.field.field.field.field.field.field.field.field.field.field
+ .field.field.field.field.field.method(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ (test
+ ? xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ : yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy)
+ .field.field.field.field.field.field.field.field.field.field.field.field.field.method(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ (test
+ ? xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ : yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy)
+ .field.field.field.field.field.field.field.field.field.field.field.field.field.field.field
+ .field.field.field.field.field.field.method(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ field.field.field.field.field.field.method(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ field.field.field.field.field.field.method(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy();
+
+ edu.oswego.cs.dl.util.concurrent.misc.ImmutableList.builder(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz);
+
+ edu.oswego.cs.dl.util.concurrent.misc.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .ImmutableList.builder(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz);
+
+ int u =
+ edu.oswego.cs.dl.util.concurrent.misc.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .ImmutableList.builder(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz;
+
+ for (String configured : errorProneOptions().getSeverityMap().keySet()) {}
+
+ return fffffffffff(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ return fffffffffff(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
+ .fffffffffffff(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20701054.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20701054.output
new file mode 100644
index 0000000..7ce6fda
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20701054.output
@@ -0,0 +1,196 @@
+class B20701054 {
+ void m() {
+ ImmutableList<String> x = ImmutableList.builder().add(1).build();
+ OptionalBinder.<ASD>newOptionalBinder(binder(), InputWhitelist.class)
+ .setBinding()
+ .to(AllInputWhitelist.class);
+
+ Foo z = Foo.INSTANCE.field;
+ Foo z = Foo.INSTANCE.field.field;
+ Foo z = Foo.INSTANCE.field.field.field;
+ Foo z = Foo.INSTANCE.field.field.field.field;
+ Foo z = Foo.INSTANCE.field.field.field.field.field;
+
+ ImmutableList<String> x = ImmutableList.INSTANCE.add(1).build();
+ ImmutableList<String> x = ImmutableList.INSTANCE.add(1).add(2).build();
+ ImmutableList<String> x = ImmutableList.INSTANCE.add(1).add(2).add(3).build();
+ ImmutableList<String> x = ImmutableList.INSTANCE.add(1).add(2).add(3).add(4).build();
+
+ ImmutableList<String> x = ImmutableList.builder().add(1).build();
+ ImmutableList<String> x = ImmutableList.builder().add(1).add(2).build();
+ ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).build();
+ ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).add(4).build();
+ ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).add(4).add(5).build();
+ ImmutableList<String> x =
+ ImmutableList.builder().add(1).add(2).add(3).add(4).add(5).add(6).build();
+
+ ImmutableList<String> x =
+ new ImmutableList.Builder<>()
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .build();
+
+ ImmutableList<String> x =
+ ImmutableList.new Builder<>()
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .add(xxxxx)
+ .build();
+
+ System.err.println(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ Class.my.contrived.example.function(
+ veryLongArgumentxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ PTable<Long, List<PageSpeedUrlResult>> latestResults =
+ FJ.joinOneShot2(accountSummaryProvider.get(), results)
+ .parallelDo("extractPageSpeedUrls", new ExtractPageSpeedUrlsFn(false));
+
+ PTable<Long, Long> adImpressionsByAccount =
+ impressionExtractor
+ .getImpressionBreakdownByAccountId()
+ .parallelDo("rekeyAdImpressionsByAccountId", new ExtractTotalImpressionsFn());
+
+ PTable<String, CrawlError> crawlerErrorsByCode =
+ crawlReportSource
+ .read("readCrawlReportTable")
+ .parallelDo("reKeyErrorsByPropertyCode", new RekeyErrorsByPropertyCodeFn());
+
+ if (ImmutableList.builder().add(1).add(2).add(3)) {}
+
+ if (ImmutableList.builder().add(1).add(2).add(3).add(4)) {}
+
+ if (value.fst.name.toString().equals("value")) {}
+
+ analysis()
+ .analyze(
+ compilationUnit,
+ context,
+ configuration,
+ new DescriptionListener() {
+ @Override
+ public void onDescribed(Description description) {
+ listener.onDescribed(
+ description.filterFixes(
+ new Predicate<Fix>() {
+ @Override
+ public boolean apply(Fix fix) {
+ return compiles(fix, (JCCompilationUnit) compilationUnit, context);
+ }
+ }));
+ }
+ });
+
+ (test ? xxx : yyy).zzz(xxx);
+
+ (test ? xxx : yyy)
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ (test
+ ? xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ : yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy)
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ zzzzzzzzzzzzzzzzzzzz.zzzzzzzzzzzzzzzzzzzzzzzzzzz.zzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ field.field.field.field.field.field.field.field.field.field.field.field.field.field.field.field
+ .field.field.field.field.field.method(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ (test
+ ? xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ : yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy)
+ .field.field.field.field.field.field.field.field.field.field.field.field.field.method(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ (test
+ ? xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ : yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy)
+ .field.field.field.field.field.field.field.field.field.field.field.field.field.field.field
+ .field.field.field.field.field.field.method(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ field.field.field.field.field.field.method(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ field
+ .field
+ .field
+ .field
+ .field
+ .field
+ .method(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy();
+
+ edu.oswego.cs.dl.util.concurrent.misc.ImmutableList.builder(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz);
+
+ edu.oswego.cs.dl.util.concurrent.misc.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .ImmutableList.builder(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzz(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz);
+
+ int u =
+ edu.oswego.cs.dl.util.concurrent.misc.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .ImmutableList.builder(
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz;
+
+ for (String configured : errorProneOptions().getSeverityMap().keySet()) {}
+
+ return fffffffffff(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ return fffffffffff(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
+ .fffffffffffff(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20844369.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20844369.input
new file mode 100644
index 0000000..86e46d5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20844369.input
@@ -0,0 +1,31 @@
+public class B20844369 {
+ private static final String ID_PATTERN =
+ // TODO(daw): add min/max lengths for the numbers here, e.g. android ID
+ "(?:(?<androidId>\\d+)\\+)?" // optional Android ID
+ + "(?<type>\\d+)" // type
+ + ":"
+ + "(?<timestamp>\\d+)" // timestamp
+ + "(?<subtype>%" // begin optional subtype
+ + "(?:(?<userId>\\d+)#)?" // subtype's optional user ID, followed by a hash
+ + "(?<categoryHash>[0-9a-fA-F]{8})" // subtype's category hash
+ + "(?<tokenHash>[0-9a-fA-F]{8})" // subtype's token hash
+ + ")?"; // end optional subtype
+
+ int x = //foo
+ 42 + //bar
+ 1;
+
+ int x =
+ //foo
+ 42 + //bar
+ 1;
+
+ int x = /*foo*/
+ 42 + //bar
+ 1;
+
+ int x =
+ /*foo*/
+ 42 + //bar
+ 1;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20844369.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20844369.output
new file mode 100644
index 0000000..982dc2b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20844369.output
@@ -0,0 +1,33 @@
+public class B20844369 {
+ private static final String ID_PATTERN =
+ // TODO(daw): add min/max lengths for the numbers here, e.g. android ID
+ "(?:(?<androidId>\\d+)\\+)?" // optional Android ID
+ + "(?<type>\\d+)" // type
+ + ":"
+ + "(?<timestamp>\\d+)" // timestamp
+ + "(?<subtype>%" // begin optional subtype
+ + "(?:(?<userId>\\d+)#)?" // subtype's optional user ID, followed by a hash
+ + "(?<categoryHash>[0-9a-fA-F]{8})" // subtype's category hash
+ + "(?<tokenHash>[0-9a-fA-F]{8})" // subtype's token hash
+ + ")?"; // end optional subtype
+
+ int x = // foo
+ 42 + // bar
+ 1;
+
+ int x =
+ // foo
+ 42
+ + // bar
+ 1;
+
+ int x = /*foo*/
+ 42 + // bar
+ 1;
+
+ int x =
+ /*foo*/
+ 42
+ + // bar
+ 1;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20915776.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20915776.input
new file mode 100644
index 0000000..29d59e5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20915776.input
@@ -0,0 +1,10 @@
+class B20915776 {
+ @Inject
+ void ChunkAnalysis(
+ // TODO(user) ...
+ @Parent DatasetSource supporters,
+ @Child DatasetSource children,
+ @Parent Provider<SupportsDecider> parentSupportsDecider,
+ @Child Provider<SupportsDecider> childSupportsDecider) {
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20915776.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20915776.output
new file mode 100644
index 0000000..54f2a6d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B20915776.output
@@ -0,0 +1,9 @@
+class B20915776 {
+ @Inject
+ void ChunkAnalysis(
+ // TODO(user) ...
+ @Parent DatasetSource supporters,
+ @Child DatasetSource children,
+ @Parent Provider<SupportsDecider> parentSupportsDecider,
+ @Child Provider<SupportsDecider> childSupportsDecider) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21031147.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21031147.input
new file mode 100644
index 0000000..ac2bf98
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21031147.input
@@ -0,0 +1,6 @@
+public class B21031147 {
+ {
+ return new StringBuilder(maxLength)
+ .append(seq, 0, truncationLength).append(truncationIndicator).toString();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21031147.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21031147.output
new file mode 100644
index 0000000..8fbeeae
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21031147.output
@@ -0,0 +1,8 @@
+public class B21031147 {
+ {
+ return new StringBuilder(maxLength)
+ .append(seq, 0, truncationLength)
+ .append(truncationIndicator)
+ .toString();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21105569.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21105569.input
new file mode 100644
index 0000000..49b6d13
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21105569.input
@@ -0,0 +1,9 @@
+class B21105569 {
+ void f() {
+ // asd
+ }
+ void g() {
+ /* asd */
+ }
+ void h() { /* asd */ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21105569.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21105569.output
new file mode 100644
index 0000000..8b5dfee
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21105569.output
@@ -0,0 +1,13 @@
+class B21105569 {
+ void f() {
+ // asd
+ }
+
+ void g() {
+ /* asd */
+ }
+
+ void h() {
+ /* asd */
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21185365.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21185365.input
new file mode 100644
index 0000000..7ffc27c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21185365.input
@@ -0,0 +1,5 @@
+class B21185365 {
+ {
+ for (int i = 0; i < s.length();) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21185365.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21185365.output
new file mode 100644
index 0000000..06beb1d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21185365.output
@@ -0,0 +1,5 @@
+class B21185365 {
+ {
+ for (int i = 0; i < s.length(); ) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21192435.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21192435.input
new file mode 100644
index 0000000..ffebae4
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21192435.input
@@ -0,0 +1,12 @@
+package testdata.output;
+
+public class B21192435 {
+
+ static class Test extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx implements Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx{}
+
+ static class Testxxxxxxxxx extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx implements Xxxxx{}
+
+ static class Testxxxxxxxxx extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx implements Xxxxxxxxxxxxxxxx{}
+
+ static class Test<Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx> extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx implements Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21192435.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21192435.output
new file mode 100644
index 0000000..9ddb4b8
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21192435.output
@@ -0,0 +1,18 @@
+package testdata.output;
+
+public class B21192435 {
+
+ static class Test extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ implements Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}
+
+ static class Testxxxxxxxxx
+ extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx implements Xxxxx {}
+
+ static class Testxxxxxxxxx
+ extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ implements Xxxxxxxxxxxxxxxx {}
+
+ static class Test<
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>
+ extends Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx implements Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21278211.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21278211.input
new file mode 100644
index 0000000..13e1559
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21278211.input
@@ -0,0 +1,23 @@
+class B21278211 {
+ void m() {
+ return names.isEmpty() || names.size() == 1 && names.get(0).toString().isEmpty();
+
+ // use unified breaks for all chained dereferences
+
+ x.f().g();
+ x.fzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz()
+ .gzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz();
+
+ x.f().g().h();
+ x
+ .f()
+ .g()
+ .hzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz();
+
+ x
+ .f()
+ .g()
+ .h()
+ .i();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21278211.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21278211.output
new file mode 100644
index 0000000..fa8ba52
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21278211.output
@@ -0,0 +1,18 @@
+class B21278211 {
+ void m() {
+ return names.isEmpty() || names.size() == 1 && names.get(0).toString().isEmpty();
+
+ // use unified breaks for all chained dereferences
+
+ x.f().g();
+ x.fzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz()
+ .gzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz();
+
+ x.f().g().h();
+ x.f()
+ .g()
+ .hzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz();
+
+ x.f().g().h().i();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21283374.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21283374.input
new file mode 100644
index 0000000..0402caa
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21283374.input
@@ -0,0 +1,57 @@
+class B21283374 {
+ void f(boolean flagA, boolean flagB, boolean flagC, boolean flagD, boolean flagE, boolean flagF, boolean flagG, boolean flagH, boolean flagI, boolean flagJ, boolean flagK) {}
+
+ {
+ f(
+ /*flagA=*/Boolean.TRUE,
+ /*flagB=*/Boolean.FALSE,
+ /*flagC=*/Boolean.FALSE,
+ /*flagD=*/Boolean.FALSE,
+ /*flagE=*/Boolean.FALSE,
+ /*flagF=*/Boolean.FALSE,
+ /*flagG=*/Boolean.FALSE,
+ /*flagH=*/Boolean.FALSE,
+ /*flagI=*/Boolean.FALSE,
+ /*flagJ=*/Boolean.FALSE,
+ /*flagK=*/Boolean.FALSE);
+
+ f(
+ /*flagA=*/ Boolean.TRUE,
+ /*flagB=*/ Boolean.FALSE,
+ /*flagC=*/ Boolean.FALSE,
+ /*flagD=*/ Boolean.FALSE,
+ /*flagE=*/ Boolean.FALSE,
+ /*flagF=*/ Boolean.FALSE,
+ /*flagG=*/ Boolean.FALSE,
+ /*flagH=*/ Boolean.FALSE,
+ /*flagI=*/ Boolean.FALSE,
+ /*flagJ=*/ Boolean.FALSE,
+ /*flagK=*/ Boolean.FALSE);
+
+ assertThat(foo.barAndBaz(/*paramName=*/false, thingy)).isEqualTo(new Something(""));
+ assertThat(foo.barAndBaz( /*paramName=*/false, thingy)).isEqualTo(new Something(""));
+ assertThat(foo.barAndBaz(/*paramName=*/ false, thingy)).isEqualTo(new Something(""));
+
+ f(/*paramName=*/ false);
+
+ assertThat__________________________________________________________(/*paramName=*/ false, thingy);
+ assertThat__________________________________________________________(
+ /*paramName=*/ false, thingy);
+
+ f(
+ arg1, /* which arg is this attached to? */
+ arg2,
+ arg3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+
+ f(
+ /* paramName1 */ arg1,
+ /* paramName2 */ arg2,
+ arg3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ f(
+ arg1, /* paramName1 */
+ arg2, /* paramName2 */
+ arg3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21283374.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21283374.output
new file mode 100644
index 0000000..f98a37b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21283374.output
@@ -0,0 +1,68 @@
+class B21283374 {
+ void f(
+ boolean flagA,
+ boolean flagB,
+ boolean flagC,
+ boolean flagD,
+ boolean flagE,
+ boolean flagF,
+ boolean flagG,
+ boolean flagH,
+ boolean flagI,
+ boolean flagJ,
+ boolean flagK) {}
+
+ {
+ f(
+ /*flagA=*/ Boolean.TRUE,
+ /*flagB=*/ Boolean.FALSE,
+ /*flagC=*/ Boolean.FALSE,
+ /*flagD=*/ Boolean.FALSE,
+ /*flagE=*/ Boolean.FALSE,
+ /*flagF=*/ Boolean.FALSE,
+ /*flagG=*/ Boolean.FALSE,
+ /*flagH=*/ Boolean.FALSE,
+ /*flagI=*/ Boolean.FALSE,
+ /*flagJ=*/ Boolean.FALSE,
+ /*flagK=*/ Boolean.FALSE);
+
+ f(
+ /*flagA=*/ Boolean.TRUE,
+ /*flagB=*/ Boolean.FALSE,
+ /*flagC=*/ Boolean.FALSE,
+ /*flagD=*/ Boolean.FALSE,
+ /*flagE=*/ Boolean.FALSE,
+ /*flagF=*/ Boolean.FALSE,
+ /*flagG=*/ Boolean.FALSE,
+ /*flagH=*/ Boolean.FALSE,
+ /*flagI=*/ Boolean.FALSE,
+ /*flagJ=*/ Boolean.FALSE,
+ /*flagK=*/ Boolean.FALSE);
+
+ assertThat(foo.barAndBaz(/*paramName=*/ false, thingy)).isEqualTo(new Something(""));
+ assertThat(foo.barAndBaz(/*paramName=*/ false, thingy)).isEqualTo(new Something(""));
+ assertThat(foo.barAndBaz(/*paramName=*/ false, thingy)).isEqualTo(new Something(""));
+
+ f(/*paramName=*/ false);
+
+ assertThat__________________________________________________________(
+ /*paramName=*/ false, thingy);
+ assertThat__________________________________________________________(
+ /*paramName=*/ false, thingy);
+
+ f(
+ arg1, /* which arg is this attached to? */
+ arg2,
+ arg3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ f(
+ /* paramName1 */ arg1,
+ /* paramName2 */ arg2,
+ arg3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+
+ f(
+ arg1, /* paramName1 */
+ arg2, /* paramName2 */
+ arg3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21305044.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21305044.input
new file mode 100644
index 0000000..049233c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21305044.input
@@ -0,0 +1,32 @@
+class B21305044 {
+
+ class T<@Nullable X extends @Nullable Object,
+ @Nullable Y extends @Nullable Object,
+ @Nullable Z extends @Nullable Object>
+ extends Function<@Nullable X, @Nullable Y>
+ implements Predicate<@Nullable Z> {
+ public T(@Nullable T this, List<@Nullable X> xs) {
+ }
+
+ public T(@Nullable T B21305044.this, List<@Nullable X> xs) {
+ }
+
+ List<@Nullable ?> a;
+ List<@Nullable ? extends @Nullable Object> b;
+ List<@Nullable ? super @Nullable Object> c;
+
+ List<? super @Crazy(key=value, other=@Crazier({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20})) Object> c;
+ }
+
+ Function f = com.foo.bar.Baz::func;
+ Function g = new Baz()::func;
+ Function h = Baz::new;
+ Function i = com.foo.bar.VeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongBaz::new;
+
+ {
+ Function f = () -> moderatelyLongResult;
+ Function f = () -> breakableResult + breakableResult + breakableResult + breakableResult + breakableResult + breakableResult + breakableResult + breakableResult + breakableResult;
+ Function f = () -> System.err.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+ Function f = (someParam) -> System.err.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21305044.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21305044.output
new file mode 100644
index 0000000..3273809
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21305044.output
@@ -0,0 +1,57 @@
+class B21305044 {
+
+ class T<
+ @Nullable X extends @Nullable Object,
+ @Nullable Y extends @Nullable Object,
+ @Nullable Z extends @Nullable Object>
+ extends Function<@Nullable X, @Nullable Y> implements Predicate<@Nullable Z> {
+ public T(@Nullable T this, List<@Nullable X> xs) {}
+
+ public T(@Nullable T B21305044.this, List<@Nullable X> xs) {}
+
+ List<@Nullable ?> a;
+ List<@Nullable ? extends @Nullable Object> b;
+ List<@Nullable ? super @Nullable Object> c;
+
+ List<
+ ? super
+ @Crazy(
+ key = value,
+ other =
+ @Crazier({
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
+ }))
+ Object>
+ c;
+ }
+
+ Function f = com.foo.bar.Baz::func;
+ Function g = new Baz()::func;
+ Function h = Baz::new;
+ Function i =
+ com.foo.bar
+ .VeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongBaz
+ ::new;
+
+ {
+ Function f = () -> moderatelyLongResult;
+ Function f =
+ () ->
+ breakableResult
+ + breakableResult
+ + breakableResult
+ + breakableResult
+ + breakableResult
+ + breakableResult
+ + breakableResult
+ + breakableResult
+ + breakableResult;
+ Function f =
+ () ->
+ System.err.println(
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+ Function f =
+ (someParam) ->
+ System.err.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21327412.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21327412.input
new file mode 100644
index 0000000..2b2d4e0
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21327412.input
@@ -0,0 +1,3 @@
+class B21327412 {
+ int i = Data._ID;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21327412.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21327412.output
new file mode 100644
index 0000000..2b2d4e0
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21327412.output
@@ -0,0 +1,3 @@
+class B21327412 {
+ int i = Data._ID;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21329312.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21329312.input
new file mode 100644
index 0000000..8fa68c0
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21329312.input
@@ -0,0 +1,6 @@
+public class B21329312 {
+ boolean test() {
+ return wasEdited || didContactsChange || medicalInfoManager.getTemporaryProfilePicture() != null
+ || inMemoryAccessMedicalProfileAboveKeyguard != originalAccessMedicalProfileAboveKeyguard;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21329312.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21329312.output
new file mode 100644
index 0000000..5731bd9
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21329312.output
@@ -0,0 +1,8 @@
+public class B21329312 {
+ boolean test() {
+ return wasEdited
+ || didContactsChange
+ || medicalInfoManager.getTemporaryProfilePicture() != null
+ || inMemoryAccessMedicalProfileAboveKeyguard != originalAccessMedicalProfileAboveKeyguard;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21331232.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21331232.input
new file mode 100644
index 0000000..3d2f94e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21331232.input
@@ -0,0 +1,13 @@
+class B21331232 {
+ private <SF, S>
+ S instantiate(
+ Class<?> wrapperType, Type t, FailureStrategy fs, SF factory, Iterable<T> data) {
+ }
+
+ public <T, C extends Iterable<T>>
+ IterableSubject<? extends IterableSubject<?, T, C>, T, C> that(@Nullable Iterable<T> target) {
+ return IterableSubject.create(getFailureStrategy(), target);
+ }
+
+ <T> boolean mightContain(T object, Funnel<? super T> funnels, int numHashFunctions, BitArray bits);
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21331232.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21331232.output
new file mode 100644
index 0000000..fb58ccf
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21331232.output
@@ -0,0 +1,12 @@
+class B21331232 {
+ private <SF, S> S instantiate(
+ Class<?> wrapperType, Type t, FailureStrategy fs, SF factory, Iterable<T> data) {}
+
+ public <T, C extends Iterable<T>> IterableSubject<? extends IterableSubject<?, T, C>, T, C> that(
+ @Nullable Iterable<T> target) {
+ return IterableSubject.create(getFailureStrategy(), target);
+ }
+
+ <T> boolean mightContain(
+ T object, Funnel<? super T> funnels, int numHashFunctions, BitArray bits);
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465217.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465217.input
new file mode 100644
index 0000000..3973420
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465217.input
@@ -0,0 +1,12 @@
+class B21465217 {
+ void m() {
+ try (JimfsOutputStream out2 = newOutputStream(false);
+ BufferedOutputStream bout = new BufferedOutputStream(out2);
+ OutputStreamWriter writer = new OutputStreamWriter(bout, UTF_8___________________________)) {
+ }
+
+ try (Writer sourceWriter = env.getFiler().createSourceFile(qualifiedNamezzzzzzzz).openWriter()) {
+ sourceWriter.append(fileContents);
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465217.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465217.output
new file mode 100644
index 0000000..add19d3
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465217.output
@@ -0,0 +1,13 @@
+class B21465217 {
+ void m() {
+ try (JimfsOutputStream out2 = newOutputStream(false);
+ BufferedOutputStream bout = new BufferedOutputStream(out2);
+ OutputStreamWriter writer =
+ new OutputStreamWriter(bout, UTF_8___________________________)) {}
+
+ try (Writer sourceWriter =
+ env.getFiler().createSourceFile(qualifiedNamezzzzzzzz).openWriter()) {
+ sourceWriter.append(fileContents);
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465477.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465477.input
new file mode 100644
index 0000000..3f0ca4b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465477.input
@@ -0,0 +1,10 @@
+class B21465477 {
+
+ @Nullable private final String simpleFieldName;
+ @Nullable private final String shortFlagName;
+ private final String containerClassName;
+ private final String type;
+ private final String doc;
+ private final DocLevel docLevel;
+ @Nullable private final String altName;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465477.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465477.output
new file mode 100644
index 0000000..3f0ca4b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21465477.output
@@ -0,0 +1,10 @@
+class B21465477 {
+
+ @Nullable private final String simpleFieldName;
+ @Nullable private final String shortFlagName;
+ private final String containerClassName;
+ private final String type;
+ private final String doc;
+ private final DocLevel docLevel;
+ @Nullable private final String altName;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21585653.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21585653.input
new file mode 100644
index 0000000..7c6a25b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21585653.input
@@ -0,0 +1,6 @@
+class B21585653 {
+ {
+ try (X x = x;) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21585653.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21585653.output
new file mode 100644
index 0000000..09c2c87
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21585653.output
@@ -0,0 +1,5 @@
+class B21585653 {
+ {
+ try (X x = x; ) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21608216.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21608216.input
new file mode 100644
index 0000000..e986a8a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21608216.input
@@ -0,0 +1,56 @@
+@PluginInfo(shortName = "chromesuggestions",
+ owner = "mathp",
+ demoLink = "unspecified",
+ modules = { BackendsModule.class,
+ CommonBackendsModule.class,
+ ChromeSuggestionsPlugin.ChromeSuggestionsModule.class })
+@Module(library = true, complete = false)
+@Module(library = true, complete = false, injects = {ClassInfoTracker.class, JslayoutMetaProto.class})
+@Module(
+ library = true,
+ complete = false,
+ injects = {
+ ClassInfoTracker.class, JslayoutMetaProto.class, JslayoutMetaProto.class, JslayoutMeta.class
+ }
+)
+@Module(
+ library = true,
+ complete = false,
+ injects = {
+ ClassInfoTracker.class, JslayoutMetaProto.class, JslayoutMetaProto.class, JslayoutMeta.class, JslayoutMeta.class
+ }
+)
+@Module(injects = {One.class, Two.class}, includes = {Three.class, Four.class})
+@Module(
+ library = true,
+ complete = false,
+ numbers = {1, 2, 3}
+)
+@Module(
+ library = true,
+ complete = false,
+ numbers = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
+ }
+)
+@Module(
+ library = true,
+ complete = false,
+ numbers = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30
+ }
+)
+@Module({ClassInfoTracker.class, JslayoutMetaProto.class, JslayoutMetaProto.class})
+@Module({
+ ClassInfoTracker.class, JslayoutMetaProto.class, JslayoutMetaProto.class, JslayoutMeta.class
+})
+@Module({
+ ClassInfoTracker.class,
+ JslayoutMetaProto.class,
+ JslayoutMetaProto.class,
+ JslayoutMeta.class,
+ JslayoutMeta.class
+})
+class B21608216 {
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21608216.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21608216.output
new file mode 100644
index 0000000..93b3f86
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21608216.output
@@ -0,0 +1,68 @@
+@PluginInfo(
+ shortName = "chromesuggestions",
+ owner = "mathp",
+ demoLink = "unspecified",
+ modules = {
+ BackendsModule.class,
+ CommonBackendsModule.class,
+ ChromeSuggestionsPlugin.ChromeSuggestionsModule.class
+ })
+@Module(library = true, complete = false)
+@Module(
+ library = true,
+ complete = false,
+ injects = {ClassInfoTracker.class, JslayoutMetaProto.class})
+@Module(
+ library = true,
+ complete = false,
+ injects = {
+ ClassInfoTracker.class,
+ JslayoutMetaProto.class,
+ JslayoutMetaProto.class,
+ JslayoutMeta.class
+ })
+@Module(
+ library = true,
+ complete = false,
+ injects = {
+ ClassInfoTracker.class,
+ JslayoutMetaProto.class,
+ JslayoutMetaProto.class,
+ JslayoutMeta.class,
+ JslayoutMeta.class
+ })
+@Module(
+ injects = {One.class, Two.class},
+ includes = {Three.class, Four.class})
+@Module(
+ library = true,
+ complete = false,
+ numbers = {1, 2, 3})
+@Module(
+ library = true,
+ complete = false,
+ numbers = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
+ })
+@Module(
+ library = true,
+ complete = false,
+ numbers = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
+ 27, 28, 29, 30
+ })
+@Module({ClassInfoTracker.class, JslayoutMetaProto.class, JslayoutMetaProto.class})
+@Module({
+ ClassInfoTracker.class,
+ JslayoutMetaProto.class,
+ JslayoutMetaProto.class,
+ JslayoutMeta.class
+})
+@Module({
+ ClassInfoTracker.class,
+ JslayoutMetaProto.class,
+ JslayoutMetaProto.class,
+ JslayoutMeta.class,
+ JslayoutMeta.class
+})
+class B21608216 {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21647014.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21647014.input
new file mode 100644
index 0000000..949c7a3
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21647014.input
@@ -0,0 +1,15 @@
+package test;
+
+import java.util.List;;
+
+;
+class Test {
+ ;
+ public int x = 42;;
+ ;;;
+ {
+ int x = 1;;;
+ }
+ ;
+}
+;
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21647014.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21647014.output
new file mode 100644
index 0000000..095ff07
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21647014.output
@@ -0,0 +1,22 @@
+package test;
+
+import java.util.List;
+;
+;
+
+class Test {
+ ;
+ public int x = 42;
+ ;
+ ;
+ ;
+ ;
+
+ {
+ int x = 1;
+ ;
+ ;
+ }
+ ;
+}
+;
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21954779.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21954779.input
new file mode 100644
index 0000000..804eb98
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21954779.input
@@ -0,0 +1,46 @@
+class B21954779 {
+ {
+ when(computeApi.findOrCreateVm(
+ VM_METADATA,
+ Optional.<InstanceProto>absent(),
+ AssignReservedIp.YES,
+ AttachDataDisk.YES))
+ .thenReturn(OPERATION);
+
+ fail(String.format(
+ " Experiment name %s is used multiple times in %s",
+ expName, envName, zzzzzzzzzzzz, zzzzzzzzzzzz));
+
+ new Handler(Looper.getMainLooper())
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ utils.showShortToast(
+ application.getString(
+ R.string.working_on_project_format,
+ Utils.getProjectDisplayName(selectedProject)));
+ }
+ });
+
+ (new Handler())
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ showErrorAndSetDone(error);
+ }
+ });
+
+ getContext()
+ .getErrorReporter()
+ .log(new RuntimeException("Called layout with a non-zero segment index: " + segmentIndex));
+
+ bind(
+ new Key<ServiceMethodRunner<SyncReferenceNumberRequest, SyncReferenceNumberResponse>>() {});
+
+ // this isn't a feature; we'd prefer to keep `new Key...` as one unit
+ bind(
+ new Key<ServiceMethodRunner<SyncReferenceNumberRequest, SyncReferenceNumberResponses>>() {}).then();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21954779.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21954779.output
new file mode 100644
index 0000000..c1628b1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B21954779.output
@@ -0,0 +1,48 @@
+class B21954779 {
+ {
+ when(computeApi.findOrCreateVm(
+ VM_METADATA,
+ Optional.<InstanceProto>absent(),
+ AssignReservedIp.YES,
+ AttachDataDisk.YES))
+ .thenReturn(OPERATION);
+
+ fail(
+ String.format(
+ " Experiment name %s is used multiple times in %s",
+ expName, envName, zzzzzzzzzzzz, zzzzzzzzzzzz));
+
+ new Handler(Looper.getMainLooper())
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ utils.showShortToast(
+ application.getString(
+ R.string.working_on_project_format,
+ Utils.getProjectDisplayName(selectedProject)));
+ }
+ });
+
+ (new Handler())
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ showErrorAndSetDone(error);
+ }
+ });
+
+ getContext()
+ .getErrorReporter()
+ .log(new RuntimeException("Called layout with a non-zero segment index: " + segmentIndex));
+
+ bind(
+ new Key<ServiceMethodRunner<SyncReferenceNumberRequest, SyncReferenceNumberResponse>>() {});
+
+ // this isn't a feature; we'd prefer to keep `new Key...` as one unit
+ bind(new Key<
+ ServiceMethodRunner<SyncReferenceNumberRequest, SyncReferenceNumberResponses>>() {})
+ .then();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22166687.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22166687.input
new file mode 100644
index 0000000..33b4c13
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22166687.input
@@ -0,0 +1,18 @@
+class B22166687 {
+ public static class Wrapper
+ extends CrossComponentSetBindingsWrapper<
+ ExtensionWrapper<FootprintsGenericSearchResponseExtension>>
+ implements CrossComponentSetBindingsWrapper<
+ ExtensionWrapper<FootprintsGenericSearchResponseExtension>> {
+ void m() {}
+ }
+ public static class Wrapper
+ extends CrossComponentSetBindingsWrapper<
+ ExtensionWrapper<FootprintsGenericSearchResponseExtension>>
+ implements CrossComponentSetBindingsWrapper<
+ ExtensionWrapper<FootprintsGenericSearchResponseExtension>>,
+ CrossComponentSetBindingsWrapper<
+ ExtensionWrapper<FootprintsGenericSearchResponseExtension>> {
+ void m() {}
+ }
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22166687.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22166687.output
new file mode 100644
index 0000000..a3deda7
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22166687.output
@@ -0,0 +1,19 @@
+class B22166687 {
+ public static class Wrapper
+ extends CrossComponentSetBindingsWrapper<
+ ExtensionWrapper<FootprintsGenericSearchResponseExtension>>
+ implements CrossComponentSetBindingsWrapper<
+ ExtensionWrapper<FootprintsGenericSearchResponseExtension>> {
+ void m() {}
+ }
+
+ public static class Wrapper
+ extends CrossComponentSetBindingsWrapper<
+ ExtensionWrapper<FootprintsGenericSearchResponseExtension>>
+ implements CrossComponentSetBindingsWrapper<
+ ExtensionWrapper<FootprintsGenericSearchResponseExtension>>,
+ CrossComponentSetBindingsWrapper<
+ ExtensionWrapper<FootprintsGenericSearchResponseExtension>> {
+ void m() {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22169269.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22169269.input
new file mode 100644
index 0000000..3caf937
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22169269.input
@@ -0,0 +1,14 @@
+class B22169269 {
+ {
+ try {
+ } catch (
+ ClassNotFoundException
+ | NoSuchMethodException
+ | InvocationTargetException
+ | IllegalAccessException e) {
+ }
+ try {
+ } catch (ClassNotFoundException | NoSuchMethodException e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22169269.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22169269.output
new file mode 100644
index 0000000..6e46a36
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22169269.output
@@ -0,0 +1,13 @@
+class B22169269 {
+ {
+ try {
+ } catch (ClassNotFoundException
+ | NoSuchMethodException
+ | InvocationTargetException
+ | IllegalAccessException e) {
+ }
+ try {
+ } catch (ClassNotFoundException | NoSuchMethodException e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22424362.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22424362.input
new file mode 100644
index 0000000..800b1f0
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22424362.input
@@ -0,0 +1,8 @@
+class Test {
+ {
+ int x =
+ 1
+
+ + 2;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22424362.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22424362.output
new file mode 100644
index 0000000..98fa938
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22424362.output
@@ -0,0 +1,5 @@
+class Test {
+ {
+ int x = 1 + 2;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22469536.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22469536.input
new file mode 100644
index 0000000..9f06d6f
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22469536.input
@@ -0,0 +1,6 @@
+public class Test {
+ public static void foo() {
+ long x = -9223372036854775808L;
+ int y = -2147483648;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22469536.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22469536.output
new file mode 100644
index 0000000..9f06d6f
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22469536.output
@@ -0,0 +1,6 @@
+public class Test {
+ public static void foo() {
+ long x = -9223372036854775808L;
+ int y = -2147483648;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22488373.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22488373.input
new file mode 100644
index 0000000..11747be
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22488373.input
@@ -0,0 +1,11 @@
+class B22488373 {
+ {
+ if (enumBindingKeys.contains(bindingKey)
+ && (bindingKey
+ .key()
+ .type()
+ .getKind()
+ .equals(DECLARED)
+ && !((DeclaredType) bindingKey.key().type()).getTypeArguments().isEmpty())) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22488373.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22488373.output
new file mode 100644
index 0000000..950c1dd
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22488373.output
@@ -0,0 +1,7 @@
+class B22488373 {
+ {
+ if (enumBindingKeys.contains(bindingKey)
+ && (bindingKey.key().type().getKind().equals(DECLARED)
+ && !((DeclaredType) bindingKey.key().type()).getTypeArguments().isEmpty())) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22610221.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22610221.input
new file mode 100644
index 0000000..eb05cfd
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22610221.input
@@ -0,0 +1,12 @@
+class B22610221 {
+ {
+ for (Map.Entry<Key<Object>, Object> entry :
+ FOO
+ .bar()
+ .bazs()
+ .<Object>thing(someThing)
+ .entrySet()) {
+ output.put(entry.getKey(), entry.getValue());
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22610221.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22610221.output
new file mode 100644
index 0000000..d0f375d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22610221.output
@@ -0,0 +1,8 @@
+class B22610221 {
+ {
+ for (Map.Entry<Key<Object>, Object> entry :
+ FOO.bar().bazs().<Object>thing(someThing).entrySet()) {
+ output.put(entry.getKey(), entry.getValue());
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22815364.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22815364.input
new file mode 100644
index 0000000..85fbf64
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22815364.input
@@ -0,0 +1,46 @@
+class B22815364 {
+ @Xxx({
+ "=0|=0", "Xxx xxxx xx xxxxxxxx xx xxxxxxxxxxxxx.",
+ "=0|xxx", "Xxx xxxx x xxxxxxxxxxxx.",
+ "xxx|=0", "Xxx xxxx x xxxxxxx.",
+ "xxx|xxx", "Xxx xxxx xxx xxxxxxx xxx xxx xxxxxxxxxxxx.",
+ "xxxxx|xxx", "Xxx xxxx {0} xxxxxxxx xxx xxx xxxxxxxxxxxx.",
+ "xxx|xxxxx", "Xxx xxxx xxx xxxxxxx xxx {1} xxxxxxxxxxxxx."
+ })
+ int x;
+
+ @Xxx({
+ "a", "b",
+ "c", "d",
+ //
+ })
+ int y;
+
+ int[] xs = {
+ 1, 2, 3,
+ 1, 2, 3,
+ 1, 2, 3,
+ };
+
+ int[] xs = {
+ 1, 2, 3,
+ 1, 2, 3,
+ 1, 2,
+ };
+
+ int[] xs = {
+ 1, 2,
+ 1,
+ };
+
+ int[][] xs = {
+ {0b111111111111111111, 0b111111111111111111, 0b111111111111111111, 0b111111111111111111,
+ 0b111111111111111111, 0b111111111111111111, 0b111111111111111111}, {0b111111111111111111,
+ 0b111111111111111111, 0b111111111111111111, 0b111111111111111111, 0b111111111111111111,
+ 0b111111111111111111, 0b111111111111111111},
+ {0b111111111111111111, 0b111111111111111111, 0b111111111111111111, 0b111111111111111111,
+ 0b111111111111111111, 0b111111111111111111, 0b111111111111111111}, {0b111111111111111111,
+ 0b111111111111111111, 0b111111111111111111, 0b111111111111111111, 0b111111111111111111,
+ 0b111111111111111111, 0b111111111111111111},
+ };
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22815364.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22815364.output
new file mode 100644
index 0000000..91f3936
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22815364.output
@@ -0,0 +1,73 @@
+class B22815364 {
+ @Xxx({
+ "=0|=0", "Xxx xxxx xx xxxxxxxx xx xxxxxxxxxxxxx.",
+ "=0|xxx", "Xxx xxxx x xxxxxxxxxxxx.",
+ "xxx|=0", "Xxx xxxx x xxxxxxx.",
+ "xxx|xxx", "Xxx xxxx xxx xxxxxxx xxx xxx xxxxxxxxxxxx.",
+ "xxxxx|xxx", "Xxx xxxx {0} xxxxxxxx xxx xxx xxxxxxxxxxxx.",
+ "xxx|xxxxx", "Xxx xxxx xxx xxxxxxx xxx {1} xxxxxxxxxxxxx."
+ })
+ int x;
+
+ @Xxx({
+ "a", "b",
+ "c", "d",
+ //
+ })
+ int y;
+
+ int[] xs = {
+ 1, 2, 3,
+ 1, 2, 3,
+ 1, 2, 3,
+ };
+
+ int[] xs = {
+ 1, 2, 3,
+ 1, 2, 3,
+ 1, 2,
+ };
+
+ int[] xs = {
+ 1, 2, 1,
+ };
+
+ int[][] xs = {
+ {
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111
+ },
+ {
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111
+ },
+ {
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111
+ },
+ {
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111,
+ 0b111111111111111111
+ },
+ };
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22848286.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22848286.input
new file mode 100644
index 0000000..18796c1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22848286.input
@@ -0,0 +1,8 @@
+class B22848286 {
+
+ @FxxxSxxx(xxxx = "xxxxxx_xxxxxxx_xxxxxxxxx_xxxx", xxxx = "Xxxxx xxxx xx xxxxxx xxxxxxx xxxxxxxxx.")
+ int x;
+
+ @FxxxGxxxxxxxx(xxxx = "Xxx xxxxxx xxxxxxxxy xxx xxx xxxxxxxxx, x.x. xxx xxxxxx xxxxxxxx xxxxxxxxy")
+ int y;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22848286.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22848286.output
new file mode 100644
index 0000000..e073fb3
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22848286.output
@@ -0,0 +1,11 @@
+class B22848286 {
+
+ @FxxxSxxx(
+ xxxx = "xxxxxx_xxxxxxx_xxxxxxxxx_xxxx",
+ xxxx = "Xxxxx xxxx xx xxxxxx xxxxxxx xxxxxxxxx.")
+ int x;
+
+ @FxxxGxxxxxxxx(
+ xxxx = "Xxx xxxxxx xxxxxxxxy xxx xxx xxxxxxxxx, x.x. xxx xxxxxx xxxxxxxx xxxxxxxxy")
+ int y;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22873322.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22873322.input
new file mode 100644
index 0000000..48984c4
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22873322.input
@@ -0,0 +1,14 @@
+class B22873322 {
+ {
+ f(param -> veryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongExpr(param));
+ f((param1, param2) -> veryLooooooooooooooooooooooooooooooooooooooooooooooooongExpr(param1, param2));
+ f((int param) -> veryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongExpr(param));
+ f((param1, param2) -> {
+ return expr(param1, param2);
+ });
+ f((param1, param2) -> {
+ Object foo = expr(param1, param2);
+ return foo;
+ });
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22873322.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22873322.output
new file mode 100644
index 0000000..7768383
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22873322.output
@@ -0,0 +1,23 @@
+class B22873322 {
+ {
+ f(
+ param ->
+ veryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongExpr(
+ param));
+ f(
+ (param1, param2) ->
+ veryLooooooooooooooooooooooooooooooooooooooooooooooooongExpr(param1, param2));
+ f(
+ (int param) ->
+ veryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongExpr(param));
+ f(
+ (param1, param2) -> {
+ return expr(param1, param2);
+ });
+ f(
+ (param1, param2) -> {
+ Object foo = expr(param1, param2);
+ return foo;
+ });
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22913048.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22913048.input
new file mode 100644
index 0000000..512c2b3
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22913048.input
@@ -0,0 +1,9 @@
+private class Yellow<B> extends Red<B>.Orange {
+ Yellow(Red<B> red) {
+ red.super();
+ }
+
+ Class<?> getClassB() {
+ return new TypeToken<B>(getClass()) {}.getRawType();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22913048.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22913048.output
new file mode 100644
index 0000000..512c2b3
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B22913048.output
@@ -0,0 +1,9 @@
+private class Yellow<B> extends Red<B>.Orange {
+ Yellow(Red<B> red) {
+ red.super();
+ }
+
+ Class<?> getClassB() {
+ return new TypeToken<B>(getClass()) {}.getRawType();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23349153.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23349153.input
new file mode 100644
index 0000000..574e345
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23349153.input
@@ -0,0 +1,28 @@
+class B23349153 {
+ enum Foo {
+ A, B,;
+ }
+ {
+ System.err.println("Hello");;
+ }
+}
+
+private enum UnsafeAtomicHelperFactory {
+ REALLY_TRY_TO_CREATE {
+ @Override
+ AtomicHelper tryCreateUnsafeAtomicHelper() {
+ return new UnsafeAtomicHelper();
+ }
+ },
+
+ DONT_EVEN_TRY_TO_CREATE {
+ @Override
+ AtomicHelper tryCreateUnsafeAtomicHelper() {
+ return null;
+ }
+ },
+
+ ;
+
+ abstract AtomicHelper tryCreateUnsafeAtomicHelper();
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23349153.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23349153.output
new file mode 100644
index 0000000..7ed9a6a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23349153.output
@@ -0,0 +1,31 @@
+class B23349153 {
+ enum Foo {
+ A,
+ B,
+ ;
+ }
+
+ {
+ System.err.println("Hello");
+ ;
+ }
+}
+
+private enum UnsafeAtomicHelperFactory {
+ REALLY_TRY_TO_CREATE {
+ @Override
+ AtomicHelper tryCreateUnsafeAtomicHelper() {
+ return new UnsafeAtomicHelper();
+ }
+ },
+
+ DONT_EVEN_TRY_TO_CREATE {
+ @Override
+ AtomicHelper tryCreateUnsafeAtomicHelper() {
+ return null;
+ }
+ },
+ ;
+
+ abstract AtomicHelper tryCreateUnsafeAtomicHelper();
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23514513.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23514513.input
new file mode 100644
index 0000000..b8351cb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23514513.input
@@ -0,0 +1,7 @@
+class Test {
+ {
+ f(
+ rrr.kkkkk.uuuuuuuu, rrr.iiiiii.ggggggggggggggggg,
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23514513.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23514513.output
new file mode 100644
index 0000000..5ec70f6
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23514513.output
@@ -0,0 +1,8 @@
+class Test {
+ {
+ f(
+ rrr.kkkkk.uuuuuuuu,
+ rrr.iiiiii.ggggggggggggggggg,
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23626035.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23626035.input
new file mode 100644
index 0000000..bfcedc5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23626035.input
@@ -0,0 +1 @@
+class Test2 { void f(Test2... xs) {} }
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23626035.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23626035.output
new file mode 100644
index 0000000..6d429ba
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23626035.output
@@ -0,0 +1,3 @@
+class Test2 {
+ void f(Test2... xs) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23708487.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23708487.input
new file mode 100644
index 0000000..e08556a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23708487.input
@@ -0,0 +1,10 @@
+class B23708487 {
+ static {
+ TASK_AS_OWNER =
+ com.google.security.acl.proto2api.ACL.Entry.newBuilder()
+ .setRole(com.google.security.acl.proto2api.ACL.Entry.ROLE.OWNER.getNumber())
+ .setScope(com.google.security.acl.proto2api.ACL.Scope.newBuilder()
+ .setMdbUser(BorgTaskInfo.getMdbUser().getMdbUserName()))
+ .build();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23708487.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23708487.output
new file mode 100644
index 0000000..6dc379d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23708487.output
@@ -0,0 +1,11 @@
+class B23708487 {
+ static {
+ TASK_AS_OWNER =
+ com.google.security.acl.proto2api.ACL.Entry.newBuilder()
+ .setRole(com.google.security.acl.proto2api.ACL.Entry.ROLE.OWNER.getNumber())
+ .setScope(
+ com.google.security.acl.proto2api.ACL.Scope.newBuilder()
+ .setMdbUser(BorgTaskInfo.getMdbUser().getMdbUserName()))
+ .build();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23749160.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23749160.input
new file mode 100644
index 0000000..b624e8d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23749160.input
@@ -0,0 +1,43 @@
+class B23749160 {
+ {
+ mockDailySummaryModuleListFiller
+ .buildDailySummaryModulesWithHeadersOnlyAndOneExpandedDayxxxxxxxxxxx(
+ anyInt(), anyInt(), anyInt());
+
+ mockDailySummaryModuleListFiller
+ .<XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY>
+ buildDailySummaryModulesWithHeadersOnlyAndOneExpandedDayxxxxxxxxxxx(
+ anyInt(), anyInt(), anyInt());
+
+ foo
+ .<XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY>
+ buildDailySummaryModulesWithHeadersOnlyAndOneExpandedDayxxxxxxxxxxx(
+ anyInt(), anyInt(), anyInt())
+ .bar();
+
+ // `.build`... and `anotherThing`... are technically at different syntactic
+ // levels, but the indentation required to express that starts to get out
+ // of hand
+ MockDailySummaryModuleListFiller
+ .buildDailySummaryModulesWithHeadersOnlyAndOneExpandedDayxxxxxxxxxxx(
+ anyInt(), anyInt(), anyInt())
+ .anotherThing();
+
+ when(
+ mockDailySummaryModuleListFiller
+ .buildDailySummaryModulesWithHeadersOnlyAndOneExpandedDay(
+ anyInt(), anyInt(), anyInt()))
+ .then();
+
+ final List<Integer> XXXXXXXX =
+ ImmutableList.of(
+ XXXXXXX, //
+ XXXXXXX, //
+ XXXXXXX, //
+ XXXXXXX, //
+ XXXXXXX //
+ );
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23749160.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23749160.output
new file mode 100644
index 0000000..e1b2e55
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23749160.output
@@ -0,0 +1,40 @@
+class B23749160 {
+ {
+ mockDailySummaryModuleListFiller
+ .buildDailySummaryModulesWithHeadersOnlyAndOneExpandedDayxxxxxxxxxxx(
+ anyInt(), anyInt(), anyInt());
+
+ mockDailySummaryModuleListFiller
+ .<XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY>
+ buildDailySummaryModulesWithHeadersOnlyAndOneExpandedDayxxxxxxxxxxx(
+ anyInt(), anyInt(), anyInt());
+
+ foo.<XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
+ YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY>
+ buildDailySummaryModulesWithHeadersOnlyAndOneExpandedDayxxxxxxxxxxx(
+ anyInt(), anyInt(), anyInt())
+ .bar();
+
+ // `.build`... and `anotherThing`... are technically at different syntactic
+ // levels, but the indentation required to express that starts to get out
+ // of hand
+ MockDailySummaryModuleListFiller
+ .buildDailySummaryModulesWithHeadersOnlyAndOneExpandedDayxxxxxxxxxxx(
+ anyInt(), anyInt(), anyInt())
+ .anotherThing();
+
+ when(mockDailySummaryModuleListFiller.buildDailySummaryModulesWithHeadersOnlyAndOneExpandedDay(
+ anyInt(), anyInt(), anyInt()))
+ .then();
+
+ final List<Integer> XXXXXXXX =
+ ImmutableList.of(
+ XXXXXXX, //
+ XXXXXXX, //
+ XXXXXXX, //
+ XXXXXXX, //
+ XXXXXXX //
+ );
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23804934.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23804934.input
new file mode 100644
index 0000000..87b75ab
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23804934.input
@@ -0,0 +1,34 @@
+class B23804934{
+
+ @Zzzzzz
+ B23804934(
+ ZzzzzzzzZzzzzzZzzzzZzzzzzzz zzzzzzzzZzzzzzZzzzzZzzzzzzz,
+ ZzzzzzzZzzzzzzz<ZzzzzzZzzzZzzz> zzzzzzZzzzZzzzZzzzzzzz,
+ @ZzzzZzzzzZzzzzzz Zzzzzz zzzzzZzzzzzz,
+ @ZzzzzzzzzzzZzzzzzz(Zz.ZZZZZZZ_ZZZZZ_ZZZZ_ZZZZZZ_ZZZZ_ZZZZZZZZZZ)
+ Zzzzzzzz<Zzzzzzz> zzzzzZzzzZzzzzzZzzzZzzzzzzzzz) {
+ zzzz.zzzzzzzzZzzzzzZzzzzZzzzzzzz = zzzzzzzzZzzzzzZzzzzZzzzzzzz;
+ zzzz.zzzzzzZzzzZzzzZzzzzzzz = zzzzzzZzzzZzzzZzzzzzzz;
+ zzzz.zzzzzZzzzzzz = zzzzzZzzzzzz;
+ zzzz.zzzzzZzzzZzzzzzZzzzZzzzzzzzzz = zzzzzZzzzZzzzzzZzzzZzzzzzzzzz;
+ }
+
+ zzzz z(
+ zzz z,
+ @Zzzzzzzz @Zzzzzzzzzz
+ ZzzzzzzzzZzzz<Zzzzzz>
+ zzzzZzzzZzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz) {}
+
+ @ZzzZzzzZzzzzzzzzzz
+ @Zzzzzzzz(zzzzzzzzz = Zzzzzzzzz.ZZ)
+ ZzzzzzzzzzZzzzzz<ZzzZzzzzzzzzzzZzzzzzzzz> zzzzzzzZzzzzzzzzzz(
+ @Zzz @Zzz zzzz zzzzZzzzZz,
+ @Zzz @ZzzzzzzzzzZzzzz(zzzz = ZzzzzZzzzzzZzzzz.ZZZZZZ_ZZZ_ZZZ_ZZZZZZZ)
+ zzzzzzz zzzzzzZzzZzzZzzzzzz,
+ @Zzzzz @ZzzzzzzzzzZzzzz(zzzz = ZzzzzZzzzzzZzzzz.ZZZZZ_ZZZ_ZZZZZZZZZZZ)
+ zzzzzzz zzzzzZzzZzzzzzzzzzz,
+ @Zzzzz ZzzzzZzzz zzzzzZzzz) {}
+
+ void fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(
+ final zzzzzzzzzzzzzz zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23804934.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23804934.output
new file mode 100644
index 0000000..310d4c7
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B23804934.output
@@ -0,0 +1,35 @@
+class B23804934 {
+
+ @Zzzzzz
+ B23804934(
+ ZzzzzzzzZzzzzzZzzzzZzzzzzzz zzzzzzzzZzzzzzZzzzzZzzzzzzz,
+ ZzzzzzzZzzzzzzz<ZzzzzzZzzzZzzz> zzzzzzZzzzZzzzZzzzzzzz,
+ @ZzzzZzzzzZzzzzzz Zzzzzz zzzzzZzzzzzz,
+ @ZzzzzzzzzzzZzzzzzz(Zz.ZZZZZZZ_ZZZZZ_ZZZZ_ZZZZZZ_ZZZZ_ZZZZZZZZZZ)
+ Zzzzzzzz<Zzzzzzz> zzzzzZzzzZzzzzzZzzzZzzzzzzzzz) {
+ zzzz.zzzzzzzzZzzzzzZzzzzZzzzzzzz = zzzzzzzzZzzzzzZzzzzZzzzzzzz;
+ zzzz.zzzzzzZzzzZzzzZzzzzzzz = zzzzzzZzzzZzzzZzzzzzzz;
+ zzzz.zzzzzZzzzzzz = zzzzzZzzzzzz;
+ zzzz.zzzzzZzzzZzzzzzZzzzZzzzzzzzzz = zzzzzZzzzZzzzzzZzzzZzzzzzzzzz;
+ }
+
+ zzzz z(
+ zzz z,
+ @Zzzzzzzz @Zzzzzzzzzz
+ ZzzzzzzzzZzzz<Zzzzzz>
+ zzzzZzzzZzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz) {}
+
+ @ZzzZzzzZzzzzzzzzzz
+ @Zzzzzzzz(zzzzzzzzz = Zzzzzzzzz.ZZ)
+ ZzzzzzzzzzZzzzzz<ZzzZzzzzzzzzzzZzzzzzzzz> zzzzzzzZzzzzzzzzzz(
+ @Zzz @Zzz zzzz zzzzZzzzZz,
+ @Zzz @ZzzzzzzzzzZzzzz(zzzz = ZzzzzZzzzzzZzzzz.ZZZZZZ_ZZZ_ZZZ_ZZZZZZZ)
+ zzzzzzz zzzzzzZzzZzzZzzzzzz,
+ @Zzzzz @ZzzzzzzzzzZzzzz(zzzz = ZzzzzZzzzzzZzzzz.ZZZZZ_ZZZ_ZZZZZZZZZZZ)
+ zzzzzzz zzzzzZzzZzzzzzzzzzz,
+ @Zzzzz ZzzzzZzzz zzzzzZzzz) {}
+
+ void fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(
+ final zzzzzzzzzzzzzz
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24202287.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24202287.input
new file mode 100644
index 0000000..ea42154
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24202287.input
@@ -0,0 +1,35 @@
+class B24202287 {
+ {
+ this.overflowContactCompositeSupportedRenderers =
+ this.getSharePanelResponse.contents.unifiedSharePanelRenderer.contents[0]
+ .connectionSection
+ .connectionsOverflowMenu
+ .connectionsOverflowMenuRenderer
+ .contents[
+ 0]
+ .overflowConnectionSectionRenderer
+ .contacts[
+ 0];
+
+ int y = ((int[]) null)[0];
+ }
+
+ IntentSource getSource() {
+ return mIntent.isPresent() && mIntent.get().hasExtra(INTENT_SOURCE_KEY)
+ ? IntentSource.values()[
+ mIntent.get().getIntExtra(INTENT_SOURCE_KEY, IntentSource.OTHER.ordinal())]
+ : IntentSource.OTHER;
+ }
+
+ {
+ mTabUiParts[mRenderedTabIndex]
+ .setData(
+ tabs[mRenderedTabIndex],
+ true /* isSelected */,
+ mIsLoadingState,
+ mErrorMessageType,
+ mOnDailySummaryHeaderClickListener,
+ mShowSpacerForSnackbar,
+ mResetScrollAndAnimationAndReplaceDoodle);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24202287.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24202287.output
new file mode 100644
index 0000000..05d418c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24202287.output
@@ -0,0 +1,35 @@
+class B24202287 {
+ {
+ this.overflowContactCompositeSupportedRenderers =
+ this.getSharePanelResponse
+ .contents
+ .unifiedSharePanelRenderer
+ .contents[0]
+ .connectionSection
+ .connectionsOverflowMenu
+ .connectionsOverflowMenuRenderer
+ .contents[0]
+ .overflowConnectionSectionRenderer
+ .contacts[0];
+
+ int y = ((int[]) null)[0];
+ }
+
+ IntentSource getSource() {
+ return mIntent.isPresent() && mIntent.get().hasExtra(INTENT_SOURCE_KEY)
+ ? IntentSource.values()[
+ mIntent.get().getIntExtra(INTENT_SOURCE_KEY, IntentSource.OTHER.ordinal())]
+ : IntentSource.OTHER;
+ }
+
+ {
+ mTabUiParts[mRenderedTabIndex].setData(
+ tabs[mRenderedTabIndex],
+ true /* isSelected */,
+ mIsLoadingState,
+ mErrorMessageType,
+ mOnDailySummaryHeaderClickListener,
+ mShowSpacerForSnackbar,
+ mResetScrollAndAnimationAndReplaceDoodle);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24494875.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24494875.input
new file mode 100644
index 0000000..c96fcbb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24494875.input
@@ -0,0 +1,4 @@
+public enum B24494875 {
+ ;
+ public static final String ONE = "ONE";
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24494875.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24494875.output
new file mode 100644
index 0000000..c96fcbb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24494875.output
@@ -0,0 +1,4 @@
+public enum B24494875 {
+ ;
+ public static final String ONE = "ONE";
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24543625.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24543625.input
new file mode 100644
index 0000000..4c8dfb5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24543625.input
@@ -0,0 +1,76 @@
+/**
+* This<br>
+* is<br>
+* a<br>
+* train.
+*/
+class TheTrain {
+/*
+ ______
+ .-"""".._'. _,##
+ _..__ |.-"""-.| | _,##'`-._
+ (_____)||_____|| |_,##'`-._,##'`
+ _| |.;-""-. | |#'`-._,##'`
+ _.;_ `--' `\ \ |.'`\._,##'`
+ /.-.\ `\ |.-";.`_, |##'`
+ |\__/ | _..;__ |'-' /
+ '.____.'_.-`)\--' /'-'`
+ //||\\(_.-'_,'-'`
+ (`-...-')_,##'`
+ jgs _,##`-..,-;##`
+ _,##'`-._,##'`
+ _,##'`-._,##'`
+ `-._,##'`
+*/
+ public static final Object THE_TRAIN = null;
+
+/*
+BLOCK COMMENT
+
+*/
+ int x;
+
+ /* .
+ BLOCK COMMENT .
+
+ */
+ int y;
+
+ /* .
+ BLOCK COMMENT .
+
+ */
+ int z;
+
+ /* .
+ BLOCK COMMENT .
+
+ */
+ int z;
+
+ /*
+ * This is almost javadoc.
+ *
+ */
+ int z;
+
+ /**
+ * This is (was) malformed javadoc.
+ * <pre>
+ System.err.println
+ * </pre>
+ */
+ int z;
+
+ private Object f; /*-{
+ System.err.println("asd");
+ }-*/
+
+ private native Object f() /*-{
+ System.err.println("asd");
+ }-*/;
+
+ private native Object f() /*-{
+System.err.println("asd");
+}-*/;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24543625.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24543625.output
new file mode 100644
index 0000000..5bd8d5a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24543625.output
@@ -0,0 +1,77 @@
+/**
+ * This<br>
+ * is<br>
+ * a<br>
+ * train.
+ */
+class TheTrain {
+ /*
+ ______
+ .-"""".._'. _,##
+ _..__ |.-"""-.| | _,##'`-._
+ (_____)||_____|| |_,##'`-._,##'`
+ _| |.;-""-. | |#'`-._,##'`
+ _.;_ `--' `\ \ |.'`\._,##'`
+ /.-.\ `\ |.-";.`_, |##'`
+ |\__/ | _..;__ |'-' /
+ '.____.'_.-`)\--' /'-'`
+ //||\\(_.-'_,'-'`
+ (`-...-')_,##'`
+ jgs _,##`-..,-;##`
+ _,##'`-._,##'`
+ _,##'`-._,##'`
+ `-._,##'`
+ */
+ public static final Object THE_TRAIN = null;
+
+ /*
+ BLOCK COMMENT
+
+ */
+ int x;
+
+ /* .
+ BLOCK COMMENT .
+
+ */
+ int y;
+
+ /* .
+ BLOCK COMMENT .
+
+ */
+ int z;
+
+ /* .
+ BLOCK COMMENT .
+
+ */
+ int z;
+
+ /*
+ * This is almost javadoc.
+ *
+ */
+ int z;
+
+ /**
+ * This is (was) malformed javadoc.
+ *
+ * <pre>
+ * System.err.println
+ * </pre>
+ */
+ int z;
+
+ private Object f; /*-{
+ System.err.println("asd");
+ }-*/
+
+ private native Object f() /*-{
+ System.err.println("asd");
+ }-*/;
+
+ private native Object f() /*-{
+System.err.println("asd");
+}-*/;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24702438.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24702438.input
new file mode 100644
index 0000000..631f076
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24702438.input
@@ -0,0 +1,54 @@
+class B24702438 {
+
+ @Inject int x;
+
+ @Inject int y;
+
+ @Inject int z;
+
+ @Inject int x;
+ @Inject int y;
+ @Inject int z;
+
+ // this is a comment
+
+ // another comment
+ int x;
+
+ // another comment
+
+ int y;
+
+ {
+ switch (x) {
+
+ case 1:
+
+ break;
+
+ case 1:
+
+ break;
+
+ default:
+
+ break;
+
+ }
+
+ // this is a comment
+
+ // another comment
+ System.err.println("asd");
+ }
+
+ void f(
+ int a,
+ @Nullable @Deprecated ImmutableList<String> veryVeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ @Nullable @Deprecated ImmutableList<String> veryVeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ @Nullable @Deprecated ImmutableList<String> veryVeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ int c) {}
+ void g(
+ @Nullable @Deprecated ImmutableList<String> veryVeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ @Nullable @Deprecated ImmutableList<String> veryVeryLoooooooooooooooooooooooooooooooooooooooooooooooooooooooong) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24702438.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24702438.output
new file mode 100644
index 0000000..b9d5a6d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24702438.output
@@ -0,0 +1,60 @@
+class B24702438 {
+
+ @Inject int x;
+
+ @Inject int y;
+
+ @Inject int z;
+
+ @Inject int x;
+ @Inject int y;
+ @Inject int z;
+
+ // this is a comment
+
+ // another comment
+ int x;
+
+ // another comment
+
+ int y;
+
+ {
+ switch (x) {
+ case 1:
+ break;
+
+ case 1:
+ break;
+
+ default:
+ break;
+ }
+
+ // this is a comment
+
+ // another comment
+ System.err.println("asd");
+ }
+
+ void f(
+ int a,
+ @Nullable @Deprecated
+ ImmutableList<String>
+ veryVeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ @Nullable @Deprecated
+ ImmutableList<String>
+ veryVeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ @Nullable @Deprecated
+ ImmutableList<String>
+ veryVeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ int c) {}
+
+ void g(
+ @Nullable @Deprecated
+ ImmutableList<String>
+ veryVeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ @Nullable @Deprecated
+ ImmutableList<String>
+ veryVeryLoooooooooooooooooooooooooooooooooooooooooooooooooooooooong) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24862959.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24862959.input
new file mode 100644
index 0000000..6f729a5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24862959.input
@@ -0,0 +1,8 @@
+public enum XxxxxxxXxxx implements XxxxxxXxxxxxxXxxx {
+ XXXXXXXXX_XXXX_XXX_XXXXXXXX(
+ " ", Xxxxxx.XXXXXXX, XxxxXxxxxxxXxxxxxxx.XXXXX),
+ //
+ //
+ //
+}
+
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24862959.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24862959.output
new file mode 100644
index 0000000..acb80a0
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24862959.output
@@ -0,0 +1,7 @@
+public enum XxxxxxxXxxx implements XxxxxxXxxxxxxXxxx {
+ XXXXXXXXX_XXXX_XXX_XXXXXXXX(
+ " ", Xxxxxx.XXXXXXX, XxxxXxxxxxxXxxxxxxx.XXXXX),
+ //
+ //
+ //
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24909927.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24909927.input
new file mode 100644
index 0000000..c9d4fbb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24909927.input
@@ -0,0 +1,353 @@
+class Xxx {
+
+ public xxxx xxx() {
+ XxxxxxXxxx xxxxxxxxXxxx = XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(XxxxxxXxxx.XXXXXXX)
+ .xxxXxxxxxXxxx(" ")
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24909927.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24909927.output
new file mode 100644
index 0000000..dce6c50
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24909927.output
@@ -0,0 +1,546 @@
+class Xxx {
+
+ public xxxx xxx() {
+ XxxxxxXxxx xxxxxxxxXxxx =
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(XxxxxxXxxx.XXXXXXX)
+ .xxxXxxxxxXxxx(" ")
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx
+ .XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(
+ XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(
+ XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(
+ -42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(
+ XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
+ XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx
+ .XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(
+ XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(
+ XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(
+ -42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(
+ XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
+ XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(
+ XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(
+ XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(
+ XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(
+ XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(
+ XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(
+ XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
+ XxxxxxxxxxXxxXxxxXxxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx
+ .XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(
+ XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(
+ XxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(
+ -42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
+ XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx
+ .XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(
+ XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(
+ XxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(
+ -42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
+ XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx
+ .XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(
+ XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(
+ XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(
+ XxxxxxxxXxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(
+ XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(
+ XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
+ XxxxxxxxxxXxxXxxxXxxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx
+ .XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(
+ XxxxXxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(
+ XxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(
+ -42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(
+ 42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
+ XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(
+ 42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XX_XXXXX)
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx
+ .XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(
+ XxxxXxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(
+ XxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(
+ -42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(
+ 42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
+ XxxxxxxxxxXxxXxxxXxxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(
+ 42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx
+ .XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(
+ XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(
+ XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(
+ XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
+ XxxxxxxxxxXxxXxxxXxxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxx(
+ XxxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxxx(Xxxxxxxx.XXXX)
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx.xxxXxxxxxx()
+ .xxxXxxxxxXxxx(
+ XxxxxxXxxx
+ .XXX_XXXXXXXXXXXXXXX_XXXX_XXXXXXX_XXXXX_XXXXX)
+ .xxxXxxxXxxxxx(
+ XxxxXxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxXxxxx(
+ XxxxXxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxx(
+ XxxxxxxxXxxxXxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxxxxxXxxxxxxxx(
+ XxxxxxxxXxxxXxxxxxxXxxxxxxxXxxxxxxxx
+ .XXXX_XXXXXX_XXXX_XXXXX_XX_XXX)
+ .xxxxx())
+ .xxxXxxxxx(
+ XxxxXxxxxxxx
+ .xxxXxxxxxx()
+ .xxxXxxxx(-42)
+ .xxxXxxx(
+ XxxxXxxx
+ .XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxx(
+ XxxxXxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxx(42)
+ .xxxXxxx(XxxxXxxx.XXX)
+ .xxxxx())
+ .xxxxx())
+ .xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
+ XxxxxxxxxxXxxXxxxXxxxxxxxx.xxxXxxxxxx()
+ .xxxXxxxxxxxxxXxxxXx(42)
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx())
+ .xxxxx();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24988078.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24988078.input
new file mode 100644
index 0000000..1a69ffc
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24988078.input
@@ -0,0 +1,7 @@
+class B24988078 {
+ public static <T> Optional</*@NonNull*/ T> fromNullable(@Nullable T nullableReference) {}
+
+ public static <T extends /*@NonNull*/ Object> Optional<T> fromNullable(@Nullable T nullableReference) {}
+
+ Table< /*@Nullable*/ Object, /*@Nullable*/ Object, /*@Nullable*/ Object > foo;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24988078.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24988078.output
new file mode 100644
index 0000000..1870892
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B24988078.output
@@ -0,0 +1,8 @@
+class B24988078 {
+ public static <T> Optional</*@NonNull*/ T> fromNullable(@Nullable T nullableReference) {}
+
+ public static <T extends /*@NonNull*/ Object> Optional<T> fromNullable(
+ @Nullable T nullableReference) {}
+
+ Table</*@Nullable*/ Object, /*@Nullable*/ Object, /*@Nullable*/ Object> foo;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25372815.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25372815.input
new file mode 100644
index 0000000..c03a6ea
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25372815.input
@@ -0,0 +1,32 @@
+class Test {
+ {
+ // For "All goals" view, show only advertiser goals tab.
+ return newPageTypeSet(
+ false /* advertisers */,
+ false /* userManagement */,
+ false /* campaigns */,
+ false /* adGroups */,
+ false /* ads */,
+ false /* keywords */,
+ false /* negativeKeywords */,
+ false /* advertiserBidStrategies */,
+ false /* bidStrategies */,
+ false /* bidStrategyRecommendations */,
+ false /* bidKeywords */,
+ false /* engineBidStrategies */,
+ false /* conversionTrackers */,
+ false /* labels */,
+ false /* labelKeywords */,
+ false /* evergreen labels */,
+ false /* searchQueries */,
+ false /* engineSearchQueries */,
+ false /* remarketingTarget */,
+ false /* sitelinks */,
+ false /* feedSitelinks */,
+ false /* locationExtensions */,
+ false /* callExtensions */,
+ false /* calloutExtensions */,
+ false /* appExtensions */,
+ false /* events */);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25372815.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25372815.output
new file mode 100644
index 0000000..c03a6ea
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25372815.output
@@ -0,0 +1,32 @@
+class Test {
+ {
+ // For "All goals" view, show only advertiser goals tab.
+ return newPageTypeSet(
+ false /* advertisers */,
+ false /* userManagement */,
+ false /* campaigns */,
+ false /* adGroups */,
+ false /* ads */,
+ false /* keywords */,
+ false /* negativeKeywords */,
+ false /* advertiserBidStrategies */,
+ false /* bidStrategies */,
+ false /* bidStrategyRecommendations */,
+ false /* bidKeywords */,
+ false /* engineBidStrategies */,
+ false /* conversionTrackers */,
+ false /* labels */,
+ false /* labelKeywords */,
+ false /* evergreen labels */,
+ false /* searchQueries */,
+ false /* engineSearchQueries */,
+ false /* remarketingTarget */,
+ false /* sitelinks */,
+ false /* feedSitelinks */,
+ false /* locationExtensions */,
+ false /* callExtensions */,
+ false /* calloutExtensions */,
+ false /* appExtensions */,
+ false /* events */);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25749989.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25749989.input
new file mode 100644
index 0000000..ca0e0ae
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25749989.input
@@ -0,0 +1,4 @@
+class Dummy {
+ /**
+ */private static void method(){MISSING();}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25749989.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25749989.output
new file mode 100644
index 0000000..bcee8b3
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25749989.output
@@ -0,0 +1,6 @@
+class Dummy {
+ /** */
+ private static void method() {
+ MISSING();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25787055.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25787055.input
new file mode 100644
index 0000000..4aae1ce
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25787055.input
@@ -0,0 +1,30 @@
+class B25787055 {
+ {
+ logger
+ .atInfo()
+ .log(
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(),
+ deletedPersistentNames);
+ logger.atInfo().atInfo().atInfo().atInfo().atInfo().atInfo().atInfo().atInfo().atInfo().atInfo().atInfo()
+ .log(
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(),
+ deletedPersistentNames);
+ logger // interior
+ .atInfo() // comments
+ .log( // are best comments
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(),
+ deletedPersistentNames);
+ logger
+ .log(
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(),
+ deletedPersistentNames)
+ .log(
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(),
+ deletedPersistentNames);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25787055.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25787055.output
new file mode 100644
index 0000000..1572889
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25787055.output
@@ -0,0 +1,23 @@
+class B25787055 {
+ {
+ logger.atInfo().log(
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(), deletedPersistentNames);
+ logger.atInfo().atInfo().atInfo().atInfo().atInfo().atInfo().atInfo().atInfo().atInfo().atInfo()
+ .atInfo().log(
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(), deletedPersistentNames);
+ logger // interior
+ .atInfo() // comments
+ .log( // are best comments
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(), deletedPersistentNames);
+ logger
+ .log(
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(), deletedPersistentNames)
+ .log(
+ "Scratch Session Cleaner exiting. Number of deleted sessions: %d, names: %s",
+ deletedPersistentNames.size(), deletedPersistentNames);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25811323.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25811323.input
new file mode 100644
index 0000000..291e34c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25811323.input
@@ -0,0 +1,5 @@
+class B25811323 {
+ void f() {
+ class Inner {};
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25811323.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25811323.output
new file mode 100644
index 0000000..0ed1ade
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B25811323.output
@@ -0,0 +1,6 @@
+class B25811323 {
+ void f() {
+ class Inner {}
+ ;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26159561.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26159561.input
new file mode 100644
index 0000000..853202d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26159561.input
@@ -0,0 +1,6 @@
+class B26159561 {
+ {
+ try (A a = a(); B b = b()) {}
+ try (A a = a();) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26159561.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26159561.output
new file mode 100644
index 0000000..d9ae269
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26159561.output
@@ -0,0 +1,7 @@
+class B26159561 {
+ {
+ try (A a = a();
+ B b = b()) {}
+ try (A a = a(); ) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26207047.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26207047.input
new file mode 100644
index 0000000..5f53f8c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26207047.input
@@ -0,0 +1,49 @@
+class B26207047 {
+ {
+ String.format(
+ "%s ", xxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ "%s ",
+ xxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ "{0} ",
+ xxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ "%s "
+ + " ",
+ xxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ "%s ",
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ "%s ",
+ xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx,
+ xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx,
+ xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx);
+ String.format(
+ "%s ", " ", " ", " ", " ", " ", " ", " ", " ",
+ " ", " ", " ", " ", " ", " ", " ", " ", " ",
+ " ", " ", " ", " ", " ", " ", " ", " ", " ");
+ Set<TimeZone> ZONES =
+ ImmutableSet.of(
+ TimeZone.getTimeZone("EST"),
+ // TODO:
+ TimeZone.getTimeZone("GMT"),
+ TimeZone.getTimeZone("HST"),
+ TimeZone.getTimeZone("MST"));
+ String.format(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + "%s",
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ " ",
+ xxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26207047.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26207047.output
new file mode 100644
index 0000000..4eea2ea
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26207047.output
@@ -0,0 +1,51 @@
+class B26207047 {
+ {
+ String.format(
+ "%s ", xxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ "%s ",
+ xxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ "{0} ",
+ xxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ "%s "
+ + " ",
+ xxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ "%s ",
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ "%s ",
+ xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx,
+ xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx,
+ xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx, xxxxxx);
+ String.format(
+ "%s ",
+ " ", " ", " ", " ", " ", " ", " ", " ", " ",
+ " ", " ", " ", " ", " ", " ", " ", " ", " ",
+ " ", " ", " ", " ", " ", " ", " ", " ");
+ Set<TimeZone> ZONES =
+ ImmutableSet.of(
+ TimeZone.getTimeZone("EST"),
+ // TODO:
+ TimeZone.getTimeZone("GMT"),
+ TimeZone.getTimeZone("HST"),
+ TimeZone.getTimeZone("MST"));
+ String.format(
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + "%s",
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx);
+ String.format(
+ " ",
+ xxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxx);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26275739.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26275739.input
new file mode 100644
index 0000000..70fdd90
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26275739.input
@@ -0,0 +1,18 @@
+public class B26275739 {
+ {
+ f(
+ /* x */ 1,
+ /* xx */ 2,
+ /* x */ 3,
+ /* xx */ 4);
+ f(
+ 1,
+ 2,
+ 3,
+ 4);
+ f(
+ /* asd */
+ 1, 2,
+ 3, 4);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26275739.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26275739.output
new file mode 100644
index 0000000..ffb0b22
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26275739.output
@@ -0,0 +1,12 @@
+public class B26275739 {
+ {
+ f(/* x */ 1, /* xx */ 2, /* x */ 3, /* xx */ 4);
+ f(
+ 1, 2,
+ 3, 4);
+ f(
+ /* asd */
+ 1, 2,
+ 3, 4);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26293162.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26293162.input
new file mode 100644
index 0000000..691bebe
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26293162.input
@@ -0,0 +1,43 @@
+class Xxxx {
+
+ public static final class XxxxXxxxxxxxXxxxxxxxxXxxxxxXxxxxxxx<
+ X, X, X extends XxxxxxxxxXxxxxxxxxXxxxxxxx<X, X>> {
+
+ int x;
+ }
+
+ public static final class XxxxXxxxxxxxXxxxxxxxxXxxxxxXxxxxxxx<
+ X, X, X extends XxxxxxxxxXxxxxxxxxXxxxxxxx<X, X>> extends Xxxxx {
+
+ int x;
+ }
+
+ class XxxxxxxxXxxxxxxxxXxxxXxxx<
+ X extends XxxxxxxxxXxxxxxx, X extends XxxxxxxxxXxxxxx, X extends Xxxxxxxxxxxxx, X>
+ extends XxxxXxxx {
+
+ int x;
+ }
+
+ @Xxx
+ class XxxxxxxxxxxXxxxxxXx<
+ X extends XxxxxxXxxxxxxxxxxXxxxxxXxxxxx, X extends Xxxxxxx, XX extends Xxxxxxxxx<X>>
+ extends XxxxxxxxXx<X, XX> {
+
+ int x;
+ }
+
+ static class XxxxxXxxxxXxxx<
+ XXXXX extends XxxxxxxxXxxxxxx<XXXXX>, XXX extends XxxxxxxxXxxxxxx<XXX>> {
+
+ int x;
+ }
+
+ @Xxx
+ class XxxxxxxxxxxXxxxxxXx<
+ X extends XxxxxxXxxxxxxxxxxXxxxxxXxxxxx, X extends Xxxxxxx, X extends Xxxxxxx,
+ XX extends Xxxxxxxxx<X>> extends XxxxxxxxXx<X, XX> {
+
+ int x;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26293162.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26293162.output
new file mode 100644
index 0000000..96361ef
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26293162.output
@@ -0,0 +1,47 @@
+class Xxxx {
+
+ public static final class XxxxXxxxxxxxXxxxxxxxxXxxxxxXxxxxxxx<
+ X, X, X extends XxxxxxxxxXxxxxxxxxXxxxxxxx<X, X>> {
+
+ int x;
+ }
+
+ public static final class XxxxXxxxxxxxXxxxxxxxxXxxxxxXxxxxxxx<
+ X, X, X extends XxxxxxxxxXxxxxxxxxXxxxxxxx<X, X>>
+ extends Xxxxx {
+
+ int x;
+ }
+
+ class XxxxxxxxXxxxxxxxxXxxxXxxx<
+ X extends XxxxxxxxxXxxxxxx, X extends XxxxxxxxxXxxxxx, X extends Xxxxxxxxxxxxx, X>
+ extends XxxxXxxx {
+
+ int x;
+ }
+
+ @Xxx
+ class XxxxxxxxxxxXxxxxxXx<
+ X extends XxxxxxXxxxxxxxxxxXxxxxxXxxxxx, X extends Xxxxxxx, XX extends Xxxxxxxxx<X>>
+ extends XxxxxxxxXx<X, XX> {
+
+ int x;
+ }
+
+ static class XxxxxXxxxxXxxx<
+ XXXXX extends XxxxxxxxXxxxxxx<XXXXX>, XXX extends XxxxxxxxXxxxxxx<XXX>> {
+
+ int x;
+ }
+
+ @Xxx
+ class XxxxxxxxxxxXxxxxxXx<
+ X extends XxxxxxXxxxxxxxxxxXxxxxxXxxxxx,
+ X extends Xxxxxxx,
+ X extends Xxxxxxx,
+ XX extends Xxxxxxxxx<X>>
+ extends XxxxxxxxXx<X, XX> {
+
+ int x;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26694550.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26694550.input
new file mode 100644
index 0000000..7af690a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26694550.input
@@ -0,0 +1,21 @@
+class B26694550 {
+ {
+ /* === not a param comment */
+ fffffffffffffffffffffffffffffff(
+ ImmutableList.copyOf(keys), /*&=*//*keepGoing=*/
+ false,
+ ggggggggggggggggggggggggggggggggggggggggggg);
+ fffffffffffffffffffffffffffffff(
+ ImmutableList.copyOf(keys),
+ /*keepGoing=*/
+ false,
+ ggggggggggggggggggggggggggggggggggggggggggg);
+ fffffffffffffffffffffffffffffff(
+ ImmutableList.copyOf(keys),
+ /*foo_bar=*/
+ false,
+ /*foo-bar=*/
+ false,
+ ggggggggggggggggggggggggggggggggggggggggggg);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26694550.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26694550.output
new file mode 100644
index 0000000..945a40f
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26694550.output
@@ -0,0 +1,18 @@
+class B26694550 {
+ {
+ /* === not a param comment */
+ fffffffffffffffffffffffffffffff(
+ ImmutableList.copyOf(keys), /*&=*/
+ /*keepGoing=*/ false,
+ ggggggggggggggggggggggggggggggggggggggggggg);
+ fffffffffffffffffffffffffffffff(
+ ImmutableList.copyOf(keys),
+ /*keepGoing=*/ false,
+ ggggggggggggggggggggggggggggggggggggggggggg);
+ fffffffffffffffffffffffffffffff(
+ ImmutableList.copyOf(keys),
+ /*foo_bar=*/ false,
+ /*foo-bar=*/ false,
+ ggggggggggggggggggggggggggggggggggggggggggg);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26884608.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26884608.input
new file mode 100644
index 0000000..52764b0
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26884608.input
@@ -0,0 +1,34 @@
+class X26884608 {
+ {
+ this
+ .<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ this
+ .xxxXxxxxXxxxxxx(
+ XxxxxXxxx.xxxxxXxxxx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+ Xxxxx.this
+ .<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ Xxxxx.this
+ .xxxXxxxxXxxx(XxxxxXxxx.xxxxxXx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+ xxx.xxxxxx.Xxxxx.this
+ .<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ xxx.xxxxxx.Xxxxx.this
+ .xxxXxxxxXxxx(XxxxxXxxx.xxxxxXx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+
+ super.<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ super.xxxXxxxxXxxx(
+ XxxxxXxxxx.xxxxxXxxxx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+ Xxxxx.super.<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ Xxxxx.super.xxxXxxxxXxxx(
+ XxxxxXxxx.xxxxxXx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+ xxx.xxxxxx.Xxxxx.super.<XxxxxXxxxxxxxXxxxxx,
+ XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(XXXXXXXXX_XXXXXXXXX);
+ xxx.xxxxxx.Xxxxx.super.xxxXxxxxXxxx(
+ XxxxxXxxx.xxxxxXx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+ }
+}
+
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26884608.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26884608.output
new file mode 100644
index 0000000..9a1cccf
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26884608.output
@@ -0,0 +1,31 @@
+class X26884608 {
+ {
+ this.<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ this.xxxXxxxxXxxxxxx(
+ XxxxxXxxx.xxxxxXxxxx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+ Xxxxx.this.<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ Xxxxx.this.xxxXxxxxXxxx(
+ XxxxxXxxx.xxxxxXx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+ xxx.xxxxxx.Xxxxx.this
+ .<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ xxx.xxxxxx.Xxxxx.this.xxxXxxxxXxxx(
+ XxxxxXxxx.xxxxxXx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+
+ super.<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ super.xxxXxxxxXxxx(
+ XxxxxXxxxx.xxxxxXxxxx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+ Xxxxx.super.<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ Xxxxx.super.xxxXxxxxXxxx(
+ XxxxxXxxx.xxxxxXx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+ xxx.xxxxxx.Xxxxx.super
+ .<XxxxxXxxxxxxxXxxxxx, XxxxxxxxxXxxxxxxXxxxxxxxx>xxxxxxxxxXxxxxxXxxxxxxXxxxxx(
+ XXXXXXXXX_XXXXXXXXX);
+ xxx.xxxxxx.Xxxxx.super.xxxXxxxxXxxx(
+ XxxxxXxxx.xxxxxXx(xxxxxx.xxxxxxxXxxxxxXxxxx(xxxxxx), xxxxxx.xxXxxxxxx()));
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26928842.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26928842.input
new file mode 100644
index 0000000..1ce9dc9
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26928842.input
@@ -0,0 +1,9 @@
+class B26928842 {
+ {
+ curr.setData(curr.getData().toBuilder()
+ .setPushCertificate( // New
+ curr.getData().getPushCertficate()) // Old, misspelled
+ .clearPushCertficate() // Old, misspelled
+ .build());
+ }
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26928842.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26928842.output
new file mode 100644
index 0000000..6623676
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26928842.output
@@ -0,0 +1,10 @@
+class B26928842 {
+ {
+ curr.setData(
+ curr.getData().toBuilder()
+ .setPushCertificate( // New
+ curr.getData().getPushCertficate()) // Old, misspelled
+ .clearPushCertficate() // Old, misspelled
+ .build());
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26952926.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26952926.input
new file mode 100644
index 0000000..dd3451d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26952926.input
@@ -0,0 +1,51 @@
+class T {
+ {
+ this(
+ x, // line comment
+ x // line comment
+ );
+ super(
+ x, // line comment
+ x // line comment
+ );
+ new t(
+ x, // line comment
+ x // line comment
+ );
+ f(
+ x, // line comment
+ x // line comment
+ );
+ fffff.fffff(
+ x, // line comment
+ x // line comment
+ );
+ fffff
+ .fffff(
+ x, // line comment
+ x // line comment
+ )
+ .fffff(
+ x, // line comment
+ x // line comment
+ );
+ }
+
+ {
+ this(x // line comment
+ );
+ super(x // line comment
+ );
+ new t(x // line comment
+ );
+ f(x // line comment
+ );
+ fffff.fffff(x // line comment
+ );
+ fffff
+ .fffff(x // line comment
+ )
+ .fffff(x // line comment
+ );
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26952926.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26952926.output
new file mode 100644
index 0000000..fa93da7
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B26952926.output
@@ -0,0 +1,58 @@
+class T {
+ {
+ this(
+ x, // line comment
+ x // line comment
+ );
+ super(
+ x, // line comment
+ x // line comment
+ );
+ new t(
+ x, // line comment
+ x // line comment
+ );
+ f(
+ x, // line comment
+ x // line comment
+ );
+ fffff.fffff(
+ x, // line comment
+ x // line comment
+ );
+ fffff
+ .fffff(
+ x, // line comment
+ x // line comment
+ )
+ .fffff(
+ x, // line comment
+ x // line comment
+ );
+ }
+
+ {
+ this(
+ x // line comment
+ );
+ super(
+ x // line comment
+ );
+ new t(
+ x // line comment
+ );
+ f(
+ x // line comment
+ );
+ fffff.fffff(
+ x // line comment
+ );
+ fffff
+ .fffff(
+ x // line comment
+ )
+ .fffff(
+ x // line comment
+ );
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27078833.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27078833.input
new file mode 100644
index 0000000..597c635
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27078833.input
@@ -0,0 +1,19 @@
+enum OpKind {
+
+ POST_INCR(Precedence.POSTFIX),
+ POST_DECR(Precedence.POSTFIX),
+
+ PRE_INCR(Precedence.UNARY),
+ PRE_DECR(Precedence.UNARY),
+ PLUS(Precedence.UNARY),
+ NEG(Precedence.UNARY),
+ COMP(Precedence.UNARY),
+ NOT(Precedence.UNARY),
+
+ MULT(Precedence.MULTIPLICATIVE),
+ DIVIDE(Precedence.MULTIPLICATIVE),
+ MODULO(Precedence.MULTIPLICATIVE),
+ ;
+
+ // ...
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27078833.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27078833.output
new file mode 100644
index 0000000..8ff9fde
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27078833.output
@@ -0,0 +1,18 @@
+enum OpKind {
+ POST_INCR(Precedence.POSTFIX),
+ POST_DECR(Precedence.POSTFIX),
+
+ PRE_INCR(Precedence.UNARY),
+ PRE_DECR(Precedence.UNARY),
+ PLUS(Precedence.UNARY),
+ NEG(Precedence.UNARY),
+ COMP(Precedence.UNARY),
+ NOT(Precedence.UNARY),
+
+ MULT(Precedence.MULTIPLICATIVE),
+ DIVIDE(Precedence.MULTIPLICATIVE),
+ MODULO(Precedence.MULTIPLICATIVE),
+ ;
+
+ // ...
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27246427.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27246427.input
new file mode 100644
index 0000000..bf4076d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27246427.input
@@ -0,0 +1,7 @@
+enum TrailingComment {
+ /** foo */ FOO, /** bar */
+ BAR;
+
+ /** a */ Object a; /** b */
+ Object b;
+}
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
new file mode 100644
index 0000000..b1e33e9
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27246427.output
@@ -0,0 +1,11 @@
+enum TrailingComment {
+ /** foo */
+ FOO,
+ /** bar */
+ BAR;
+
+ /** a */
+ Object a;
+ /** b */
+ Object b;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27602933.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27602933.input
new file mode 100644
index 0000000..1046a63
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27602933.input
@@ -0,0 +1,11 @@
+class B27602933 {
+ {
+ try {
+ } catch (final A | B x) {
+ }
+
+ try {
+ } catch (@SuppressWarnings("unused") IllegalArgumentException | RuntimeException e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27602933.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27602933.output
new file mode 100644
index 0000000..1046a63
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B27602933.output
@@ -0,0 +1,11 @@
+class B27602933 {
+ {
+ try {
+ } catch (final A | B x) {
+ }
+
+ try {
+ } catch (@SuppressWarnings("unused") IllegalArgumentException | RuntimeException e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28066276.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28066276.input
new file mode 100644
index 0000000..f8d336b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28066276.input
@@ -0,0 +1,13 @@
+class B28066276 {
+ class A extends S
+ implements I________________, I________________, I________________, I________________,
+ I________________, I________________, I________________, I________________,
+ I________________, I________________, I________________, I________________,
+ I________________, I________________, I________________, I________________ {}
+
+ interface B
+ extends I________________, I________________, I________________, I________________,
+ I________________, I________________, I________________, I________________,
+ I________________, I________________, I________________, I________________,
+ I________________, I________________, I________________, I________________ {}
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28066276.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28066276.output
new file mode 100644
index 0000000..36f2217
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28066276.output
@@ -0,0 +1,37 @@
+class B28066276 {
+ class A extends S
+ implements I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________ {}
+
+ interface B
+ extends I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________,
+ I________________ {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28774859.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28774859.input
new file mode 100644
index 0000000..796e7d4
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28774859.input
@@ -0,0 +1,3 @@
+class B28774859 {
+ java.util. /*comment*/ Map. /*comment*/ Entry e;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28774859.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28774859.output
new file mode 100644
index 0000000..ae59d75
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28774859.output
@@ -0,0 +1,3 @@
+class B28774859 {
+ java.util./*comment*/ Map./*comment*/ Entry e;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28786284.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28786284.input
new file mode 100644
index 0000000..18ac2d7
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28786284.input
@@ -0,0 +1,5 @@
+enum E {
+ ONE,
+ TWO;
+ ;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28786284.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28786284.output
new file mode 100644
index 0000000..18ac2d7
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28786284.output
@@ -0,0 +1,5 @@
+enum E {
+ ONE,
+ TWO;
+ ;
+}
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
new file mode 100644
index 0000000..a38d10b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28788559.input
@@ -0,0 +1,7 @@
+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
new file mode 100644
index 0000000..8a1db80
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B28788559.output
@@ -0,0 +1,8 @@
+import a.A;
+;
+import b.B;
+
+class Test {
+ A a;
+ B b;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29368546.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29368546.input
new file mode 100644
index 0000000..32da76c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29368546.input
@@ -0,0 +1,60 @@
+public class B29368546 {
+ /**
+ * Example:
+ *
+ * <pre>{@code
+ * class T {
+ * }
+ * }</pre>
+ */
+ int x;
+
+ /**
+ * Example:
+ *
+ * <pre> {@code
+ * class T {
+ * }
+ * }</pre>
+ */
+ int x;
+
+ /**
+ * Example:
+ *
+ * <pre>{@code
+ * class T {
+ * }}</pre>
+ */
+ int x;
+
+ /**
+ * Example:
+ *
+ * <pre>{@code
+ * foo bar
+ * }
+ *
+ * more stuff
+ * </pre>
+ *
+ * <pre>{@code
+ * foo bar
+ * }
+ *
+ * more stuff that ends with {}
+ * </pre>
+ */
+ int x;
+
+ /**
+ * Example:
+ *
+ * <pre>{@code
+ * class T {}
+ * </pre> // oops, we forgot the close brace
+ *
+ * more stuff that ends with {}
+ */
+ int x;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29368546.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29368546.output
new file mode 100644
index 0000000..9f0fdd2
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29368546.output
@@ -0,0 +1,61 @@
+public class B29368546 {
+ /**
+ * Example:
+ *
+ * <pre>{@code
+ * class T {
+ * }
+ * }</pre>
+ */
+ int x;
+
+ /**
+ * Example:
+ *
+ * <pre>{@code
+ * class T {
+ * }
+ * }</pre>
+ */
+ int x;
+
+ /**
+ * Example:
+ *
+ * <pre>{@code
+ * class T {
+ * }
+ * }</pre>
+ */
+ int x;
+
+ /**
+ * Example:
+ *
+ * <pre>{@code
+ * foo bar
+ * }
+ *
+ * more stuff
+ * </pre>
+ *
+ * <pre>{@code
+ * foo bar
+ * }
+ *
+ * more stuff that ends with {
+ * }</pre>
+ */
+ int x;
+
+ /**
+ * Example:
+ *
+ * <pre>{@code
+ * class T {}
+ * </pre> // oops, we forgot the close brace
+ *
+ * more stuff that ends with {}
+ */
+ int x;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29618429.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29618429.input
new file mode 100644
index 0000000..2beb2a7
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29618429.input
@@ -0,0 +1,13 @@
+class B29618429 {
+ /**
+ * Hello
+ *
+ * <p>World
+ * <pre>
+ * @LooksLikeATag(
+ * foo = "bar"
+ * )
+ * </pre>
+ */
+ int x;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29618429.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29618429.output
new file mode 100644
index 0000000..f118c53
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29618429.output
@@ -0,0 +1,14 @@
+class B29618429 {
+ /**
+ * Hello
+ *
+ * <p>World
+ *
+ * <pre>
+ * @LooksLikeATag(
+ * foo = "bar"
+ * )
+ * </pre>
+ */
+ int x;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29705613.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29705613.input
new file mode 100644
index 0000000..0adb884
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29705613.input
@@ -0,0 +1,8 @@
+class B29705613 {
+ /**
+ * This tag isn't closed: {@link Foo
+ *
+ * <p>A paragraph
+ */
+ int x;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29705613.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29705613.output
new file mode 100644
index 0000000..df46d19
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B29705613.output
@@ -0,0 +1,8 @@
+class B29705613 {
+ /**
+ * This tag isn't closed: {@link Foo
+ *
+ * <p>A paragraph
+ */
+ int x;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30153869.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30153869.input
new file mode 100644
index 0000000..d5fae20
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30153869.input
@@ -0,0 +1,26 @@
+/**
+ * one
+ *
+ * <ol>
+ * <li>two
+ * </ol>
+ *
+ * <p>
+ * <li>four
+ */
+class T {
+
+ /**
+ * One
+ *
+ * <ul>
+ * <li>a
+ * <ul>
+ * <li>b
+ * <li>c
+ * </ul>
+ * <li>e
+ * </ul>
+ */
+ int x;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30153869.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30153869.output
new file mode 100644
index 0000000..5cef3b8
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30153869.output
@@ -0,0 +1,26 @@
+/**
+ * one
+ *
+ * <ol>
+ * <li>two
+ * </ol>
+ *
+ * <p>
+ * <li>four
+ */
+class T {
+
+ /**
+ * One
+ *
+ * <ul>
+ * <li>a
+ * <ul>
+ * <li>b
+ * <li>c
+ * </ul>
+ * <li>e
+ * </ul>
+ */
+ int x;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30223986.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30223986.input
new file mode 100644
index 0000000..5b6fbb2
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30223986.input
@@ -0,0 +1,45 @@
+/**
+ * Base class for {@link Foo}.
+ *
+ * <p>The subclass should implement {@link #bar()}, with the implementation returning a
+ * new instance of the foo relevant to that baz.
+ *
+ * Example:
+ * <code>
+ * @Override
+ * protected Foo bar() {
+ * return new Foo();
+ * }
+ * </code>
+ *
+ * The subclass should call {@link #get()} to get the foo object,
+ * and should not cache it in the subclass.
+ * Example:
+ * <code>
+ * @Annotation("something")
+ * public void thing() {
+ * }
+ * </code>
+ *
+ * @param <T> concrete subclass
+ */
+class Test {
+ /**
+ * Field
+ *
+ * Example:
+ * <pre><code>
+ * @Annotation("something")
+ * public void thing() {
+ * }
+ * </code></pre>
+ *
+ * @param asd
+ */
+ int x() {}
+
+ /**
+ * Inline <code>foo</code>.
+ */
+ int y() {}
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30223986.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30223986.output
new file mode 100644
index 0000000..7e7c427
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30223986.output
@@ -0,0 +1,39 @@
+/**
+ * Base class for {@link Foo}.
+ *
+ * <p>The subclass should implement {@link #bar()}, with the implementation returning a new instance
+ * of the foo relevant to that baz.
+ *
+ * <p>Example: <code>
+ * @Override
+ * protected Foo bar() {
+ * return new Foo();
+ * }
+ * </code> The subclass should call {@link #get()} to get the foo object, and should not cache it in
+ * the subclass. Example: <code>
+ * @Annotation("something")
+ * public void thing() {
+ * }
+ * </code>
+ *
+ * @param <T> concrete subclass
+ */
+class Test {
+ /**
+ * Field
+ *
+ * <p>Example:
+ *
+ * <pre><code>
+ * @Annotation("something")
+ * public void thing() {
+ * }
+ * </code></pre>
+ *
+ * @param asd
+ */
+ int x() {}
+
+ /** Inline <code>foo</code>. */
+ int y() {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30764810.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30764810.input
new file mode 100644
index 0000000..0643db0
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30764810.input
@@ -0,0 +1,16 @@
+class B30764810 {
+ @Foo(
+ x = {0},
+ y = 0
+ // comment
+ )
+ int a;
+
+ @Foo(
+ x = {0},
+ y = 0
+ /* comment
+ */
+ )
+ int b;
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30764810.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30764810.output
new file mode 100644
index 0000000..94f0f9b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30764810.output
@@ -0,0 +1,16 @@
+class B30764810 {
+ @Foo(
+ x = {0},
+ y = 0
+ // comment
+ )
+ int a;
+
+ @Foo(
+ x = {0},
+ y = 0
+ /* comment
+ */
+ )
+ int b;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30789352.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30789352.input
new file mode 100644
index 0000000..2559b1f
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30789352.input
@@ -0,0 +1,6 @@
+/**
+ * Body.
+ *
+ * @param <P> the protobuf type
+ */
+class B30789352<P> {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30789352.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30789352.output
new file mode 100644
index 0000000..2559b1f
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B30789352.output
@@ -0,0 +1,6 @@
+/**
+ * Body.
+ *
+ * @param <P> the protobuf type
+ */
+class B30789352<P> {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32114928.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32114928.input
new file mode 100644
index 0000000..95da03c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32114928.input
@@ -0,0 +1,9 @@
+class B32114928 {
+ {
+ Class<T> tClass =
+ (Class<T>)
+ verifyNotNull((ParameterizedType) getClass().getGenericSuperclass())
+ .getActualTypeArguments()[
+ 0];
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32114928.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32114928.output
new file mode 100644
index 0000000..6535ae1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32114928.output
@@ -0,0 +1,8 @@
+class B32114928 {
+ {
+ Class<T> tClass =
+ (Class<T>)
+ verifyNotNull((ParameterizedType) getClass().getGenericSuperclass())
+ .getActualTypeArguments()[0];
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32284705.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32284705.input
new file mode 100644
index 0000000..916c588
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32284705.input
@@ -0,0 +1,8 @@
+/**
+ * Foo <code>
+ * <pre>
+ * bar
+ * </pre>
+ * </code>
+ */
+class B32284705 {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32284705.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32284705.output
new file mode 100644
index 0000000..916c588
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32284705.output
@@ -0,0 +1,8 @@
+/**
+ * Foo <code>
+ * <pre>
+ * bar
+ * </pre>
+ * </code>
+ */
+class B32284705 {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32397217.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32397217.input
new file mode 100644
index 0000000..b402a4a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32397217.input
@@ -0,0 +1,6 @@
+class T {
+ {
+ f(
+ 1 /* 1 */, 2 /* 2 */);
+ }
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32397217.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32397217.output
new file mode 100644
index 0000000..a962f63
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32397217.output
@@ -0,0 +1,7 @@
+class T {
+ {
+ f(
+ 1 /* 1 */,
+ 2 /* 2 */);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32729600.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32729600.input
new file mode 100644
index 0000000..4fd5fc5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32729600.input
@@ -0,0 +1,5 @@
+class B32729600 {
+ {
+ f(() -> {});
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32729600.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32729600.output
new file mode 100644
index 0000000..4fd5fc5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B32729600.output
@@ -0,0 +1,5 @@
+class B32729600 {
+ {
+ f(() -> {});
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B33358723.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B33358723.input
new file mode 100644
index 0000000..5d461e0
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B33358723.input
@@ -0,0 +1,24 @@
+class B33358723 {
+ {
+ f(
+ //
+ x ->
+ System.err.println(
+ //
+ "hello"));
+ f(
+ //
+ ( //
+ x) ->
+ System.err.println(
+ //
+ "hello"));
+ f(
+ //
+ (int //
+ x) ->
+ System.err.println(
+ //
+ "hello"));
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B33358723.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B33358723.output
new file mode 100644
index 0000000..da0000c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B33358723.output
@@ -0,0 +1,24 @@
+class B33358723 {
+ {
+ f(
+ //
+ x ->
+ System.err.println(
+ //
+ "hello"));
+ f(
+ //
+ ( //
+ x) ->
+ System.err.println(
+ //
+ "hello"));
+ f(
+ //
+ (int //
+ x) ->
+ System.err.println(
+ //
+ "hello"));
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35644813.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35644813.input
new file mode 100644
index 0000000..37ae322
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35644813.input
@@ -0,0 +1,19 @@
+class B35644813 {
+ {
+ return foo____________
+ .bar__________()
+ .baz____________()
+ .stream()
+ .map(Baz::getId)
+ .collect(toList());
+ }
+
+ private static final ImmutableSet<String> SCANDINAVIA =
+ ImmutableSet.of(
+ DENMARK____________________________________________________,
+ NORWAY_____________________________________________________,
+ SWEDEN_____________________________________________________)
+ .stream()
+ .map(x -> String.format("country: %s", x.toLowerCase()))
+ .collect(toImmutableSet());
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35644813.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35644813.output
new file mode 100644
index 0000000..cee0e3b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35644813.output
@@ -0,0 +1,16 @@
+class B35644813 {
+ {
+ return foo____________.bar__________().baz____________().stream()
+ .map(Baz::getId)
+ .collect(toList());
+ }
+
+ private static final ImmutableSet<String> SCANDINAVIA =
+ ImmutableSet.of(
+ DENMARK____________________________________________________,
+ NORWAY_____________________________________________________,
+ SWEDEN_____________________________________________________)
+ .stream()
+ .map(x -> String.format("country: %s", x.toLowerCase()))
+ .collect(toImmutableSet());
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35797074.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35797074.input
new file mode 100644
index 0000000..aa4dc09
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35797074.input
@@ -0,0 +1,4 @@
+class B35797074 {
+ /** javadoc */
+ /* package */ final int x;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35797074.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35797074.output
new file mode 100644
index 0000000..aa4dc09
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B35797074.output
@@ -0,0 +1,4 @@
+class B35797074 {
+ /** javadoc */
+ /* package */ final int x;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B36194204.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B36194204.input
new file mode 100644
index 0000000..2a3c702
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B36194204.input
@@ -0,0 +1,5 @@
+class B36194204 {
+ {
+ Function<String[], String> f = (String... s) -> s[0];
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B36194204.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B36194204.output
new file mode 100644
index 0000000..2a3c702
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B36194204.output
@@ -0,0 +1,5 @@
+class B36194204 {
+ {
+ Function<String[], String> f = (String... s) -> s[0];
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37629610.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37629610.input
new file mode 100644
index 0000000..65d9d47
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37629610.input
@@ -0,0 +1,16 @@
+class B37629610 {
+ private static ImmutableList<InputOutputPair> toInputOutputPairs(Options options) {
+ for (Iterator<Path> inputIt = options.inputJars.iterator(),
+ outputIt = options.outputJars.iterator();
+ inputIt.hasNext();
+ ) {}
+ for (Iterator<Path> inputIt = options.inputJars.iterator(),
+ outputIt = options.outputJars.iterator();
+ ;
+ inputIt.next()) {}
+ for (inputIt = options.inputJars.iterator(), //
+ outputIt = options.outputJars.iterator();
+ ;
+ inputIt.next()) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37629610.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37629610.output
new file mode 100644
index 0000000..9942776
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37629610.output
@@ -0,0 +1,15 @@
+class B37629610 {
+ private static ImmutableList<InputOutputPair> toInputOutputPairs(Options options) {
+ for (Iterator<Path> inputIt = options.inputJars.iterator(),
+ outputIt = options.outputJars.iterator();
+ inputIt.hasNext(); ) {}
+ for (Iterator<Path> inputIt = options.inputJars.iterator(),
+ outputIt = options.outputJars.iterator();
+ ;
+ inputIt.next()) {}
+ for (inputIt = options.inputJars.iterator(), //
+ outputIt = options.outputJars.iterator();
+ ;
+ inputIt.next()) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37895033.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37895033.input
new file mode 100644
index 0000000..9ec5e46
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37895033.input
@@ -0,0 +1,20 @@
+class B37895033 {
+ String[][] xs =
+ new String[][] {
+ {"B3", "C3", "D3"},
+ {"B4", "C4", "D4"},
+ {"B5", "C5", "D5"}
+ };
+ String[] ys =
+ new String[] {
+ "B3",
+ "B4",
+ "B5",
+ };
+ String[][] zs =
+ new String[][] {
+ {"B3"},
+ {"B4"},
+ {"B5"},
+ };
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37895033.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37895033.output
new file mode 100644
index 0000000..1ea50bb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B37895033.output
@@ -0,0 +1,16 @@
+class B37895033 {
+ String[][] xs =
+ new String[][] {
+ {"B3", "C3", "D3"},
+ {"B4", "C4", "D4"},
+ {"B5", "C5", "D5"}
+ };
+ String[] ys =
+ new String[] {
+ "B3", "B4", "B5",
+ };
+ String[][] zs =
+ new String[][] {
+ {"B3"}, {"B4"}, {"B5"},
+ };
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38203081.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38203081.input
new file mode 100644
index 0000000..1241987
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38203081.input
@@ -0,0 +1,5 @@
+class B38203081 {
+ void f(int @A []... xs) {}
+
+ void g(int @A [] @B... xs) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38203081.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38203081.output
new file mode 100644
index 0000000..39bb11a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38203081.output
@@ -0,0 +1,5 @@
+class B38203081 {
+ void f(int @A []... xs) {}
+
+ void g(int @A [] @B ... xs) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38241237.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38241237.input
new file mode 100644
index 0000000..1909e49
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38241237.input
@@ -0,0 +1,7 @@
+class B38241237 {
+ //foo
+ //bar
+ //one long incredibly unbroken sentence moving from topic to topic so that no-one had a chance to interrupt
+ ///baz
+ ////
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38241237.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38241237.output
new file mode 100644
index 0000000..3dcebb1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38241237.output
@@ -0,0 +1,8 @@
+class B38241237 {
+ // foo
+ // bar
+ // one long incredibly unbroken sentence moving from topic to topic so that no-one had a chance to
+ // interrupt
+ /// baz
+ ////
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38352414.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38352414.input
new file mode 100644
index 0000000..60726fb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38352414.input
@@ -0,0 +1,2 @@
+@Redacted(Redacted.REDACTED + "/redacted", redacted = Redacted.class)
+class B38352414 {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38352414.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38352414.output
new file mode 100644
index 0000000..60726fb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B38352414.output
@@ -0,0 +1,2 @@
+@Redacted(Redacted.REDACTED + "/redacted", redacted = Redacted.class)
+class B38352414 {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B64493529.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B64493529.input
new file mode 100644
index 0000000..e0d65cb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B64493529.input
@@ -0,0 +1,10 @@
+/** Copies from {@link Foo} to {@link Bar}. */
+// XXXX.XxXxxxxx()
+class B64493529 {
+ // XXXX.XxxxXxxxxx(//xxxxx/xxxxxxx/xxxx/xxx/xxxxxx/xxx/xxxxxxx/xxxx/xxx/xxxxxxx/xxxxxxx/\
+ // XxxxxxXxxxxxXXxxxxxxXxxxxx.java)
+ // XXXX.XxxxXxxxxx(//xxxxx/xxxxxxx/xxxx/xxx/xxxxxx/xxx/xxxxxxx/xxxx/xxx/xxxxxxx/xxxxxxx/\
+ // XxxxxxXxxxxxXXxxxxxxXxxxxx.java)
+}
+// XXXX.XxxxXxxxxx(//xxxxx/xxxxxxx/xxxx/xxx/xxxxxx/xxx/xxxxxxx/xxxx/xxx/xxxxxxx/xxxxxxx/\
+// XxxxxxXxxxxxXXxxxxxxXxxxxx.java)
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B64493529.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B64493529.output
new file mode 100644
index 0000000..e0d65cb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B64493529.output
@@ -0,0 +1,10 @@
+/** Copies from {@link Foo} to {@link Bar}. */
+// XXXX.XxXxxxxx()
+class B64493529 {
+ // XXXX.XxxxXxxxxx(//xxxxx/xxxxxxx/xxxx/xxx/xxxxxx/xxx/xxxxxxx/xxxx/xxx/xxxxxxx/xxxxxxx/\
+ // XxxxxxXxxxxxXXxxxxxxXxxxxx.java)
+ // XXXX.XxxxXxxxxx(//xxxxx/xxxxxxx/xxxx/xxx/xxxxxx/xxx/xxxxxxx/xxxx/xxx/xxxxxxx/xxxxxxx/\
+ // XxxxxxXxxxxxXXxxxxxxXxxxxx.java)
+}
+// XXXX.XxxxXxxxxx(//xxxxx/xxxxxxx/xxxx/xxx/xxxxxx/xxx/xxxxxxx/xxxx/xxx/xxxxxxx/xxxxxxx/\
+// XxxxxxXxxxxxXXxxxxxxXxxxxx.java)
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B65214682.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B65214682.input
new file mode 100644
index 0000000..d6e22f6
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B65214682.input
@@ -0,0 +1,17 @@
+@BugPattern(
+ name = "AsyncFunctionReturnsImmediate",
+ summary = SIMPLIFY,
+ category = GUAVA,
+ severity = NOT_A_PROBLEM,
+ maturity = EXPERIMENTAL
+)
+@BugPattern(
+ name = "AsyncFunctionReturnsImmediate",
+ summary = SIMPLIFY,
+ explanation =
+ "If an AsyncFunction always returns immediateFuture() and never throws, it can "
+ + "be replaced with a Function.",
+ category = GUAVA,
+ severity = NOT_A_PROBLEM,
+ maturity = EXPERIMENTAL)
+class Test {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B65214682.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B65214682.output
new file mode 100644
index 0000000..ba49493
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B65214682.output
@@ -0,0 +1,16 @@
+@BugPattern(
+ name = "AsyncFunctionReturnsImmediate",
+ summary = SIMPLIFY,
+ category = GUAVA,
+ severity = NOT_A_PROBLEM,
+ maturity = EXPERIMENTAL)
+@BugPattern(
+ name = "AsyncFunctionReturnsImmediate",
+ summary = SIMPLIFY,
+ explanation =
+ "If an AsyncFunction always returns immediateFuture() and never throws, it can "
+ + "be replaced with a Function.",
+ category = GUAVA,
+ severity = NOT_A_PROBLEM,
+ maturity = EXPERIMENTAL)
+class Test {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B72507902.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B72507902.input
new file mode 100644
index 0000000..18d1900
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B72507902.input
@@ -0,0 +1,6 @@
+class B72507902 {
+ private static class Xxxxxxxxxxxxxxxxxxxx
+ extends Xxxxxxxxxxx<
+ Pair<xxxxxxxxxxxxxxxxxxx, Xxxxxxxxxx<Xxxxxxxxx>>, Xxxxxxxxxxxxxxxxxxxx,
+ List<Xxxxxxxxx>> {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B72507902.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B72507902.output
new file mode 100644
index 0000000..cbcfe0f
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B72507902.output
@@ -0,0 +1,7 @@
+class B72507902 {
+ private static class Xxxxxxxxxxxxxxxxxxxx
+ extends Xxxxxxxxxxx<
+ Pair<xxxxxxxxxxxxxxxxxxx, Xxxxxxxxxx<Xxxxxxxxx>>,
+ Xxxxxxxxxxxxxxxxxxxx,
+ List<Xxxxxxxxx>> {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/C.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/C.input
new file mode 100644
index 0000000..7baed6c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/C.input
@@ -0,0 +1,81 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for CastExpressions, CatchClauses, CharacterLiterals, ClassInstanceCreations,
+ * CommentHelper, ConditionalExpressions, ConstructorInvocations, ContinueStatements, and
+ * CreationReferences.
+ */
+class C<T> {
+ // TODO(jdd): Test higher-language-level constructs.
+
+ C() {
+ this(
+ 0, 1,
+ 2, 3,
+ 4, 5,
+ 6, 7,
+ 8, 9,
+ 10, 11,
+ 12, 13,
+ 14, 15,
+ 16, 17,
+ 18, 19,
+ 20, 21,
+ 22, 23,
+ 24, 25,
+ 26, 27,
+ 28, 29,
+ 30, 31);
+ }
+
+ C(int... x) {}
+
+ void f() {
+ try {
+ } catch (RuntimeException e) {
+ Object x =
+ (Pair<
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>,
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>>)
+ null;
+ C<Integer> c =
+ new C<Integer>(
+ 0, 1,
+ 2, 3,
+ 4, 5,
+ 6, 7,
+ 8, 9,
+ 10, 11,
+ 12, 13,
+ 14, 15,
+ 16, 17,
+ 18, 19,
+ 20, 21,
+ 22, 23,
+ 24, 25,
+ 26, 27,
+ 28, 29,
+ 30, 31);
+ int i = 0;
+ int j =
+ i == 0
+ ? 0
+ : i == 1
+ ? 1
+ : i == 2 ? 2 : i == 3 ? 3 : i == 4 ? 4 : i == 5 ? 5 : i == 6 ? 6 : i == 7 ? 7 : i;
+ LABEL:
+ while (true != false) {
+ if (false == true)
+ continue;
+ if (false == false)
+ continue LABEL;
+ // Comment indented +2
+ // Comment indented +2
+ }
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/C.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/C.output
new file mode 100644
index 0000000..fcf773e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/C.output
@@ -0,0 +1,79 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for CastExpressions, CatchClauses, CharacterLiterals, ClassInstanceCreations,
+ * CommentHelper, ConditionalExpressions, ConstructorInvocations, ContinueStatements, and
+ * CreationReferences.
+ */
+class C<T> {
+ // TODO(jdd): Test higher-language-level constructs.
+
+ C() {
+ this(
+ 0, 1,
+ 2, 3,
+ 4, 5,
+ 6, 7,
+ 8, 9,
+ 10, 11,
+ 12, 13,
+ 14, 15,
+ 16, 17,
+ 18, 19,
+ 20, 21,
+ 22, 23,
+ 24, 25,
+ 26, 27,
+ 28, 29,
+ 30, 31);
+ }
+
+ C(int... x) {}
+
+ void f() {
+ try {
+ } catch (RuntimeException e) {
+ Object x =
+ (Pair<
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>,
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>>)
+ null;
+ C<Integer> c =
+ new C<Integer>(
+ 0, 1,
+ 2, 3,
+ 4, 5,
+ 6, 7,
+ 8, 9,
+ 10, 11,
+ 12, 13,
+ 14, 15,
+ 16, 17,
+ 18, 19,
+ 20, 21,
+ 22, 23,
+ 24, 25,
+ 26, 27,
+ 28, 29,
+ 30, 31);
+ int i = 0;
+ int j =
+ i == 0
+ ? 0
+ : i == 1
+ ? 1
+ : i == 2 ? 2 : i == 3 ? 3 : i == 4 ? 4 : i == 5 ? 5 : i == 6 ? 6 : i == 7 ? 7 : i;
+ LABEL:
+ while (true != false) {
+ if (false == true) continue;
+ if (false == false) continue LABEL;
+ // Comment indented +2
+ // Comment indented +2
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..be76390
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/CL240367479.input
@@ -0,0 +1,6 @@
+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
new file mode 100644
index 0000000..025d237
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/CL240367479.output
@@ -0,0 +1,6 @@
+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/D.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/D.input
new file mode 100644
index 0000000..daca973
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/D.input
@@ -0,0 +1,20 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for Dimensions and DoStatements. */
+class D {
+ // TODO(jdd): Test higher-language-level features.
+
+ int[][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
+ [][][][]
+ array[][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
+ [][][][][][][];
+
+ void f() {
+ do {
+ assert false;
+ } while (1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ == 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/D.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/D.output
new file mode 100644
index 0000000..daca973
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/D.output
@@ -0,0 +1,20 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for Dimensions and DoStatements. */
+class D {
+ // TODO(jdd): Test higher-language-level features.
+
+ int[][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
+ [][][][]
+ array[][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
+ [][][][][][][];
+
+ void f() {
+ do {
+ assert false;
+ } while (1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ == 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/E.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/E.input
new file mode 100644
index 0000000..0e98139
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/E.input
@@ -0,0 +1,87 @@
+package com.google.googlejavaformat.java.test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+/**
+ * Tests for EmptyStatements, EnhancedForStatements, EnumConstantDeclarations, EnumDeclarations,
+ * ExpressionMethodReferences, ExpressionStatements, Expressions, and ExtendedModifiers.
+ */
+@MarkerAnnotation
+class E<T> {
+ // TODO(jdd): Test higher language-level features.
+
+ enum Enum1 {
+ A, B, C, D;
+
+ Enum1() {}
+ }
+
+ @MarkerAnnotation
+ public enum Enum2 {
+ A,
+ B,
+ C,
+ D,
+ ;
+
+ Enum2() {}
+ }
+
+ enum Enum3 {
+ A(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0),
+ B(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1),
+ C(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2),
+ D(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3);
+
+ Enum3(int x) {}
+ }
+
+ enum Enum4 {
+ A(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0),
+ B(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1),
+ C(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2),
+ D(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3),
+ ;
+
+ Enum4(int x) {}
+ }
+
+ int f(int value) {
+ ;
+ ;
+ ;
+ ;
+ ;
+ for (Integer x :
+ ImmutableList.<Integer>of(
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) {}
+ for (Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ x :
+ Lists
+ .<Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>>
+ newArrayList()) {}
+ f(10);
+ return f(20);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/E.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/E.output
new file mode 100644
index 0000000..4dd603a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/E.output
@@ -0,0 +1,90 @@
+package com.google.googlejavaformat.java.test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+/**
+ * Tests for EmptyStatements, EnhancedForStatements, EnumConstantDeclarations, EnumDeclarations,
+ * ExpressionMethodReferences, ExpressionStatements, Expressions, and ExtendedModifiers.
+ */
+@MarkerAnnotation
+class E<T> {
+ // TODO(jdd): Test higher language-level features.
+
+ enum Enum1 {
+ A,
+ B,
+ C,
+ D;
+
+ Enum1() {}
+ }
+
+ @MarkerAnnotation
+ public enum Enum2 {
+ A,
+ B,
+ C,
+ D,
+ ;
+
+ Enum2() {}
+ }
+
+ enum Enum3 {
+ A(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0),
+ B(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1),
+ C(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2),
+ D(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3);
+
+ Enum3(int x) {}
+ }
+
+ enum Enum4 {
+ A(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0),
+ B(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1),
+ C(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2),
+ D(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3),
+ ;
+
+ Enum4(int x) {}
+ }
+
+ int f(int value) {
+ ;
+ ;
+ ;
+ ;
+ ;
+ for (Integer x :
+ ImmutableList.<Integer>of(
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) {}
+ for (Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ x :
+ Lists
+ .<Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>>
+ newArrayList()) {}
+ f(10);
+ return f(20);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/EnumAnnotation.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/EnumAnnotation.input
new file mode 100644
index 0000000..43384fd
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/EnumAnnotation.input
@@ -0,0 +1,5 @@
+enum E {
+ @A ONE,
+ TWO,
+ @B @C THREE;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/EnumAnnotation.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/EnumAnnotation.output
new file mode 100644
index 0000000..0ff2a7a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/EnumAnnotation.output
@@ -0,0 +1,8 @@
+enum E {
+ @A
+ ONE,
+ TWO,
+ @B
+ @C
+ THREE;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/F.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/F.input
new file mode 100644
index 0000000..dbefdaf
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/F.input
@@ -0,0 +1,61 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for FieldAccesses, FieldDeclarations, and ForStatements. */
+class F {
+ int x;
+ int x1 = 0;
+ int y, z;
+ int y1 = 0, z1 = 0;
+ F f = null;
+ Object field0 =
+ true
+ ? true ? true ? null : null : true ? null : null
+ : true ? true ? null : null : true ? null : null;
+
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>
+ field1 = null;
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>
+ field2 =
+ true
+ ? true ? true ? null : null : true ? null : null
+ : true ? true ? null : null : true ? null : null;
+
+ @MarkerAnnotation
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>
+ field3 =
+ true
+ ? true ? true ? null : null : true ? null : null
+ : true ? true ? null : null : true ? null : null;
+
+ void f() {
+ for (int i =
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0;
+ i
+ < 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 10;
+ i++) {
+ f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.Ff = null;
+ }
+ for (int i =
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0;
+ i
+ < 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 10;
+ i++) {
+ f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.Ff =
+ null;
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/F.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/F.output
new file mode 100644
index 0000000..dbefdaf
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/F.output
@@ -0,0 +1,61 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for FieldAccesses, FieldDeclarations, and ForStatements. */
+class F {
+ int x;
+ int x1 = 0;
+ int y, z;
+ int y1 = 0, z1 = 0;
+ F f = null;
+ Object field0 =
+ true
+ ? true ? true ? null : null : true ? null : null
+ : true ? true ? null : null : true ? null : null;
+
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>
+ field1 = null;
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>
+ field2 =
+ true
+ ? true ? true ? null : null : true ? null : null
+ : true ? true ? null : null : true ? null : null;
+
+ @MarkerAnnotation
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>
+ field3 =
+ true
+ ? true ? true ? null : null : true ? null : null
+ : true ? true ? null : null : true ? null : null;
+
+ void f() {
+ for (int i =
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0;
+ i
+ < 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 10;
+ i++) {
+ f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.Ff = null;
+ }
+ for (int i =
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0;
+ i
+ < 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 10;
+ i++) {
+ f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.Ff =
+ null;
+ }
+ }
+}
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
new file mode 100644
index 0000000..9dc3426
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Fields.input
@@ -0,0 +1,7 @@
+class Fields {
+
+ 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
new file mode 100644
index 0000000..376e8b6
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Fields.output
@@ -0,0 +1,6 @@
+class Fields {
+
+ int x = 1;
+
+ int y = 1;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/ForMany.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/ForMany.input
new file mode 100644
index 0000000..6927ee9
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/ForMany.input
@@ -0,0 +1,6 @@
+class ForMany {
+ {
+ A a;
+ for (b = B; c != C; d = D) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/ForMany.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/ForMany.output
new file mode 100644
index 0000000..6927ee9
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/ForMany.output
@@ -0,0 +1,6 @@
+class ForMany {
+ {
+ A a;
+ for (b = B; c != C; d = D) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I.input
new file mode 100644
index 0000000..9f53760
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I.input
@@ -0,0 +1,77 @@
+package com.google.googlejavaformat.java.test;
+
+import java.util.List;
+
+/**
+ * Tests for IfStatements, ImportDeclarations, InfixExpressions, Initializers,
+ * InstanceofExpressions, and IntersectionTypes.
+ */
+class I {
+ interface I0 {}
+
+ interface I1 {}
+
+ interface I2 {}
+
+ interface I3 {}
+
+ interface I4 {}
+
+ interface I5 {}
+
+ interface I6 {}
+
+ interface I7 {}
+
+ interface I8 {}
+
+ interface I9 {}
+
+ interface I10 {}
+
+ interface I11 {}
+
+ interface I12 {}
+
+ interface I13 {}
+
+ interface I14 {}
+
+ interface I15 {}
+
+ interface I16 {}
+
+ interface I17 {}
+
+ interface I18 {}
+
+ interface I19 {}
+
+ class II<
+ T extends
+ I0 & I1 & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9 & I10 & I11 & I12 & I13 & I14 & I15 & I16
+ & I17 & I18 & I19> {}
+
+ static class CC {
+ static {
+ int i = 0;
+ }
+ }
+
+ int x =
+ 0 >>> 0 + 0 / 0 * 0 - 0 & 0 << 0 * 0 / 0 >> 0 - 0 ^ 0 * 0 / 0 >>> 0 << 0 * 0 - 0 / 0
+ | 0 * 0 >> 0 + 0 / 0 * 0 - 0 << 0
+ & 0 * 0 / 0 >>> 0 - 0 * 0 >> 0 / 0 << 0 * 0 + 0 - 0 / 0 * 0
+ | 0 - 0 * 0 >>> 0 << 0 / 0 * 0 >> 0 - 0 ^ 0 * 0 / 0 & 0 << 0 + 0;
+
+ void f() {
+ if (0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ == 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0) {
+ } else if (null instanceof List) {
+ } else {
+ }
+ if (true) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I.output
new file mode 100644
index 0000000..9f53760
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I.output
@@ -0,0 +1,77 @@
+package com.google.googlejavaformat.java.test;
+
+import java.util.List;
+
+/**
+ * Tests for IfStatements, ImportDeclarations, InfixExpressions, Initializers,
+ * InstanceofExpressions, and IntersectionTypes.
+ */
+class I {
+ interface I0 {}
+
+ interface I1 {}
+
+ interface I2 {}
+
+ interface I3 {}
+
+ interface I4 {}
+
+ interface I5 {}
+
+ interface I6 {}
+
+ interface I7 {}
+
+ interface I8 {}
+
+ interface I9 {}
+
+ interface I10 {}
+
+ interface I11 {}
+
+ interface I12 {}
+
+ interface I13 {}
+
+ interface I14 {}
+
+ interface I15 {}
+
+ interface I16 {}
+
+ interface I17 {}
+
+ interface I18 {}
+
+ interface I19 {}
+
+ class II<
+ T extends
+ I0 & I1 & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9 & I10 & I11 & I12 & I13 & I14 & I15 & I16
+ & I17 & I18 & I19> {}
+
+ static class CC {
+ static {
+ int i = 0;
+ }
+ }
+
+ int x =
+ 0 >>> 0 + 0 / 0 * 0 - 0 & 0 << 0 * 0 / 0 >> 0 - 0 ^ 0 * 0 / 0 >>> 0 << 0 * 0 - 0 / 0
+ | 0 * 0 >> 0 + 0 / 0 * 0 - 0 << 0
+ & 0 * 0 / 0 >>> 0 - 0 * 0 >> 0 / 0 << 0 * 0 + 0 - 0 / 0 * 0
+ | 0 - 0 * 0 >>> 0 << 0 / 0 * 0 >> 0 - 0 ^ 0 * 0 / 0 & 0 << 0 + 0;
+
+ void f() {
+ if (0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ == 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0) {
+ } else if (null instanceof List) {
+ } else {
+ }
+ if (true) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I1.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I1.input
new file mode 100644
index 0000000..7a1cd14
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I1.input
@@ -0,0 +1,49 @@
+class Test {
+ final CreationMechanism creationMechanism;
+ final @Nullable String creationUserAgent;
+ final ClientId clientId;
+ final @Nullable String creationUserAgentXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;
+ final Token externalId;
+
+ {
+ final CreationMechanism creationMechanism;
+ final @Nullable String creationUserAgent;
+ final ClientId clientId;
+ final @Nullable String creationUserAgentXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;
+ final Token externalId;
+ }
+}
+
+class Test {
+ final CreationMechanism creationMechanism;
+ @Nullable final String creationUserAgent;
+ final ClientId clientId;
+ @Nullable final String creationUserAgentXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;
+ final Token externalId;
+
+ {
+ final CreationMechanism creationMechanism;
+ @Nullable final String creationUserAgent;
+ final ClientId clientId;
+ @Nullable final String creationUserAgentXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;
+ final Token externalId;
+ }
+}
+
+class Test {
+
+ @Nullable
+ final String creationUserAgent;
+
+ {
+ @Nullable
+ final String creationUserAgent;
+ }
+
+ {
+
+ @Nullable
+ final String creationUserAgent;
+
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I1.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I1.output
new file mode 100644
index 0000000..2637dde
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I1.output
@@ -0,0 +1,50 @@
+class Test {
+ final CreationMechanism creationMechanism;
+ final @Nullable String creationUserAgent;
+ final ClientId clientId;
+ final @Nullable String
+ creationUserAgentXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;
+ final Token externalId;
+
+ {
+ final CreationMechanism creationMechanism;
+ final @Nullable String creationUserAgent;
+ final ClientId clientId;
+ final @Nullable String
+ creationUserAgentXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;
+ final Token externalId;
+ }
+}
+
+class Test {
+ final CreationMechanism creationMechanism;
+ @Nullable final String creationUserAgent;
+ final ClientId clientId;
+
+ @Nullable
+ final String creationUserAgentXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;
+
+ final Token externalId;
+
+ {
+ final CreationMechanism creationMechanism;
+ @Nullable final String creationUserAgent;
+ final ClientId clientId;
+ @Nullable
+ final String creationUserAgentXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;
+ final Token externalId;
+ }
+}
+
+class Test {
+
+ @Nullable final String creationUserAgent;
+
+ {
+ @Nullable final String creationUserAgent;
+ }
+
+ {
+ @Nullable final String creationUserAgent;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I12.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I12.input
new file mode 100644
index 0000000..5d8a058
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I12.input
@@ -0,0 +1,19 @@
+public class Test {
+
+
+
+}
+
+public class Test {
+
+
+}
+
+public class Test {
+
+}
+
+public class Test {
+}
+
+public class Test {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I12.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I12.output
new file mode 100644
index 0000000..0b72fab
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I12.output
@@ -0,0 +1,9 @@
+public class Test {}
+
+public class Test {}
+
+public class Test {}
+
+public class Test {}
+
+public class Test {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I13.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I13.input
new file mode 100644
index 0000000..079e78b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I13.input
@@ -0,0 +1,10 @@
+class I13 {
+
+ @Nullable
+
+ public int f;
+
+ @Override
+
+ public void m() {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I13.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I13.output
new file mode 100644
index 0000000..b902fe6
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I13.output
@@ -0,0 +1,7 @@
+class I13 {
+
+ @Nullable public int f;
+
+ @Override
+ public void m() {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I202.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I202.input
new file mode 100644
index 0000000..c3766ec
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I202.input
@@ -0,0 +1,6 @@
+class I202 {
+ {
+ //noinspection CheckResult
+ methodWhoseResultShouldBeChecked();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I202.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I202.output
new file mode 100644
index 0000000..c3766ec
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I202.output
@@ -0,0 +1,6 @@
+class I202 {
+ {
+ //noinspection CheckResult
+ methodWhoseResultShouldBeChecked();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I365.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I365.input
new file mode 100644
index 0000000..e3866d8
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I365.input
@@ -0,0 +1,10 @@
+class I365 {
+ {
+ return foo____________
+ .bar__________()
+ .baz____________()
+ .parallelStream()
+ .map(Baz::getId)
+ .collect(toList());
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I365.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I365.output
new file mode 100644
index 0000000..0985d2a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I365.output
@@ -0,0 +1,7 @@
+class I365 {
+ {
+ return foo____________.bar__________().baz____________().parallelStream()
+ .map(Baz::getId)
+ .collect(toList());
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I374.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I374.input
new file mode 100644
index 0000000..2364f0c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I374.input
@@ -0,0 +1,9 @@
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@interface MyTypeAnno {}
+
+public class GjfFailure {
+ void m(int a @MyTypeAnno []) {}
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I374.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I374.output
new file mode 100644
index 0000000..a36919e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I374.output
@@ -0,0 +1,9 @@
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@interface MyTypeAnno {}
+
+public class GjfFailure {
+ void m(int a @MyTypeAnno []) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/L.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/L.input
new file mode 100644
index 0000000..f0b3e66
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/L.input
@@ -0,0 +1,11 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for LabeledStatements and LambdaExpressions. */
+class L {
+ // TODO(jdd): Include high language-level tests.
+
+ void f() {
+ LABEL:
+ for (int i = 0; i < 10; i++) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/L.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/L.output
new file mode 100644
index 0000000..f0b3e66
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/L.output
@@ -0,0 +1,11 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for LabeledStatements and LambdaExpressions. */
+class L {
+ // TODO(jdd): Include high language-level tests.
+
+ void f() {
+ LABEL:
+ for (int i = 0; i < 10; i++) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/LocalAnnotations.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/LocalAnnotations.input
new file mode 100644
index 0000000..87b9aa2
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/LocalAnnotations.input
@@ -0,0 +1,7 @@
+class LocalAnnotations {
+ {
+ @Foo final Object x;
+ @Foo(1) final Object y;
+ @Foo(x=1) final Object y;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/LocalAnnotations.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/LocalAnnotations.output
new file mode 100644
index 0000000..181d729
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/LocalAnnotations.output
@@ -0,0 +1,9 @@
+class LocalAnnotations {
+ {
+ @Foo final Object x;
+ @Foo(1)
+ final Object y;
+ @Foo(x = 1)
+ final Object y;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/M.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M.input
new file mode 100644
index 0000000..56f4ccf
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M.input
@@ -0,0 +1,399 @@
+package com.google.googlejavaformat.java.test;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Tests for MarkerAnnotations, MemberValuePairs, MethodDeclarations, MethodInvocations, and
+ * Modifiers.
+ */
+@MarkerAnnotation
+class M {
+ class Exception0 extends Exception {}
+
+ class Exception1 extends Exception {}
+
+ class Exception2 extends Exception {}
+
+ class Exception3 extends Exception {}
+
+ class Exception4 extends Exception {}
+
+ class Exception5 extends Exception {}
+
+ class Exception6 extends Exception {}
+
+ class Exception7 extends Exception {}
+
+ class Exception8 extends Exception {}
+
+ class Exception9 extends Exception {}
+
+ void methodName1(int x) {}
+
+ void methodName2(int x) throws Exception {}
+
+ /**
+ * JavaDoc.
+ */
+ void methodName3(int x)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {}
+
+ /**
+ * JavaDoc.
+ */
+ void methodName4(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m,
+ int n, int o, int p, int q, int r, int s, int t, int u, int v) {}
+
+ /**
+ * JavaDoc.
+ */
+ void methodName5(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m,
+ int n, int o, int p, int q, int r, int s, int t, int u, int v)
+ throws Exception {}
+
+ /**
+ * JavaDoc.
+ */
+ void methodName6(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m,
+ int n, int o, int p, int q, int r, int s, int t, int u, int v)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {}
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName10(int x) {
+ return null;
+ }
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName12(int x) throws Exception {
+ return null;
+ }
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName11(int x)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName15(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m,
+ int n, int o, int p, int q, int r, int s, int t, int u, int v) {
+ return null;
+ }
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName14(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m,
+ int n, int o, int p, int q, int r, int s, int t, int u, int v)
+ throws Exception {
+ return null;
+ }
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName13(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m,
+ int n, int o, int p, int q, int r, int s, int t, int u, int v)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ T methodName20(int x) {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ T methodName21(int x) throws Exception {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ T methodName22(int x)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ T methodName22(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m,
+ int n, int o, int p, int q, int r, int s, int t, int u, int v) {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ T methodName23(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m,
+ int n, int o, int p, int q, int r, int s, int t, int u, int v)
+ throws Exception {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ T methodName30(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m,
+ int n, int o, int p, int q, int r, int s, int t, int u, int v)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName31(int x) {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName32(int x) throws Exception {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName(int x)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5,
+ Exception6, Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName41(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l,
+ int m, int n, int o, int p, int q, int r, int s, int t, int u, int v) {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName40(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l,
+ int m, int n, int o, int p, int q, int r, int s, int t, int u, int v)
+ throws Exception {
+ return null;
+ }
+
+ <T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20,
+ T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName(
+ int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l,
+ int m, int n, int o, int p, int q, int r, int s, int t, int u, int v)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5,
+ Exception6, Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ static <T0, T1, T2> void m() {}
+
+ M identity() {
+ return this;
+ }
+
+ void f(double x) {
+ List<Pair<Integer, Integer>> list1 = ImmutableList.<Pair<Integer, Integer>>of(null);
+ List<Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>> list2 =
+ ImmutableList.<Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>of(null);
+ List<
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>>
+ list3 =
+ ImmutableList
+ .<Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>>
+ of(null);
+ Pair<Integer, Integer> pair1 =
+ ImmutableList.<Pair<Integer, Integer>>of(null).get(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0);
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>> pair2 =
+ ImmutableList.<Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>of(null).get(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0);
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>
+ pair3 =
+ ImmutableList
+ .<Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>>
+ of(null)
+ .get(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0);
+ }
+
+ void f(int... x) {
+ M m = null;
+ ((m.identity().identity().identity().identity()).identity().identity().identity().identity())
+ .identity()
+ .identity()
+ .identity()
+ .identity();
+ f(
+ x[0] + x[1] + x[2] + x[3] + x[4] + x[5] + x[6] + x[7] + x[8] + x[9] + x[11] + x[12] + x[13]
+ + x[14] + x[15]);
+ }
+
+ static void method999(Object... args) {
+ method999(1, 2, 3, 4, 5);
+ method999(1, 2, 3, 4, 5);
+ method999(
+ 1, 2,
+ 3, 4,
+ 5, 6);
+ method999(1, 2, 3, 4, 5, 6);
+ method999("one", 1, "two", 2, "three", 3);
+ method999("one", 1);
+ method999(
+ "one", 1,
+ "two", 2);
+ method999(
+ "one", 1,
+ "two", 1 + 1,
+ "three", 1 + 1 + 1);
+ method999(
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx");
+ }
+
+ static class Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {
+ static Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx fff(
+ int x) {
+ return null;
+ }
+ }
+
+ static
+ class Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {
+ static Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx fff(
+ int x) {
+ return null;
+ }
+
+ static Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ ffffffffff(int x) {
+ return null;
+ }
+ }
+
+ M xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = null;
+ M xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx =
+ null;
+
+ M fff(int x) {
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.fff(0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.fff(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ .ffffffffff(0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.fff(0).fff(0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.fff(0 + 0).fff(
+ 0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ .fff(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0)
+ .fff(0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ .ffffffffff(0)
+ .fff(0);
+ return this;
+ }
+
+ M ffffffffff(int x) {
+ return this;
+ }
+
+ M ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(int x) {
+ new M().ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(
+ 0 + 0 + 0 + 0);
+ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(0);
+ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0);
+ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(0).f(0);
+ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(0)
+ .fff(0)
+ .fff(0)
+ .f(0);
+ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(0).ffffffffff(
+ 0);
+ return this;
+ }
+
+ <XXXXXXXXXX, XXXXXXXXXX, XXXXXXXXXX, XXXXXXXXXX, XXXXXXXXXX, XXXXXXXXXX, XXXXXXXXXX, XXXXXXXXXX,
+ XXXXXXXXXX, XXXXXXXXXX, XXXXXXXXXX, XXXXXXXXXX>
+ M(
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z)
+ throws EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE,
+ EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/M.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M.output
new file mode 100644
index 0000000..dbad1bb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M.output
@@ -0,0 +1,1016 @@
+package com.google.googlejavaformat.java.test;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Tests for MarkerAnnotations, MemberValuePairs, MethodDeclarations, MethodInvocations, and
+ * Modifiers.
+ */
+@MarkerAnnotation
+class M {
+ class Exception0 extends Exception {}
+
+ class Exception1 extends Exception {}
+
+ class Exception2 extends Exception {}
+
+ class Exception3 extends Exception {}
+
+ class Exception4 extends Exception {}
+
+ class Exception5 extends Exception {}
+
+ class Exception6 extends Exception {}
+
+ class Exception7 extends Exception {}
+
+ class Exception8 extends Exception {}
+
+ class Exception9 extends Exception {}
+
+ void methodName1(int x) {}
+
+ void methodName2(int x) throws Exception {}
+
+ /** JavaDoc. */
+ void methodName3(int x)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {}
+
+ /** JavaDoc. */
+ void methodName4(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v) {}
+
+ /** JavaDoc. */
+ void methodName5(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v)
+ throws Exception {}
+
+ /** JavaDoc. */
+ void methodName6(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {}
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName10(int x) {
+ return null;
+ }
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName12(int x) throws Exception {
+ return null;
+ }
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName11(int x)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName15(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v) {
+ return null;
+ }
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName14(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v)
+ throws Exception {
+ return null;
+ }
+
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName13(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ T methodName20(int x) {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ T methodName21(int x) throws Exception {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ T methodName22(int x)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ T methodName22(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v) {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ T methodName23(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v)
+ throws Exception {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ T methodName30(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5, Exception6,
+ Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName31(int x) {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName32(int x) throws Exception {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName(int x)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5,
+ Exception6, Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName41(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v) {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName40(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v)
+ throws Exception {
+ return null;
+ }
+
+ <
+ T0,
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24,
+ T25,
+ T26,
+ T27,
+ T28,
+ T29,
+ T30,
+ T31>
+ Pair<
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>,
+ Pair<Pair<Pair<T, T>, Pair<T, T>>, Pair<Pair<T, T>, Pair<T, T>>>>
+ methodName(
+ int a,
+ int b,
+ int c,
+ int d,
+ int e,
+ int f,
+ int g,
+ int h,
+ int i,
+ int j,
+ int k,
+ int l,
+ int m,
+ int n,
+ int o,
+ int p,
+ int q,
+ int r,
+ int s,
+ int t,
+ int u,
+ int v)
+ throws Exception0, Exception1, Exception2, Exception3, Exception4, Exception5,
+ Exception6, Exception7, Exception8, Exception9 {
+ return null;
+ }
+
+ static <T0, T1, T2> void m() {}
+
+ M identity() {
+ return this;
+ }
+
+ void f(double x) {
+ List<Pair<Integer, Integer>> list1 = ImmutableList.<Pair<Integer, Integer>>of(null);
+ List<Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>> list2 =
+ ImmutableList.<Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>of(null);
+ List<
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>>
+ list3 =
+ ImmutableList
+ .<Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>>
+ of(null);
+ Pair<Integer, Integer> pair1 =
+ ImmutableList.<Pair<Integer, Integer>>of(null)
+ .get(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0);
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>> pair2 =
+ ImmutableList.<Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>of(null)
+ .get(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0);
+ Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>
+ pair3 =
+ ImmutableList
+ .<Pair<
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>,
+ Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>>
+ of(null)
+ .get(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0);
+ }
+
+ void f(int... x) {
+ M m = null;
+ ((m.identity().identity().identity().identity()).identity().identity().identity().identity())
+ .identity()
+ .identity()
+ .identity()
+ .identity();
+ f(
+ x[0] + x[1] + x[2] + x[3] + x[4] + x[5] + x[6] + x[7] + x[8] + x[9] + x[11] + x[12] + x[13]
+ + x[14] + x[15]);
+ }
+
+ static void method999(Object... args) {
+ method999(1, 2, 3, 4, 5);
+ method999(1, 2, 3, 4, 5);
+ method999(
+ 1, 2,
+ 3, 4,
+ 5, 6);
+ method999(1, 2, 3, 4, 5, 6);
+ method999("one", 1, "two", 2, "three", 3);
+ method999("one", 1);
+ method999(
+ "one", 1,
+ "two", 2);
+ method999(
+ "one", 1,
+ "two", 1 + 1,
+ "three", 1 + 1 + 1);
+ method999(
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxx");
+ }
+
+ static class Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {
+ static Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx fff(
+ int x) {
+ return null;
+ }
+ }
+
+ static
+ class Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {
+ static Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx fff(
+ int x) {
+ return null;
+ }
+
+ static Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ ffffffffff(int x) {
+ return null;
+ }
+ }
+
+ M xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = null;
+ M xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx =
+ null;
+
+ M fff(int x) {
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.fff(0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.fff(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ .ffffffffff(0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.fff(0).fff(0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.fff(0 + 0)
+ .fff(0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.fff(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0)
+ .fff(0);
+ Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ .ffffffffff(0)
+ .fff(0);
+ return this;
+ }
+
+ M ffffffffff(int x) {
+ return this;
+ }
+
+ M ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(int x) {
+ new M()
+ .ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(
+ 0 + 0 + 0 + 0);
+ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(0);
+ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0);
+ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(0).f(0);
+ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(0)
+ .fff(0)
+ .fff(0)
+ .f(0);
+ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(0)
+ .ffffffffff(0);
+ return this;
+ }
+
+ <
+ XXXXXXXXXX,
+ XXXXXXXXXX,
+ XXXXXXXXXX,
+ XXXXXXXXXX,
+ XXXXXXXXXX,
+ XXXXXXXXXX,
+ XXXXXXXXXX,
+ XXXXXXXXXX,
+ XXXXXXXXXX,
+ XXXXXXXXXX,
+ XXXXXXXXXX,
+ XXXXXXXXXX>
+ M(
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z,
+ ZZZZZZZZZZ z)
+ throws EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE,
+ EEEEEEEEEE, EEEEEEEEEE, EEEEEEEEEE {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/M1.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M1.input
new file mode 100644
index 0000000..c5ff9b5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M1.input
@@ -0,0 +1,22 @@
+import a.A;
+import a.B;
+import com.google.Foo;
+
+@A @B
+module com.google.m {
+requires com.google.r1;
+requires transitive com.google.r2;
+requires static com.google.r3;
+exports com.google.e1;
+exports com.google.e1 to com.google.e2;
+exports com.google.e1 to com.google.e2, com.google.e3;
+exports com.google.e1 to com.google.e2, com.google.e3, com.google.e4;
+opens com.google.o1;
+opens com.google.o1 to com.google.o2;
+opens com.google.o1 to com.google.o2, com.google.o3;
+opens com.google.o1 to com.google.o2, com.google.o3, com.googleoe4;
+uses Foo;
+uses com.google.Bar;
+provides com.google.Baz with Foo;
+provides com.google.Baz with Foo, com.google.Bar;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/M1.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M1.output
new file mode 100644
index 0000000..514662b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M1.output
@@ -0,0 +1,42 @@
+import a.A;
+import a.B;
+import com.google.Foo;
+
+@A
+@B
+module com.google.m {
+ requires com.google.r1;
+ requires transitive com.google.r2;
+ requires static com.google.r3;
+
+ exports com.google.e1;
+ exports com.google.e1 to
+ com.google.e2;
+ exports com.google.e1 to
+ com.google.e2,
+ com.google.e3;
+ exports com.google.e1 to
+ com.google.e2,
+ com.google.e3,
+ com.google.e4;
+
+ opens com.google.o1;
+ opens com.google.o1 to
+ com.google.o2;
+ opens com.google.o1 to
+ com.google.o2,
+ com.google.o3;
+ opens com.google.o1 to
+ com.google.o2,
+ com.google.o3,
+ com.googleoe4;
+
+ uses Foo;
+ uses com.google.Bar;
+
+ provides com.google.Baz with
+ Foo;
+ provides com.google.Baz with
+ Foo,
+ com.google.Bar;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/M2.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M2.input
new file mode 100644
index 0000000..c7d8b16
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M2.input
@@ -0,0 +1,2 @@
+open module com.google.m {
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/M2.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M2.output
new file mode 100644
index 0000000..dd22a0c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M2.output
@@ -0,0 +1 @@
+open module com.google.m {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/M3.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M3.input
new file mode 100644
index 0000000..c5d2966
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M3.input
@@ -0,0 +1,6 @@
+@Deprecated
+module moduletags {
+ requires transitive static moduleA;
+
+ exports testpkgmdltags;
+}
\ No newline at end of file
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/M3.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M3.output
new file mode 100644
index 0000000..563ef46
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/M3.output
@@ -0,0 +1,6 @@
+@Deprecated
+module moduletags {
+ requires transitive static moduleA;
+
+ exports testpkgmdltags;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/MarkerAnnotation.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/MarkerAnnotation.input
new file mode 100644
index 0000000..b8dd3e3
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/MarkerAnnotation.input
@@ -0,0 +1,4 @@
+package com.google.googlejavaformat.java.test;
+
+/** JavaDoc comment. */
+public @interface MarkerAnnotation {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/MarkerAnnotation.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/MarkerAnnotation.output
new file mode 100644
index 0000000..b8dd3e3
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/MarkerAnnotation.output
@@ -0,0 +1,4 @@
+package com.google.googlejavaformat.java.test;
+
+/** JavaDoc comment. */
+public @interface MarkerAnnotation {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Multivariables.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Multivariables.input
new file mode 100644
index 0000000..a5e0b4d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Multivariables.input
@@ -0,0 +1,17 @@
+class Multivariables {
+ int xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy,
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz;
+
+ // Horizontal and veritcal paddings used for most cells in the standings table.
+ private final int hpad, vpad;
+
+ {
+ {
+ for (c = cx.iterator(), m = mx.iterator();
+ c.hasNext() && m.hasNext(); ) {
+ f(c, m);
+ }
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Multivariables.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Multivariables.output
new file mode 100644
index 0000000..fc9a05e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Multivariables.output
@@ -0,0 +1,16 @@
+class Multivariables {
+ int xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy,
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz;
+
+ // Horizontal and veritcal paddings used for most cells in the standings table.
+ private final int hpad, vpad;
+
+ {
+ {
+ for (c = cx.iterator(), m = mx.iterator(); c.hasNext() && m.hasNext(); ) {
+ f(c, m);
+ }
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/N.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/N.input
new file mode 100644
index 0000000..0359141
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/N.input
@@ -0,0 +1,13 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for NameQualifiedTypes, Names, NormalAnnotations, NullLiterals, and NumberLiterals. */
+class N {
+ @NormalAnnotation(
+ a = 1, b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8, i = 9, j = 10, k = 11, l = 12, m = 13,
+ n = 14, o = 15, p = 16, q = 17, r = 18, s = 19, t = 20, u = 21, v = 22, w = 23, x = 24,
+ y = 25, z = 26)
+ void f() {
+ assert 00 == 0 && 0x0 == 0 && 1.0e0f == 1.00e0f && 0.2D == .2D;
+ java.lang. /* @MarkerAnnotation */String s = null;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/N.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/N.output
new file mode 100644
index 0000000..764390a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/N.output
@@ -0,0 +1,36 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for NameQualifiedTypes, Names, NormalAnnotations, NullLiterals, and NumberLiterals. */
+class N {
+ @NormalAnnotation(
+ a = 1,
+ b = 2,
+ c = 3,
+ d = 4,
+ e = 5,
+ f = 6,
+ g = 7,
+ h = 8,
+ i = 9,
+ j = 10,
+ k = 11,
+ l = 12,
+ m = 13,
+ n = 14,
+ o = 15,
+ p = 16,
+ q = 17,
+ r = 18,
+ s = 19,
+ t = 20,
+ u = 21,
+ v = 22,
+ w = 23,
+ x = 24,
+ y = 25,
+ z = 26)
+ void f() {
+ assert 00 == 0 && 0x0 == 0 && 1.0e0f == 1.00e0f && 0.2D == .2D;
+ java.lang./* @MarkerAnnotation */ String s = null;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/NormalAnnotation.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/NormalAnnotation.input
new file mode 100644
index 0000000..4a3a07e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/NormalAnnotation.input
@@ -0,0 +1,56 @@
+package com.google.googlejavaformat.java.test;
+
+/** JavaDoc comment. */
+public @interface NormalAnnotation {
+ int a();
+
+ int b();
+
+ int c();
+
+ int d();
+
+ int e();
+
+ int f();
+
+ int g();
+
+ int h();
+
+ int i();
+
+ int j();
+
+ int k();
+
+ int l();
+
+ int m();
+
+ int n();
+
+ int o();
+
+ int p();
+
+ int q();
+
+ int r();
+
+ int s();
+
+ int t();
+
+ int u();
+
+ int v();
+
+ int w();
+
+ int x();
+
+ int y();
+
+ int z();
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/NormalAnnotation.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/NormalAnnotation.output
new file mode 100644
index 0000000..4a3a07e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/NormalAnnotation.output
@@ -0,0 +1,56 @@
+package com.google.googlejavaformat.java.test;
+
+/** JavaDoc comment. */
+public @interface NormalAnnotation {
+ int a();
+
+ int b();
+
+ int c();
+
+ int d();
+
+ int e();
+
+ int f();
+
+ int g();
+
+ int h();
+
+ int i();
+
+ int j();
+
+ int k();
+
+ int l();
+
+ int m();
+
+ int n();
+
+ int o();
+
+ int p();
+
+ int q();
+
+ int r();
+
+ int s();
+
+ int t();
+
+ int u();
+
+ int v();
+
+ int w();
+
+ int x();
+
+ int y();
+
+ int z();
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/P.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/P.input
new file mode 100644
index 0000000..8cc377c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/P.input
@@ -0,0 +1,21 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for PackageDeclarations, ParameterizedTypes, ParenthesizedExpressions, PostfixExpressions,
+ * PrefixExpressions, and PrimitiveTypes.
+ */
+class P<
+ T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21,
+ T22, T23, T24> {
+ void f() {
+ int x = (1 + 2) * 3;
+ ++x;
+ x++;
+ int j = + + +x;
+ int k = + + ++x;
+ int jj = - - -x;
+ int kk = - - --x;
+ boolean b = false;
+ boolean bb = !!b;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/P.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/P.output
new file mode 100644
index 0000000..9c63831
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/P.output
@@ -0,0 +1,43 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for PackageDeclarations, ParameterizedTypes, ParenthesizedExpressions, PostfixExpressions,
+ * PrefixExpressions, and PrimitiveTypes.
+ */
+class P<
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16,
+ T17,
+ T18,
+ T19,
+ T20,
+ T21,
+ T22,
+ T23,
+ T24> {
+ void f() {
+ int x = (1 + 2) * 3;
+ ++x;
+ x++;
+ int j = + + +x;
+ int k = + + ++x;
+ int jj = - - -x;
+ int kk = - - --x;
+ boolean b = false;
+ boolean bb = !!b;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Pair.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Pair.input
new file mode 100644
index 0000000..a596748
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Pair.input
@@ -0,0 +1,12 @@
+package com.google.googlejavaformat.java.test;
+
+/** JavaDoc comment. */
+public class Pair<T1, T2> {
+ public final T1 x;
+ public final T2 y;
+
+ Pair(T1 x, T2 y) {
+ this.x = x;
+ this.y = y;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Pair.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Pair.output
new file mode 100644
index 0000000..a596748
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Pair.output
@@ -0,0 +1,12 @@
+package com.google.googlejavaformat.java.test;
+
+/** JavaDoc comment. */
+public class Pair<T1, T2> {
+ public final T1 x;
+ public final T2 y;
+
+ Pair(T1 x, T2 y) {
+ this.x = x;
+ this.y = y;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/PairedArguments.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/PairedArguments.input
new file mode 100644
index 0000000..1a654d0
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/PairedArguments.input
@@ -0,0 +1,40 @@
+class T {
+ {
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxx(), xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxx(),
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxx(),
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxx(),
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/PairedArguments.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/PairedArguments.output
new file mode 100644
index 0000000..a88a1b1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/PairedArguments.output
@@ -0,0 +1,55 @@
+class T {
+ {
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxx(),
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxx(),
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxx(),
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxx(),
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx);
+ f(
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxx);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Q.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Q.input
new file mode 100644
index 0000000..f467201
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Q.input
@@ -0,0 +1,15 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for QualifiedNames and QualifiedTypes. */
+class Q {
+ static final int zero = 0;
+
+ class T {}
+
+ static void f() {
+ Q. /* @MarkerAnnotation */T t;
+ int zero = Q.zero;
+ java.lang. /* @MarkerAnnotation */String s = null;
+ Q.f();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Q.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Q.output
new file mode 100644
index 0000000..369f38e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Q.output
@@ -0,0 +1,15 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for QualifiedNames and QualifiedTypes. */
+class Q {
+ static final int zero = 0;
+
+ class T {}
+
+ static void f() {
+ Q./* @MarkerAnnotation */ T t;
+ int zero = Q.zero;
+ java.lang./* @MarkerAnnotation */ String s = null;
+ Q.f();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/R.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/R.input
new file mode 100644
index 0000000..053d46e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/R.input
@@ -0,0 +1,13 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for ReturnStatements. */
+class R {
+ void f() {
+ return;
+ }
+
+ int f(int x) {
+ return x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x
+ + x + x + x + x + x + x + x + x + x;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/R.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/R.output
new file mode 100644
index 0000000..053d46e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/R.output
@@ -0,0 +1,13 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for ReturnStatements. */
+class R {
+ void f() {
+ return;
+ }
+
+ int f(int x) {
+ return x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x
+ + x + x + x + x + x + x + x + x + x;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/S.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/S.input
new file mode 100644
index 0000000..a1e07d1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/S.input
@@ -0,0 +1,46 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for SimpleNames, SimpleTypes, SingleMemberAnnotations, SingleVariableDeclarations,
+ * Statements, StringLiterals, SuperConstructorInvocations, SuperFieldAccesses,
+ * SuperMethodInvocations, SuperMethodReferences, SwitchCases, SwitchStatements, and
+ * SynchronizedStatements.
+ */
+class S {
+ // TODO(jdd): Add tests for higher language levels.
+
+ int x = 0;
+
+ @SingleMemberAnnotation(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0)
+ S() {
+ super();
+ }
+
+ class SS extends S {
+ SS() {
+ super();
+ super.x = 0;
+ super.foo();
+ }
+ }
+
+ void foo() {
+ Object[] object = null;
+ synchronized (
+ object[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]) {
+ switch ("abc") {
+ case "one":
+ break;
+ case "two":
+ break;
+ case "three":
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/S.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/S.output
new file mode 100644
index 0000000..a1e07d1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/S.output
@@ -0,0 +1,46 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for SimpleNames, SimpleTypes, SingleMemberAnnotations, SingleVariableDeclarations,
+ * Statements, StringLiterals, SuperConstructorInvocations, SuperFieldAccesses,
+ * SuperMethodInvocations, SuperMethodReferences, SwitchCases, SwitchStatements, and
+ * SynchronizedStatements.
+ */
+class S {
+ // TODO(jdd): Add tests for higher language levels.
+
+ int x = 0;
+
+ @SingleMemberAnnotation(
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0)
+ S() {
+ super();
+ }
+
+ class SS extends S {
+ SS() {
+ super();
+ super.x = 0;
+ super.foo();
+ }
+ }
+
+ void foo() {
+ Object[] object = null;
+ synchronized (
+ object[
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]) {
+ switch ("abc") {
+ case "one":
+ break;
+ case "two":
+ break;
+ case "three":
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SingleMemberAnnotation.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SingleMemberAnnotation.input
new file mode 100644
index 0000000..66045d5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SingleMemberAnnotation.input
@@ -0,0 +1,5 @@
+package com.google.googlejavaformat.java.test;
+
+public @interface SingleMemberAnnotation {
+ int value();
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SingleMemberAnnotation.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SingleMemberAnnotation.output
new file mode 100644
index 0000000..66045d5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SingleMemberAnnotation.output
@@ -0,0 +1,5 @@
+package com.google.googlejavaformat.java.test;
+
+public @interface SingleMemberAnnotation {
+ int value();
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SuperQualifier.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SuperQualifier.input
new file mode 100644
index 0000000..993a96d
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SuperQualifier.input
@@ -0,0 +1,18 @@
+class SuperQualifier {
+ String a() {
+ return this.toStringHelper()
+ .add("foo", foo_________________________________________)
+ .add("bar", bar_________________________________________);
+ }
+
+ String b() {
+ return super.toStringHelper()
+ .add("foo", foo_________________________________________)
+ .add("bar", bar_________________________________________);
+ }
+
+ {
+ this.aaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbb(
+ ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SuperQualifier.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SuperQualifier.output
new file mode 100644
index 0000000..193ef99
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SuperQualifier.output
@@ -0,0 +1,18 @@
+class SuperQualifier {
+ String a() {
+ return this.toStringHelper()
+ .add("foo", foo_________________________________________)
+ .add("bar", bar_________________________________________);
+ }
+
+ String b() {
+ return super.toStringHelper()
+ .add("foo", foo_________________________________________)
+ .add("bar", bar_________________________________________);
+ }
+
+ {
+ this.aaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbb(
+ ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/T.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/T.input
new file mode 100644
index 0000000..c8cd293
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/T.input
@@ -0,0 +1,19 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for ThisExpressions, ThrowStatements, TryStatements, TypeDeclarationStatements,
+ * TypeDeclarations, TypeLiterals, TypeMethodReferences, TypeParameters, and Types.
+ */
+class T<T1, T2, T3> {
+ // TODO(jdd): Add tests for higher language levels.
+
+ T f(int x) throws Exception {
+ class TT {}
+ if (x == 0 || T.class == null) {
+ return this;
+ }
+ try (AutoCloseable y = null) {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/T.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/T.output
new file mode 100644
index 0000000..c8cd293
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/T.output
@@ -0,0 +1,19 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for ThisExpressions, ThrowStatements, TryStatements, TypeDeclarationStatements,
+ * TypeDeclarations, TypeLiterals, TypeMethodReferences, TypeParameters, and Types.
+ */
+class T<T1, T2, T3> {
+ // TODO(jdd): Add tests for higher language levels.
+
+ T f(int x) throws Exception {
+ class TT {}
+ if (x == 0 || T.class == null) {
+ return this;
+ }
+ try (AutoCloseable y = null) {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/TabularMixedSignInitializer.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TabularMixedSignInitializer.input
new file mode 100644
index 0000000..2715158
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TabularMixedSignInitializer.input
@@ -0,0 +1,17 @@
+public class T {
+ private static final double[] f = {
+ 95.0, 75.0, -95.0, 75.0,
+ -95.0, 75.0, +95.0, 75.0
+ };
+
+ private static final int[] g = {
+ x++, y, ++z,
+ x, y, ~z,
+ --x, ++y, z--
+ };
+
+ private static final bool[] h = {
+ a, b, c, d,
+ !e, a, b, c
+ };
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/TabularMixedSignInitializer.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TabularMixedSignInitializer.output
new file mode 100644
index 0000000..2715158
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TabularMixedSignInitializer.output
@@ -0,0 +1,17 @@
+public class T {
+ private static final double[] f = {
+ 95.0, 75.0, -95.0, 75.0,
+ -95.0, 75.0, +95.0, 75.0
+ };
+
+ private static final int[] g = {
+ x++, y, ++z,
+ x, y, ~z,
+ --x, ++y, z--
+ };
+
+ private static final bool[] h = {
+ a, b, c, d,
+ !e, a, b, c
+ };
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/TryWithResources.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TryWithResources.input
new file mode 100644
index 0000000..5d294f9
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TryWithResources.input
@@ -0,0 +1,21 @@
+class TryWtihResources {
+ {
+ try (@A C c = c(); ) {}
+ try (final @A C c = c(); ) {}
+ try (@A final C c = c(); ) {}
+ try (@A final @B C c = c(); ) {}
+
+ try (final BufferedWriter writer =
+ new BufferedWriter(new OutputStreamWriter(testFile, Charset.defaultCharset()))) {
+ writer.append("tom cruise\n").append("avatar\n");
+ writer.flush();
+ }
+
+ try (@SuppressWarnings("resource")
+ Scanner inputScanner = new Scanner(inputStream).useDelimiter("\\s+|,")) {
+ while (inputScanner.hasNextLong()) {
+ placementIds.add(inputScanner.nextLong());
+ }
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/TryWithResources.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TryWithResources.output
new file mode 100644
index 0000000..5d294f9
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TryWithResources.output
@@ -0,0 +1,21 @@
+class TryWtihResources {
+ {
+ try (@A C c = c(); ) {}
+ try (final @A C c = c(); ) {}
+ try (@A final C c = c(); ) {}
+ try (@A final @B C c = c(); ) {}
+
+ try (final BufferedWriter writer =
+ new BufferedWriter(new OutputStreamWriter(testFile, Charset.defaultCharset()))) {
+ writer.append("tom cruise\n").append("avatar\n");
+ writer.flush();
+ }
+
+ try (@SuppressWarnings("resource")
+ Scanner inputScanner = new Scanner(inputStream).useDelimiter("\\s+|,")) {
+ while (inputScanner.hasNextLong()) {
+ placementIds.add(inputScanner.nextLong());
+ }
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/U.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/U.input
new file mode 100644
index 0000000..555cbf8
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/U.input
@@ -0,0 +1,47 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for UnionTypes. */
+class U {
+ void f() {
+ class Exception0 extends Exception {}
+ class Exception1 extends Exception {}
+ class Exception2 extends Exception {}
+ class Exception3 extends Exception {}
+ class Exception4 extends Exception {}
+ class Exception5 extends Exception {}
+ class Exception6 extends Exception {}
+ class Exception7 extends Exception {}
+ class Exception8 extends Exception {}
+ class Exception9 extends Exception {}
+ try {
+ char c = '\123';
+ switch (c) {
+ case '0':
+ throw new Exception0();
+ case '1':
+ throw new Exception1();
+ case '2':
+ throw new Exception2();
+ case '3':
+ throw new Exception3();
+ case '4':
+ throw new Exception4();
+ case '5':
+ throw new Exception5();
+ case '6':
+ throw new Exception6();
+ case '7':
+ throw new Exception7();
+ case '8':
+ throw new Exception8();
+ case '9':
+ default:
+ throw new Exception9();
+ }
+ } catch (
+ Exception0 | Exception1 | Exception2 | Exception3 | Exception4 | Exception5 | Exception6
+ | Exception7 | Exception8 | Exception9
+ e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/U.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/U.output
new file mode 100644
index 0000000..928d0f4
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/U.output
@@ -0,0 +1,53 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for UnionTypes. */
+class U {
+ void f() {
+ class Exception0 extends Exception {}
+ class Exception1 extends Exception {}
+ class Exception2 extends Exception {}
+ class Exception3 extends Exception {}
+ class Exception4 extends Exception {}
+ class Exception5 extends Exception {}
+ class Exception6 extends Exception {}
+ class Exception7 extends Exception {}
+ class Exception8 extends Exception {}
+ class Exception9 extends Exception {}
+ try {
+ char c = '\123';
+ switch (c) {
+ case '0':
+ throw new Exception0();
+ case '1':
+ throw new Exception1();
+ case '2':
+ throw new Exception2();
+ case '3':
+ throw new Exception3();
+ case '4':
+ throw new Exception4();
+ case '5':
+ throw new Exception5();
+ case '6':
+ throw new Exception6();
+ case '7':
+ throw new Exception7();
+ case '8':
+ throw new Exception8();
+ case '9':
+ default:
+ throw new Exception9();
+ }
+ } catch (Exception0
+ | Exception1
+ | Exception2
+ | Exception3
+ | Exception4
+ | Exception5
+ | Exception6
+ | Exception7
+ | Exception8
+ | Exception9 e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/UnaryMinus.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/UnaryMinus.input
new file mode 100644
index 0000000..a512fe9
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/UnaryMinus.input
@@ -0,0 +1,5 @@
+class UnaryMinus {
+ {
+ int b = - 1;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/UnaryMinus.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/UnaryMinus.output
new file mode 100644
index 0000000..f6329a4
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/UnaryMinus.output
@@ -0,0 +1,5 @@
+class UnaryMinus {
+ {
+ int b = -1;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted.input
new file mode 100644
index 0000000..c936d9e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted.input
@@ -0,0 +1,43 @@
+package com . google . googlejavaformat . java . javatests ;
+
+import com . google . common . base . Charsets ; import com . google . common
+. collect . Range ; import com . google . common . collect . RangeSet ; import
+com . google . common . collect . TreeRangeSet ; import com . google .
+googlejavaformat . java . Formatter ; import com . google . googlejavaformat .
+java . JavaCommentsHelper ; import com . google . googlejavaformat . java .
+JavaInput ; import com . google . googlejavaformat . java . JavaOutput ;
+import junit . framework . TestCase ;
+
+import java . io . File ; import java . io . StringWriter ; import java . io .
+Writer ; import java . nio . file . FileSystems ; import java . nio . file .
+Files ; import java . nio . file . Path ; import java . util . ArrayList ;
+import java . util . List ;
+
+/**
+* Integration test for google-java-format. Format each file
+* in the input directory, and confirm that the result is
+* the same as the file in the output directory.
+*/
+public final class Unformatted extends TestCase { private static final int
+MAX_WIDTH = 100 ; public void testFormatter ( ) throws Exception { Path
+inputPath = FileSystems . getDefault ( ) . getPath ( TestPath . getDir ( ) ,
+"input" ) ; Path outputPath = FileSystems . getDefault ( ) . getPath (
+TestPath . getDir ( ) , "output" ) ; File inputDirectory = new File (
+inputPath . toString ( ) ) ; File outputDirectory = new File ( outputPath .
+toString ( ) ) ; for ( File file : inputDirectory . listFiles ( ) ) {
+assertTrue ( file . isFile ( ) ) ; String fileName = file . getName ( ) ;
+assertTrue ( fileName . endsWith ( ".java" ) ) ; byte [ ] inputBytes = Files .
+readAllBytes ( FileSystems . getDefault ( ) . getPath ( inputDirectory .
+toString ( ) , fileName ) ) ; String inputString = new String ( inputBytes ,
+Charsets . UTF_8 ) ; byte [ ] expectedOutputBytes = Files . readAllBytes (
+FileSystems . getDefault ( ) . getPath ( outputDirectory . toString ( ) ,
+fileName ) ) ; String expectedOutputString = new String ( expectedOutputBytes
+, Charsets . UTF_8 ) ; JavaInput javaInput = new JavaInput ( inputString ) ;
+JavaOutput javaOutput = new JavaOutput ( javaInput , new JavaCommentsHelper (
+) , false ) ; List < String > errors = new ArrayList < > ( ) ; Formatter .
+format ( javaInput , javaOutput , MAX_WIDTH , errors , 1 ) ; assertTrue (
+errors . isEmpty ( ) ) ; Writer stringWriter = new StringWriter ( ) ; RangeSet
+< Integer > linesFlag = TreeRangeSet . create ( ) ; linesFlag . add ( Range .<
+Integer > all ( ) ) ; javaOutput . writeMerged ( stringWriter , linesFlag ,
+MAX_WIDTH , errors ) ; String outputString = stringWriter . toString ( ) ;
+assertEquals ( outputString , expectedOutputString ) ; } } }
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted.output
new file mode 100644
index 0000000..29f7963
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted.output
@@ -0,0 +1,58 @@
+package com.google.googlejavaformat.java.javatests;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+import com.google.googlejavaformat.java.Formatter;
+import com.google.googlejavaformat.java.JavaCommentsHelper;
+import com.google.googlejavaformat.java.JavaInput;
+import com.google.googlejavaformat.java.JavaOutput;
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Integration test for google-java-format. Format each file in the input directory, and confirm
+ * that the result is the same as the file in the output directory.
+ */
+public final class Unformatted extends TestCase {
+ private static final int MAX_WIDTH = 100;
+
+ public void testFormatter() throws Exception {
+ Path inputPath = FileSystems.getDefault().getPath(TestPath.getDir(), "input");
+ Path outputPath = FileSystems.getDefault().getPath(TestPath.getDir(), "output");
+ File inputDirectory = new File(inputPath.toString());
+ File outputDirectory = new File(outputPath.toString());
+ for (File file : inputDirectory.listFiles()) {
+ assertTrue(file.isFile());
+ String fileName = file.getName();
+ assertTrue(fileName.endsWith(".java"));
+ byte[] inputBytes =
+ Files.readAllBytes(FileSystems.getDefault().getPath(inputDirectory.toString(), fileName));
+ String inputString = new String(inputBytes, Charsets.UTF_8);
+ byte[] expectedOutputBytes =
+ Files.readAllBytes(
+ FileSystems.getDefault().getPath(outputDirectory.toString(), fileName));
+ String expectedOutputString = new String(expectedOutputBytes, Charsets.UTF_8);
+ JavaInput javaInput = new JavaInput(inputString);
+ JavaOutput javaOutput = new JavaOutput(javaInput, new JavaCommentsHelper(), false);
+ List<String> errors = new ArrayList<>();
+ Formatter.format(javaInput, javaOutput, MAX_WIDTH, errors, 1);
+ assertTrue(errors.isEmpty());
+ Writer stringWriter = new StringWriter();
+ RangeSet<Integer> linesFlag = TreeRangeSet.create();
+ linesFlag.add(Range.<Integer>all());
+ javaOutput.writeMerged(stringWriter, linesFlag, MAX_WIDTH, errors);
+ String outputString = stringWriter.toString();
+ assertEquals(outputString, expectedOutputString);
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted2.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted2.input
new file mode 100644
index 0000000..d968aeb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted2.input
@@ -0,0 +1,16 @@
+package com . google . googlejavaformat ;
+
+/**
+* Rewrite comments. This interface is implemented by
+* {@link com.google.googlejavaformat.java.JavaCommentsHelper JavaCommentsHelper}.
+*/
+public interface CommentsHelper {
+ /**
+ * Try to rewrite comments, returning rewritten text.
+ * @param text0 the literal comment text to rewrite
+ * @param maxWidth the line length for the output
+ * @param column0 the current column
+ * @return the rewritten comment
+ */
+ String rewrite ( String text0 , int maxWidth , int column0 ) ;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted2.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted2.output
new file mode 100644
index 0000000..bc150a5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted2.output
@@ -0,0 +1,17 @@
+package com.google.googlejavaformat;
+
+/**
+ * Rewrite comments. This interface is implemented by {@link
+ * com.google.googlejavaformat.java.JavaCommentsHelper JavaCommentsHelper}.
+ */
+public interface CommentsHelper {
+ /**
+ * Try to rewrite comments, returning rewritten text.
+ *
+ * @param text0 the literal comment text to rewrite
+ * @param maxWidth the line length for the output
+ * @param column0 the current column
+ * @return the rewritten comment
+ */
+ String rewrite(String text0, int maxWidth, int column0);
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted3.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted3.input
new file mode 100644
index 0000000..a2649f0
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted3.input
@@ -0,0 +1,9 @@
+package com . google . googlejavaformat ;
+
+class Unformatted3 {
+ /**
+ * This comment is not reflowed
+ * because it contains <pre>preformatted</pre>
+ * content.
+ */
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted3.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted3.output
new file mode 100644
index 0000000..b99debe
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unformatted3.output
@@ -0,0 +1,11 @@
+package com.google.googlejavaformat;
+
+class Unformatted3 {
+ /**
+ * This comment is not reflowed because it contains
+ *
+ * <pre>preformatted</pre>
+ *
+ * content.
+ */
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/V.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/V.input
new file mode 100644
index 0000000..b3e9d1b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/V.input
@@ -0,0 +1,13 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for VariableDeclarationExpressions, VariableDeclarationFragments, and
+ * VariableDeclarationStatements.
+ */
+class V {
+ int x = 0, y = 1;
+
+ void f() {
+ for (int a = 0, b = 1; a < b;) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/V.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/V.output
new file mode 100644
index 0000000..f61e97b
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/V.output
@@ -0,0 +1,13 @@
+package com.google.googlejavaformat.java.test;
+
+/**
+ * Tests for VariableDeclarationExpressions, VariableDeclarationFragments, and
+ * VariableDeclarationStatements.
+ */
+class V {
+ int x = 0, y = 1;
+
+ void f() {
+ for (int a = 0, b = 1; a < b; ) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/W.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/W.input
new file mode 100644
index 0000000..53f50d4
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/W.input
@@ -0,0 +1,9 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for WhileStatements and WildcardTypes. */
+class W {
+ <T extends Object> T f() {
+ while (true == true == true == true == true == true == true == true == true == true == true
+ == true == true) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/W.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/W.output
new file mode 100644
index 0000000..53f50d4
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/W.output
@@ -0,0 +1,9 @@
+package com.google.googlejavaformat.java.test;
+
+/** Tests for WhileStatements and WildcardTypes. */
+class W {
+ <T extends Object> T f() {
+ while (true == true == true == true == true == true == true == true == true == true == true
+ == true == true) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/WildBound.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/WildBound.input
new file mode 100644
index 0000000..fa61386
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/WildBound.input
@@ -0,0 +1,5 @@
+class WildBound {
+ <T> T f() {
+ return (T) X.class.newInstance((F<?>) f, t);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/WildBound.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/WildBound.output
new file mode 100644
index 0000000..fa61386
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/WildBound.output
@@ -0,0 +1,5 @@
+class WildBound {
+ <T> T f() {
+ return (T) X.class.newInstance((F<?>) f, t);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i100.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i100.input
new file mode 100644
index 0000000..b7c88f8
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i100.input
@@ -0,0 +1,7 @@
+public class I100 {
+ void f() {
+ Object o;
+ o = new String @A [] {"foo", "bar"};
+ o = new String @A @B [] @C [] {"foo", "bar"};
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i100.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i100.output
new file mode 100644
index 0000000..b7c88f8
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i100.output
@@ -0,0 +1,7 @@
+public class I100 {
+ void f() {
+ Object o;
+ o = new String @A [] {"foo", "bar"};
+ o = new String @A @B [] @C [] {"foo", "bar"};
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i101.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i101.input
new file mode 100644
index 0000000..97c321e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i101.input
@@ -0,0 +1 @@
+public @interface kHz {} // No prefix defined in the annotation itself
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i101.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i101.output
new file mode 100644
index 0000000..97c321e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i101.output
@@ -0,0 +1 @@
+public @interface kHz {} // No prefix defined in the annotation itself
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i155.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i155.input
new file mode 100644
index 0000000..1a525de
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i155.input
@@ -0,0 +1,5 @@
+class I155 {
+ void walkAndClose(Stream<?> stream) {
+ try (stream) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i155.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i155.output
new file mode 100644
index 0000000..1a525de
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i155.output
@@ -0,0 +1,5 @@
+class I155 {
+ void walkAndClose(Stream<?> stream) {
+ try (stream) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i176.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i176.input
new file mode 100644
index 0000000..78aabff
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i176.input
@@ -0,0 +1,6 @@
+interface
+InterfaceWithPrivateStaticMethod
+{
+ private static void bar() { }
+ default void foo() { bar(); }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i176.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i176.output
new file mode 100644
index 0000000..0e33b90
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i176.output
@@ -0,0 +1,7 @@
+interface InterfaceWithPrivateStaticMethod {
+ private static void bar() {}
+
+ default void foo() {
+ bar();
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i221.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i221.input
new file mode 100644
index 0000000..6320a38
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i221.input
@@ -0,0 +1,8 @@
+class I221 {
+ {
+ logger.log(
+ Level.WARNING,
+ "Unable to obtain jst.web facet version from selected project", //$NON-NLS-1$
+ ex);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i221.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i221.output
new file mode 100644
index 0000000..6320a38
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i221.output
@@ -0,0 +1,8 @@
+class I221 {
+ {
+ logger.log(
+ Level.WARNING,
+ "Unable to obtain jst.web facet version from selected project", //$NON-NLS-1$
+ ex);
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i281.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i281.input
new file mode 100644
index 0000000..0b42d08
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i281.input
@@ -0,0 +1,8 @@
+public enum Empty {}
+public enum Empty {;}
+public enum Empty {;;;}
+public enum Empty {; // comment
+;;}
+public enum Empty {;
+;; // comment
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i281.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i281.output
new file mode 100644
index 0000000..ca3861a
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i281.output
@@ -0,0 +1,23 @@
+public enum Empty {}
+
+public enum Empty {
+ ;
+}
+
+public enum Empty {
+ ;
+ ;
+ ;
+}
+
+public enum Empty {
+ ; // comment
+ ;
+ ;
+}
+
+public enum Empty {
+ ;
+ ;
+ ; // comment
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i282.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i282.input
new file mode 100644
index 0000000..a8bf796
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i282.input
@@ -0,0 +1,11 @@
+public class ArrayVsVarargs {
+
+ void f(String[] arg) {}
+ void g(String... arg) {}
+ void h(String @A [] arg) {}
+ void i(String @B... arg) {}
+ void j(String @C [] @D [] arg) {}
+ void k(String @E [] @F... arg) {}
+
+ Class<?> c = byte[].class;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i282.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i282.output
new file mode 100644
index 0000000..6c92e51
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i282.output
@@ -0,0 +1,16 @@
+public class ArrayVsVarargs {
+
+ void f(String[] arg) {}
+
+ void g(String... arg) {}
+
+ void h(String @A [] arg) {}
+
+ void i(String @B ... arg) {}
+
+ void j(String @C [] @D [] arg) {}
+
+ void k(String @E [] @F ... arg) {}
+
+ Class<?> c = byte[].class;
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i55.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i55.input
new file mode 100644
index 0000000..425f8eb
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i55.input
@@ -0,0 +1,13 @@
+import java.lang.annotation.*;
+
+@Target(ElementType.TYPE_USE)
+@interface Interned {}
+
+public class GjfTypeAnnotationOnArrayTest {
+
+ public static void test() {
+ String @Interned [] iarray1 = new String @Interned [2];
+ }
+
+}
+
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i55.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i55.output
new file mode 100644
index 0000000..f7710c9
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i55.output
@@ -0,0 +1,11 @@
+import java.lang.annotation.*;
+
+@Target(ElementType.TYPE_USE)
+@interface Interned {}
+
+public class GjfTypeAnnotationOnArrayTest {
+
+ public static void test() {
+ String @Interned [] iarray1 = new String @Interned [2];
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i59.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i59.input
new file mode 100644
index 0000000..937f40f
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i59.input
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.tainting.qual.*;
+class Outer {
+ class Nested {
+ class InnerMost {
+ @A Outer context(@B Outer.@C Nested.@D InnerMost this) {
+ return Outer.this;
+ }
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i59.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i59.output
new file mode 100644
index 0000000..600b300
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i59.output
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.tainting.qual.*;
+
+class Outer {
+ class Nested {
+ class InnerMost {
+ @A
+ Outer context(@B Outer.@C Nested.@D InnerMost this) {
+ return Outer.this;
+ }
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i60.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i60.input
new file mode 100644
index 0000000..8b0a378
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i60.input
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FullyQualified {
+
+ public void format1(java.lang.String a1, java.lang. @Nullable Object... a2) {
+ // body
+ }
+
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i60.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i60.output
new file mode 100644
index 0000000..85cdb40
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i60.output
@@ -0,0 +1,8 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FullyQualified {
+
+ public void format1(java.lang.String a1, java.lang.@Nullable Object... a2) {
+ // body
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i65.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i65.input
new file mode 100644
index 0000000..d4a4593
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i65.input
@@ -0,0 +1,5 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+class UnannoPrimitives {
+ Object ar = new @Nullable byte[] {4};
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i65.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i65.output
new file mode 100644
index 0000000..d4a4593
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i65.output
@@ -0,0 +1,5 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+class UnannoPrimitives {
+ Object ar = new @Nullable byte[] {4};
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i66.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i66.input
new file mode 100644
index 0000000..54e3cce
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i66.input
@@ -0,0 +1,7 @@
+public class AnnotatedCast {
+
+ public static @Sibling1 int convertSibling2ToSibling1(@Sibling2 int a) {
+ return (@Sibling1 int) 1;
+ }
+
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i66.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i66.output
new file mode 100644
index 0000000..00cbea7
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i66.output
@@ -0,0 +1,6 @@
+public class AnnotatedCast {
+
+ public static @Sibling1 int convertSibling2ToSibling1(@Sibling2 int a) {
+ return (@Sibling1 int) 1;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i71.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i71.input
new file mode 100644
index 0000000..324c3d5
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i71.input
@@ -0,0 +1,16 @@
+import java.util.Arrays;
+
+class Test {
+ Wrapper w = new Wrapper(
+ Arrays.asList(
+ "a", "b",
+ "c", "d",
+ "e"
+ ), 2);
+ Wrapper w = new Wrapper(
+ Arrays.asList(
+ "a", "b",
+ "c", "d",
+ "e"
+ ));
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i71.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i71.output
new file mode 100644
index 0000000..ad0bece
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i71.output
@@ -0,0 +1,6 @@
+import java.util.Arrays;
+
+class Test {
+ Wrapper w = new Wrapper(Arrays.asList("a", "b", "c", "d", "e"), 2);
+ Wrapper w = new Wrapper(Arrays.asList("a", "b", "c", "d", "e"));
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i95.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i95.input
new file mode 100644
index 0000000..624664e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i95.input
@@ -0,0 +1,3 @@
+class I95 {
+ public void format2(Object @Nullable ... a2) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i95.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i95.output
new file mode 100644
index 0000000..624664e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i95.output
@@ -0,0 +1,3 @@
+class I95 {
+ public void format2(Object @Nullable ... a2) {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i98.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i98.input
new file mode 100644
index 0000000..abc1698
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i98.input
@@ -0,0 +1,8 @@
+public class AnonInnerDefaults {
+
+ public void tryStuff(final UIElement e) {
+ PolyIface p =
+ new @UI PolyIface() {
+ };
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/i98.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i98.output
new file mode 100644
index 0000000..c96813e
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/i98.output
@@ -0,0 +1,6 @@
+public class AnonInnerDefaults {
+
+ public void tryStuff(final UIElement e) {
+ PolyIface p = new @UI PolyIface() {};
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/java11.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java11.input
new file mode 100644
index 0000000..97bd4ac
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java11.input
@@ -0,0 +1,9 @@
+class Java11 {
+ interface I {
+ private default void f() {}
+ }
+
+ public static void main(String[] args) {
+ var x = 42;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/java11.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java11.output
new file mode 100644
index 0000000..97bd4ac
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java11.output
@@ -0,0 +1,9 @@
+class Java11 {
+ interface I {
+ private default void f() {}
+ }
+
+ public static void main(String[] args) {
+ var x = 42;
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.input
new file mode 100644
index 0000000..10d4522
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.input
@@ -0,0 +1,50 @@
+class Java14 {
+ void f() {
+ if (obj instanceof String s) {
+ } else {
+ }
+ }
+
+ record Range<T extends Number>(T lo, T hi) implements Comparable<Range<T>> {
+
+ public Range {}
+
+ Range(T lo) {
+ this(lo, lo);
+ }
+
+ @Override
+ public int compareTo(Range<T> other) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ record Foo(int id) {}
+
+ record Rcv(int id) {
+ public Rcv(Rcv this, int id) {
+ this.id = id;
+ }
+ }
+
+ void g() {
+ var block = """
+ hello
+ text
+ blocks
+ """.indent(6);
+ }
+
+ void h() {
+ int numLetters = switch (day) {
+ case MONDAY, FRIDAY, SUNDAY -> 6;
+ case TUESDAY -> 7;
+ case THURSDAY, SATURDAY -> 8;
+ case WEDNESDAY -> 9;
+ };
+ }
+
+ {
+ for (var arg : List.of()) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.output
new file mode 100644
index 0000000..9779198
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.output
@@ -0,0 +1,51 @@
+class Java14 {
+ void f() {
+ if (obj instanceof String s) {
+ } else {
+ }
+ }
+
+ record Range<T extends Number>(T lo, T hi) implements Comparable<Range<T>> {
+
+ public Range {}
+
+ Range(T lo) {
+ this(lo, lo);
+ }
+
+ @Override
+ public int compareTo(Range<T> other) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ record Foo(int id) {}
+
+ record Rcv(int id) {
+ public Rcv(Rcv this, int id) {
+ this.id = id;
+ }
+ }
+
+ void g() {
+ var block = """
+ hello
+ text
+ blocks
+ """.indent(6);
+ }
+
+ void h() {
+ int numLetters =
+ switch (day) {
+ case MONDAY, FRIDAY, SUNDAY -> 6;
+ case TUESDAY -> 7;
+ case THURSDAY, SATURDAY -> 8;
+ case WEDNESDAY -> 9;
+ };
+ }
+
+ {
+ for (var arg : List.of()) {}
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.formatting-and-import-sorting b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.formatting-and-import-sorting
new file mode 100644
index 0000000..e6994f7
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.formatting-and-import-sorting
@@ -0,0 +1,29 @@
+package com.google.example;
+
+import static com.google.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Preconditions;
+import java.util.List;
+import java.util.Set;
+import javax.annotations.Nullable;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SomeTest {
+
+ <T> void check(@Nullable List<T> x) {
+ Preconditions.checkNodeNull(x);
+ }
+
+ void f() {
+ List<String> xs = null;
+ assertThat(xs).isNull();
+ try {
+ check(xs);
+ fail();
+ } catch (NullPointerException e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.formatting-and-unused-import-removal b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.formatting-and-unused-import-removal
new file mode 100644
index 0000000..7d5df53
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.formatting-and-unused-import-removal
@@ -0,0 +1,31 @@
+package com.google.example;
+
+import com.google.common.base.Preconditions;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+import javax.annotations.Nullable;
+
+import static org.junit.Assert.fail;
+import static com.google.truth.Truth.assertThat;
+
+@RunWith(JUnit4.class)
+public class SomeTest {
+
+ <T> void check(@Nullable List<T> x) {
+ Preconditions.checkNodeNull(x);
+ }
+
+ void f() {
+ List<String> xs = null;
+ assertThat(xs).isNull();
+ try {
+ check(xs);
+ fail();
+ } catch (NullPointerException e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.formatting-only b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.formatting-only
new file mode 100644
index 0000000..7d5df53
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.formatting-only
@@ -0,0 +1,31 @@
+package com.google.example;
+
+import com.google.common.base.Preconditions;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+import javax.annotations.Nullable;
+
+import static org.junit.Assert.fail;
+import static com.google.truth.Truth.assertThat;
+
+@RunWith(JUnit4.class)
+public class SomeTest {
+
+ <T> void check(@Nullable List<T> x) {
+ Preconditions.checkNodeNull(x);
+ }
+
+ void f() {
+ List<String> xs = null;
+ assertThat(xs).isNull();
+ try {
+ check(xs);
+ fail();
+ } catch (NullPointerException e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.imports-and-formatting b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.imports-and-formatting
new file mode 100644
index 0000000..887d43c
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.imports-and-formatting
@@ -0,0 +1,28 @@
+package com.google.example;
+
+import static com.google.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Preconditions;
+import java.util.List;
+import javax.annotations.Nullable;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SomeTest {
+
+ <T> void check(@Nullable List<T> x) {
+ Preconditions.checkNodeNull(x);
+ }
+
+ void f() {
+ List<String> xs = null;
+ assertThat(xs).isNull();
+ try {
+ check(xs);
+ fail();
+ } catch (NullPointerException e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.imports-only b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.imports-only
new file mode 100644
index 0000000..88f83f1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.imports-only
@@ -0,0 +1,27 @@
+package com.google.example;
+
+import static com.google.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Preconditions;
+import java.util.List;
+import javax.annotations.Nullable;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith( JUnit4.class ) public class SomeTest {
+
+ <T> void check(@Nullable List<T> x) {
+ Preconditions.checkNodeNull(x);
+ }
+
+ void f() {
+ List<String> xs = null;
+ assertThat(xs).isNull();
+ try {
+ check(xs);
+ fail();
+ } catch (NullPointerException e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.input b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.input
new file mode 100644
index 0000000..be1eacd
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testimports/A.input
@@ -0,0 +1,31 @@
+package com.google.example;
+
+import com.google.common.base.Preconditions;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.annotations.Nullable;
+
+import static org.junit.Assert.fail;
+import static com.google.truth.Truth.assertThat;
+
+@RunWith( JUnit4.class ) public class SomeTest {
+
+ <T> void check(@Nullable List<T> x) {
+ Preconditions.checkNodeNull(x);
+ }
+
+ void f() {
+ List<String> xs = null;
+ assertThat(xs).isNull();
+ try {
+ check(xs);
+ fail();
+ } catch (NullPointerException e) {
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B28750242.input b/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B28750242.input
new file mode 100644
index 0000000..022e0ea
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B28750242.input
@@ -0,0 +1,70 @@
+class Test {
+ /**
+ * Lorum Ipsum.
+ *
+ * <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ * ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ * laboris nisi ut aliquip ex ea commodo consequat.
+ *
+ * <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
+ * nulla pariatur.
+ *
+ * <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ * anim id est laborum."
+ */
+ void f() {}
+
+ /**
+ * Lorum Ipsum.
+ * <p>
+ * Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ * ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ * laboris nisi ut aliquip ex ea commodo consequat.
+ * <p>
+ * Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
+ * nulla pariatur.
+ * <p>
+ * Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ * anim id est laborum."
+ */
+ void f() {}
+
+ /**
+ * Lorum Ipsum.
+ *
+ *
+ * <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ * ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ * laboris nisi ut aliquip ex ea commodo consequat.
+ *
+ *
+ * <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
+ * nulla pariatur.
+ *
+ *
+ * <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ * anim id est laborum."
+ */
+ void f() {}
+
+ /**
+ * Lorum Ipsum.
+ *
+ * <p>
+ *
+ * Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ * ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ * laboris nisi ut aliquip ex ea commodo consequat.
+ *
+ * <p>
+ *
+ * Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
+ * nulla pariatur.
+ *
+ * <p>
+ *
+ * Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ * anim id est laborum."
+ */
+ void f() {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B28750242.output b/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B28750242.output
new file mode 100644
index 0000000..23901a9
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B28750242.output
@@ -0,0 +1,61 @@
+class Test {
+ /**
+ * Lorum Ipsum.
+ *
+ * <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
+ * labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ * laboris nisi ut aliquip ex ea commodo consequat.
+ *
+ * <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
+ * pariatur.
+ *
+ * <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ * anim id est laborum."
+ */
+ void f() {}
+
+ /**
+ * Lorum Ipsum.
+ *
+ * <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
+ * labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ * laboris nisi ut aliquip ex ea commodo consequat.
+ *
+ * <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
+ * pariatur.
+ *
+ * <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ * anim id est laborum."
+ */
+ void f() {}
+
+ /**
+ * Lorum Ipsum.
+ *
+ * <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
+ * labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ * laboris nisi ut aliquip ex ea commodo consequat.
+ *
+ * <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
+ * pariatur.
+ *
+ * <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ * anim id est laborum."
+ */
+ void f() {}
+
+ /**
+ * Lorum Ipsum.
+ *
+ * <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
+ * labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ * laboris nisi ut aliquip ex ea commodo consequat.
+ *
+ * <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
+ * pariatur.
+ *
+ * <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ * anim id est laborum."
+ */
+ void f() {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B31404367.input b/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B31404367.input
new file mode 100644
index 0000000..5bf96da
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B31404367.input
@@ -0,0 +1,8 @@
+/**
+ * Foo.
+ *
+ * <ul>
+ * <li> a
+ * </ul>
+ */
+class Foo {}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B31404367.output b/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B31404367.output
new file mode 100644
index 0000000..39d08bc
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testjavadoc/B31404367.output
@@ -0,0 +1,8 @@
+/**
+ * Foo.
+ *
+ * <ul>
+ * <li>a
+ * </ul>
+ */
+class Foo {}
diff --git a/eclipse_plugin/META-INF/MANIFEST.MF b/eclipse_plugin/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..27613e9
--- /dev/null
+++ b/eclipse_plugin/META-INF/MANIFEST.MF
@@ -0,0 +1,15 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: google-java-format
+Bundle-SymbolicName: google-java-format-eclipse-plugin;singleton:=true
+Bundle-Version: 1.6.0
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Require-Bundle: org.eclipse.jdt.core;bundle-version="3.10.0",
+ org.eclipse.jface,
+ org.eclipse.text,
+ org.eclipse.ui,
+ org.eclipse.equinox.common
+Bundle-ClassPath: .,
+ lib/guava-22.0.jar,
+ lib/javac-shaded-9+181-r4173-1.jar,
+ lib/google-java-format-1.6.jar
diff --git a/eclipse_plugin/README.md b/eclipse_plugin/README.md
new file mode 100644
index 0000000..332468d
--- /dev/null
+++ b/eclipse_plugin/README.md
@@ -0,0 +1,26 @@
+# google-java-format Eclipse Plugin
+
+## Enabling
+
+See https://github.com/google/google-java-format#eclipse
+
+## Development
+
+1) Uncomment `<module>eclipse_plugin</module>` in the parent `pom.xml`
+
+2) Run `mvn install`, which will copy the dependences of the plugin to
+`eclipse_plugin/lib`.
+
+2) If you are using Java 9, add
+
+ ```
+ -vm
+ /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/bin/java
+ ```
+
+ to `/Applications/Eclipse.app/Contents/Eclipse/eclipse.ini`.
+
+3) Open the `eclipse_plugin` project in a recent Eclipse SDK build.
+
+4) From `File > Export` select `Plugin-in Development > Deployable plugin-ins
+and fragments` and follow the wizard to export a plugin jar.
diff --git a/eclipse_plugin/build.properties b/eclipse_plugin/build.properties
new file mode 100644
index 0000000..dc5ae7c
--- /dev/null
+++ b/eclipse_plugin/build.properties
@@ -0,0 +1,8 @@
+source.. = src/
+output.. = target/classes
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml,\
+ lib/javac-shaded-9+181-r4173-1.jar,\
+ lib/guava-22.0.jar,\
+ lib/google-java-format-1.6.jar
diff --git a/eclipse_plugin/plugin.xml b/eclipse_plugin/plugin.xml
new file mode 100644
index 0000000..5d0bbf9
--- /dev/null
+++ b/eclipse_plugin/plugin.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+ <extension point="org.eclipse.jdt.core.javaFormatter">
+ <javaFormatter
+ class="com.google.googlejavaformat.java.GoogleJavaFormatter"
+ id="com.google.googlejavaformat.java.GoogleJavaFormatter"
+ name="google-java-format">
+ </javaFormatter>
+ </extension>
+</plugin>
diff --git a/eclipse_plugin/pom.xml b/eclipse_plugin/pom.xml
new file mode 100644
index 0000000..bb581cd
--- /dev/null
+++ b/eclipse_plugin/pom.xml
@@ -0,0 +1,103 @@
+<!--
+ ~ Copyright 2015 Google Inc.
+ ~
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.google.googlejavaformat</groupId>
+ <artifactId>google-java-format-parent</artifactId>
+ <version>1.6</version>
+ </parent>
+
+ <artifactId>google-java-format-eclipse-plugin</artifactId>
+ <version>1.6.0</version>
+ <packaging>eclipse-plugin</packaging>
+ <name>google-java-format Plugin for Eclipse 4.5+</name>
+
+ <description>
+ A Java source code formatter that follows Google Java Style.
+ </description>
+
+ <properties>
+ <tycho-version>0.26.0</tycho-version>
+ </properties>
+
+ <repositories>
+ <repository>
+ <id>mars</id>
+ <layout>p2</layout>
+ <url>https://download.eclipse.org/releases/mars</url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.googlejavaformat</groupId>
+ <artifactId>google-java-format</artifactId>
+ <version>1.6</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+
+ <plugin>
+ <groupId>org.eclipse.tycho</groupId>
+ <artifactId>tycho-maven-plugin</artifactId>
+ <version>${tycho-version}</version>
+ <extensions>true</extensions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.eclipse.tycho</groupId>
+ <artifactId>target-platform-configuration</artifactId>
+ <version>${tycho-version}</version>
+ <configuration>
+ <environments>
+ <environment>
+ <os>linux</os>
+ <ws>gtk</ws>
+ <arch>x86</arch>
+ </environment>
+ <environment>
+ <os>linux</os>
+ <ws>gtk</ws>
+ <arch>x86_64</arch>
+ </environment>
+ <environment>
+ <os>win32</os>
+ <ws>win32</ws>
+ <arch>x86</arch>
+ </environment>
+ <environment>
+ <os>win32</os>
+ <ws>win32</ws>
+ <arch>x86_64</arch>
+ </environment>
+ <environment>
+ <os>macosx</os>
+ <ws>cocoa</ws>
+ <arch>x86_64</arch>
+ </environment>
+ </environments>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </build>
+
+</project>
diff --git a/eclipse_plugin/src/com/google/googlejavaformat/java/GoogleJavaFormatter.java b/eclipse_plugin/src/com/google/googlejavaformat/java/GoogleJavaFormatter.java
new file mode 100644
index 0000000..c300514
--- /dev/null
+++ b/eclipse_plugin/src/com/google/googlejavaformat/java/GoogleJavaFormatter.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * 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.java;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Range;
+import com.google.googlejavaformat.java.SnippetFormatter.SnippetKind;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.formatter.CodeFormatter;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+
+/** Runs the Google Java formatter on the given code. */
+public class GoogleJavaFormatter extends CodeFormatter {
+
+ private static final int INDENTATION_SIZE = 2;
+
+ @Override
+ public TextEdit format(
+ int kind, String source, int offset, int length, int indentationLevel, String lineSeparator) {
+ IRegion[] regions = new IRegion[] {new Region(offset, length)};
+ return formatInternal(kind, source, regions, indentationLevel);
+ }
+
+ @Override
+ public TextEdit format(
+ int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator) {
+ return formatInternal(kind, source, regions, indentationLevel);
+ }
+
+ @Override
+ public String createIndentationString(int indentationLevel) {
+ Preconditions.checkArgument(
+ indentationLevel >= 0,
+ "Indentation level cannot be less than zero. Given: %s",
+ indentationLevel);
+ int spaces = indentationLevel * INDENTATION_SIZE;
+ StringBuilder buf = new StringBuilder(spaces);
+ for (int i = 0; i < spaces; i++) {
+ buf.append(' ');
+ }
+ return buf.toString();
+ }
+
+ /** Runs the Google Java formatter on the given source, with only the given ranges specified. */
+ private TextEdit formatInternal(int kind, String source, IRegion[] regions, int initialIndent) {
+ try {
+ boolean includeComments =
+ (kind & CodeFormatter.F_INCLUDE_COMMENTS) == CodeFormatter.F_INCLUDE_COMMENTS;
+ kind &= ~CodeFormatter.F_INCLUDE_COMMENTS;
+ SnippetKind snippetKind;
+ switch (kind) {
+ case ASTParser.K_EXPRESSION:
+ snippetKind = SnippetKind.EXPRESSION;
+ break;
+ case ASTParser.K_STATEMENTS:
+ snippetKind = SnippetKind.STATEMENTS;
+ break;
+ case ASTParser.K_CLASS_BODY_DECLARATIONS:
+ snippetKind = SnippetKind.CLASS_BODY_DECLARATIONS;
+ break;
+ case ASTParser.K_COMPILATION_UNIT:
+ snippetKind = SnippetKind.COMPILATION_UNIT;
+ break;
+ default:
+ throw new IllegalArgumentException(String.format("Unknown snippet kind: %d", kind));
+ }
+ List<Replacement> replacements =
+ new SnippetFormatter()
+ .format(
+ snippetKind, source, rangesFromRegions(regions), initialIndent, includeComments);
+ if (idempotent(source, regions, replacements)) {
+ // Do not create edits if there's no diff.
+ return null;
+ }
+ // Convert replacements to text edits.
+ return editFromReplacements(replacements);
+ } catch (IllegalArgumentException | FormatterException exception) {
+ // Do not format on errors.
+ return null;
+ }
+ }
+
+ private List<Range<Integer>> rangesFromRegions(IRegion[] regions) {
+ List<Range<Integer>> ranges = new ArrayList<>();
+ for (IRegion region : regions) {
+ ranges.add(Range.closedOpen(region.getOffset(), region.getOffset() + region.getLength()));
+ }
+ return ranges;
+ }
+
+ /** @return {@code true} if input and output texts are equal, else {@code false}. */
+ private boolean idempotent(String source, IRegion[] regions, List<Replacement> replacements) {
+ // This implementation only checks for single replacement.
+ if (replacements.size() == 1) {
+ Replacement replacement = replacements.get(0);
+ String output = replacement.getReplacementString();
+ // Entire source case: input = output, nothing changed.
+ if (output.equals(source)) {
+ return true;
+ }
+ // Single region and single replacement case: if they are equal, nothing changed.
+ if (regions.length == 1) {
+ Range<Integer> range = replacement.getReplaceRange();
+ String snippet = source.substring(range.lowerEndpoint(), range.upperEndpoint());
+ if (output.equals(snippet)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private TextEdit editFromReplacements(List<Replacement> replacements) {
+ // Split the replacements that cross line boundaries.
+ TextEdit edit = new MultiTextEdit();
+ for (Replacement replacement : replacements) {
+ Range<Integer> replaceRange = replacement.getReplaceRange();
+ edit.addChild(
+ new ReplaceEdit(
+ replaceRange.lowerEndpoint(),
+ replaceRange.upperEndpoint() - replaceRange.lowerEndpoint(),
+ replacement.getReplacementString()));
+ }
+ return edit;
+ }
+}
diff --git a/idea_plugin/build.gradle b/idea_plugin/build.gradle
new file mode 100644
index 0000000..7b2f389
--- /dev/null
+++ b/idea_plugin/build.gradle
@@ -0,0 +1,58 @@
+/*
+ * 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 "0.4.18"
+}
+
+repositories {
+ mavenCentral()
+}
+
+ext {
+ googleJavaFormatVersion = '1.7'
+}
+
+apply plugin: 'org.jetbrains.intellij'
+apply plugin: 'java'
+
+intellij {
+ pluginName = "google-java-format"
+ version = "2020.1"
+}
+
+patchPluginXml {
+ pluginDescription = "Formats source code using the google-java-format tool. This version of " +
+ "the plugin uses version ${googleJavaFormatVersion} of the tool."
+ version = "${googleJavaFormatVersion}.0.5"
+ sinceBuild = '201'
+ untilBuild = ''
+}
+
+publishPlugin {
+ token = project.ext.properties.jetbrainsPluginRepoToken
+}
+
+sourceSets {
+ main {
+ java.srcDir 'src'
+ resources.srcDir 'resources'
+ }
+}
+
+dependencies {
+ compile "com.google.googlejavaformat:google-java-format:${googleJavaFormatVersion}"
+}
diff --git a/idea_plugin/google-java-format.iml b/idea_plugin/google-java-format.iml
new file mode 100644
index 0000000..568c04f
--- /dev/null
+++ b/idea_plugin/google-java-format.iml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PLUGIN_MODULE" version="4">
+ <component name="DevKit.ModuleBuildProperties" url="file://$MODULE_DIR$/resources/META-INF/plugin.xml" />
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
+ <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/com/google/googlejavaformat/intellij/v2017_2" isTestSource="false" packagePrefix="com.google.googlejavaformat.intellij" />
+ <excludeFolder url="file://$MODULE_DIR$/src/com/google/googlejavaformat/intellij/v2016_2" />
+ <excludeFolder url="file://$MODULE_DIR$/src/com/google/googlejavaformat/intellij/v2017_1" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="google-java-format-1.4-all-deps" level="project" />
+ </component>
+</module>
diff --git a/idea_plugin/resources/META-INF/plugin.xml b/idea_plugin/resources/META-INF/plugin.xml
new file mode 100644
index 0000000..d633b40
--- /dev/null
+++ b/idea_plugin/resources/META-INF/plugin.xml
@@ -0,0 +1,44 @@
+<idea-plugin url="https://github.com/google/google-java-format/tree/master/idea_plugin">
+ <id>google-java-format</id>
+ <name>google-java-format</name>
+ <vendor url="https://github.com/google/google-java-format">
+ Google
+ </vendor>
+
+ <!-- Mark it as available on all JetBrains IDEs. It's really only useful in
+ IDEA and Android Studio, but there's no way to specify that for some
+ reason. It won't crash PyCharm or anything, so whatever. -->
+ <depends>com.intellij.modules.lang</depends>
+
+ <change-notes><![CDATA[
+ <dl>
+ <dt>1.7.0.5</dt>
+ <dd>Added a version for 2020.1+ IDEs.</dd>
+ <dt>1.7.0.4</dt>
+ <dd>Marked the plugin as being incompatible with 2020.1+ IDEs.</dd>
+ <dt>1.7.0.3</dt>
+ <dd>Fixed the plugin on 2019.3 IDEs.</dd>
+ <dt>1.7.0.2</dt>
+ <dd>Added support for all IDEs after 2017.3.</dd>
+ <dt>1.7.0.1</dt>
+ <dd>Added support for 2019.1 IDEs.</dd>
+ <dt>1.7.0.0</dt>
+ <dd>Upgraded to google-java-format 1.7.</dd>
+ </dl>
+ ]]></change-notes>
+
+ <applicationListeners>
+ <listener class="com.google.googlejavaformat.intellij.InitialConfigurationProjectManagerListener"
+ topic="com.intellij.openapi.project.ProjectManagerListener"/>
+ <listener class="com.google.googlejavaformat.intellij.GoogleJavaFormatInstaller"
+ topic="com.intellij.openapi.project.ProjectManagerListener"/>
+ </applicationListeners>
+
+ <extensions defaultExtensionNs="com.intellij">
+ <projectConfigurable instance="com.google.googlejavaformat.intellij.GoogleJavaFormatConfigurable"
+ id="google-java-format.settings"
+ displayName="google-java-format Settings"/>
+ <projectService serviceImplementation="com.google.googlejavaformat.intellij.GoogleJavaFormatSettings"/>
+ </extensions>
+
+</idea-plugin>
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/CodeStyleManagerDecorator.java b/idea_plugin/src/com/google/googlejavaformat/intellij/CodeStyleManagerDecorator.java
new file mode 100644
index 0000000..ee28e1b
--- /dev/null
+++ b/idea_plugin/src/com/google/googlejavaformat/intellij/CodeStyleManagerDecorator.java
@@ -0,0 +1,234 @@
+/*
+ * 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;
+
+/**
+ * Decorates the {@link CodeStyleManager} abstract class by delegating to a concrete implementation
+ * instance (likely IJ'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 Project getProject() {
+ return delegate.getProject();
+ }
+
+ @Override
+ public PsiElement reformat(PsiElement element) throws IncorrectOperationException {
+ return delegate.reformat(element);
+ }
+
+ @Override
+ public PsiElement reformat(PsiElement element, boolean canChangeWhiteSpacesOnly)
+ throws IncorrectOperationException {
+ return delegate.reformat(element, canChangeWhiteSpacesOnly);
+ }
+
+ @Override
+ public PsiElement reformatRange(PsiElement element, int startOffset, int endOffset)
+ throws IncorrectOperationException {
+ return delegate.reformatRange(element, startOffset, endOffset);
+ }
+
+ @Override
+ public PsiElement reformatRange(
+ PsiElement element, int startOffset, int endOffset, boolean canChangeWhiteSpacesOnly)
+ throws IncorrectOperationException {
+ return delegate.reformatRange(element, startOffset, endOffset, canChangeWhiteSpacesOnly);
+ }
+
+ @Override
+ public void reformatText(PsiFile file, int startOffset, int endOffset)
+ throws IncorrectOperationException {
+ delegate.reformatText(file, startOffset, endOffset);
+ }
+
+ @Override
+ public void reformatText(PsiFile file, Collection<TextRange> ranges)
+ throws IncorrectOperationException {
+ delegate.reformatText(file, ranges);
+ }
+
+ @Override
+ public void reformatTextWithContext(PsiFile psiFile, ChangedRangesInfo changedRangesInfo)
+ throws IncorrectOperationException {
+ delegate.reformatTextWithContext(psiFile, changedRangesInfo);
+ }
+
+ @Override
+ public void reformatTextWithContext(PsiFile file, Collection<TextRange> ranges)
+ throws IncorrectOperationException {
+ delegate.reformatTextWithContext(file, ranges);
+ }
+
+ @Override
+ public void adjustLineIndent(PsiFile file, TextRange rangeToAdjust)
+ throws IncorrectOperationException {
+ delegate.adjustLineIndent(file, rangeToAdjust);
+ }
+
+ @Override
+ public int adjustLineIndent(PsiFile file, int offset) throws IncorrectOperationException {
+ return delegate.adjustLineIndent(file, offset);
+ }
+
+ @Override
+ public int adjustLineIndent(Document document, int offset) {
+ return delegate.adjustLineIndent(document, offset);
+ }
+
+ public void scheduleIndentAdjustment(Document document, int offset) {
+ delegate.scheduleIndentAdjustment(document, offset);
+ }
+
+ @Override
+ public boolean isLineToBeIndented(PsiFile file, int offset) {
+ return delegate.isLineToBeIndented(file, offset);
+ }
+
+ @Override
+ @Nullable
+ public String getLineIndent(PsiFile file, int offset) {
+ return delegate.getLineIndent(file, offset);
+ }
+
+ @Override
+ @Nullable
+ public String getLineIndent(PsiFile file, int offset, FormattingMode mode) {
+ return delegate.getLineIndent(file, offset, mode);
+ }
+
+ @Override
+ @Nullable
+ public String getLineIndent(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(ASTNode block, 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 <T extends Throwable> void performActionWithFormatterDisabled(ThrowableRunnable<T> r)
+ throws T {
+ delegate.performActionWithFormatterDisabled(r);
+ }
+
+ @Override
+ public <T> T performActionWithFormatterDisabled(Computable<T> r) {
+ return delegate.performActionWithFormatterDisabled(r);
+ }
+
+ @Override
+ public int getSpacing(PsiFile file, int offset) {
+ return delegate.getSpacing(file, offset);
+ }
+
+ @Override
+ public int getMinLineFeeds(PsiFile file, int offset) {
+ return delegate.getMinLineFeeds(file, offset);
+ }
+
+ @Override
+ public void runWithDocCommentFormattingDisabled(PsiFile file, Runnable runnable) {
+ delegate.runWithDocCommentFormattingDisabled(file, runnable);
+ }
+
+ @Override
+ public DocCommentSettings getDocCommentSettings(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 Document document, final int offset, FormattingMode mode)
+ throws IncorrectOperationException {
+ if (delegate instanceof FormattingModeAwareIndentAdjuster) {
+ return ((FormattingModeAwareIndentAdjuster) delegate)
+ .adjustLineIndent(document, offset, mode);
+ }
+ return offset;
+ }
+}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/FormatterUtil.java b/idea_plugin/src/com/google/googlejavaformat/intellij/FormatterUtil.java
new file mode 100644
index 0000000..b6e21f7
--- /dev/null
+++ b/idea_plugin/src/com/google/googlejavaformat/intellij/FormatterUtil.java
@@ -0,0 +1,65 @@
+/*
+ * 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<TextRange, String> getReplacements(
+ Formatter formatter, String text, Collection<TextRange> ranges) {
+ try {
+ ImmutableMap.Builder<TextRange, String> 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<Range<Integer>> toRanges(Collection<TextRange> textRanges) {
+ return textRanges
+ .stream()
+ .map(textRange -> Range.closedOpen(textRange.getStartOffset(), textRange.getEndOffset()))
+ .collect(Collectors.toList());
+ }
+
+ private static TextRange toTextRange(Range<Integer> 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
new file mode 100644
index 0000000..52424c2
--- /dev/null
+++ b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatCodeStyleManager.java
@@ -0,0 +1,154 @@
+/*
+ * 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.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+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.CodeStyleManager;
+import com.intellij.psi.impl.CheckUtil;
+import com.intellij.util.IncorrectOperationException;
+import java.util.Collection;
+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 IJ's default implementation.
+ */
+class GoogleJavaFormatCodeStyleManager extends CodeStyleManagerDecorator {
+
+ public GoogleJavaFormatCodeStyleManager(@NotNull CodeStyleManager original) {
+ super(original);
+ }
+
+ @Override
+ public void reformatText(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(PsiFile file, Collection<TextRange> ranges)
+ throws IncorrectOperationException {
+ if (overrideFormatterForFile(file)) {
+ formatInternal(file, ranges);
+ } else {
+ super.reformatText(file, ranges);
+ }
+ }
+
+ @Override
+ public void reformatTextWithContext(PsiFile file, Collection<TextRange> ranges) {
+ if (overrideFormatterForFile(file)) {
+ formatInternal(file, ranges);
+ } else {
+ super.reformatTextWithContext(file, ranges);
+ }
+ }
+
+ @Override
+ public PsiElement reformatRange(
+ 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 or not this formatter can handle formatting the given file. */
+ private boolean overrideFormatterForFile(PsiFile file) {
+ return StdFileTypes.JAVA.equals(file.getFileType())
+ && GoogleJavaFormatSettings.getInstance(getProject()).isEnabled();
+ }
+
+ private void formatInternal(PsiFile file, Collection<TextRange> 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.
+ *
+ * <p>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<TextRange> ranges) {
+ Style style = GoogleJavaFormatSettings.getInstance(getProject()).getStyle();
+ Formatter formatter = new Formatter(JavaFormatterOptions.builder().style(style).build());
+ performReplacements(
+ document, FormatterUtil.getReplacements(formatter, document.getText(), ranges));
+ }
+
+ private void performReplacements(
+ final Document document, final Map<TextRange, String> replacements) {
+
+ if (replacements.isEmpty()) {
+ return;
+ }
+
+ TreeMap<TextRange, String> sorted = new TreeMap<>(comparing(TextRange::getStartOffset));
+ sorted.putAll(replacements);
+ WriteCommandAction.runWriteCommandAction(
+ getProject(),
+ () -> {
+ for (Entry<TextRange, String> 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/GoogleJavaFormatConfigurable.form b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form
new file mode 100644
index 0000000..1db1d79
--- /dev/null
+++ b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.google.googlejavaformat.intellij.GoogleJavaFormatConfigurable">
+ <grid id="27dc6" binding="panel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="20" y="20" width="500" height="400"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="4a87f" class="javax.swing.JCheckBox" binding="enable" default-binding="true">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Enable google-java-format"/>
+ </properties>
+ </component>
+ <vspacer id="19e83">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ <component id="c93e1" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Code style"/>
+ </properties>
+ </component>
+ <component id="31761" class="javax.swing.JComboBox" binding="styleComboBox" custom-create="true">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="1" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+</form>
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java
new file mode 100644
index 0000000..2f34b74
--- /dev/null
+++ b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java
@@ -0,0 +1,206 @@
+/*
+ * 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 com.google.googlejavaformat.intellij.GoogleJavaFormatSettings.EnabledState;
+import com.intellij.openapi.options.BaseConfigurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SearchableConfigurable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.uiDesigner.core.GridConstraints;
+import com.intellij.uiDesigner.core.GridLayoutManager;
+import com.intellij.uiDesigner.core.Spacer;
+import java.awt.Insets;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NotNull;
+
+class GoogleJavaFormatConfigurable extends BaseConfigurable implements SearchableConfigurable {
+
+ private final Project project;
+ private JPanel panel;
+ private JCheckBox enable;
+ private JComboBox styleComboBox;
+
+ public GoogleJavaFormatConfigurable(Project project) {
+ this.project = project;
+ }
+
+ @NotNull
+ @Override
+ public String getId() {
+ return "google-java-format.settings";
+ }
+
+ @Nullable
+ @Override
+ public Runnable enableSearch(String option) {
+ return null;
+ }
+
+ @Nls
+ @Override
+ public String getDisplayName() {
+ return "google-java-format Settings";
+ }
+
+ @Nullable
+ @Override
+ public String getHelpTopic() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public JComponent createComponent() {
+ return panel;
+ }
+
+ @Override
+ public void apply() throws ConfigurationException {
+ GoogleJavaFormatSettings settings = GoogleJavaFormatSettings.getInstance(project);
+ settings.setEnabled(enable.isSelected() ? EnabledState.ENABLED : getDisabledState());
+ settings.setStyle(((UiFormatterStyle) styleComboBox.getSelectedItem()).convert());
+ }
+
+ private EnabledState getDisabledState() {
+ // The default settings (inherited by new projects) are either 'enabled' or
+ // 'show notification'. There's no way to default new projects to disabled. If someone wants
+ // that, we can add another checkbox, I suppose.
+ return project.isDefault() ? EnabledState.UNKNOWN : EnabledState.DISABLED;
+ }
+
+ @Override
+ public void reset() {
+ GoogleJavaFormatSettings settings = GoogleJavaFormatSettings.getInstance(project);
+ enable.setSelected(settings.isEnabled());
+ styleComboBox.setSelectedItem(UiFormatterStyle.convert(settings.getStyle()));
+ }
+
+ @Override
+ public boolean isModified() {
+ GoogleJavaFormatSettings settings = GoogleJavaFormatSettings.getInstance(project);
+ return enable.isSelected() != settings.isEnabled()
+ || !styleComboBox.getSelectedItem().equals(UiFormatterStyle.convert(settings.getStyle()));
+ }
+
+ @Override
+ public void disposeUIResources() {}
+
+ private void createUIComponents() {
+ styleComboBox = new ComboBox<>(UiFormatterStyle.values());
+ }
+
+ {
+ // GUI initializer generated by IntelliJ IDEA GUI Designer
+ // >>> IMPORTANT!! <<<
+ // DO NOT EDIT OR ADD ANY CODE HERE!
+ $$$setupUI$$$();
+ }
+
+ /**
+ * Method generated by IntelliJ IDEA GUI Designer >>> IMPORTANT!! <<< DO NOT edit this method OR
+ * call it in your code!
+ *
+ * @noinspection ALL
+ */
+ private void $$$setupUI$$$() {
+ createUIComponents();
+ panel = new JPanel();
+ panel.setLayout(new GridLayoutManager(3, 2, new Insets(0, 0, 0, 0), -1, -1));
+ enable = new JCheckBox();
+ enable.setText("Enable google-java-format");
+ panel.add(
+ enable,
+ new GridConstraints(
+ 0,
+ 0,
+ 1,
+ 2,
+ GridConstraints.ANCHOR_WEST,
+ GridConstraints.FILL_NONE,
+ GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+ GridConstraints.SIZEPOLICY_FIXED,
+ null,
+ null,
+ null,
+ 0,
+ false));
+ final Spacer spacer1 = new Spacer();
+ panel.add(
+ spacer1,
+ new GridConstraints(
+ 2,
+ 0,
+ 1,
+ 2,
+ GridConstraints.ANCHOR_CENTER,
+ GridConstraints.FILL_VERTICAL,
+ 1,
+ GridConstraints.SIZEPOLICY_WANT_GROW,
+ null,
+ null,
+ null,
+ 0,
+ false));
+ final JLabel label1 = new JLabel();
+ label1.setText("Code style");
+ panel.add(
+ label1,
+ new GridConstraints(
+ 1,
+ 0,
+ 1,
+ 1,
+ GridConstraints.ANCHOR_WEST,
+ GridConstraints.FILL_NONE,
+ GridConstraints.SIZEPOLICY_FIXED,
+ GridConstraints.SIZEPOLICY_FIXED,
+ null,
+ null,
+ null,
+ 0,
+ false));
+ panel.add(
+ styleComboBox,
+ new GridConstraints(
+ 1,
+ 1,
+ 1,
+ 1,
+ GridConstraints.ANCHOR_WEST,
+ GridConstraints.FILL_HORIZONTAL,
+ GridConstraints.SIZEPOLICY_CAN_GROW,
+ GridConstraints.SIZEPOLICY_FIXED,
+ null,
+ null,
+ null,
+ 1,
+ false));
+ }
+
+ /** @noinspection ALL */
+ public JComponent $$$getRootComponent$$$() {
+ return panel;
+ }
+}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatInstaller.java b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatInstaller.java
new file mode 100644
index 0000000..c606736
--- /dev/null
+++ b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatInstaller.java
@@ -0,0 +1,57 @@
+/*
+ * 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/GoogleJavaFormatSettings.java b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java
new file mode 100644
index 0000000..f6d9b5f
--- /dev/null
+++ b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java
@@ -0,0 +1,106 @@
+/*
+ * 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 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;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+@State(
+ name = "GoogleJavaFormatSettings",
+ storages = {@Storage("google-java-format.xml")})
+class GoogleJavaFormatSettings implements PersistentStateComponent<GoogleJavaFormatSettings.State> {
+
+ private State state = new State();
+
+ static GoogleJavaFormatSettings getInstance(Project project) {
+ return ServiceManager.getService(project, GoogleJavaFormatSettings.class);
+ }
+
+ @Nullable
+ @Override
+ public State getState() {
+ return state;
+ }
+
+ @Override
+ public void loadState(State state) {
+ this.state = state;
+ }
+
+ boolean isEnabled() {
+ return state.enabled.equals(EnabledState.ENABLED);
+ }
+
+ void setEnabled(boolean enabled) {
+ setEnabled(enabled ? EnabledState.ENABLED : EnabledState.DISABLED);
+ }
+
+ void setEnabled(EnabledState enabled) {
+ state.enabled = enabled;
+ }
+
+ boolean isUninitialized() {
+ return state.enabled.equals(EnabledState.UNKNOWN);
+ }
+
+ JavaFormatterOptions.Style getStyle() {
+ return state.style;
+ }
+
+ void setStyle(JavaFormatterOptions.Style style) {
+ state.style = style;
+ }
+
+ enum EnabledState {
+ UNKNOWN,
+ ENABLED,
+ DISABLED;
+ }
+
+ static class State {
+
+ private EnabledState enabled = EnabledState.UNKNOWN;
+ public JavaFormatterOptions.Style style = JavaFormatterOptions.Style.GOOGLE;
+
+ // enabled used to be a boolean so we use bean property methods for backwards compatibility
+ public void setEnabled(@Nullable String enabledStr) {
+ if (enabledStr == null) {
+ enabled = EnabledState.UNKNOWN;
+ } else if (Boolean.valueOf(enabledStr)) {
+ enabled = EnabledState.ENABLED;
+ } else {
+ enabled = EnabledState.DISABLED;
+ }
+ }
+
+ public String getEnabled() {
+ switch (enabled) {
+ case ENABLED:
+ return "true";
+ case DISABLED:
+ return "false";
+ default:
+ return null;
+ }
+ }
+ }
+}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/InitialConfigurationProjectManagerListener.java b/idea_plugin/src/com/google/googlejavaformat/intellij/InitialConfigurationProjectManagerListener.java
new file mode 100644
index 0000000..da02310
--- /dev/null
+++ b/idea_plugin/src/com/google/googlejavaformat/intellij/InitialConfigurationProjectManagerListener.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.intellij.notification.Notification;
+import com.intellij.notification.NotificationDisplayType;
+import com.intellij.notification.NotificationGroup;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManagerListener;
+import org.jetbrains.annotations.NotNull;
+
+final class InitialConfigurationProjectManagerListener implements ProjectManagerListener {
+
+ private static final String NOTIFICATION_TITLE = "Enable google-java-format";
+ private static final NotificationGroup NOTIFICATION_GROUP =
+ new NotificationGroup(NOTIFICATION_TITLE, NotificationDisplayType.STICKY_BALLOON, true);
+
+ @Override
+ public void projectOpened(@NotNull Project project) {
+
+ GoogleJavaFormatSettings settings = GoogleJavaFormatSettings.getInstance(project);
+
+ if (settings.isUninitialized()) {
+ settings.setEnabled(false);
+ displayNewUserNotification(project, settings);
+ }
+ }
+
+ private void displayNewUserNotification(Project project, GoogleJavaFormatSettings settings) {
+ Notification notification =
+ new Notification(
+ NOTIFICATION_GROUP.getDisplayId(),
+ NOTIFICATION_TITLE,
+ "The google-java-format plugin is disabled by default. "
+ + "<a href=\"enable\">Enable for this project</a>.",
+ NotificationType.INFORMATION,
+ (n, e) -> {
+ settings.setEnabled(true);
+ n.expire();
+ });
+ notification.notify(project);
+ }
+}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/UiFormatterStyle.java b/idea_plugin/src/com/google/googlejavaformat/intellij/UiFormatterStyle.java
new file mode 100644
index 0000000..24ed6f6
--- /dev/null
+++ b/idea_plugin/src/com/google/googlejavaformat/intellij/UiFormatterStyle.java
@@ -0,0 +1,52 @@
+/*
+ * 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 com.google.googlejavaformat.java.JavaFormatterOptions;
+import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
+import java.util.Arrays;
+import java.util.Objects;
+
+/** Configuration options for the formatting style. */
+enum UiFormatterStyle {
+ GOOGLE("Default Google Java style", Style.GOOGLE),
+ AOSP("Android Open Source Project (AOSP) style", Style.AOSP);
+
+ private final String description;
+ private final JavaFormatterOptions.Style style;
+
+ UiFormatterStyle(String description, JavaFormatterOptions.Style style) {
+ this.description = description;
+ this.style = style;
+ }
+
+ @Override
+ public String toString() {
+ return description;
+ }
+
+ public JavaFormatterOptions.Style convert() {
+ return style;
+ }
+
+ static UiFormatterStyle convert(JavaFormatterOptions.Style style) {
+ return Arrays.stream(UiFormatterStyle.values())
+ .filter(value -> Objects.equals(value.style, style))
+ .findFirst()
+ .get();
+ }
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..2ece22d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,253 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015 Google Inc.
+ ~
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.sonatype.oss</groupId>
+ <artifactId>oss-parent</artifactId>
+ <version>7</version>
+ </parent>
+
+ <groupId>com.google.googlejavaformat</groupId>
+ <artifactId>google-java-format-parent</artifactId>
+ <packaging>pom</packaging>
+ <version>1.8-SNAPSHOT</version>
+
+ <modules>
+ <module>core</module>
+ <!-- google-java-format#24
+ <module>idea_plugin</module>
+ <module>eclipse_plugin</module>
+ -->
+ </modules>
+
+ <name>Google Java Format Parent</name>
+
+ <description>
+ A Java source code formatter that follows Google Java Style.
+ </description>
+
+ <url>https://github.com/google/google-java-format</url>
+
+ <inceptionYear>2015</inceptionYear>
+
+ <organization>
+ <name>Google Inc.</name>
+ <url>http://www.google.com/</url>
+ </organization>
+
+ <licenses>
+ <license>
+ <name>The Apache Software License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <developers>
+ <developer>
+ <id>jdd</id>
+ <name>John DeTreville</name>
+ <email>jdd@google.com</email>
+ <organization>Google Inc.</organization>
+ <organizationUrl>http://www.google.com/</organizationUrl>
+ <roles>
+ <role>owner</role>
+ <role>developer</role>
+ </roles>
+ <timezone>-8</timezone>
+ </developer>
+ </developers>
+
+ <scm>
+ <url>http://github.com/google/google-java-format/</url>
+ <connection>scm:git:git://github.com/google/google-java-format.git</connection>
+ <developerConnection>scm:git:ssh://git@github.com/google/google-java-format.git</developerConnection>
+ <tag>HEAD</tag>
+ </scm>
+
+ <issueManagement>
+ <system>GitHub Issues</system>
+ <url>http://github.com/google/google-java-format/issues</url>
+ </issueManagement>
+
+ <prerequisites>
+ <maven>3.0.3</maven>
+ </prerequisites>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <java.version>1.8</java.version>
+ <guava.version>28.1-jre</guava.version>
+ <truth.version>1.0</truth.version>
+ <checker.version>2.0.0</checker.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- Required runtime dependencies -->
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>${guava.version}</version>
+ </dependency>
+
+ <!-- Compile-time dependencies -->
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ <version>${checker.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.errorprone</groupId>
+ <artifactId>error_prone_annotations</artifactId>
+ <version>2.0.8</version>
+ </dependency>
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-testlib</artifactId>
+ <version>${guava.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ <version>${truth.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.7.0</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.0.2</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.1.2</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>3.2.0</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.4</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.4.0</version>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ <compilerArgs>
+ <arg>-XDcompilePolicy=simple</arg>
+ <arg>-Xplugin:ErrorProne</arg>
+ <arg>--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
+ <arg>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
+ <arg>--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
+ <arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
+ <arg>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
+ <arg>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
+ <arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
+ </compilerArgs>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>com.google.errorprone</groupId>
+ <artifactId>error_prone_core</artifactId>
+ <version>2.3.2</version>
+ </path>
+ </annotationProcessorPaths>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>com.google.googlejavaformat.java.Main</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>3.2.0</version>
+ <configuration>
+ <doclint>none</doclint>
+ </configuration>
+ <executions>
+ <execution>
+ <id>attach-javadocs</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.18</version>
+ <configuration>
+ <!-- set heap size to work around http://github.com/travis-ci/travis-ci/issues/3396 -->
+ <argLine>-Xmx1024m</argLine>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/scripts/google-java-format-diff.py b/scripts/google-java-format-diff.py
new file mode 100755
index 0000000..4c6724b
--- /dev/null
+++ b/scripts/google-java-format-diff.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python2.7
+#
+#===- google-java-format-diff.py - google-java-format Diff Reformatter -----===#
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+
+"""
+google-java-format Diff Reformatter
+============================
+
+This script reads input from a unified diff and reformats all the changed
+lines. This is useful to reformat all the lines touched by a specific patch.
+Example usage for git/svn users:
+
+ git diff -U0 HEAD^ | google-java-format-diff.py -p1 -i
+ svn diff --diff-cmd=diff -x-U0 | google-java-format-diff.py -i
+
+For perforce users:
+
+ P4DIFF="git --no-pager diff --no-index" p4 diff | ./google-java-format-diff.py -i -p7
+
+"""
+
+import argparse
+import difflib
+import re
+import string
+import subprocess
+import StringIO
+import sys
+from distutils.spawn import find_executable
+
+def main():
+ parser = argparse.ArgumentParser(description=
+ 'Reformat changed lines in diff. Without -i '
+ 'option just output the diff that would be '
+ 'introduced.')
+ parser.add_argument('-i', action='store_true', default=False,
+ help='apply edits to files instead of displaying a diff')
+
+ parser.add_argument('-p', metavar='NUM', default=0,
+ help='strip the smallest prefix containing P slashes')
+ parser.add_argument('-regex', metavar='PATTERN', default=None,
+ help='custom pattern selecting file paths to reformat '
+ '(case sensitive, overrides -iregex)')
+ parser.add_argument('-iregex', metavar='PATTERN', default=r'.*\.java',
+ help='custom pattern selecting file paths to reformat '
+ '(case insensitive, overridden by -regex)')
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help='be more verbose, ineffective without -i')
+ parser.add_argument('-a', '--aosp', action='store_true',
+ help='use AOSP style instead of Google Style (4-space indentation)')
+ parser.add_argument('--skip-sorting-imports', action='store_true',
+ help='do not fix the import order')
+ parser.add_argument('-b', '--binary', help='path to google-java-format binary')
+ parser.add_argument('--google-java-format-jar', metavar='ABSOLUTE_PATH', default=None,
+ help='use a custom google-java-format jar')
+
+ args = parser.parse_args()
+
+ # Extract changed lines for each file.
+ filename = None
+ lines_by_file = {}
+
+ for line in sys.stdin:
+ match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
+ if match:
+ filename = match.group(2)
+ if filename == None:
+ continue
+
+ if args.regex is not None:
+ if not re.match('^%s$' % args.regex, filename):
+ continue
+ else:
+ if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
+ continue
+
+ match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
+ if match:
+ start_line = int(match.group(1))
+ line_count = 1
+ if match.group(3):
+ line_count = int(match.group(3))
+ if line_count == 0:
+ continue
+ end_line = start_line + line_count - 1;
+ lines_by_file.setdefault(filename, []).extend(
+ ['-lines', str(start_line) + ':' + str(end_line)])
+
+ if args.binary:
+ base_command = [args.binary]
+ elif args.google_java_format_jar:
+ base_command = ['java', '-jar', args.google_java_format_jar]
+ else:
+ binary = find_executable('google-java-format') or '/usr/bin/google-java-format'
+ base_command = [binary]
+
+ # Reformat files containing changes in place.
+ for filename, lines in lines_by_file.iteritems():
+ if args.i and args.verbose:
+ print 'Formatting', filename
+ command = base_command[:]
+ if args.i:
+ command.append('-i')
+ if args.aosp:
+ command.append('--aosp')
+ if args.skip_sorting_imports:
+ command.append('--skip-sorting-imports')
+ command.extend(lines)
+ command.append(filename)
+ p = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=None, stdin=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ if p.returncode != 0:
+ sys.exit(p.returncode);
+
+ if not args.i:
+ with open(filename) as f:
+ code = f.readlines()
+ formatted_code = StringIO.StringIO(stdout).readlines()
+ diff = difflib.unified_diff(code, formatted_code,
+ filename, filename,
+ '(before formatting)', '(after formatting)')
+ diff_string = string.join(diff, '')
+ if len(diff_string) > 0:
+ sys.stdout.write(diff_string)
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/mvn-deploy.sh b/scripts/mvn-deploy.sh
new file mode 100755
index 0000000..f6db093
--- /dev/null
+++ b/scripts/mvn-deploy.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+if [ $# -lt 1 ]; then
+ echo "usage $0 <ssl-key> [<param> ...]"
+ exit 1;
+fi
+key=${1}
+shift
+params=${@}
+
+#validate key
+keystatus=$(gpg --list-keys | grep ${key} | awk '{print $1}')
+if [ "${keystatus}" != "pub" ]; then
+ echo "Could not find public key with label ${key}"
+ echo -n "Available keys from: "
+ gpg --list-keys | grep --invert-match '^sub'
+
+ exit 1
+fi
+
+mvn ${params} -P sonatype-oss-release -Dgpg.keyname=${key} deploy
diff --git a/util/publish-snapshot-on-commit.sh b/util/publish-snapshot-on-commit.sh
new file mode 100755
index 0000000..6da8da4
--- /dev/null
+++ b/util/publish-snapshot-on-commit.sh
@@ -0,0 +1,12 @@
+# https://github.com/google/dagger/blob/master/util/publish-snapshot-on-commit.sh
+
+if [ "$TRAVIS_REPO_SLUG" == "google/google-java-format" ] && \
+ [ "$TRAVIS_JDK_VERSION" == "oraclejdk8" ] && \
+ [ "$TRAVIS_PULL_REQUEST" == "false" ] && \
+ [ "$TRAVIS_BRANCH" == "master" ]; then
+ echo -e "Publishing maven snapshot...\n"
+
+ mvn clean source:jar deploy --settings="util/settings.xml" -DskipTests=true -Dinvoker.skip=true -Dmaven.javadoc.skip=true
+
+ echo -e "Published maven snapshot"
+fi
diff --git a/util/settings.xml b/util/settings.xml
new file mode 100644
index 0000000..91f444b
--- /dev/null
+++ b/util/settings.xml
@@ -0,0 +1,9 @@
+<settings>
+ <servers>
+ <server>
+ <id>sonatype-nexus-snapshots</id>
+ <username>${env.CI_DEPLOY_USERNAME}</username>
+ <password>${env.CI_DEPLOY_PASSWORD}</password>
+ </server>
+ </servers>
+</settings>