Skip to content

Commit 9104c05

Browse files
authored
Support textBlock attribute in CsvSourceToValueSource (#989)
* Support `textBlock` attribute in `CsvSourceToValueSource` Resolves #985 * Skip textBlock conversion when values contain single quotes Single quote is CsvSource's default quote character, so converting 'apple' to a ValueSource string literal would change the parsed value. * Use `StringUtils.containsWhitespace` and inline trivial checks Replaces three local helpers with `StringUtils.containsWhitespace` for the whitespace check and inline `String#indexOf` calls for the single- character checks.
1 parent b23a699 commit 9104c05

2 files changed

Lines changed: 236 additions & 18 deletions

File tree

src/main/java/org/openrewrite/java/testing/junit5/CsvSourceToValueSource.java

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.openrewrite.Recipe;
2323
import org.openrewrite.TreeVisitor;
2424
import org.openrewrite.internal.ListUtils;
25+
import org.openrewrite.internal.StringUtils;
2526
import org.openrewrite.java.AnnotationMatcher;
2627
import org.openrewrite.java.JavaIsoVisitor;
2728
import org.openrewrite.java.JavaParser;
@@ -34,6 +35,7 @@
3435
import org.openrewrite.java.tree.Space;
3536
import org.openrewrite.java.tree.TypeUtils;
3637

38+
import java.util.ArrayList;
3739
import java.util.List;
3840
import java.util.Optional;
3941

@@ -74,18 +76,33 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex
7476
for (J.Annotation annotation : m.getLeadingAnnotations()) {
7577
Optional<Annotated> annotated = new Annotated.Matcher(CSV_SOURCE_MATCHER).get(annotation, getCursor());
7678
if (annotated.isPresent() && annotation.getArguments() != null && annotation.getArguments().size() == 1) {
77-
// Skip if textBlock attribute is used
78-
if (annotated.get().getAttribute("textBlock").isPresent()) {
79-
return m;
80-
}
8179
// Get the parameter type
8280
String paramType = getParameterType((J.VariableDeclarations) m.getParameters().get(0));
8381
if (paramType == null) {
8482
return m;
8583
}
8684

87-
// For Strings, merely swap out the annotation
88-
if ("String".equals(paramType)) {
85+
Optional<Literal> textBlockAttribute = annotated.get().getAttribute("textBlock");
86+
List<String> values;
87+
if (textBlockAttribute.isPresent()) {
88+
String textBlock = textBlockAttribute.get().getString();
89+
if (textBlock == null) {
90+
return m;
91+
}
92+
values = parseTextBlockLines(textBlock);
93+
if (values.isEmpty() || values.stream().anyMatch(v -> v.indexOf(',') >= 0)) {
94+
return m;
95+
}
96+
// Single quotes are CsvSource's default quote char; conversion would change the parsed value
97+
if (values.stream().anyMatch(v -> v.indexOf('\'') >= 0)) {
98+
return m;
99+
}
100+
// Non-String types can't represent values containing whitespace as unquoted literals
101+
if (!"String".equals(paramType) && values.stream().anyMatch(StringUtils::containsWhitespace)) {
102+
return m;
103+
}
104+
} else if ("String".equals(paramType)) {
105+
// Preserve original argument formatting (e.g. text blocks, custom spacing)
89106
maybeRemoveImport("org.junit.jupiter.params.provider.CsvSource");
90107
maybeAddImport("org.junit.jupiter.params.provider.ValueSource");
91108
Expression templateArg = annotation.getArguments().get(0) instanceof J.Assignment ?
@@ -96,22 +113,20 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex
96113
.imports("org.junit.jupiter.params.provider.ValueSource")
97114
.build()
98115
.apply(getCursor(), annotation.getCoordinates().replace());
99-
// Retain formatting by swapping in the original argument
100116
return updated.withLeadingAnnotations(ListUtils.map(updated.getLeadingAnnotations(), ann ->
101117
VALUE_SOURCE_MATCHER.matches(ann) ?
102118
ann.withArguments(ListUtils.map(ann.getArguments(),
103119
arg -> ((J.Assignment) arg).withAssignment(templateArg.withPrefix(Space.SINGLE_SPACE)))) :
104120
ann));
105-
}
106-
107-
Optional<Literal> valueAttribute = annotated.get().getDefaultAttribute("value");
108-
if (!valueAttribute.isPresent()) {
109-
return m;
110-
}
111-
List<String> values = valueAttribute.get().getStrings();
112-
// Extract values from CsvSource
113-
if (values.isEmpty()) {
114-
return m;
121+
} else {
122+
Optional<Literal> valueAttribute = annotated.get().getDefaultAttribute("value");
123+
if (!valueAttribute.isPresent()) {
124+
return m;
125+
}
126+
values = valueAttribute.get().getStrings();
127+
if (values.isEmpty()) {
128+
return m;
129+
}
115130
}
116131

117132
// Build a new ValueSource annotation
@@ -139,6 +154,10 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex
139154
String formattedValues;
140155

141156
switch (paramType) {
157+
case "String":
158+
attributeName = "strings";
159+
formattedValues = formatStringValues(values);
160+
break;
142161
case "int":
143162
case "Integer":
144163
attributeName = "ints";
@@ -213,6 +232,24 @@ private String formatCharValues(List<String> values) {
213232
.toArray(String[]::new));
214233
}
215234

235+
private String formatStringValues(List<String> values) {
236+
return String.join(", ", values.stream()
237+
.map(v -> "\"" + v.trim().replace("\\", "\\\\").replace("\"", "\\\"") + "\"")
238+
.toArray(String[]::new));
239+
}
240+
241+
private List<String> parseTextBlockLines(String textBlock) {
242+
List<String> result = new ArrayList<>();
243+
for (String line : textBlock.split("\n")) {
244+
String trimmed = line.trim();
245+
if (trimmed.isEmpty() || trimmed.startsWith("#")) {
246+
continue;
247+
}
248+
result.add(trimmed);
249+
}
250+
return result;
251+
}
252+
216253
private @Nullable String getParameterType(J.VariableDeclarations param) {
217254
if (param.getType() == null) {
218255
return null;

src/test/java/org/openrewrite/java/testing/junit5/CsvSourceToValueSourceTest.java

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,48 @@ void testWithProperCsv(String fruit, int count, String color) {
456456
}
457457

458458
@Test
459-
void skipTextBlock() {
459+
void preserveTextBlockAsValue() {
460+
rewriteRun(
461+
//language=java
462+
java(
463+
"""
464+
import org.junit.jupiter.params.ParameterizedTest;
465+
import org.junit.jupiter.params.provider.CsvSource;
466+
467+
class TestClass {
468+
@ParameterizedTest
469+
@CsvSource(\"""
470+
# Lower-case letters
471+
asdf
472+
a
473+
\""")
474+
void testWithStrings(String fruit) {
475+
System.out.println(fruit);
476+
}
477+
}
478+
""",
479+
"""
480+
import org.junit.jupiter.params.ParameterizedTest;
481+
import org.junit.jupiter.params.provider.ValueSource;
482+
483+
class TestClass {
484+
@ParameterizedTest
485+
@ValueSource(strings = \"""
486+
# Lower-case letters
487+
asdf
488+
a
489+
\""")
490+
void testWithStrings(String fruit) {
491+
System.out.println(fruit);
492+
}
493+
}
494+
"""
495+
)
496+
);
497+
}
498+
499+
@Test
500+
void replaceTextBlockAttribute() {
460501
rewriteRun(
461502
//language=java
462503
java(
@@ -475,6 +516,146 @@ void testWithStrings(String fruit) {
475516
System.out.println(fruit);
476517
}
477518
}
519+
""",
520+
"""
521+
import org.junit.jupiter.params.ParameterizedTest;
522+
import org.junit.jupiter.params.provider.ValueSource;
523+
524+
class TestClass {
525+
@ParameterizedTest
526+
@ValueSource(strings = {"apple", "banana", "cherry"})
527+
void testWithStrings(String fruit) {
528+
System.out.println(fruit);
529+
}
530+
}
531+
"""
532+
)
533+
);
534+
}
535+
536+
@Test
537+
void replaceTextBlockAttributeIgnoringCommentsAndBlankLines() {
538+
rewriteRun(
539+
//language=java
540+
java(
541+
"""
542+
import org.junit.jupiter.params.ParameterizedTest;
543+
import org.junit.jupiter.params.provider.CsvSource;
544+
545+
class TestClass {
546+
@ParameterizedTest
547+
@CsvSource(textBlock = \"""
548+
# Lower-case letters
549+
asdf
550+
a
551+
552+
# Numbers
553+
1
554+
123
555+
\""")
556+
void testWithStrings(String fruit) {
557+
System.out.println(fruit);
558+
}
559+
}
560+
""",
561+
"""
562+
import org.junit.jupiter.params.ParameterizedTest;
563+
import org.junit.jupiter.params.provider.ValueSource;
564+
565+
class TestClass {
566+
@ParameterizedTest
567+
@ValueSource(strings = {"asdf", "a", "1", "123"})
568+
void testWithStrings(String fruit) {
569+
System.out.println(fruit);
570+
}
571+
}
572+
"""
573+
)
574+
);
575+
}
576+
577+
@Test
578+
void replaceTextBlockAttributeForIntegers() {
579+
rewriteRun(
580+
//language=java
581+
java(
582+
"""
583+
import org.junit.jupiter.params.ParameterizedTest;
584+
import org.junit.jupiter.params.provider.CsvSource;
585+
586+
class TestClass {
587+
@ParameterizedTest
588+
@CsvSource(textBlock = \"""
589+
# numbers
590+
1
591+
2
592+
3
593+
\""")
594+
void testWithIntegers(int number) {
595+
System.out.println(number);
596+
}
597+
}
598+
""",
599+
"""
600+
import org.junit.jupiter.params.ParameterizedTest;
601+
import org.junit.jupiter.params.provider.ValueSource;
602+
603+
class TestClass {
604+
@ParameterizedTest
605+
@ValueSource(ints = {1, 2, 3})
606+
void testWithIntegers(int number) {
607+
System.out.println(number);
608+
}
609+
}
610+
"""
611+
)
612+
);
613+
}
614+
615+
@Test
616+
void doNotReplaceTextBlockWithSingleQuotes() {
617+
rewriteRun(
618+
//language=java
619+
java(
620+
"""
621+
import org.junit.jupiter.params.ParameterizedTest;
622+
import org.junit.jupiter.params.provider.CsvSource;
623+
624+
class TestClass {
625+
@ParameterizedTest
626+
@CsvSource(textBlock = \"""
627+
'apple'
628+
banana
629+
\""")
630+
void testWithStrings(String fruit) {
631+
System.out.println(fruit);
632+
}
633+
}
634+
"""
635+
)
636+
);
637+
}
638+
639+
@Test
640+
void doNotReplaceTextBlockWithMultipleColumns() {
641+
rewriteRun(
642+
//language=java
643+
java(
644+
"""
645+
import org.junit.jupiter.params.ParameterizedTest;
646+
import org.junit.jupiter.params.provider.CsvSource;
647+
648+
class TestClass {
649+
@ParameterizedTest
650+
@CsvSource(textBlock = \"""
651+
apple, 1
652+
banana, 2
653+
cherry, 3
654+
\""")
655+
void testWithMultipleParams(String fruit, int count) {
656+
System.out.println(fruit + ": " + count);
657+
}
658+
}
478659
"""
479660
)
480661
);

0 commit comments

Comments
 (0)