3030import com .palantir .javaformat .doc .DocBuilder ;
3131import com .palantir .javaformat .doc .FillMode ;
3232import com .palantir .javaformat .doc .NonBreakingSpace ;
33+ import com .palantir .javaformat .doc .ReservedWidth ;
3334import com .palantir .javaformat .doc .State ;
3435import com .palantir .javaformat .doc .Token ;
3536import com .palantir .javaformat .doc .Token .RealOrImaginary ;
3637import com .palantir .javaformat .java .FormatterDiagnostic ;
3738import com .palantir .javaformat .java .InputMetadata ;
3839import com .palantir .javaformat .java .InputMetadataBuilder ;
40+ import com .palantir .javaformat .java .NonNlsMarkers ;
3941import java .util .ArrayList ;
42+ import java .util .HashMap ;
4043import java .util .List ;
44+ import java .util .Map ;
4145import java .util .Optional ;
4246
4347/** An {@code OpsBuilder} creates a list of {@link Op}s, which is turned into a {@link Doc} by {@link DocBuilder}. */
@@ -505,6 +509,10 @@ public OpsOutput build() {
505509 markForPartialFormat ();
506510 // Rewrite the ops to insert comments.
507511 Multimap <Integer , Op > tokOps = ArrayListMultimap .create ();
512+ // The room after a string literal for the NON-NLS markers written on its line, see Comment.makeNonNlsMarkers.
513+ var reservations = ArrayListMultimap .<Integer , Op >create ();
514+ var tokenOps = new HashMap <Input .Token , Integer >();
515+ Map <Integer , Input .Token > tokenByIndex = null ;
508516 int opsN = ops .size ();
509517 for (int i = 0 ; i < opsN ; i ++) {
510518 Op op = ops .get (i );
@@ -528,6 +536,7 @@ public OpsOutput build() {
528536 * Regular input token. Copy out toksBefore before token, and toksAfter after it. Insert
529537 * this token's toksBefore at position j.
530538 */
539+ tokenOps .put (token , i );
531540 int newlines = 0 ; // Count of newlines in a row.
532541 boolean space = false ; // Do we need an extra space after a previous "/*" comment?
533542 boolean lastWasComment = false ; // Was the last thing we output a comment?
@@ -591,7 +600,29 @@ public OpsOutput build() {
591600 } else {
592601 tokOps .put (tokAfterPos , SPACE );
593602 }
594- tokOps .putAll (tokAfterPos , makeComment (tokAfter ));
603+ if (tokAfter .isSlashSlashComment () && NonNlsMarkers .isMarkersOnly (tokAfter .getText ())) {
604+ if (tokenByIndex == null ) {
605+ tokenByIndex = new HashMap <>();
606+ for (Input .Token t : input .getTokens ()) {
607+ tokenByIndex .put (t .getTok ().getIndex (), t );
608+ }
609+ }
610+ var literals = nonNlsLiterals (token , tokAfter , tokenByIndex , tokenOps );
611+ var numbers = NonNlsMarkers .numbers (tokAfter .getText ());
612+ for (var m = 0 ; m < literals .size (); m ++) {
613+ var literalOp = literals .get (m ) < 0
614+ ? null
615+ : tokenOps .get (tokenByIndex .get (literals .get (m )));
616+ if (literalOp != null ) {
617+ var marker = NonNlsMarkers .marker (numbers .get (m ));
618+ reservations .put (literalOp + 1 , ReservedWidth .make (marker .length () + 1 ));
619+ }
620+ }
621+ tokOps .put (tokAfterPos , Comment .makeNonNlsMarkers (tokAfter , literals ));
622+ tokOps .put (tokAfterPos , Break .makeForced ());
623+ } else {
624+ tokOps .putAll (tokAfterPos , makeComment (tokAfter ));
625+ }
595626 if (breakAfter ) {
596627 tokOps .put (tokAfterPos , Break .make (FillMode .FORCED , "" , ZERO ));
597628 }
@@ -630,6 +661,7 @@ public OpsOutput build() {
630661 ImmutableList .Builder <Op > newOps = ImmutableList .builder ();
631662 boolean afterForcedBreak = false ; // Was the last Op a forced break? If so, suppress spaces.
632663 for (int i = 0 ; i < opsN ; i ++) {
664+ newOps .addAll (reservations .get (i ));
633665 for (Op op : tokOps .get (i )) {
634666 if (!(afterForcedBreak && op instanceof NonBreakingSpace )) {
635667 newOps .add (op );
@@ -662,6 +694,39 @@ private static boolean isNonNlsComment(Input.Tok tokAfter) {
662694 return tokAfter .isSlashSlashComment () && tokAfter .getText ().contains ("$NON-NLS" );
663695 }
664696
697+ /**
698+ * For each marker of a {@code //$NON-NLS-1$ //$NON-NLS-2$} comment that trails {@code token}, the index of the
699+ * string literal it marks, the n-th one on the comment's input line, or -1 when there is none. A literal in
700+ * another partial-format region counts as none too: a partial format of its region would otherwise lose the
701+ * marker, which belongs to the comment's region. So does a literal without an op of its own.
702+ */
703+ private List <Integer > nonNlsLiterals (
704+ Input .Token token ,
705+ Input .Tok comment ,
706+ Map <Integer , Input .Token > tokenByIndex ,
707+ Map <Input .Token , Integer > tokenOps ) {
708+ var line = input .getLineNumber (token .getTok ().getPosition ()) - 1 ;
709+ var toks = input .getRanges (line );
710+ var literals = new ArrayList <Integer >();
711+ for (int k = toks .lowerEndpoint (); k < Math .min (toks .upperEndpoint (), comment .getIndex ()); k ++) {
712+ var candidate = tokenByIndex .get (k );
713+ if (candidate != null && candidate .getTok ().getText ().startsWith ("\" " )) {
714+ literals .add (k );
715+ }
716+ }
717+ var region = inputMetadataBuilder .regionContaining (comment .getIndex ());
718+ var result = new ArrayList <Integer >();
719+ for (int n : NonNlsMarkers .numbers (comment .getText ())) {
720+ var literal = 1 <= n && n <= literals .size () ? literals .get (n - 1 ) : -1 ;
721+ var usable = literal >= 0
722+ && region != null
723+ && region .contains (literal )
724+ && tokenOps .containsKey (tokenByIndex .get (literal ));
725+ result .add (usable ? literal : -1 );
726+ }
727+ return List .copyOf (result );
728+ }
729+
665730 private static boolean isForcedBreak (Op op ) {
666731 return op instanceof Break b && b .isForced ();
667732 }
0 commit comments