[#1134] Bugfix: annotation processor should allow @Spec-annotated field in version provider

Closes #1134
This commit is contained in:
Remko Popma
2020-07-31 08:25:03 +09:00
parent 673195947e
commit 63065d70c5
5 changed files with 160 additions and 1 deletions

View File

@@ -23,6 +23,7 @@ Picocli follows [semantic versioning](http://semver.org/).
* [#1126] Enhancement: Make picocli trace levels case-insensitive.
* [#1128] Enhancement: `ParameterException` caused by `TypeConversionException` now have their cause exception set.
* [#1137] Bugfix: The `picocli-codegen` annotation processor causes the build to fail with a `ClassCastException` when an option has `completionCandidates` defined.
* [#1134] Bugfix: The `picocli-codegen` annotation processor should allow `@Spec`-annotated field in classes implementing `IVersionProvider`.
* [#1127] DOC: Custom ShortErrorMessageHandler manual example should use bold red for error message.
* [#1130] DOC: Clarify how to run picocli-based applications.
* [#1131] DOC: Add anchor links before section titles in user manual.

View File

@@ -0,0 +1,40 @@
package picocli.annotation.processing.tests;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import org.junit.Test;
import javax.annotation.processing.Processor;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.Compiler.javac;
import static picocli.annotation.processing.tests.Resources.slurp;
import static picocli.annotation.processing.tests.YamlAssert.compareCommandYamlDump;
public class Issue1134Test {
@Test
public void testIssue1134() {
Processor processor = new AnnotatedCommandSourceGeneratorProcessor();
Compilation compilation =
javac()
.withProcessors(processor)
.compile(JavaFileObjects.forResource(
"picocli/issue1134/Issue1134.java"));
assertThat(compilation).succeeded();
}
@Test
public void testIssue1134Details() {
Compilation compilation = compareCommandYamlDump(slurp("/picocli/issue1134/Issue1134.yaml"),
JavaFileObjects.forResource("picocli/issue1134/Issue1134.java"));
assertOnlySourceVersionWarning(compilation);
}
private void assertOnlySourceVersionWarning(Compilation compilation) {
assertThat(compilation).hadWarningCount(0); // #826 version warnings are now suppressed
// assertThat(compilation).hadWarningContaining("Supported source version 'RELEASE_6' from annotation processor 'picocli.annotation.processing.tests");
}
}

View File

@@ -0,0 +1,30 @@
package picocli.issue1134;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.IVersionProvider;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.Spec;
@Command(name = "top",
versionProvider = MyVersionProvider.class,
resourceBundle = "mybundle5")
public class Issue1134 {
@CommandLine.Option(names = "--level")
private String level;
@Spec
CommandSpec spec;
}
class MyVersionProvider implements IVersionProvider {
@Spec
CommandSpec spec;
public String[] getVersion() {
return new String[] {spec.qualifiedName() + " 1.0"};
}
}

View File

@@ -0,0 +1,70 @@
---
CommandSpec:
name: 'top'
aliases: []
userObject: picocli.issue1134.Issue1134
helpCommand: false
defaultValueProvider: null
versionProvider: VersionProviderMetaData(picocli.issue1134.MyVersionProvider)
version: []
ArgGroups: []
Options:
- names: [--level]
usageHelp: false
versionHelp: false
description: []
descriptionKey: ''
typeInfo: CompileTimeTypeInfo(java.lang.String, aux=[java.lang.String], collection=false, map=false)
arity: 1
splitRegex: ''
interactive: false
required: false
hidden: false
hideParamSyntax: false
defaultValue: 'null'
showDefaultValue: ON_DEMAND
hasInitialValue: false
initialValue: 'null'
paramLabel: '<level>'
converters: []
completionCandidates: null
getter: AnnotatedElementHolder(FIELD level in picocli.issue1134.Issue1134)
setter: AnnotatedElementHolder(FIELD level in picocli.issue1134.Issue1134)
PositionalParams: []
UnmatchedArgsBindings: []
Mixins: []
UsageMessageSpec:
width: 80
abbreviateSynopsis: false
hidden: false
showDefaultValues: false
sortOptions: true
requiredOptionMarker: ' '
headerHeading: ''
header: []
synopsisHeading: 'Usage: '
customSynopsis: []
descriptionHeading: ''
description: []
parameterListHeading: ''
optionListHeading: ''
commandListHeading: 'Commands:%n'
footerHeading: ''
footer: []
ParserSpec:
separator: '='
endOfOptionsDelimiter: '--'
expandAtFiles: true
atFileCommentChar: '#'
overwrittenOptionsAllowed: false
unmatchedArgumentsAllowed: false
unmatchedOptionsArePositionalParams: false
stopAtUnmatched: false
stopAtPositional: false
posixClusteredShortOptionsAllowed: true
aritySatisfiedByAttachedOptionParam: false
caseInsensitiveEnumValuesAllowed: false
collectErrors: false
limitSplit: false
toggleBooleanFlags: false
Subcommands: []

View File

@@ -33,7 +33,9 @@ import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.util.SimpleElementVisitor6;
import javax.lang.model.util.TypeKindVisitor6;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.io.PrintWriter;
@@ -840,7 +842,23 @@ public abstract class AbstractCommandSpecProcessor extends AbstractProcessor {
logger.fine("Adding " + entry + " to commandSpec " + commandSpec1);
commandSpec1.addSpecElement(entry.getValue());
} else {
proc.error(entry.getKey(), "@Spec must be enclosed in a @Command, but was %s: %s", entry.getKey().getEnclosingElement(), entry.getKey().getEnclosingElement().getSimpleName());
Element enclosingElement = entry.getKey().getEnclosingElement();
if (enclosingElement.getKind() == ElementKind.CLASS || enclosingElement.getKind() == ENUM) {
TypeMirror typeMirror = enclosingElement.asType();
TypeElement typeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
List<? extends TypeMirror> interfaces = typeElement.getInterfaces();
boolean valid = false;
for (TypeMirror interf : interfaces) {
if (interf.toString().equals("picocli.CommandLine.IVersionProvider")) {
valid = true;
}
}
if (!valid) {
proc.error(entry.getKey(), "@Spec must be enclosed in a @Command, or in a class that implements IVersionProvider but was %s: %s", entry.getKey().getEnclosingElement(), entry.getKey().getEnclosingElement().getSimpleName());
}
} else {
proc.error(entry.getKey(), "@Spec must be enclosed in a @Command, but was %s: %s", entry.getKey().getEnclosingElement(), entry.getKey().getEnclosingElement().getSimpleName());
}
}
}
for (Map.Entry<Element, IAnnotatedElement> entry : parentCommandElements.entrySet()) {