Skip to content

Commit b155248

Browse files
AddOrUpdateAnnotationAttribute respects appendArray with string literals (#6598)
* AddOrUpdateAnnotationAttribute respects appendArray with string literals When appendArray=true and the existing annotation value is a string literal (not already an array), the recipe now converts it to an array and appends the new values instead of replacing. Before: @foo(bars = "abc") + appendArray=true + value="xyz" → @foo(bars = "xyz") After: @foo(bars = "abc") + appendArray=true + value="xyz" → @foo(bars = {"abc", "xyz"}) Fixes moderneinc/customer-requests#1410 * Fix for test that somehow didn't run
1 parent c0589e4 commit b155248

2 files changed

Lines changed: 143 additions & 1 deletion

File tree

rewrite-java-test/src/test/java/org/openrewrite/java/AddOrUpdateAnnotationAttributeTest.java

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,8 @@ void arrayInputMoreThanOneInAnnotationLiteralAttribute() {
728728
"newTest1,newTest2",
729729
null,
730730
false,
731-
true)),
731+
false
732+
)),
732733
java(
733734
"""
734735
package org.example;
@@ -2365,4 +2366,120 @@ public class A {
23652366
);
23662367
}
23672368

2369+
@Test
2370+
void respectsAppendArrayWhenGoingFromLiteralToArraySingle() {
2371+
rewriteRun(
2372+
spec -> spec.recipe(new AddOrUpdateAnnotationAttribute(
2373+
"org.example.Foo",
2374+
"bars",
2375+
"xyz",
2376+
null,
2377+
null,
2378+
true
2379+
)),
2380+
//language=java
2381+
java(
2382+
"""
2383+
package org.example;
2384+
public @interface Foo {
2385+
String[] bars() default {};
2386+
}
2387+
""",
2388+
SourceSpec::skip
2389+
),
2390+
//language=java
2391+
java(
2392+
"""
2393+
import org.example.Foo;
2394+
2395+
@Foo(bars = "abc")
2396+
public class A {}
2397+
""",
2398+
"""
2399+
import org.example.Foo;
2400+
2401+
@Foo(bars = {"abc", "xyz"})
2402+
public class A {}
2403+
"""
2404+
)
2405+
);
2406+
}
2407+
2408+
@Test
2409+
void respectsAppendArrayWhenGoingFromLiteralToArrayMulti() {
2410+
rewriteRun(
2411+
spec -> spec.recipe(new AddOrUpdateAnnotationAttribute(
2412+
"org.example.Foo",
2413+
"bars",
2414+
"xyz,def",
2415+
null,
2416+
null,
2417+
true
2418+
)),
2419+
//language=java
2420+
java(
2421+
"""
2422+
package org.example;
2423+
public @interface Foo {
2424+
String[] bars() default {};
2425+
}
2426+
""",
2427+
SourceSpec::skip
2428+
),
2429+
//language=java
2430+
java(
2431+
"""
2432+
import org.example.Foo;
2433+
2434+
@Foo(bars = "abc")
2435+
public class A {}
2436+
""",
2437+
"""
2438+
import org.example.Foo;
2439+
2440+
@Foo(bars = {"abc", "xyz", "def"})
2441+
public class A {}
2442+
"""
2443+
)
2444+
);
2445+
}
2446+
2447+
@Test
2448+
void respectsAppendArrayWhenGoingFromImplicitLiteralValueToArray() {
2449+
rewriteRun(
2450+
spec -> spec.recipe(new AddOrUpdateAnnotationAttribute(
2451+
"org.example.Foo",
2452+
null,
2453+
"xyz",
2454+
null,
2455+
null,
2456+
true
2457+
)),
2458+
//language=java
2459+
java(
2460+
"""
2461+
package org.example;
2462+
public @interface Foo {
2463+
String[] value() default {};
2464+
}
2465+
""",
2466+
SourceSpec::skip
2467+
),
2468+
//language=java
2469+
java(
2470+
"""
2471+
import org.example.Foo;
2472+
2473+
@Foo("abc")
2474+
public class A {}
2475+
""",
2476+
"""
2477+
import org.example.Foo;
2478+
2479+
@Foo({"abc", "xyz"})
2480+
public class A {}
2481+
"""
2482+
)
2483+
);
2484+
}
23682485
}

rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ public J.Annotation visitAnnotation(J.Annotation original, ExecutionContext ctx)
171171
if (!valueMatches(exp, oldAttributeValue) || newAttributeValue.equals(((J.Literal) exp).getValueSource())) {
172172
return as;
173173
}
174+
// If appendArray is true and attribute is an array, convert literal to array and append
175+
if (TRUE.equals(appendArray) && attributeIsArray(annotation)) {
176+
return as.withAssignment(createNewArrayWithExistingAndNew(annotation, (J.Literal) exp, getAttributeValues()));
177+
}
174178
return as.withAssignment(createAnnotationLiteral(annotation, newAttributeValue));
175179
}
176180
if (exp instanceof J.FieldAccess) {
@@ -196,6 +200,10 @@ public J.Annotation visitAnnotation(J.Annotation original, ExecutionContext ctx)
196200
if (!valueMatches(literal, oldAttributeValue) || newAttributeValue.equals(literal.getValueSource())) {
197201
return literal;
198202
}
203+
// If appendArray is true and attribute is an array, convert literal to array and append
204+
if (TRUE.equals(appendArray) && attributeIsArray(annotation)) {
205+
return createNewArrayWithExistingAndNew(annotation, literal, getAttributeValues());
206+
}
199207
return createAnnotationLiteral(annotation, newAttributeValue);
200208
}
201209
if (oldAttributeValue == null && newAttributeValue != null) {
@@ -251,6 +259,23 @@ private J.Assignment createAnnotationAssignment(J.Annotation annotation, String
251259
return (J.Assignment) JavaTemplate.<J.Annotation>apply(name + " = " + (parameter instanceof J ? "#{any()}" : "#{}"), getCursor(), annotation.getCoordinates().replaceArguments(), parameter)
252260
.getArguments().get(0);
253261
}
262+
263+
private J.NewArray createNewArrayWithExistingAndNew(J.Annotation annotation, J.Literal existingLiteral, List<String> newValues) {
264+
List<Expression> initializer = new ArrayList<>();
265+
// Add the existing literal value first
266+
initializer.add(existingLiteral.withPrefix(SINGLE_SPACE));
267+
// Add new values, skipping duplicates
268+
for (String attribute : newValues) {
269+
if (!attributeNameOrValIsAlreadyPresent(initializer, singleton(attribute))) {
270+
initializer.add(new J.Literal(randomId(), SINGLE_SPACE, EMPTY, attribute, maybeQuoteStringArgument(annotation, attribute), null, JavaType.Primitive.String));
271+
}
272+
}
273+
// Use a template to create the array structure, then replace the initializer
274+
//noinspection ConstantConditions
275+
J.NewArray template = (J.NewArray) JavaTemplate.<J.Annotation>apply("{#{any()}}", getCursor(), annotation.getCoordinates().replaceArguments(), existingLiteral)
276+
.getArguments().get(0);
277+
return template.withInitializer(initializer);
278+
}
254279
});
255280
}
256281

0 commit comments

Comments
 (0)