Compare commits

...

21 Commits

Author SHA1 Message Date
Stephan Schroevers
1971d00dd0 WIP: A check 2023-09-19 21:41:57 +02:00
Stephan Schroevers
4b5f985f59 Test more 2023-09-18 21:52:21 +02:00
Stephan Schroevers
8184a2565a Simplify 2023-09-18 21:41:12 +02:00
Stephan Schroevers
b9557c7612 Checkpoint 2023-09-18 21:20:04 +02:00
Stephan Schroevers
a925289854 Simplify 2023-09-18 19:20:51 +02:00
Stephan Schroevers
7d4f71d806 Simplify 2023-09-18 18:46:42 +02:00
Stephan Schroevers
74ccae2d71 Tweaks 2023-09-17 20:59:05 +02:00
Stephan Schroevers
29b06ba502 X 2023-09-17 15:41:32 +02:00
Stephan Schroevers
a6999e3c32 WIP 2023-09-17 14:38:22 +02:00
Stephan Schroevers
49b5d2f51f WIP 2023-09-16 12:53:16 +02:00
Stephan Schroevers
77501067b5 WIP 2023-09-16 11:51:23 +02:00
Stephan Schroevers
6423d397fa Suggestions 2023-09-07 19:13:30 +02:00
Rick Ossendrijver
245dcf903a Drop another line 2023-09-07 18:46:55 +02:00
Rick Ossendrijver
e98cc88c2d Further improvements 2023-09-07 18:46:55 +02:00
Rick Ossendrijver
0f24503754 Revert to Mo s suggestion 2023-09-07 18:46:55 +02:00
Rick Ossendrijver
bec7cddcb6 Format 2023-09-07 18:46:55 +02:00
Rick Ossendrijver
7f78ac52fe Apply suggestions 2023-09-07 18:46:55 +02:00
Rick Ossendrijver
1d2d286e07 Tweak 2023-09-07 18:46:55 +02:00
Rick Ossendrijver
7e649bd9ae Tweaks 2023-09-07 18:46:55 +02:00
Rick Ossendrijver
747af1b8d1 Suggestions 2023-09-07 18:46:55 +02:00
mohamedsamehsalah
ffeeeee7a5 Exclude classes annotated with lombok's @Data from DirectReturn bug checker
While here, add a new Matcher that determines whether a class is annotated with lombok's @Data.
2023-09-07 18:46:55 +02:00
9 changed files with 442 additions and 9 deletions

View File

@@ -83,5 +83,10 @@
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,13 @@
package tech.picnic.errorprone.documentation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.jspecify.annotations.Nullable;
// XXX: Rename all this stuff.
@Data
@SuppressWarnings("EqualsMissingNullable") // XXX: Drop after EP upgrade.
final class LombokCanary {
@JsonProperty("custom_field_name")
private @Nullable String field;
}

View File

@@ -181,6 +181,11 @@
<artifactId>mongodb-driver-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>

View File

@@ -51,6 +51,11 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
// if (!isSourceAccurateCodeAvailable(tree, state)) {
// /* Without the source code there's not much we can do. */
// return Description.NO_MATCH;
// }
return FIX_FACTORIES.stream()
.map(op -> op.apply(tree, state))
.flatMap(Optional::stream)
@@ -59,6 +64,14 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
.orElse(Description.NO_MATCH);
}
// XXX: Explain, link to Lombok issue.
// private static boolean isSourceAccurateCodeAvailable(AnnotationTree tree, VisitorState state)
// {
// String source = state.getSourceForNode(tree);
// String parentSource = state.getSourceForNode(state.getPath().getParentPath().getLeaf());
// return source != null && parentSource != null && parentSource.contains(source);
// }
private static Optional<Fix> dropRedundantParentheses(AnnotationTree tree, VisitorState state) {
if (!tree.getArguments().isEmpty()) {
/* Parentheses are necessary. */
@@ -98,8 +111,8 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
}
ExpressionTree expr = AnnotationMatcherUtils.getArgument(tree, "value");
if (expr == null) {
/* This is not an explicit assignment to the `value` attribute. */
if (expr == null || !SourceCode.isAccurateSourceLikelyAvailable(state)) {
/* This is (probably) not an explicit assignment to the `value` attribute. */
return Optional.empty();
}

View File

@@ -0,0 +1,84 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Verify.verify;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static java.util.stream.Collectors.joining;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that files for which no accurate source code appears to be available. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "This type's source code is unavailable or inaccurate",
link = BUG_PATTERNS_BASE_URL + "SourceCodeAvailability",
linkType = CUSTOM,
severity = SUGGESTION)
public final class SourceCodeAvailability extends BugChecker implements CompilationUnitTreeMatcher {
private static final long serialVersionUID = 1L;
/** Instantiates a new {@link SourceCodeAvailability} instance. */
public SourceCodeAvailability() {}
@Override
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
Deque<Boolean> seenAccurateSource = new ArrayDeque<>();
Set<Tree> maximalAccurateSubtrees = new LinkedHashSet<>();
new TreeScanner<@Nullable Void, TreePath>() {
@Override
public @Nullable Void scan(Tree node, TreePath treePath) {
if (node == null) {
return null;
}
TreePath path = new TreePath(treePath, node);
boolean isAccurate = SourceCode.isAccurateSourceLikelyAvailable(state.withPath(path));
if (!isAccurate) {
verify(!Boolean.TRUE.equals(seenAccurateSource.peek()));
} else if (!Boolean.TRUE.equals(seenAccurateSource.peek())) {
maximalAccurateSubtrees.add(node);
}
seenAccurateSource.push(isAccurate);
try {
return super.scan(node, path);
} finally {
seenAccurateSource.pop();
}
}
}.scan(tree, state.getPath());
if (maximalAccurateSubtrees.equals(ImmutableSet.of(tree))) {
return Description.NO_MATCH;
}
String accurateSubtrees =
maximalAccurateSubtrees.stream()
.map(
t ->
String.format(
"%s tree at position %s:\n%s",
t.getKind(), ASTHelpers.getStartPosition(t), state.getSourceForNode(t)))
.collect(joining("\n\n", "Maximally accurate subtrees found:\n\n", ""));
return buildDescription(tree).setMessage(accurateSubtrees).build();
}
}

View File

@@ -1,22 +1,40 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.sun.source.tree.Tree.Kind.*;
import static com.sun.tools.javac.parser.Tokens.TokenKind.RPAREN;
import static com.sun.tools.javac.util.Position.NOPOS;
import static java.util.stream.Collectors.joining;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.errorprone.VisitorState;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.ErrorProneToken;
import com.google.errorprone.util.ErrorProneTokens;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Kinds;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.Position;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
/**
* A collection of Error Prone utility methods for dealing with the source code representation of
@@ -26,8 +44,136 @@ public final class SourceCode {
/** The complement of {@link CharMatcher#whitespace()}. */
private static final CharMatcher NON_WHITESPACE_MATCHER = CharMatcher.whitespace().negate();
// XXX: Document.
// XXX: Once we raise the baseline to JDK 17+, review whether `BINDING_PATTERN` and
// `GUARDED_PATTERN` should be added.
// XXX: Review which JDK >17 tree kinds should be added.
private static final ImmutableSet<Tree.Kind> TREE_KINDS_WITHOUT_EXPLICIT_SYNTAX =
Sets.immutableEnumSet(COMPILATION_UNIT, MODIFIERS, ERRONEOUS, OTHER);
private SourceCode() {}
// XXX: Document that that the test is relative to the state's current path.
public static boolean isAccurateSourceLikelyAvailable(VisitorState state) {
return new IsAccurateSourceLikelyAvailable().test(state);
}
// XXX: Move this class down or to a separate file.
// XXX: Cache data in `VisitorState.config`.
private static final class IsAccurateSourceLikelyAvailable
extends TreeScanner<@Nullable Boolean, VisitorState> implements Predicate<VisitorState> {
@Override
public boolean test(VisitorState state) {
return isValidAncestorSourceContainment(state) && isValidDescendantSourceContainment(state);
}
@Override
public @Nullable Boolean scan(@Nullable Tree tree, VisitorState parentState) {
if (tree == null) {
return Boolean.TRUE;
}
VisitorState childState = parentState.withPath(new TreePath(parentState.getPath(), tree));
return isValidSourceContainment(childState) ? super.scan(tree, childState) : Boolean.FALSE;
}
@Override
public @Nullable Boolean reduce(@Nullable Boolean a, @Nullable Boolean b) {
return !(Boolean.FALSE.equals(a) || Boolean.FALSE.equals(b));
}
private boolean isValidDescendantSourceContainment(VisitorState state) {
return !Boolean.FALSE.equals(super.scan(state.getPath().getLeaf(), state));
}
private static boolean isValidAncestorSourceContainment(VisitorState state) {
return Stream.iterate(state.getPath(), Objects::nonNull, TreePath::getParentPath)
.allMatch(path -> isValidSourceContainment(state.withPath(path)));
}
private static boolean isValidSourceContainment(VisitorState state) {
Tree tree = state.getPath().getLeaf();
TreePath parentPath = state.getPath().getParentPath();
if (parentPath == null) {
return getSourceRange(tree, state) != null;
}
Tree enclosingTree = parentPath.getLeaf();
@Nullable Range<Integer> enclosingSourceRange = getSourceRange(enclosingTree, state);
@Nullable Range<Integer> sourceRange = getSourceRange(tree, state);
if (enclosingSourceRange == null) {
return sourceRange == null || mayBeImplicit(enclosingTree, state);
// XXX: Test code; drop.
// return sourceRange == null && mayBeImplicit(enclosingTree, state);
// (sourceRange == null || mayBeImplicit(enclosingTree, state)) != (sourceRange ==
// null && mayBeImplicit(enclosingTree, state))
}
if (sourceRange == null) {
return mayBeImplicit(tree, state);
}
return enclosingSourceRange.encloses(sourceRange)
&& (TREE_KINDS_WITHOUT_EXPLICIT_SYNTAX.contains(enclosingTree.getKind())
|| !enclosingSourceRange.equals(sourceRange));
}
private static boolean mayBeImplicit(Tree tree, VisitorState state) {
if (tree instanceof ModifiersTree) {
ModifiersTree modifiers = (ModifiersTree) tree;
return modifiers.getAnnotations().isEmpty() && modifiers.getFlags().isEmpty();
}
// XXX: We could even *require* the generated constructor subtree to not have any associated
// source.
if (ASTHelpers.isGeneratedConstructor(state.findEnclosing(MethodTree.class))) {
return true;
}
AnnotationTree annotation = state.findEnclosing(AnnotationTree.class);
if (annotation != null && annotation.getArguments().size() == 1) {
// XXX: The `IdentifierTree` check here has no impact because its parent will in practice be
// `null`, which prevents `mayBeImplicit` being called review how we can be stricter.
Symbol symbol =
tree instanceof AssignmentTree
? ASTHelpers.getSymbol(((AssignmentTree) tree).getVariable())
: tree instanceof IdentifierTree ? ASTHelpers.getSymbol(tree) : null;
/* The `value` attribute may be omitted from single-argument annotation declarations. */
return symbol != null
&& symbol.kind == Kinds.Kind.MTH
&& symbol.name.contentEquals("value");
}
// XXX: Many branches aren't covered yet.
return isSuperInvocation(tree)
|| (tree instanceof IdentifierTree
&& isSuperInvocation(state.getPath().getParentPath().getLeaf()));
}
private static boolean isSuperInvocation(Tree tree) {
if (tree instanceof ExpressionStatementTree) {
return isSuperInvocation(((ExpressionStatementTree) tree).getExpression());
}
if (!(tree instanceof MethodInvocationTree)) {
return false;
}
MethodInvocationTree invocation = (MethodInvocationTree) tree;
return invocation.getArguments().isEmpty()
&& ASTHelpers.isSuper(invocation.getMethodSelect());
}
private static @Nullable Range<Integer> getSourceRange(Tree tree, VisitorState state) {
int startPosition = ASTHelpers.getStartPosition(tree);
int endPosition = state.getEndPosition(tree);
return startPosition == Position.NOPOS || endPosition == Position.NOPOS
? null
: Range.closedOpen(startPosition, endPosition);
}
}
/**
* Returns a string representation of the given {@link Tree}, preferring the original source code
* (if available) over its prettified representation.
@@ -58,7 +204,7 @@ public final class SourceCode {
public static SuggestedFix deleteWithTrailingWhitespace(Tree tree, VisitorState state) {
CharSequence sourceCode = state.getSourceCode();
int endPos = state.getEndPosition(tree);
if (sourceCode == null || endPos == NOPOS) {
if (sourceCode == null || endPos == Position.NOPOS) {
/* We can't identify the trailing whitespace; delete just the tree. */
return SuggestedFix.delete(tree);
}

View File

@@ -276,4 +276,25 @@ final class CanonicalAnnotationSyntaxTest {
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void ignoreLombokViolations() {
CompilationTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass())
.setArgs("-processor", "lombok.launch.AnnotationProcessorHider$AnnotationProcessor")
.addSourceLines(
"A.java",
"import com.fasterxml.jackson.annotation.JsonProperty;",
"import lombok.Data;",
"",
"@Data",
"class A {",
" @JsonProperty(\"custom_field_name\")",
" private String field;",
"",
" // Should be flagged because of the redundant parentheses, but Lombok doesn't properly link the",
" // annotation to its source code.",
" @JsonProperty() private String field2;",
"}")
.doTest();
}
}

View File

@@ -1,6 +1,8 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static java.util.stream.Collectors.joining;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
@@ -8,18 +10,92 @@ import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.lang.model.element.Name;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
final class SourceCodeTest {
@Test
void isAccurateSourceLikelyAvailable() {
BugCheckerRefactoringTestHelper.newInstance(
IsAccurateSourceLikelyAvailableTestChecker.class, getClass())
.setArgs("-processor", "lombok.launch.AnnotationProcessorHider$AnnotationProcessor")
.allowBreakingChanges()
.addInputLines(
"A.java",
"import com.fasterxml.jackson.annotation.JsonProperty;",
"import lombok.Data;",
"",
"class A {",
" class WithExplicitNullaryConstructor {",
" WithExplicitNullaryConstructor() {}",
" }",
"",
" class WithoutLombok {",
" @JsonProperty(\"custom_field_name\")",
" private String field;",
" }",
"",
" // BUG: Diagnostic contains:",
" @Data",
" class WithLombok {",
" // BUG: Diagnostic contains:",
" @JsonProperty(\"custom_field_name\")",
" private String field;",
" }",
"}")
.addOutputLines(
"A.java",
"Maximally accurate subtrees found:",
"",
"IMPORT tree at position 0:",
"import com.fasterxml.jackson.annotation.JsonProperty;",
"",
"IMPORT tree at position 54:",
"import lombok.Data;",
"",
"MODIFIERS tree at position -1:",
"null",
"",
"METHOD tree at position 75:",
"null",
"",
"CLASS tree at position 87:",
"class WithExplicitNullaryConstructor {",
" WithExplicitNullaryConstructor() {}",
" }",
"",
"CLASS tree at position 173:",
"class WithoutLombok {",
" @JsonProperty(\"custom_field_name\")",
" private String field;",
" }",
"",
"MODIFIERS tree at position 298:",
"@Data",
"",
"IDENTIFIER tree at position 409:",
"String")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void deleteWithTrailingWhitespaceAnnotations() {
BugCheckerRefactoringTestHelper.newInstance(
@@ -228,6 +304,56 @@ final class SourceCodeTest {
.doTest(TestMode.TEXT_MATCH);
}
/**
* A {@link BugChecker} that uses {@link SourceCode#isAccurateSourceLikelyAvailable(VisitorState)}
* to flag AST nodes for which accurate source code does not appear to be available.
*/
@BugPattern(severity = ERROR, summary = "Interacts with `SourceCode` for testing purposes")
public static final class IsAccurateSourceLikelyAvailableTestChecker extends BugChecker
implements CompilationUnitTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
Deque<Boolean> seenAccurateSource = new ArrayDeque<>();
Set<Tree> maximalAccurateSubtrees = new LinkedHashSet<>();
new TreeScanner<@Nullable Void, TreePath>() {
@Override
public @Nullable Void scan(Tree node, TreePath treePath) {
if (node == null) {
return null;
}
TreePath path = new TreePath(treePath, node);
boolean isAccurate = SourceCode.isAccurateSourceLikelyAvailable(state.withPath(path));
if (!isAccurate) {
assertThat(seenAccurateSource.peek()).isNotIn(Boolean.TRUE);
} else if (!Boolean.TRUE.equals(seenAccurateSource.peek())) {
maximalAccurateSubtrees.add(node);
}
seenAccurateSource.push(isAccurate);
try {
return super.scan(node, path);
} finally {
seenAccurateSource.pop();
}
}
}.scan(tree, state.getPath());
String accurateSubtrees =
maximalAccurateSubtrees.stream()
.map(
t ->
String.format(
"%s tree at position %s:\n%s",
t.getKind(), ASTHelpers.getStartPosition(t), state.getSourceForNode(t)))
.collect(joining("\n\n", "Maximally accurate subtrees found:\n\n", ""));
return describeMatch(tree, SuggestedFix.replace(tree, accurateSubtrees));
}
}
/**
* A {@link BugChecker} that uses {@link SourceCode#deleteWithTrailingWhitespace(Tree,
* VisitorState)} to suggest the deletion of annotations and methods with a name containing

30
pom.xml
View File

@@ -201,6 +201,7 @@
<version.auto-value>1.10.3</version.auto-value>
<version.error-prone>${version.error-prone-orig}</version.error-prone>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
<!--<version.error-prone-orig>HEAD-SNAPSHOT</version.error-prone-orig>-->
<version.error-prone-orig>2.21.1</version.error-prone-orig>
<version.error-prone-slf4j>0.1.20</version.error-prone-slf4j>
<version.guava-beta-checker>1.0</version.guava-beta-checker>
@@ -456,6 +457,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
@@ -918,16 +924,17 @@
<artifactId>nullaway</artifactId>
<version>${version.nullaway}</version>
</path>
<path>
<groupId>jp.skypencil.errorprone.slf4j</groupId>
<artifactId>errorprone-slf4j</artifactId>
<version>${version.error-prone-slf4j}</version>
</path>
<path>
<groupId>org.mockito</groupId>
<artifactId>mockito-errorprone</artifactId>
<version>${version.mockito}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- Replace version. -->
<version>1.18.28</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
@@ -1427,6 +1434,19 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<!--<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version>
<executions>
<execution>
<goals>
<goal>delombok</goal>
</goals>
<phase>generate-sources</phase>
</execution>
</executions>
</plugin>-->
</plugins>
</build>