1 /* 2 * Copyright 2015 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.google.googlejavaformat.java; 16 17 import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION; 18 import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 21 import com.google.common.collect.ImmutableList; 22 import com.google.common.collect.Iterables; 23 import com.google.common.collect.Iterators; 24 import com.google.common.collect.Range; 25 import com.google.common.collect.RangeSet; 26 import com.google.common.collect.TreeRangeSet; 27 import com.google.common.io.CharSink; 28 import com.google.common.io.CharSource; 29 import com.google.errorprone.annotations.Immutable; 30 import com.google.googlejavaformat.Doc; 31 import com.google.googlejavaformat.DocBuilder; 32 import com.google.googlejavaformat.FormattingError; 33 import com.google.googlejavaformat.Newlines; 34 import com.google.googlejavaformat.Op; 35 import com.google.googlejavaformat.OpsBuilder; 36 import com.sun.tools.javac.file.JavacFileManager; 37 import com.sun.tools.javac.parser.JavacParser; 38 import com.sun.tools.javac.parser.ParserFactory; 39 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; 40 import com.sun.tools.javac.util.Context; 41 import com.sun.tools.javac.util.Log; 42 import com.sun.tools.javac.util.Options; 43 import java.io.IOError; 44 import java.io.IOException; 45 import java.lang.reflect.Method; 46 import java.net.URI; 47 import java.util.ArrayList; 48 import java.util.Collection; 49 import java.util.List; 50 import javax.tools.Diagnostic; 51 import javax.tools.DiagnosticCollector; 52 import javax.tools.DiagnosticListener; 53 import javax.tools.JavaFileObject; 54 import javax.tools.SimpleJavaFileObject; 55 import javax.tools.StandardLocation; 56 57 /** 58 * This is google-java-format, a new Java formatter that follows the Google Java Style Guide quite 59 * precisely---to the letter and to the spirit. 60 * 61 * <p>This formatter uses the javac parser to generate an AST. Because the AST loses information 62 * about the non-tokens in the input (including newlines, comments, etc.), and even some tokens 63 * (e.g., optional commas or semicolons), this formatter lexes the input again and follows along in 64 * the resulting list of tokens. Its lexer splits all multi-character operators (like ">>") 65 * into multiple single-character operators. Each non-token is assigned to a token---non-tokens 66 * following a token on the same line go with that token; those following go with the next token--- 67 * and there is a final EOF token to hold final comments. 68 * 69 * <p>The formatter walks the AST to generate a Greg Nelson/Derek Oppen-style list of formatting 70 * {@link Op}s [1--2] that then generates a structured {@link Doc}. Each AST node type has a visitor 71 * to emit a sequence of {@link Op}s for the node. 72 * 73 * <p>Some data-structure operations are easier in the list of {@link Op}s, while others become 74 * easier in the {@link Doc}. The {@link Op}s are walked to attach the comments. As the {@link Op}s 75 * are generated, missing input tokens are inserted and incorrect output tokens are dropped, 76 * ensuring that the output matches the input even in the face of formatter errors. Finally, the 77 * formatter walks the {@link Doc} to format it in the given width. 78 * 79 * <p>This formatter also produces data structures of which tokens and comments appear where on the 80 * input, and on the output, to help output a partial reformatting of a slightly edited input. 81 * 82 * <p>Instances of the formatter are immutable and thread-safe. 83 * 84 * <p>[1] Nelson, Greg, and John DeTreville. Personal communication. 85 * 86 * <p>[2] Oppen, Derek C. "Prettyprinting". ACM Transactions on Programming Languages and Systems, 87 * Volume 2 Issue 4, Oct. 1980, pp. 465–483. 88 */ 89 @Immutable 90 public final class Formatter { 91 92 public static final int MAX_LINE_LENGTH = 100; 93 94 static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1); 95 96 private final JavaFormatterOptions options; 97 98 /** A new Formatter instance with default options. */ Formatter()99 public Formatter() { 100 this(JavaFormatterOptions.defaultOptions()); 101 } 102 Formatter(JavaFormatterOptions options)103 public Formatter(JavaFormatterOptions options) { 104 this.options = options; 105 } 106 107 /** 108 * Construct a {@code Formatter} given a Java compilation unit. Parses the code; builds a {@link 109 * JavaInput} and the corresponding {@link JavaOutput}. 110 * 111 * @param javaInput the input, a Java compilation unit 112 * @param javaOutput the {@link JavaOutput} 113 * @param options the {@link JavaFormatterOptions} 114 */ format(final JavaInput javaInput, JavaOutput javaOutput, JavaFormatterOptions options)115 static void format(final JavaInput javaInput, JavaOutput javaOutput, JavaFormatterOptions options) 116 throws FormatterException { 117 Context context = new Context(); 118 DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>(); 119 context.put(DiagnosticListener.class, diagnostics); 120 Options.instance(context).put("allowStringFolding", "false"); 121 Options.instance(context).put("--enable-preview", "true"); 122 JCCompilationUnit unit; 123 JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8); 124 try { 125 fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of()); 126 } catch (IOException e) { 127 // impossible 128 throw new IOError(e); 129 } 130 SimpleJavaFileObject source = 131 new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) { 132 @Override 133 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { 134 return javaInput.getText(); 135 } 136 }; 137 Log.instance(context).useSource(source); 138 ParserFactory parserFactory = ParserFactory.instance(context); 139 JavacParser parser = 140 parserFactory.newParser( 141 javaInput.getText(), 142 /*keepDocComments=*/ true, 143 /*keepEndPos=*/ true, 144 /*keepLineMap=*/ true); 145 unit = parser.parseCompilationUnit(); 146 unit.sourcefile = source; 147 148 javaInput.setCompilationUnit(unit); 149 Iterable<Diagnostic<? extends JavaFileObject>> errorDiagnostics = 150 Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic); 151 if (!Iterables.isEmpty(errorDiagnostics)) { 152 throw FormatterException.fromJavacDiagnostics(errorDiagnostics); 153 } 154 OpsBuilder builder = new OpsBuilder(javaInput, javaOutput); 155 // Output the compilation unit. 156 JavaInputAstVisitor visitor; 157 if (getMajor() >= 14) { 158 try { 159 visitor = 160 Class.forName("com.google.googlejavaformat.java.java14.Java14InputAstVisitor") 161 .asSubclass(JavaInputAstVisitor.class) 162 .getConstructor(OpsBuilder.class, int.class) 163 .newInstance(builder, options.indentationMultiplier()); 164 } catch (ReflectiveOperationException e) { 165 throw new LinkageError(e.getMessage(), e); 166 } 167 } else { 168 visitor = new JavaInputAstVisitor(builder, options.indentationMultiplier()); 169 } 170 visitor.scan(unit, null); 171 builder.sync(javaInput.getText().length()); 172 builder.drain(); 173 Doc doc = new DocBuilder().withOps(builder.build()).build(); 174 doc.computeBreaks(javaOutput.getCommentsHelper(), MAX_LINE_LENGTH, new Doc.State(+0, 0)); 175 doc.write(javaOutput); 176 javaOutput.flush(); 177 } 178 179 // Runtime.Version was added in JDK 9, so use reflection to access it to preserve source 180 // compatibility with Java 8. getMajor()181 private static int getMajor() { 182 try { 183 Method versionMethod = Runtime.class.getMethod("version"); 184 Object version = versionMethod.invoke(null); 185 return (int) version.getClass().getMethod("major").invoke(version); 186 } catch (Exception e) { 187 // continue below 188 } 189 int version = (int) Double.parseDouble(JAVA_CLASS_VERSION.value()); 190 if (49 <= version && version <= 52) { 191 return version - (49 - 5); 192 } 193 throw new IllegalStateException("Unknown Java version: " + JAVA_SPECIFICATION_VERSION.value()); 194 } 195 errorDiagnostic(Diagnostic<?> input)196 static boolean errorDiagnostic(Diagnostic<?> input) { 197 if (input.getKind() != Diagnostic.Kind.ERROR) { 198 return false; 199 } 200 switch (input.getCode()) { 201 case "compiler.err.invalid.meth.decl.ret.type.req": 202 // accept constructor-like method declarations that don't match the name of their 203 // enclosing class 204 return false; 205 default: 206 break; 207 } 208 return true; 209 } 210 211 /** 212 * Format the given input (a Java compilation unit) into the output stream. 213 * 214 * @throws FormatterException if the input cannot be parsed 215 */ formatSource(CharSource input, CharSink output)216 public void formatSource(CharSource input, CharSink output) 217 throws FormatterException, IOException { 218 // TODO(cushon): proper support for streaming input/output. Input may 219 // not be feasible (parsing) but output should be easier. 220 output.write(formatSource(input.read())); 221 } 222 223 /** 224 * Format an input string (a Java compilation unit) into an output string. 225 * 226 * <p>Leaves import statements untouched. 227 * 228 * @param input the input string 229 * @return the output string 230 * @throws FormatterException if the input string cannot be parsed 231 */ formatSource(String input)232 public String formatSource(String input) throws FormatterException { 233 return formatSource(input, ImmutableList.of(Range.closedOpen(0, input.length()))); 234 } 235 236 /** 237 * Formats an input string (a Java compilation unit) and fixes imports. 238 * 239 * <p>Fixing imports includes ordering, spacing, and removal of unused import statements. 240 * 241 * @param input the input string 242 * @return the output string 243 * @throws FormatterException if the input string cannot be parsed 244 * @see <a 245 * href="https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing"> 246 * Google Java Style Guide - 3.3.3 Import ordering and spacing</a> 247 */ formatSourceAndFixImports(String input)248 public String formatSourceAndFixImports(String input) throws FormatterException { 249 input = ImportOrderer.reorderImports(input, options.style()); 250 input = RemoveUnusedImports.removeUnusedImports(input); 251 String formatted = formatSource(input); 252 formatted = StringWrapper.wrap(formatted, this); 253 return formatted; 254 } 255 256 /** 257 * Format an input string (a Java compilation unit), for only the specified character ranges. 258 * These ranges are extended as necessary (e.g., to encompass whole lines). 259 * 260 * @param input the input string 261 * @param characterRanges the character ranges to be reformatted 262 * @return the output string 263 * @throws FormatterException if the input string cannot be parsed 264 */ formatSource(String input, Collection<Range<Integer>> characterRanges)265 public String formatSource(String input, Collection<Range<Integer>> characterRanges) 266 throws FormatterException { 267 return JavaOutput.applyReplacements(input, getFormatReplacements(input, characterRanges)); 268 } 269 270 /** 271 * Emit a list of {@link Replacement}s to convert from input to output. 272 * 273 * @param input the input compilation unit 274 * @param characterRanges the character ranges to reformat 275 * @return a list of {@link Replacement}s, sorted from low index to high index, without overlaps 276 * @throws FormatterException if the input string cannot be parsed 277 */ getFormatReplacements( String input, Collection<Range<Integer>> characterRanges)278 public ImmutableList<Replacement> getFormatReplacements( 279 String input, Collection<Range<Integer>> characterRanges) throws FormatterException { 280 JavaInput javaInput = new JavaInput(input); 281 282 // TODO(cushon): this is only safe because the modifier ordering doesn't affect whitespace, 283 // and doesn't change the replacements that are output. This is not true in general for 284 // 'de-linting' changes (e.g. import ordering). 285 javaInput = ModifierOrderer.reorderModifiers(javaInput, characterRanges); 286 287 String lineSeparator = Newlines.guessLineSeparator(input); 288 JavaOutput javaOutput = 289 new JavaOutput(lineSeparator, javaInput, new JavaCommentsHelper(lineSeparator, options)); 290 try { 291 format(javaInput, javaOutput, options); 292 } catch (FormattingError e) { 293 throw new FormatterException(e.diagnostics()); 294 } 295 RangeSet<Integer> tokenRangeSet = javaInput.characterRangesToTokenRanges(characterRanges); 296 return javaOutput.getFormatReplacements(tokenRangeSet); 297 } 298 299 /** 300 * Converts zero-indexed, [closed, open) line ranges in the given source file to character ranges. 301 */ lineRangesToCharRanges( String input, RangeSet<Integer> lineRanges)302 public static RangeSet<Integer> lineRangesToCharRanges( 303 String input, RangeSet<Integer> lineRanges) { 304 List<Integer> lines = new ArrayList<>(); 305 Iterators.addAll(lines, Newlines.lineOffsetIterator(input)); 306 lines.add(input.length() + 1); 307 308 final RangeSet<Integer> characterRanges = TreeRangeSet.create(); 309 for (Range<Integer> lineRange : 310 lineRanges.subRangeSet(Range.closedOpen(0, lines.size() - 1)).asRanges()) { 311 int lineStart = lines.get(lineRange.lowerEndpoint()); 312 // Exclude the trailing newline. This isn't strictly necessary, but handling blank lines 313 // as empty ranges is convenient. 314 int lineEnd = lines.get(lineRange.upperEndpoint()) - 1; 315 Range<Integer> range = Range.closedOpen(lineStart, lineEnd); 316 characterRanges.add(range); 317 } 318 return characterRanges; 319 } 320 } 321