@@ -186,10 +186,12 @@ private ImportOrderer(String text, ImmutableList<Tok> toks, Style style) {
186186 class Import {
187187 private final String imported ;
188188 private final boolean isStatic ;
189+ private final String leading ;
189190 private final String trailing ;
190191
191- Import (String imported , String trailing , boolean isStatic ) {
192+ Import (String imported , String leading , String trailing , boolean isStatic ) {
192193 this .imported = imported ;
194+ this .leading = leading ;
193195 this .trailing = trailing ;
194196 this .isStatic = isStatic ;
195197 }
@@ -227,9 +229,18 @@ boolean isJava() {
227229 }
228230
229231 /**
230- * The {@code //} comment lines after the final {@code ;}, up to and including the line terminator of the last
231- * one. Note: In case two imports were separated by a space (which is disallowed by the style guide), the
232- * trailing whitespace of the first import does not include a line terminator.
232+ * The comments that stood between the previous import and this one, including the line terminator after the
233+ * last of them, or empty. They move with this import.
234+ */
235+ String leading () {
236+ return leading ;
237+ }
238+
239+ /**
240+ * A block comment on the import's own line and the {@code //} comment lines after the final {@code ;}, up to
241+ * and including the line terminator of the last one. Note: In case two imports were separated by a space
242+ * (which is disallowed by the style guide), the trailing whitespace of the first import does not include a
243+ * line terminator.
233244 */
234245 String trailing () {
235246 return trailing ;
@@ -240,11 +251,12 @@ public boolean isThirdParty() {
240251 return !(isAndroid () || isJava ());
241252 }
242253
243- // One or multiple lines, the import itself and following comments, including the line
244- // terminator.
254+ // One or multiple lines, the comments before the import, the import itself and following comments, including
255+ // the line terminator.
245256 @ Override
246257 public String toString () {
247258 StringBuilder sb = new StringBuilder ();
259+ sb .append (leading ());
248260 sb .append ("import " );
249261 if (isStatic ()) {
250262 sb .append ("static " );
@@ -282,18 +294,22 @@ private static class ImportsAndIndex {
282294 *
283295 * <pre>{@code
284296 * <imports> -> (<end-of-line> | <import>)*
285- * <import> -> "import" <whitespace> ("static" <whitespace>)?
297+ * <import> -> <comments-and-line-breaks>? "import" <whitespace> ("static" <whitespace>)?
286298 * <identifier> ("." <identifier>)* ("." "*")? <whitespace>? ";"
287- * <whitespace>? <end-of-line>? (<line-comment> <end-of-line>)*
299+ * <whitespace>? (<block-comment> <whitespace>?)? <end-of-line>? (<line-comment> <end-of-line>)*
288300 * }</pre>
289301 *
302+ * The comments before an import are the ones between it and the previous import, so the first import has none: the
303+ * text before it is left where it is.
304+ *
290305 * @param i the index to start parsing at.
291306 * @return the result of parsing the imports.
292307 * @throws FormatterException if imports could not parsed according to the grammar.
293308 */
294309 private ImportsAndIndex scanImports (int i ) throws FormatterException {
295310 int afterLastImport = i ;
296311 ImmutableSortedSet .Builder <Import > imports = ImmutableSortedSet .orderedBy (importComparator );
312+ String leading = "" ;
297313 // JavaInput.buildToks appends a zero-width EOF token after all tokens. It won't match any
298314 // of our tests here and protects us from running off the end of the toks list. Since it is
299315 // zero-width it doesn't matter if we include it in our string concatenation at the end.
@@ -330,6 +346,15 @@ private ImportsAndIndex scanImports(int i) throws FormatterException {
330346 trailing .append (tokenAt (i ));
331347 i ++;
332348 }
349+ // A block comment on the import's own line stays with the import, as a line comment there does.
350+ if (isBlockCommentToken (i )) {
351+ trailing .append (tokenAt (i ));
352+ i ++;
353+ if (isSpaceToken (i )) {
354+ trailing .append (tokenAt (i ));
355+ i ++;
356+ }
357+ }
333358 if (isNewlineToken (i )) {
334359 trailing .append (tokenAt (i ));
335360 i ++;
@@ -344,14 +369,25 @@ private ImportsAndIndex scanImports(int i) throws FormatterException {
344369 i ++;
345370 }
346371 }
347- imports .add (new Import (importedName , trailing .toString (), isStatic ));
372+ imports .add (new Import (importedName , leading , trailing .toString (), isStatic ));
348373 // Remember the position just after the import we just saw, before skipping blank lines.
349374 // If the next thing after the blank lines is not another import then we don't want to
350375 // include those blank lines in the text to be replaced.
351376 afterLastImport = i ;
352377 while (isNewlineToken (i ) || isSpaceToken (i )) {
353378 i ++;
354379 }
380+ // Comments between this import and the next one go with the next one, so they move with it when the
381+ // imports are sorted. Comments after the last import belong to whatever follows it.
382+ leading = "" ;
383+ int next = i ;
384+ while (isCommentToken (next ) || isNewlineToken (next ) || isSpaceToken (next )) {
385+ next ++;
386+ }
387+ if (next > i && tokenAt (next ).equals ("import" )) {
388+ leading = CharMatcher .whitespace ().trimTrailingFrom (tokString (i , next )) + lineSeparator ;
389+ i = next ;
390+ }
355391 }
356392 return new ImportsAndIndex (imports .build (), afterLastImport );
357393 }
@@ -470,6 +506,14 @@ private boolean isSlashSlashCommentToken(int i) {
470506 return toks .get (i ).isSlashSlashComment ();
471507 }
472508
509+ private boolean isBlockCommentToken (int i ) {
510+ return toks .get (i ).isComment () && !toks .get (i ).isSlashSlashComment ();
511+ }
512+
513+ private boolean isCommentToken (int i ) {
514+ return toks .get (i ).isComment ();
515+ }
516+
473517 private boolean isNewlineToken (int i ) {
474518 return toks .get (i ).isNewline ();
475519 }
0 commit comments