Remove pre-emptive resolving mechanism, relying instead on built-in Groovyc resolution.

This commit is contained in:
Jesse Brault 2025-01-26 13:23:31 -06:00
parent 5ce934b3fe
commit 1a528465f9
12 changed files with 19 additions and 373 deletions

View File

@ -199,7 +199,10 @@ tasks.register('uberJar', Jar) {
group 'groovyc'
archiveBaseName = 'web-view-components-uber'
from sourceSets.main.output
from sourceSets.main.runtimeClasspath.filter(File.&exists).collect { it.isDirectory() ? it : zipTree(it) }
from sourceSets.main.runtimeClasspath
.filter(File.&exists)
.filter { !(it.name =~ /groovy.*.jar/) }
.collect { it.isDirectory() ? it : zipTree(it) }
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

View File

@ -4,14 +4,14 @@ if [ "$1" == "--debug" ]; then
shift
gradle -q uberJar && \
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8192 \
-cp build/libs/web-view-components-uber-0.1.0.jar \
-cp build/libs/web-view-components-uber-0.1.2.jar \
org.codehaus.groovy.tools.FileSystemCompiler \
--configscript src/main/resources/groowt/view/component/web/groovyc/groovycConfigurationScript.groovy \
-d groovyc-out \
"$@"
else
gradle -q uberJar && \
groovyc -cp build/libs/web-view-components-uber-0.1.0.jar \
groovyc -cp build/libs/web-view-components-uber-0.1.2.jar \
--configscript src/main/resources/groowt/view/component/web/groovyc/groovycConfigurationScript.groovy \
-d groovyc-out \
"$@"

View File

@ -6,7 +6,6 @@ import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.*;
import groowt.view.component.web.transpile.groovy.GroovyUtil;
import groowt.view.component.web.transpile.groovy.GroovyUtil.ConvertResult;
import groowt.view.component.web.transpile.resolve.ComponentClassNodeResolver;
import groowt.view.component.web.util.SourcePosition;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
@ -15,7 +14,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import static groowt.view.component.web.transpile.TranspilerUtil.*;
@ -25,13 +23,9 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
private static final ClassNode COMPONENT_RESOLVE_EXCEPTION_TYPE = ClassHelper.make(ComponentResolveException.class);
private static final ClassNode COMPONENT_CREATE_EXCEPTION_TYPE = ClassHelper.make(ComponentCreateException.class);
private static final Pattern isFqn = Pattern.compile("^(\\p{Ll}.+\\.)+\\p{Lu}.+$");
private static final Pattern isWithPackage = Pattern.compile("^\\p{Ll}.+\\.");
private LeftShiftFactory leftShiftFactory;
private ValueNodeTranspiler valueNodeTranspiler;
private BodyTranspiler bodyTranspiler;
private ComponentClassNodeResolver componentClassNodeResolver;
public void setLeftShiftFactory(LeftShiftFactory leftShiftFactory) {
this.leftShiftFactory = leftShiftFactory;
@ -45,10 +39,6 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
this.bodyTranspiler = bodyTranspiler;
}
public void setComponentClassNodeResolver(ComponentClassNodeResolver componentClassNodeResolver) {
this.componentClassNodeResolver = componentClassNodeResolver;
}
/* UTIL */
protected String getComponentName(int componentNumber) {
@ -74,49 +64,14 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* RESOLVE */
protected List<Expression> getArgsAsList(
TypedComponentNode componentNode,
TranspilerState state
) {
protected List<Expression> getArgsAsList(TypedComponentNode componentNode) {
return switch (componentNode.getArgs().getType()) {
case ClassComponentTypeNode classComponentTypeNode -> {
final String identifier = classComponentTypeNode.getIdentifier();
final ConstantExpression alias = getStringLiteral(identifier);
final var matcher = isFqn.matcher(identifier);
if (matcher.matches()) {
final ClassNode classNode = ClassHelper.make(identifier);
final ClassExpression classExpression = new ClassExpression(classNode);
final var classExpression = new ClassExpression(classNode);
yield List.of(alias, classExpression);
} else {
// we need to resolve it
final var isWithPackageMatcher = isWithPackage.matcher(identifier);
if (isWithPackageMatcher.matches()) {
final var resolveResult = this.componentClassNodeResolver.getClassForFqn(identifier);
if (resolveResult.isLeft()) {
final var error = resolveResult.getLeft();
error.setNode(componentNode.getArgs().getType());
state.addError(error);
yield List.of();
} else {
final ClassNode classNode = resolveResult.getRight();
final ClassExpression classExpression = new ClassExpression(classNode); // TODO: pos
yield List.of(alias, classExpression);
}
} else {
final var resolveResult =
this.componentClassNodeResolver.getClassForNameWithoutPackage(identifier);
if (resolveResult.isLeft()) {
final var error = resolveResult.getLeft();
error.setNode(componentNode.getArgs().getType());
state.addError(error);
yield List.of();
} else {
final ClassNode classNode = resolveResult.getRight();
final ClassExpression classExpression = new ClassExpression(classNode); // TODO: pos
yield List.of(alias, classExpression);
}
}
}
}
case StringComponentTypeNode stringComponentTypeNode -> {
final String identifier = stringComponentTypeNode.getIdentifier();
@ -127,8 +82,8 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
}
// 'h1' | 'MyComponent', MyComponent(.class)
protected ArgumentListExpression getResolveArgs(TypedComponentNode componentNode, TranspilerState state) {
final List<Expression> args = this.getArgsAsList(componentNode, state);
protected ArgumentListExpression getResolveArgs(TypedComponentNode componentNode) {
final List<Expression> args = this.getArgsAsList(componentNode);
final ArgumentListExpression argsListExpr = new ArgumentListExpression();
args.forEach(argsListExpr::addExpression);
return argsListExpr;
@ -142,7 +97,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
return new MethodCallExpression(
new VariableExpression(state.getRenderContext()),
"resolve",
this.getResolveArgs(componentNode, state)
this.getResolveArgs(componentNode)
);
}

View File

@ -10,7 +10,6 @@ import groowt.view.component.web.compiler.MultipleWebViewComponentCompileErrorsE
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileException;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.groovy.GroovyUtil;
import groowt.view.component.web.transpile.resolve.ClassLoaderComponentClassNodeResolver;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.BlockStatement;
@ -31,10 +30,8 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
private static final Logger logger = LoggerFactory.getLogger(DefaultGroovyTranspiler.class);
protected TranspilerConfiguration getConfiguration(
ClassLoaderComponentClassNodeResolver classLoaderComponentClassNodeResolver
) {
return SimpleTranspilerConfiguration.withDefaults(classLoaderComponentClassNodeResolver);
protected TranspilerConfiguration getConfiguration() {
return SimpleTranspilerConfiguration.withDefaults();
}
protected void addAutomaticImports(WebViewComponentModuleNode moduleNode, TranspilerConfiguration configuration) {
@ -227,6 +224,8 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
compilerConfiguration.getGroovyClassLoader()
);
final var transpilerConfiguration = this.getConfiguration(resolver);
// transpilerConfiguration, and positionSetter
final var transpilerConfiguration = this.getConfiguration();
final PositionSetter positionSetter = transpilerConfiguration.getPositionSetter();
// prepare sourceUnit
@ -245,9 +244,6 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
compileUnit, sourceUnit, transpilerConfiguration
);
// set resolver's moduleNode
resolver.setModuleNode(moduleNode);
// prepare mainClassNode
final ClassNode mainClassNode = this.initMainClassNode(compileUnit, templateClassSimpleName, moduleNode);

View File

@ -1,7 +1,6 @@
package groowt.view.component.web.transpile;
import groovy.lang.Tuple3;
import groowt.view.component.web.transpile.resolve.ComponentClassNodeResolver;
import org.codehaus.groovy.ast.ClassNode;
import java.util.Map;
@ -12,9 +11,8 @@ import static groowt.view.component.web.transpile.TranspilerUtil.*;
public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
public static TranspilerConfiguration withDefaults(ComponentClassNodeResolver componentClassNodeResolver) {
public static TranspilerConfiguration withDefaults() {
final var c = new SimpleTranspilerConfiguration();
c.setComponentClassNodeResolver(componentClassNodeResolver);
final var ct = new DefaultComponentTranspiler();
final PositionSetter ps = new SimplePositionSetter();
@ -27,7 +25,6 @@ public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
ct.setLeftShiftFactory(lsf);
ct.setBodyTranspiler(bt);
ct.setValueNodeTranspiler(vnt);
ct.setComponentClassNodeResolver(componentClassNodeResolver);
c.setComponentTranspiler(ct);
c.setPositionSetter(ps);
@ -40,7 +37,6 @@ public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
return c;
}
private ComponentClassNodeResolver componentClassNodeResolver;
private ComponentTranspiler componentTranspiler;
private PositionSetter positionSetter;
private LeftShiftFactory leftShiftFactory;
@ -49,14 +45,6 @@ public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
private BodyTranspiler bodyTranspiler;
private ValueNodeTranspiler valueNodeTranspiler;
public ComponentClassNodeResolver getComponentClassNodeResolver() {
return Objects.requireNonNull(this.componentClassNodeResolver);
}
public void setComponentClassNodeResolver(ComponentClassNodeResolver componentClassNodeResolver) {
this.componentClassNodeResolver = componentClassNodeResolver;
}
public ComponentTranspiler getComponentTranspiler() {
return Objects.requireNonNull(this.componentTranspiler);
}

View File

@ -1,60 +0,0 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.WebViewComponent;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import java.util.ArrayList;
import java.util.List;
public class CachingComponentClassNodeResolver implements ComponentClassNodeResolver {
private final List<ClassNode> classNodes = new ArrayList<>();
protected final WebViewComponentTemplateCompileUnit compileUnit;
public CachingComponentClassNodeResolver(WebViewComponentTemplateCompileUnit compileUnit) {
this.compileUnit = compileUnit;
}
public void addClass(Class<? extends WebViewComponent> clazz) {
this.classNodes.add(ClassHelper.make(clazz));
}
public void addClassNode(ClassNode classNode) {
this.classNodes.add(classNode);
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForFqn(String fqn) {
for (final var classNode : this.classNodes) {
if (classNode.getName().equals(fqn)) {
return Either.right(classNode);
}
}
return Either.left(new ClassNodeResolveException(
this.compileUnit,
fqn,
"Could not resolve ClassNode for fqn: " + fqn,
null
));
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage) {
for (final var classNode : this.classNodes) {
if (classNode.getNameWithoutPackage().equals(nameWithoutPackage)) {
return Either.right(classNode);
}
}
return Either.left(new ClassNodeResolveException(
this.compileUnit,
nameWithoutPackage,
"Could not resolve ClassNode for nameWithoutPackage: " + nameWithoutPackage,
null
));
}
}

View File

@ -1,53 +0,0 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClassLoaderComponentClassNodeResolver extends ModuleNodeComponentClassNodeResolver {
private static final Logger logger = LoggerFactory.getLogger(ClassLoaderComponentClassNodeResolver.class);
protected final ClassLoader classLoader;
public ClassLoaderComponentClassNodeResolver(
WebViewComponentTemplateCompileUnit compileUnit,
ClassLoader classLoader
) {
super(compileUnit);
this.classLoader = classLoader;
}
protected final Either<ClassNodeResolveException, ClassNode> resolveWithClassLoader(String fqn) {
logger.debug("Trying to resolve {} via ClassLoader", fqn);
try {
Class<?> clazz = this.classLoader.loadClass(ResolveUtil.convertCanonicalNameToBinaryName(fqn));
final var classNode = ResolveUtil.getClassNode(clazz);
return Either.right(classNode);
} catch (ClassNotFoundException classNotFoundException) {
return Either.left(
new ClassNodeResolveException(
this.compileUnit,
fqn,
"Could not find class " + fqn + " with classLoader " +
this.classLoader,
classNotFoundException
)
);
}
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForFqn(String fqn) {
return super.getClassForFqn(fqn).flatMapLeft(ignored -> {
final var classLoaderResult = this.resolveWithClassLoader(fqn);
if (classLoaderResult.isRight()) {
this.addClassNode(classLoaderResult.getRight());
}
return classLoaderResult;
});
}
}

View File

@ -1,42 +0,0 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileException;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassNode;
public interface ComponentClassNodeResolver {
final class ClassNodeResolveException extends WebViewComponentTemplateCompileException {
private final String identifier;
public ClassNodeResolveException(
WebViewComponentTemplateCompileUnit compileUnit,
String identifier,
String message
) {
super(compileUnit, message);
this.identifier = identifier;
}
public ClassNodeResolveException(
WebViewComponentTemplateCompileUnit compileUnit,
String identifier,
String message,
Throwable cause
) {
super(compileUnit, message, cause);
this.identifier = identifier;
}
public String getIdentifier() {
return this.identifier;
}
}
Either<ClassNodeResolveException, ClassNode> getClassForFqn(String fqn);
Either<ClassNodeResolveException, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage);
}

View File

@ -1,84 +0,0 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import java.util.Objects;
public class ModuleNodeComponentClassNodeResolver extends CachingComponentClassNodeResolver {
private ModuleNode moduleNode;
public ModuleNodeComponentClassNodeResolver(WebViewComponentTemplateCompileUnit compileUnit) {
super(compileUnit);
}
public ModuleNode getModuleNode() {
return Objects.requireNonNull(this.moduleNode);
}
public void setModuleNode(ModuleNode moduleNode) {
this.moduleNode = Objects.requireNonNull(moduleNode);
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage) {
return super.getClassForNameWithoutPackage(nameWithoutPackage).flatMapLeft(ignored -> {
// try regular imports first
final var importedClassNode = this.getModuleNode().getImportType(nameWithoutPackage);
if (importedClassNode != null) {
this.addClassNode(importedClassNode);
return Either.right(importedClassNode);
}
// try star imports
final var starImports = this.getModuleNode().getStarImports();
for (final var starImport : starImports) {
final var packageName = starImport.getPackageName();
final String fqn;
if (!packageName.equals(".") && packageName.endsWith(".")) {
fqn = packageName + nameWithoutPackage;
} else {
fqn = packageName + "." + nameWithoutPackage;
}
final var withPackage = this.getClassForFqn(fqn);
if (withPackage.isRight()) {
return withPackage;
}
}
// try pre-pending package and asking for fqn
final String moduleNodePackageName = this.getModuleNode().getPackageName();
final String packageName;
if (moduleNodePackageName != null) {
packageName = moduleNodePackageName;
} else {
packageName = "";
}
final String fqn;
if (packageName.equals(".") || packageName.isEmpty()) {
fqn = nameWithoutPackage;
} else if (packageName.endsWith(".")) {
fqn = packageName + nameWithoutPackage;
} else {
fqn = packageName + "." + nameWithoutPackage;
}
final var withPackage = this.getClassForFqn(fqn);
if (withPackage.isRight()) {
return withPackage;
} else {
return Either.left(new ClassNodeResolveException(
this.compileUnit,
nameWithoutPackage,
"Cannot resolve " + nameWithoutPackage
+ " from imports, package-local classes, or pre-added classes."
));
}
});
}
}

View File

@ -1,31 +0,0 @@
package groowt.view.component.web.transpile.resolve;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import java.util.regex.Pattern;
public final class ResolveUtil {
private static final Pattern packageSplitter = Pattern.compile("^(?<package>(?>\\p{Ll}[^.]*\\.)*)(?<top>\\p{Lu}[^.]*)(?<members>(?>\\.\\p{Lu}[^.]*)*)$");
public static ClassNode getClassNode(Class<?> clazz) {
return ClassHelper.makeCached(clazz);
}
public static String convertCanonicalNameToBinaryName(String canonicalName) {
final var matcher = packageSplitter.matcher(canonicalName);
if (matcher.matches()) {
return new StringBuilder()
.append(matcher.group("package"))
.append(matcher.group("top"))
.append(matcher.group("members").replaceAll("\\.", "\\$"))
.toString();
} else {
throw new IllegalArgumentException("Cannot split apart " + canonicalName);
}
}
private ResolveUtil() {}
}

View File

@ -3,7 +3,6 @@ package groowt.view.component.web.transpiler;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.SimpleTranspilerConfiguration;
import groowt.view.component.web.transpile.TranspilerConfiguration;
import groowt.view.component.web.transpile.resolve.CachingComponentClassNodeResolver;
import org.codehaus.groovy.ast.ModuleNode;
public class DefaultBodyTranspilerTests extends BodyTranspilerTests {
@ -13,7 +12,7 @@ public class DefaultBodyTranspilerTests extends BodyTranspilerTests {
WebViewComponentTemplateCompileUnit compileUnit,
ModuleNode moduleNode
) {
return SimpleTranspilerConfiguration.withDefaults(new CachingComponentClassNodeResolver(compileUnit));
return SimpleTranspilerConfiguration.withDefaults();
}
}

View File

@ -1,25 +0,0 @@
package groowt.view.component.web.transpiler;
import org.junit.jupiter.api.Test;
import static groowt.view.component.web.transpile.resolve.ResolveUtil.convertCanonicalNameToBinaryName;
import static org.junit.jupiter.api.Assertions.*;
public class ResolveUtilTests {
@Test
public void abcABC() {
assertEquals("a.b.c.A$B$C", convertCanonicalNameToBinaryName("a.b.c.A.B.C"));
}
@Test
public void ABC() {
assertEquals("A$B$C", convertCanonicalNameToBinaryName("A.B.C"));
}
@Test
public void abcA() {
assertEquals("a.b.c.A", convertCanonicalNameToBinaryName("a.b.c.A"));
}
}