Avoid imports from current package

Closes gh-1421
This commit is contained in:
Stephane Nicoll
2023-06-07 15:44:48 +02:00
parent 8acbad503a
commit 4c77196504
6 changed files with 132 additions and 104 deletions

View File

@@ -39,6 +39,7 @@ import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock.FormattingOptions;
import io.spring.initializr.generator.language.CompilationUnit;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.SourceCode;
import io.spring.initializr.generator.language.SourceCodeWriter;
@@ -259,34 +260,30 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
private Set<String> determineImports(GroovyCompilationUnit compilationUnit) {
List<String> imports = new ArrayList<>();
for (GroovyTypeDeclaration typeDeclaration : compilationUnit.getTypeDeclarations()) {
if (requiresImport(typeDeclaration.getExtends())) {
imports.add(typeDeclaration.getExtends());
}
imports.addAll(getRequiredImports(typeDeclaration.getAnnotations(), this::determineImports));
imports.add(typeDeclaration.getExtends());
imports.addAll(appendImports(typeDeclaration.getAnnotations(), this::determineImports));
for (GroovyFieldDeclaration fieldDeclaration : typeDeclaration.getFieldDeclarations()) {
if (requiresImport(fieldDeclaration.getReturnType())) {
imports.add(fieldDeclaration.getReturnType());
}
imports.addAll(getRequiredImports(fieldDeclaration.getAnnotations(), this::determineImports));
imports.add(fieldDeclaration.getReturnType());
imports.addAll(appendImports(fieldDeclaration.getAnnotations(), this::determineImports));
}
for (GroovyMethodDeclaration methodDeclaration : typeDeclaration.getMethodDeclarations()) {
if (requiresImport(methodDeclaration.getReturnType())) {
imports.add(methodDeclaration.getReturnType());
}
imports.addAll(getRequiredImports(methodDeclaration.getAnnotations(), this::determineImports));
imports.addAll(getRequiredImports(methodDeclaration.getParameters(),
imports.add(methodDeclaration.getReturnType());
imports.addAll(appendImports(methodDeclaration.getAnnotations(), this::determineImports));
imports.addAll(appendImports(methodDeclaration.getParameters(),
(parameter) -> Collections.singletonList(parameter.getType())));
imports.addAll(methodDeclaration.getCode().getImports());
determineImportsFromStatements(imports, methodDeclaration);
}
}
Collections.sort(imports);
return new LinkedHashSet<>(imports);
return imports.stream()
.filter((candidate) -> isImportCandidate(compilationUnit, candidate))
.sorted()
.collect(Collectors.toCollection(LinkedHashSet::new));
}
@SuppressWarnings("removal")
private void determineImportsFromStatements(List<String> imports, GroovyMethodDeclaration methodDeclaration) {
imports.addAll(getRequiredImports(
imports.addAll(appendImports(
methodDeclaration.getStatements()
.stream()
.filter(GroovyExpressionStatement.class::isInstance)
@@ -314,15 +311,12 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
return imports;
}
private <T> List<String> getRequiredImports(List<T> candidates, Function<T, Collection<String>> mapping) {
return getRequiredImports(candidates.stream(), mapping);
private <T> List<String> appendImports(List<T> candidates, Function<T, Collection<String>> mapping) {
return appendImports(candidates.stream(), mapping);
}
private <T> List<String> getRequiredImports(Stream<T> candidates, Function<T, Collection<String>> mapping) {
return candidates.map(mapping)
.flatMap(Collection::stream)
.filter(this::requiresImport)
.collect(Collectors.toList());
private <T> List<String> appendImports(Stream<T> candidates, Function<T, Collection<String>> mapping) {
return candidates.map(mapping).flatMap(Collection::stream).collect(Collectors.toList());
}
private String getUnqualifiedName(String name) {
@@ -332,12 +326,12 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
return name.substring(name.lastIndexOf(".") + 1);
}
private boolean requiresImport(String name) {
private boolean isImportCandidate(CompilationUnit<?> compilationUnit, String name) {
if (name == null || !name.contains(".")) {
return false;
}
String packageName = name.substring(0, name.lastIndexOf('.'));
return !"java.lang".equals(packageName);
return !"java.lang".equals(packageName) && !compilationUnit.getPackageName().equals(packageName);
}
static class GroovyFormattingOptions implements FormattingOptions {

View File

@@ -39,6 +39,7 @@ import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.CompilationUnit;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.SourceCode;
import io.spring.initializr.generator.language.SourceCodeWriter;
@@ -260,34 +261,31 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
private Set<String> determineImports(JavaCompilationUnit compilationUnit) {
List<String> imports = new ArrayList<>();
for (JavaTypeDeclaration typeDeclaration : compilationUnit.getTypeDeclarations()) {
if (requiresImport(typeDeclaration.getExtends())) {
imports.add(typeDeclaration.getExtends());
}
imports.addAll(getRequiredImports(typeDeclaration.getAnnotations(), this::determineImports));
imports.add(typeDeclaration.getExtends());
imports.addAll(appendImports(typeDeclaration.getAnnotations(), this::determineImports));
for (JavaFieldDeclaration fieldDeclaration : typeDeclaration.getFieldDeclarations()) {
if (requiresImport(fieldDeclaration.getReturnType())) {
imports.add(fieldDeclaration.getReturnType());
}
imports.addAll(getRequiredImports(fieldDeclaration.getAnnotations(), this::determineImports));
imports.add(fieldDeclaration.getReturnType());
imports.addAll(appendImports(fieldDeclaration.getAnnotations(), this::determineImports));
}
for (JavaMethodDeclaration methodDeclaration : typeDeclaration.getMethodDeclarations()) {
if (requiresImport(methodDeclaration.getReturnType())) {
imports.add(methodDeclaration.getReturnType());
}
imports.addAll(getRequiredImports(methodDeclaration.getAnnotations(), this::determineImports));
imports.addAll(getRequiredImports(methodDeclaration.getParameters(),
imports.add(methodDeclaration.getReturnType());
imports.addAll(appendImports(methodDeclaration.getAnnotations(), this::determineImports));
imports.addAll(appendImports(methodDeclaration.getParameters(),
(parameter) -> Collections.singletonList(parameter.getType())));
determineImportsFromStatements(imports, methodDeclaration);
imports.addAll(methodDeclaration.getCode().getImports());
}
}
Collections.sort(imports);
return new LinkedHashSet<>(imports);
return imports.stream()
.filter((candidate) -> isImportCandidate(compilationUnit, candidate))
.sorted()
.collect(Collectors.toCollection(LinkedHashSet::new));
}
@SuppressWarnings("removal")
private void determineImportsFromStatements(List<String> imports, JavaMethodDeclaration methodDeclaration) {
imports.addAll(getRequiredImports(
imports.addAll(appendImports(
methodDeclaration.getStatements()
.stream()
.filter(JavaExpressionStatement.class::isInstance)
@@ -315,15 +313,12 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
return imports;
}
private <T> List<String> getRequiredImports(List<T> candidates, Function<T, Collection<String>> mapping) {
return getRequiredImports(candidates.stream(), mapping);
private <T> List<String> appendImports(List<T> candidates, Function<T, Collection<String>> mapping) {
return appendImports(candidates.stream(), mapping);
}
private <T> List<String> getRequiredImports(Stream<T> candidates, Function<T, Collection<String>> mapping) {
return candidates.map(mapping)
.flatMap(Collection::stream)
.filter(this::requiresImport)
.collect(Collectors.toList());
private <T> List<String> appendImports(Stream<T> candidates, Function<T, Collection<String>> mapping) {
return candidates.map(mapping).flatMap(Collection::stream).collect(Collectors.toList());
}
private String getUnqualifiedName(String name) {
@@ -333,12 +328,12 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
return name.substring(name.lastIndexOf(".") + 1);
}
private boolean requiresImport(String name) {
private boolean isImportCandidate(CompilationUnit<?> compilationUnit, String name) {
if (name == null || !name.contains(".")) {
return false;
}
String packageName = name.substring(0, name.lastIndexOf('.'));
return !"java.lang".equals(packageName);
return !"java.lang".equals(packageName) && !compilationUnit.getPackageName().equals(packageName);
}
}

View File

@@ -35,6 +35,7 @@ import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock.FormattingOptions;
import io.spring.initializr.generator.language.CompilationUnit;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.SourceCode;
import io.spring.initializr.generator.language.SourceCodeWriter;
@@ -299,10 +300,8 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
private Set<String> determineImports(KotlinCompilationUnit compilationUnit) {
List<String> imports = new ArrayList<>();
for (KotlinTypeDeclaration typeDeclaration : compilationUnit.getTypeDeclarations()) {
if (requiresImport(typeDeclaration.getExtends())) {
imports.add(typeDeclaration.getExtends());
}
imports.addAll(getRequiredImports(typeDeclaration.getAnnotations(), this::determineImports));
imports.add(typeDeclaration.getExtends());
imports.addAll(appendImports(typeDeclaration.getAnnotations(), this::determineImports));
typeDeclaration.getPropertyDeclarations()
.forEach(((propertyDeclaration) -> imports.addAll(determinePropertyImports(propertyDeclaration))));
typeDeclaration.getFunctionDeclarations()
@@ -310,32 +309,29 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
}
compilationUnit.getTopLevelFunctions()
.forEach((functionDeclaration) -> imports.addAll(determineFunctionImports(functionDeclaration)));
Collections.sort(imports);
return new LinkedHashSet<>(imports);
return imports.stream()
.filter((candidate) -> isImportCandidate(compilationUnit, candidate))
.sorted()
.collect(Collectors.toCollection(LinkedHashSet::new));
}
private Set<String> determinePropertyImports(KotlinPropertyDeclaration propertyDeclaration) {
Set<String> imports = new LinkedHashSet<>();
if (requiresImport(propertyDeclaration.getReturnType())) {
imports.add(propertyDeclaration.getReturnType());
}
return imports;
return (propertyDeclaration.getReturnType() != null) ? Set.of(propertyDeclaration.getReturnType())
: Collections.emptySet();
}
private Set<String> determineFunctionImports(KotlinFunctionDeclaration functionDeclaration) {
Set<String> imports = new LinkedHashSet<>();
if (requiresImport(functionDeclaration.getReturnType())) {
imports.add(functionDeclaration.getReturnType());
}
imports.addAll(getRequiredImports(functionDeclaration.getAnnotations(), this::determineImports));
imports.addAll(getRequiredImports(functionDeclaration.getParameters(),
imports.add(functionDeclaration.getReturnType());
imports.addAll(appendImports(functionDeclaration.getAnnotations(), this::determineImports));
imports.addAll(appendImports(functionDeclaration.getParameters(),
(parameter) -> Collections.singleton(parameter.getType())));
imports.addAll(functionDeclaration.getCode().getImports());
imports.addAll(getRequiredImports(
imports.addAll(appendImports(
getKotlinExpressions(functionDeclaration).filter(KotlinFunctionInvocation.class::isInstance)
.map(KotlinFunctionInvocation.class::cast),
(invocation) -> Collections.singleton(invocation.getTarget())));
imports.addAll(getRequiredImports(
imports.addAll(appendImports(
getKotlinExpressions(functionDeclaration).filter(KotlinReifiedFunctionInvocation.class::isInstance)
.map(KotlinReifiedFunctionInvocation.class::cast),
(invocation) -> Collections.singleton(invocation.getName())));
@@ -368,15 +364,12 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
.map(KotlinExpressionStatement::getExpression);
}
private <T> List<String> getRequiredImports(List<T> candidates, Function<T, Collection<String>> mapping) {
return getRequiredImports(candidates.stream(), mapping);
private <T> List<String> appendImports(List<T> candidates, Function<T, Collection<String>> mapping) {
return appendImports(candidates.stream(), mapping);
}
private <T> List<String> getRequiredImports(Stream<T> candidates, Function<T, Collection<String>> mapping) {
return candidates.map(mapping)
.flatMap(Collection::stream)
.filter(this::requiresImport)
.collect(Collectors.toList());
private <T> List<String> appendImports(Stream<T> candidates, Function<T, Collection<String>> mapping) {
return candidates.map(mapping).flatMap(Collection::stream).collect(Collectors.toList());
}
private String getUnqualifiedName(String name) {
@@ -386,12 +379,12 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
return name.substring(name.lastIndexOf(".") + 1);
}
private boolean requiresImport(String name) {
private boolean isImportCandidate(CompilationUnit<?> compilationUnit, String name) {
if (name == null || !name.contains(".")) {
return false;
}
String packageName = name.substring(0, name.lastIndexOf('.'));
return !"java.lang".equals(packageName);
return !"java.lang".equals(packageName) && !compilationUnit.getPackageName().equals(packageName);
}
static class KotlinFormattingOptions implements FormattingOptions {

View File

@@ -137,6 +137,20 @@ class GroovySourceCodeWriterTests {
" String trim(String value) {", " value.trim()", " }", "", "}");
}
@Test
void importsFromSamePackageAreDiscarded() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addFieldDeclaration(GroovyFieldDeclaration.field("another").returning("com.example.Another"));
test.addFieldDeclaration(GroovyFieldDeclaration.field("sibling").returning("com.example.Sibling"));
test.addFieldDeclaration(GroovyFieldDeclaration.field("external").returning("com.example.another.External"));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).doesNotContain("import com.example.Another")
.doesNotContain("import com.example.Sibling")
.contains("import com.example.another.External");
}
@Test
void springBootApplication() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
@@ -205,11 +219,12 @@ class GroovySourceCodeWriterTests {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addFieldDeclaration(
GroovyFieldDeclaration.field("testString").modifiers(Modifier.PUBLIC).returning("com.example.One"));
test.addFieldDeclaration(GroovyFieldDeclaration.field("testString")
.modifiers(Modifier.PUBLIC)
.returning("com.example.another.One"));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "", "import com.example.One", "", "class Test {", "",
" public One testString", "", "}");
assertThat(lines).containsExactly("package com.example", "", "import com.example.another.One", "",
"class Test {", "", " public One testString", "", "}");
}
@Test
@@ -261,11 +276,12 @@ class GroovySourceCodeWriterTests {
@Test
void annotationWithClassArrayAttribute() throws IOException {
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("target", Class.class, "com.example.One", "com.example.Two")));
assertThat(lines).containsExactly("package com.example", "", "import com.example.One", "import com.example.Two",
"import org.springframework.test.TestApplication", "", "@TestApplication(target = [ One, Two ])",
"class Test {", "", "}");
List<String> lines = writeClassAnnotation(
Annotation.name("org.springframework.test.TestApplication", (builder) -> builder.attribute("target",
Class.class, "com.example.another.One", "com.example.another.Two")));
assertThat(lines).containsExactly("package com.example", "", "import com.example.another.One",
"import com.example.another.Two", "import org.springframework.test.TestApplication", "",
"@TestApplication(target = [ One, Two ])", "class Test {", "", "}");
}
@Test
@@ -273,8 +289,8 @@ class GroovySourceCodeWriterTests {
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("target", Class.class, "com.example.One")
.attribute("unit", ChronoUnit.class, "java.time.temporal.ChronoUnit.NANOS")));
assertThat(lines).containsExactly("package com.example", "", "import com.example.One",
"import java.time.temporal.ChronoUnit", "import org.springframework.test.TestApplication", "",
assertThat(lines).containsExactly("package com.example", "", "import java.time.temporal.ChronoUnit",
"import org.springframework.test.TestApplication", "",
"@TestApplication(target = One, unit = ChronoUnit.NANOS)", "class Test {", "", "}");
}

View File

@@ -157,9 +157,9 @@ class JavaSourceCodeWriterTests {
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addFieldDeclaration(
JavaFieldDeclaration.field("testString").modifiers(Modifier.PUBLIC).returning("com.example.One"));
JavaFieldDeclaration.field("testString").modifiers(Modifier.PUBLIC).returning("com.another.One"));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
assertThat(lines).containsExactly("package com.example;", "", "import com.example.One;", "", "class Test {", "",
assertThat(lines).containsExactly("package com.example;", "", "import com.another.One;", "", "class Test {", "",
" public One testString;", "", "}");
}
@@ -213,6 +213,20 @@ class JavaSourceCodeWriterTests {
" public float testFloat = 99.999f;", "", " boolean testBool = true;", "", "}");
}
@Test
void importsFromSamePackageAreDiscarded() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addFieldDeclaration(JavaFieldDeclaration.field("another").returning("com.example.Another"));
test.addFieldDeclaration(JavaFieldDeclaration.field("sibling").returning("com.example.Sibling"));
test.addFieldDeclaration(JavaFieldDeclaration.field("external").returning("com.example.another.External"));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
assertThat(lines).doesNotContain("import com.example.Another;")
.doesNotContain("import com.example.Sibling;")
.contains("import com.example.another.External;");
}
@Test
void springBootApplication() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();
@@ -272,9 +286,9 @@ class JavaSourceCodeWriterTests {
@Test
void annotationWithClassArrayAttribute() throws IOException {
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("target", Class.class, "com.example.One", "com.example.Two")));
assertThat(lines).containsExactly("package com.example;", "", "import com.example.One;",
"import com.example.Two;", "import org.springframework.test.TestApplication;", "",
(builder) -> builder.attribute("target", Class.class, "com.another.One", "com.another.Two")));
assertThat(lines).containsExactly("package com.example;", "", "import com.another.One;",
"import com.another.Two;", "import org.springframework.test.TestApplication;", "",
"@TestApplication(target = { One.class, Two.class })", "class Test {", "", "}");
}
@@ -283,8 +297,8 @@ class JavaSourceCodeWriterTests {
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("target", Class.class, "com.example.One")
.attribute("unit", ChronoUnit.class, "java.time.temporal.ChronoUnit.NANOS")));
assertThat(lines).containsExactly("package com.example;", "", "import com.example.One;",
"import java.time.temporal.ChronoUnit;", "import org.springframework.test.TestApplication;", "",
assertThat(lines).containsExactly("package com.example;", "", "import java.time.temporal.ChronoUnit;",
"import org.springframework.test.TestApplication;", "",
"@TestApplication(target = One.class, unit = ChronoUnit.NANOS)", "class Test {", "", "}");
}

View File

@@ -168,10 +168,10 @@ class KotlinSourceCodeWriterTests {
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addPropertyDeclaration(
KotlinPropertyDeclaration.val("testProp").returning("com.example.One").emptyValue());
KotlinPropertyDeclaration.val("testProp").returning("com.example.another.One").emptyValue());
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "import com.example.One", "", "class Test {", "",
" val testProp: One", "", "}");
assertThat(lines).containsExactly("package com.example", "", "import com.example.another.One", "",
"class Test {", "", " val testProp: One", "", "}");
}
@Test
@@ -295,6 +295,21 @@ class KotlinSourceCodeWriterTests {
" lateinit var testProp: Int", "", "}");
}
@Test
void importsFromSamePackageAreDiscarded() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addPropertyDeclaration(KotlinPropertyDeclaration.var("another").returning("com.example.Another").empty());
test.addPropertyDeclaration(KotlinPropertyDeclaration.var("sibling").returning("com.example.Sibling").empty());
test.addPropertyDeclaration(
KotlinPropertyDeclaration.var("external").returning("com.example.another.External").empty());
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).doesNotContain("import com.example.Another")
.doesNotContain("import com.example.Sibling")
.contains("import com.example.another.External");
}
@Test
void springBootApplication() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
@@ -346,10 +361,11 @@ class KotlinSourceCodeWriterTests {
@Test
void annotationWithClassArrayAttribute() throws IOException {
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("target", Class.class, "com.example.One", "com.example.Two")));
assertThat(lines).containsExactly("package com.example", "", "import com.example.One", "import com.example.Two",
"import org.springframework.test.TestApplication", "",
List<String> lines = writeClassAnnotation(
Annotation.name("org.springframework.test.TestApplication", (builder) -> builder.attribute("target",
Class.class, "com.example.another.One", "com.example.another.Two")));
assertThat(lines).containsExactly("package com.example", "", "import com.example.another.One",
"import com.example.another.Two", "import org.springframework.test.TestApplication", "",
"@TestApplication(target = [One::class, Two::class])", "class Test");
}
@@ -358,8 +374,8 @@ class KotlinSourceCodeWriterTests {
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("target", Class.class, "com.example.One")
.attribute("unit", ChronoUnit.class, "java.time.temporal.ChronoUnit.NANOS")));
assertThat(lines).containsExactly("package com.example", "", "import com.example.One",
"import java.time.temporal.ChronoUnit", "import org.springframework.test.TestApplication", "",
assertThat(lines).containsExactly("package com.example", "", "import java.time.temporal.ChronoUnit",
"import org.springframework.test.TestApplication", "",
"@TestApplication(target = One::class, unit = ChronoUnit.NANOS)", "class Test");
}