Skip to content

Commit 55f6ae0

Browse files
committed
Spotless plugin doesn't work with lombok in Eclipse/VS Code/Cursor
1 parent 67f7df7 commit 55f6ae0

File tree

2 files changed

+149
-1
lines changed

2 files changed

+149
-1
lines changed

lib/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ tasks.named("check").configure {
6767
}
6868

6969
dependencies {
70+
implementation 'net.bytebuddy:byte-buddy:1.18.3'
7071
compileOnly 'org.slf4j:slf4j-api:2.0.17'
7172
testCommonImplementation 'org.slf4j:slf4j-api:2.0.17'
7273

lib/src/main/java/com/diffplug/spotless/FeatureClassLoader.java

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2023 DiffPlug
2+
* Copyright 2016-2025 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,12 +18,23 @@
1818
import java.io.ByteArrayOutputStream;
1919
import java.io.IOException;
2020
import java.io.InputStream;
21+
import java.lang.reflect.Array;
22+
import java.lang.reflect.Method;
2123
import java.net.URL;
2224
import java.net.URLClassLoader;
2325
import java.nio.ByteBuffer;
2426
import java.security.ProtectionDomain;
2527
import java.util.Objects;
2628

29+
import net.bytebuddy.ByteBuddy;
30+
import net.bytebuddy.description.modifier.Ownership;
31+
import net.bytebuddy.description.modifier.Visibility;
32+
import net.bytebuddy.description.type.TypeDescription;
33+
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
34+
import net.bytebuddy.implementation.MethodCall;
35+
import net.bytebuddy.implementation.StubMethod;
36+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
37+
2738
/**
2839
* This class loader is used to load classes of Spotless features from a search
2940
* path of URLs.<br/>
@@ -73,6 +84,142 @@ protected Class<?> findClass(String name) throws ClassNotFoundException {
7384
} catch (IOException e) {
7485
throw new ClassNotFoundException(name, e);
7586
}
87+
} else if (name.equals("lombok.core.FieldAugment")) {
88+
return new ByteBuddy()
89+
.subclass(Object.class)
90+
.name(name)
91+
.defineMethod("augment", name.equals("lombok.core.FieldAugment") ? Object.class : Object.class, Visibility.PUBLIC, Ownership.STATIC)
92+
.withParameters(Class.class, Class.class, String.class)
93+
.intercept(StubMethod.INSTANCE)
94+
.defineMethod("get", Object.class, Visibility.PUBLIC)
95+
.withParameters(Object.class).intercept(StubMethod.INSTANCE)
96+
.defineMethod("set", void.class, Visibility.PUBLIC)
97+
.withParameters(Object.class, Object.class).intercept(StubMethod.INSTANCE)
98+
.defineMethod("getAndSet", Object.class, Visibility.PUBLIC)
99+
.withParameters(Object.class, Object.class).intercept(StubMethod.INSTANCE)
100+
.defineMethod("clear", Object.class, Visibility.PUBLIC)
101+
.withParameters(Object.class).intercept(StubMethod.INSTANCE)
102+
.defineMethod("compareAndClear", Object.class, Visibility.PUBLIC)
103+
.withParameters(Object.class, Object.class).intercept(StubMethod.INSTANCE)
104+
.defineMethod("setIfAbsent", Object.class, Visibility.PUBLIC)
105+
.withParameters(Object.class, Object.class).intercept(StubMethod.INSTANCE)
106+
.defineMethod("compareAndSet", Object.class, Visibility.PUBLIC)
107+
.withParameters(Object.class, Object.class, Object.class).intercept(StubMethod.INSTANCE)
108+
.make()
109+
.load(this, ClassLoadingStrategy.Default.INJECTION)
110+
.getLoaded();
111+
} else if (name.equals("lombok.eclipse.EcjAugments")) {
112+
Class<?> fieldAugmentClass = loadClass("lombok.core.FieldAugment");
113+
Class<?> ecjAugmentsClass = new ByteBuddy()
114+
.subclass(Object.class)
115+
.name(name)
116+
.defineField("ASTNode_generatedBy", fieldAugmentClass, Visibility.PUBLIC, Ownership.STATIC)
117+
.defineField("ASTNode_tokens", fieldAugmentClass, Visibility.PUBLIC, Ownership.STATIC)
118+
.make()
119+
.load(this, ClassLoadingStrategy.Default.INJECTION)
120+
.getLoaded();
121+
122+
try {
123+
Object object = fieldAugmentClass.getDeclaredConstructor().newInstance();
124+
ecjAugmentsClass.getField("ASTNode_generatedBy").set(null, object);
125+
ecjAugmentsClass.getField("ASTNode_tokens").set(null, object);
126+
} catch (Exception e) {
127+
throw new IllegalArgumentException(e);
128+
}
129+
return ecjAugmentsClass;
130+
} else if (name.startsWith("lombok.")) {
131+
try {
132+
return super.findClass(name);
133+
} catch (ClassNotFoundException e) {
134+
Class<?> astNodeClass = loadClass("org.eclipse.jdt.internal.compiler.ast.ASTNode");
135+
Class<?> astNodeDomClass = loadClass("org.eclipse.jdt.core.dom.ASTNode");
136+
Class<?> astVisitorClass = loadClass("org.eclipse.jdt.core.dom.ASTVisitor");
137+
Class<?> rewriteEventClass = loadClass("org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent");
138+
// RewriteEvent[]
139+
TypeDescription rewriteEventArrayType = TypeDescription.Generic.Builder
140+
.rawType(rewriteEventClass)
141+
.asArray()
142+
.build()
143+
.asErasure();
144+
Method nullArray;
145+
try {
146+
nullArray = Array.class.getMethod("newInstance", Class.class, int.class);
147+
} catch (NoSuchMethodException e1) {
148+
throw new IllegalArgumentException(e1);
149+
}
150+
Class<? extends Object> c = new ByteBuddy()
151+
.subclass(Object.class)
152+
.name(name)
153+
.defineMethod("parserClinit", void.class, Visibility.PUBLIC, Ownership.STATIC)
154+
.intercept(StubMethod.INSTANCE)
155+
.defineMethod("setLine", void.class, Visibility.PUBLIC, Ownership.STATIC)
156+
.withParameters(Object.class, int.class, Object.class)
157+
.intercept(StubMethod.INSTANCE)
158+
.defineMethod("setRange", void.class, Visibility.PUBLIC, Ownership.STATIC)
159+
.withParameters(Object.class, int.class, int.class)
160+
.intercept(StubMethod.INSTANCE)
161+
.defineMethod("transform_swapped", void.class, Visibility.PUBLIC, Ownership.STATIC)
162+
.withParameters(Object.class, Object.class)
163+
.intercept(StubMethod.INSTANCE)
164+
.defineMethod("transform", void.class, Visibility.PUBLIC, Ownership.STATIC)
165+
.withParameters(Object.class, Object.class)
166+
.intercept(StubMethod.INSTANCE)
167+
.defineMethod("copyInitializationOfLocalDeclaration", void.class, Visibility.PUBLIC, Ownership.STATIC)
168+
.withParameters(Object.class).intercept(StubMethod.INSTANCE)
169+
.defineMethod("copyInitializationOfForEachIterable", void.class, Visibility.PUBLIC, Ownership.STATIC)
170+
.withParameters(Object.class).intercept(StubMethod.INSTANCE)
171+
.defineMethod("addFinalAndValAnnotationToVariableDeclaration", void.class, Visibility.PUBLIC, Ownership.STATIC)
172+
.withParameters(Object.class).intercept(StubMethod.INSTANCE)
173+
.defineMethod("isStatic", boolean.class, Visibility.PUBLIC, Ownership.STATIC)
174+
.withParameters(Object.class).intercept(StubMethod.INSTANCE)
175+
.defineMethod("onMethodEnter", void.class, Visibility.PUBLIC, Ownership.STATIC)
176+
.withParameters(Object.class, Object.class).intercept(StubMethod.INSTANCE)
177+
.defineMethod("onMethodExit", void.class, Visibility.PUBLIC, Ownership.STATIC)
178+
.withParameters(Object.class, Object.class).intercept(StubMethod.INSTANCE)
179+
.defineMethod("setSourceRangeCheck", boolean.class, Visibility.PUBLIC, Ownership.STATIC)
180+
.withParameters(Object.class, int.class, int.class)
181+
.intercept(StubMethod.INSTANCE)
182+
.defineMethod("isGenerated", boolean.class, Visibility.PUBLIC, Ownership.STATIC)
183+
.withParameters(astNodeClass)
184+
.intercept(StubMethod.INSTANCE)
185+
.defineMethod("isGenerated", boolean.class, Visibility.PUBLIC, Ownership.STATIC)
186+
.withParameters(astNodeDomClass)
187+
.intercept(StubMethod.INSTANCE)
188+
.defineMethod("addFinalAndValAnnotationToVariableDeclarationStatement", void.class, Visibility.PUBLIC, Ownership.STATIC)
189+
.withParameters(Object.class, Object.class, Object.class)
190+
.intercept(StubMethod.INSTANCE)
191+
.defineMethod("isBlockedVisitorAndGenerated", boolean.class, Visibility.PUBLIC, Ownership.STATIC)
192+
.withParameters(astNodeDomClass, astVisitorClass)
193+
.intercept(StubMethod.INSTANCE)
194+
.defineMethod("pop", void.class, Visibility.PUBLIC, Ownership.STATIC)
195+
.intercept(StubMethod.INSTANCE)
196+
.defineMethod("push", void.class, Visibility.PUBLIC, Ownership.STATIC)
197+
.withParameters(String.class)
198+
.intercept(StubMethod.INSTANCE)
199+
.defineMethod("hasSymbol", boolean.class, Visibility.PUBLIC, Ownership.STATIC)
200+
.withParameters(String.class)
201+
.intercept(StubMethod.INSTANCE)
202+
.defineMethod("returnFalse", boolean.class, Visibility.PUBLIC, Ownership.STATIC)
203+
.withParameters(Object.class)
204+
.intercept(StubMethod.INSTANCE)
205+
.defineMethod("returnTrue", boolean.class, Visibility.PUBLIC, Ownership.STATIC)
206+
.withParameters(Object.class)
207+
.intercept(StubMethod.INSTANCE)
208+
.defineMethod("isEmpty", boolean.class, Visibility.PUBLIC, Ownership.STATIC)
209+
.intercept(StubMethod.INSTANCE)
210+
.defineMethod("size", int.class, Visibility.PUBLIC, Ownership.STATIC)
211+
.intercept(StubMethod.INSTANCE)
212+
.defineMethod("listRewriteHandleGeneratedMethods", rewriteEventArrayType, Visibility.PUBLIC, Ownership.STATIC)
213+
.withParameters(rewriteEventClass)
214+
.intercept(MethodCall.invoke(nullArray)
215+
.with(rewriteEventClass)
216+
.with(0)
217+
.withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
218+
.make()
219+
.load(this, ClassLoadingStrategy.Default.INJECTION)
220+
.getLoaded();
221+
return c;
222+
}
76223
} else if (useBuildToolClassLoader(name)) {
77224
return buildToolClassLoader.loadClass(name);
78225
} else {

0 commit comments

Comments
 (0)