1717
1818package org .checkstyle .autofix .recipe ;
1919
20+ import java .nio .file .Path ;
21+ import java .util .ArrayList ;
22+ import java .util .List ;
23+ import java .util .concurrent .CancellationException ;
24+ import java .util .function .Function ;
25+
26+ import org .checkstyle .autofix .parser .CheckstyleViolation ;
27+ import org .openrewrite .Cursor ;
2028import org .openrewrite .ExecutionContext ;
29+ import org .openrewrite .PrintOutputCapture ;
2130import org .openrewrite .Recipe ;
2231import org .openrewrite .TreeVisitor ;
32+ import org .openrewrite .internal .RecipeRunException ;
2333import org .openrewrite .java .JavaIsoVisitor ;
2434import org .openrewrite .java .tree .J ;
2535import org .openrewrite .java .tree .JavaType ;
3040 */
3141public class UpperEll extends Recipe {
3242
43+ private List <CheckstyleViolation > violations ;
44+
45+ public UpperEll () {
46+ this .violations = new ArrayList <>();
47+ }
48+
49+ public UpperEll (List <CheckstyleViolation > violations ) {
50+ this .violations = violations ;
51+ }
52+
3353 @ Override
3454 public String getDisplayName () {
3555 return "UpperEll recipe" ;
@@ -43,23 +63,128 @@ public String getDescription() {
4363
4464 @ Override
4565 public TreeVisitor <?, ExecutionContext > getVisitor () {
46- return new UpperEllVisitor ();
66+ return new UpperEllVisitor (violations );
4767 }
4868
49- /**
50- * Visitor that replaces lowercase 'l' suffixes in long literals with uppercase 'L'.
51- */
5269 private static final class UpperEllVisitor extends JavaIsoVisitor <ExecutionContext > {
70+
71+ private static final String LOWERCASE_L = "l" ;
72+ private static final String UPPERCASE_L = "L" ;
73+
74+ private final List <CheckstyleViolation > violations ;
75+ private String currentFileName ;
76+
77+ UpperEllVisitor (List <CheckstyleViolation > violations ) {
78+ this .violations = violations ;
79+ }
80+
81+ @ Override
82+ public J .CompilationUnit visitCompilationUnit (J .CompilationUnit cu , ExecutionContext ctx ) {
83+ this .currentFileName = cu .getSourcePath ().toString ();
84+ return super .visitCompilationUnit (cu , ctx );
85+ }
86+
5387 @ Override
5488 public J .Literal visitLiteral (J .Literal literal , ExecutionContext ctx ) {
5589 J .Literal result = super .visitLiteral (literal , ctx );
5690 final String valueSource = result .getValueSource ();
5791
58- if (valueSource != null && valueSource .endsWith ("l" )
59- && result .getType () == JavaType .Primitive .Long ) {
92+ if (valueSource != null && valueSource .endsWith (LOWERCASE_L )
93+ && result .getType () == JavaType .Primitive .Long
94+ && isAtViolationLocation (result )) {
95+
6096 final String numericPart = valueSource .substring (0 , valueSource .length () - 1 );
61- final String newValueSource = numericPart + "L" ;
62- result = result .withValueSource (newValueSource );
97+ result = result .withValueSource (numericPart + UPPERCASE_L );
98+ }
99+
100+ return result ;
101+ }
102+
103+ private boolean isAtViolationLocation (J .Literal literal ) {
104+ final J .CompilationUnit cursor = getCursor ().firstEnclosing (J .CompilationUnit .class );
105+
106+ final int line = computeLinePosition (cursor , literal , getCursor ());
107+ final int column = computeColumnPosition (cursor , literal , getCursor ());
108+
109+ return violations .stream ().anyMatch (violation -> {
110+ return violation .getLine () == line
111+ && violation .getColumn () == column
112+ && Path .of (violation .getFileName ()).equals (Path .of (currentFileName ));
113+ });
114+ }
115+
116+ /**
117+ * Computes the position of a target element within a syntax tree using position calculator.
118+ * This method traverses the given syntax tree and captures the printed output until the
119+ * target element is encountered. When the target is found, a CancellationException
120+ * is thrown to interrupt traversal, and the captured output is passed to the provided
121+ * positionCalculator to compute the position.
122+ *
123+ * @param tree the root of the syntax tree to traverse
124+ * @param targetElement the element whose position is to be computed
125+ * @param cursor the current cursor in the tree traversal
126+ * @param positionCalculator a function to compute the position from the printed output
127+ * @return the computed position of the target element
128+ * @throws IllegalStateException if the target element is not found in the tree
129+ * @throws RecipeRunException if an error occurs during traversal
130+ */
131+ private int computePosition (
132+ J tree ,
133+ J targetElement ,
134+ Cursor cursor ,
135+ Function <String , Integer > positionCalculator
136+ ) {
137+ final TreeVisitor <?, PrintOutputCapture <TreeVisitor <?, ?>>> printer =
138+ tree .printer (cursor );
139+
140+ final PrintOutputCapture <TreeVisitor <?, ?>> capture =
141+ new PrintOutputCapture <>(printer ) {
142+ @ Override
143+ public PrintOutputCapture <TreeVisitor <?, ?>> append (String text ) {
144+ if (targetElement .isScope (getContext ().getCursor ().getValue ())) {
145+ super .append (targetElement .getPrefix ().getWhitespace ());
146+ throw new CancellationException ();
147+ }
148+ return super .append (text );
149+ }
150+ };
151+
152+ final int result ;
153+ try {
154+ printer .visit (tree , capture , cursor .getParentOrThrow ());
155+ throw new IllegalStateException ("Target element not found in tree" );
156+ }
157+ catch (CancellationException exception ) {
158+ result = positionCalculator .apply (capture .getOut ());
159+ }
160+ catch (RecipeRunException exception ) {
161+ if (exception .getCause () instanceof CancellationException ) {
162+ result = positionCalculator .apply (capture .getOut ());
163+ }
164+ else {
165+ throw exception ;
166+ }
167+ }
168+ return result ;
169+ }
170+
171+ private int computeLinePosition (J tree , J targetElement , Cursor cursor ) {
172+ return computePosition (tree , targetElement , cursor ,
173+ out -> 1 + (int ) out .chars ().filter (chr -> chr == '\n' ).count ());
174+ }
175+
176+ private int computeColumnPosition (J tree , J targetElement , Cursor cursor ) {
177+ return computePosition (tree , targetElement , cursor , this ::calculateColumnOffset );
178+ }
179+
180+ private int calculateColumnOffset (String out ) {
181+ final int lineBreakIndex = out .lastIndexOf ('\n' );
182+ final int result ;
183+ if (lineBreakIndex == -1 ) {
184+ result = out .length ();
185+ }
186+ else {
187+ result = out .length () - lineBreakIndex - 1 ;
63188 }
64189 return result ;
65190 }
0 commit comments