Skip to content

[Bug]: Sniper printer drops the class keyword when reprinting a type annotated with a class-literal argument #6848

Description

@GooDer

Describe the bug

When the sniper printer reprints a type declaration that is annotated with an annotation whose argument contains a class literal (e.g. @Foo(exclude = { String.class })), and any part of the type is modified so the
sniper has to reprint it, the printer silently drops the class keyword of the type declaration:

@Foo(exclude = { String.class })
class Bar { ... }

is reprinted as

@Foo(exclude = { String.class })                                                                                                                                                                                                 
Bar { ... }                                                                                                                                                                                                                      

which is not compilable Java.

Root cause

AbstractSourceFragmentContextCollection.knowsHowToPrint decides whether the current (possibly muted) fragment-collection context owns the next token. For a KEYWORD token it matches the keyword against its child
fragments with a loose substring check:

if (TokenType.KEYWORD.equals(tpe.getTokenType())) {                                                                                                                                                                              
    for (SourceFragment f : childFragments) {                                                                                                                                                                                    
        if (f.getSourceCode().contains(tpe.getToken())) {   // "String.class".contains("class") == true                                                                                                                          
            return true;                                                                                                                                                                                                         
        }                                                                                                                                                                                                                        
    }                                                                                                                                                                                                                            
    return false;                                                                                                                                                                                                                
}                                                                                                                                                                                                                                

The source of the annotation-argument fragment String.class contains the substring class. So when the printer reaches the class keyword of the following type declaration, the (muted) annotation collection context
wrongly claims it and, being muted, prints nothing — swallowing the keyword.

Source code you are trying to analyze/transform

@EnableAutoConfiguration(exclude = {
		java.lang.String.class,
		java.lang.Integer.class
})
class ClassKeywordAnnotation {

	static class Nested {

		void notFound() {
			throw new RuntimeException("boom");
		}
	}
}

@interface EnableAutoConfiguration {
	Class<?>[] exclude() default {};
}

Source code for your Spoon processing

Actual output

@EnableAutoConfiguration(exclude = {
		java.lang.String.class,
		java.lang.Integer.class
})
ClassKeywordAnnotation {

	static class Nested {

		void notFound() {
			throw new RuntimeException("boom");
		}
	}
}

@interface EnableAutoConfiguration {
	Class<?>[] exclude() default {};
}

Expected output

@EnableAutoConfiguration(exclude = {
		java.lang.String.class,
		java.lang.Integer.class
})
class ClassKeywordAnnotation {

	static class Nested {

		void notFound() {
			throw new RuntimeException("boom");
		}
	}
}

@interface EnableAutoConfiguration {
	Class<?>[] exclude() default {};
}

Spoon Version

v11.5.0 also in v11.5.1-beta-4

JVM Version

21

What operating system are you using?

Windows 11

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions