#598: Errors/Warnings now end up in the @Mapper annotated class. (#2634)

#598: Errors/Warnings now end up in the @Mapper annotated class.

Co-authored-by: Ben Zegveld <Ben.Zegveld@gmail.com>
This commit is contained in:
Zegveld 2021-10-31 16:46:35 +01:00 committed by GitHub
parent e32fc8c283
commit ca2529f862
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 325 additions and 37 deletions

View File

@ -0,0 +1,136 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.internal.processor;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.TypeUtils;
/**
* Handles redirection of errors/warnings so that they're shown on the mapper instead of hidden on a superclass.
*
* @author Ben Zegveld
*/
public class MapperAnnotatedFormattingMessenger implements FormattingMessager {
private FormattingMessager delegateMessager;
private TypeElement mapperTypeElement;
private TypeUtils typeUtils;
public MapperAnnotatedFormattingMessenger(FormattingMessager delegateMessager, TypeElement mapperTypeElement,
TypeUtils typeUtils) {
this.delegateMessager = delegateMessager;
this.mapperTypeElement = mapperTypeElement;
this.typeUtils = typeUtils;
}
@Override
public void printMessage(Message msg, Object... args) {
delegateMessager.printMessage( msg, args );
}
@Override
public void printMessage(Element e, Message msg, Object... args) {
delegateMessager
.printMessage(
determineDelegationElement( e ),
determineDelegationMessage( e, msg ),
determineDelegationArguments( e, msg, args ) );
}
@Override
public void printMessage(Element e, AnnotationMirror a, Message msg, Object... args) {
delegateMessager
.printMessage(
determineDelegationElement( e ),
a,
determineDelegationMessage( e, msg ),
determineDelegationArguments( e, msg, args ) );
}
@Override
public void printMessage(Element e, AnnotationMirror a, AnnotationValue v, Message msg, Object... args) {
delegateMessager
.printMessage(
determineDelegationElement( e ),
a,
v,
determineDelegationMessage( e, msg ),
determineDelegationArguments( e, msg, args ) );
}
@Override
public void note(int level, Message log, Object... args) {
delegateMessager.note( level, log, args );
}
@Override
public boolean isErroneous() {
return delegateMessager.isErroneous();
}
private Object[] determineDelegationArguments(Element e, Message msg, Object[] args) {
if ( methodInMapperClass( e ) ) {
return args;
}
String originalMessage = String.format( msg.getDescription(), args );
return new Object[] { originalMessage, constructMethod( e ), e.getEnclosingElement().getSimpleName() };
}
/**
* ExecutableElement.toString() has different values depending on the compiler. Constructing the method itself
* manually will ensure that the message is always traceable to it's source.
*/
private String constructMethod(Element e) {
if ( e instanceof ExecutableElement ) {
ExecutableElement ee = (ExecutableElement) e;
StringBuilder method = new StringBuilder();
method.append( typeUtils.asElement( ee.getReturnType() ).getSimpleName() );
method.append( " " );
method.append( ee.getSimpleName() );
method.append( "(" );
method.append( ee.getParameters()
.stream()
.map( this::parameterToString )
.collect( Collectors.joining( ", " ) ) );
method.append( ")" );
return method.toString();
}
return e.toString();
}
private String parameterToString(VariableElement element) {
return typeUtils.asElement( element.asType() ).getSimpleName() + " " + element.getSimpleName();
}
private Message determineDelegationMessage(Element e, Message msg) {
if ( methodInMapperClass( e ) ) {
return msg;
}
if ( msg.getDiagnosticKind() == Kind.ERROR ) {
return Message.MESSAGE_MOVED_TO_MAPPER_ERROR;
}
return Message.MESSAGE_MOVED_TO_MAPPER_WARNING;
}
private Element determineDelegationElement(Element e) {
return methodInMapperClass( e ) ? e : mapperTypeElement;
}
private boolean methodInMapperClass(Element e) {
return mapperTypeElement == null || e.equals( mapperTypeElement )
|| e.getEnclosingElement().equals( mapperTypeElement );
}
}

View File

@ -96,7 +96,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) {
this.elementUtils = context.getElementUtils();
this.typeUtils = context.getTypeUtils();
this.messager = context.getMessager();
this.messager =
new MapperAnnotatedFormattingMessenger( context.getMessager(), mapperTypeElement, context.getTypeUtils() );
this.options = context.getOptions();
this.versionInformation = context.getVersionInformation();
this.typeFactory = context.getTypeFactory();

View File

@ -20,6 +20,8 @@ public enum Message {
// CHECKSTYLE:OFF
PROCESSING_NOTE( "processing: %s.", Diagnostic.Kind.NOTE ),
CONFIG_NOTE( "applying mapper configuration: %s.", Diagnostic.Kind.NOTE ),
MESSAGE_MOVED_TO_MAPPER_ERROR( "%s Occured at '%s' in '%s'." ),
MESSAGE_MOVED_TO_MAPPER_WARNING( "%s Occured at '%s' in '%s'.", Diagnostic.Kind.WARNING ),
BEANMAPPING_CREATE_NOTE( "creating bean mapping method implementation for %s.", Diagnostic.Kind.NOTE ),
BEANMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'nullValuePropertyMappingStrategy', 'resultType' and 'qualifiedBy' are undefined in @BeanMapping, define at least one of them." ),

View File

@ -0,0 +1,11 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.erroneous.supermappingwithsubclassmapper;
public interface AbstractMapper<SOURCE, TARGET> {
TARGET map(SOURCE source);
}

View File

@ -0,0 +1,13 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.erroneous.supermappingwithsubclassmapper;
import org.mapstruct.Mapper;
@Mapper
public interface ErroneousMapper1 extends AbstractMapper<Source, Target> {
}

View File

@ -0,0 +1,14 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.erroneous.supermappingwithsubclassmapper;
import org.mapstruct.Mapper;
@Mapper
public interface ErroneousMapper2 extends AbstractMapper<Source, Target> {
@Override
Target map(Source source);
}

View File

@ -0,0 +1,51 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.erroneous.supermappingwithsubclassmapper;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
@IssueKey( "598" )
@WithClasses( { Source.class, Target.class, UnmappableClass.class, AbstractMapper.class } )
public class ErroneousPropertyMappingTest {
@ProcessorTest
@WithClasses( ErroneousMapper1.class )
@IssueKey( "598" )
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapper1.class,
line = 11,
messageRegExp = "Can't map property \"UnmappableClass property\" to \"String property\"\\. " +
"Consider to declare/implement a mapping method: \"String map\\(UnmappableClass value\\)\"\\. " +
"Occured at 'TARGET map\\(SOURCE source\\)' in 'AbstractMapper'\\.")
}
)
public void testUnmappableSourcePropertyInSuperclass() {
}
@ProcessorTest
@WithClasses( ErroneousMapper2.class )
@IssueKey( "598" )
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousMapper2.class,
line = 13,
message = "Can't map property \"UnmappableClass property\" to \"String property\". " +
"Consider to declare/implement a mapping method: \"String map(UnmappableClass value)\".") } )
public void testMethodOverride() {
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.erroneous.supermappingwithsubclassmapper;
public class Source {
private UnmappableClass property;
public UnmappableClass getProperty() {
return property;
}
public void setProperty(UnmappableClass property) {
this.property = property;
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.erroneous.supermappingwithsubclassmapper;
public class Target {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@ -0,0 +1,9 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.erroneous.supermappingwithsubclassmapper;
public class UnmappableClass {
}

View File

@ -93,11 +93,12 @@ public class DottedErrorMessageTest {
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = BaseDeepNestingMapper.class,
@Diagnostic(type = UnmappableDeepNestingMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 10,
line = 14,
message = "Unmapped target property: \"rgb\". Mapping from " + PROPERTY +
" \"Color house.roof.color\" to \"ColorDto house.roof.color\".")
" \"Color house.roof.color\" to \"ColorDto house.roof.color\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepNestingMapper'.")
}
)
public void testDeepNestedBeans() {
@ -110,11 +111,12 @@ public class DottedErrorMessageTest {
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = BaseDeepListMapper.class,
@Diagnostic(type = UnmappableDeepListMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 10,
line = 14,
message = "Unmapped target property: \"left\". Mapping from " + COLLECTION_ELEMENT +
" \"Wheel car.wheels\" to \"WheelDto car.wheels\".")
" \"Wheel car.wheels\" to \"WheelDto car.wheels\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepListMapper'.")
}
)
public void testIterables() {
@ -127,11 +129,12 @@ public class DottedErrorMessageTest {
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = BaseDeepMapKeyMapper.class,
@Diagnostic(type = UnmappableDeepMapKeyMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 10,
line = 14,
message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_KEY +
" \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\".")
" \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapKeyMapper'.")
}
)
public void testMapKeys() {
@ -144,11 +147,12 @@ public class DottedErrorMessageTest {
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = BaseDeepMapValueMapper.class,
@Diagnostic(type = UnmappableDeepMapValueMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 10,
line = 14,
message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_VALUE +
" \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\".")
" \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapValueMapper'.")
}
)
public void testMapValues() {
@ -161,11 +165,12 @@ public class DottedErrorMessageTest {
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = BaseCollectionElementPropertyMapper.class,
@Diagnostic(type = UnmappableCollectionElementPropertyMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 10,
line = 14,
message = "Unmapped target property: \"color\". Mapping from " + PROPERTY +
" \"Info computers[].info\" to \"InfoDto computers[].info\".")
" \"Info computers[].info\" to \"InfoDto computers[].info\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseCollectionElementPropertyMapper'.")
}
)
public void testCollectionElementProperty() {
@ -178,11 +183,12 @@ public class DottedErrorMessageTest {
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = BaseValuePropertyMapper.class,
@Diagnostic(type = UnmappableValuePropertyMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 10,
line = 14,
message = "Unmapped target property: \"color\". Mapping from " + PROPERTY +
" \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\".")
" \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseValuePropertyMapper'.")
}
)
public void testMapValueProperty() {
@ -220,36 +226,42 @@ public class DottedErrorMessageTest {
@ExpectedCompilationOutcome(
value = CompilationResult.SUCCEEDED,
diagnostics = {
@Diagnostic(type = BaseDeepNestingMapper.class,
@Diagnostic(type = UnmappableWarnDeepNestingMapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 10,
line = 13,
message = "Unmapped target property: \"rgb\". Mapping from " + PROPERTY +
" \"Color house.roof.color\" to \"ColorDto house.roof.color\"."),
@Diagnostic(type = BaseDeepListMapper.class,
" \"Color house.roof.color\" to \"ColorDto house.roof.color\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepNestingMapper'."),
@Diagnostic(type = UnmappableWarnDeepListMapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 10,
line = 13,
message = "Unmapped target property: \"left\". Mapping from " + COLLECTION_ELEMENT +
" \"Wheel car.wheels\" to \"WheelDto car.wheels\"."),
@Diagnostic(type = BaseDeepMapKeyMapper.class,
" \"Wheel car.wheels\" to \"WheelDto car.wheels\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepListMapper'."),
@Diagnostic(type = UnmappableWarnDeepMapKeyMapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 10,
line = 13,
message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_KEY +
" \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\"."),
@Diagnostic(type = BaseDeepMapValueMapper.class,
" \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapKeyMapper'."),
@Diagnostic(type = UnmappableWarnDeepMapValueMapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 10,
line = 13,
message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_VALUE +
" \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\"."),
@Diagnostic(type = BaseCollectionElementPropertyMapper.class,
" \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapValueMapper'."),
@Diagnostic(type = UnmappableWarnCollectionElementPropertyMapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 10,
line = 13,
message = "Unmapped target property: \"color\". Mapping from " + PROPERTY +
" \"Info computers[].info\" to \"InfoDto computers[].info\"."),
@Diagnostic(type = BaseValuePropertyMapper.class,
" \"Info computers[].info\" to \"InfoDto computers[].info\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseCollectionElementPropertyMapper'."),
@Diagnostic(type = UnmappableWarnValuePropertyMapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 10,
line = 13,
message = "Unmapped target property: \"color\". Mapping from " + PROPERTY +
" \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\".")
" \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\"." +
" Occured at 'UserDto userToUserDto(User user)' in 'BaseValuePropertyMapper'.")
}
)
public void testWarnUnmappedTargetProperties() {