Skip to content

Commit

Permalink
fix: Extend support for member references without a leading # and jav…
Browse files Browse the repository at this point in the history
…a modules (#5867)
  • Loading branch information
I-Al-Istannen authored Jun 30, 2024
1 parent db1af15 commit 98d1c16
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,28 +54,45 @@ public Optional<CtReference> resolve(String string) {
// Format:
// <classname>
// <package name>
// <field name>
// <method name>
// <classname>#<field name>
// <classname>#<method name>
// <classname>#<constructor name>
// <classname>#<method name>()
// <classname>#<method name>(<param type>[,<param type>])
// <classname>#<method name>(<param type> [^,]*)
// module/package.class#member label
String query = string;

if (!string.contains("#")) {
return resolveModulePackageOrClassRef(string);
if (!query.contains("#")) {
Optional<CtReference> existingTypePackageModule = resolveModulePackageOrClassRef(query);
if (existingTypePackageModule.isPresent()) {
return existingTypePackageModule;
}
// This is surely a module, no need to try as a member reference
if (query.endsWith("/")) {
return guessPackageOrModuleReferenceFromName(query);
}
// This contains a dot in the name (not a parameter), so this must be a type or module.
// Do not try as local member reference.
if (!query.contains("(") && query.contains(".")) {
return guessPackageOrModuleReferenceFromName(query);
}
// If we did not find it, try our luck as a member reference
query = "#" + query;
}
int fragmentIndex = string.indexOf('#');
String modulePackage = string.substring(0, fragmentIndex);
int fragmentIndex = query.indexOf('#');
String modulePackage = query.substring(0, fragmentIndex);
Optional<CtReference> contextRef = resolveModulePackageOrClassRef(modulePackage);

// Fragment qualifier only works on types (Foo#bar)
if (contextRef.isEmpty() || !(contextRef.get() instanceof CtTypeReference)) {
return contextRef;
return contextRef.or(() -> guessPackageOrModuleReferenceFromName(modulePackage));
}

CtType<?> outerType = ((CtTypeReference<?>) contextRef.get()).getTypeDeclaration();
String fragment = string.substring(fragmentIndex + 1);
String fragment = query.substring(fragmentIndex + 1);

return qualifyName(outerType, extractMemberName(fragment), extractParameters(fragment));
}
Expand Down Expand Up @@ -120,9 +137,9 @@ private Optional<CtReference> resolveModulePackageOrClassRef(String name) {
}
if (name.endsWith("/")) {
// Format: "module/"
CtModule module = factory.Module().getModule(name.replace("/", ""));
if (module != null) {
return Optional.of(module.getReference());
Optional<CtReference> module = getModuleRef(name.replace("/", ""));
if (module.isPresent()) {
return module;
}
}

Expand All @@ -132,8 +149,24 @@ private Optional<CtReference> resolveModulePackageOrClassRef(String name) {
private Optional<CtReference> resolveTypePackageModuleAsIs(String name) {
return qualifyTypeName(name).map(it -> (CtReference) it)
.or(() -> Optional.ofNullable(factory.Package().get(name)).map(CtPackage::getReference))
.or(() -> Optional.ofNullable(factory.Module().getModule(name)).map(CtModule::getReference))
.or(() -> guessPackageOrModuleReferenceFromName(name));
.or(() -> getModuleRef(name));
}

private Optional<CtReference> getModuleRef(String name) {
CtModule module = factory.Module().getModule(name);
if (module != null) {
return Optional.of(module.getReference());
}
ModuleLayer layer = factory.getEnvironment()
.getInputClassLoader()
.getUnnamedModule()
.getLayer();
if (layer == null) {
layer = ModuleLayer.boot();
}
Optional<Module> javaModule = layer.findModule(name);

return javaModule.map(it -> factory.Module().getOrCreate(it.getName()).getReference());
}

private Optional<CtReference> guessPackageOrModuleReferenceFromName(String name) {
Expand All @@ -143,11 +176,16 @@ private Optional<CtReference> guessPackageOrModuleReferenceFromName(String name)
}

try {
if (name.contains("/")) {
if (name.endsWith("/")) {
return Optional.of(
factory.Core().createModuleReference().setSimpleName(name.replace("/", ""))
);
}
if (name.contains("/")) {
// We have something like java.base/java.lang.String but we do not know java.base/
// We can't properly handle this, return nothing and keep it as text.
return Optional.empty();
}
return Optional.of(factory.Package().createReference(name));
} catch (JLSViolation ignored) {
// Looks like that name wasn't quite valid...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import spoon.javadoc.api.JavadocTagType;
import spoon.javadoc.api.TestHelper;
Expand Down Expand Up @@ -262,8 +263,7 @@ private static List<JavadocElement> sampleReferencedBlockTags(Factory factory) {
block(SEE, text("<a href=\"url\">label me</a>")),
block(SEE, refPackage(factory, "spoon.javadoc")),
block(SEE, refModule(factory, "java.base")),
// This is wrong, but we currently live with it.
block(SEE, refPackage(factory, "java.base")),
block(SEE, refModule(factory, "java.base")),
block(SEE, ref(factory, String.class))
);
}
Expand Down Expand Up @@ -398,6 +398,37 @@ private static List<JavadocElement> sampleBrokenLinks(Factory factory) {
);
}

@Test
void testClassWithMemberRefs() {
CtType<?> element = TestHelper.parseType(getClass())
.getNestedType(ClassWithMemberRef.class.getSimpleName());
Factory factory = element.getFactory();

List<JavadocElement> javadoc = JavadocParser.forElement(element);
assertThat(javadoc).containsExactly(
inline(LINK, ref(factory, String.class)),
text(" "),
inline(LINK, ref(factory, ClassWithMemberRef.class, "String")),
text(" "),
inline(LINK, ref(factory, ClassWithMemberRef.class, "foo")),
text(" "),
inline(LINK, ref(factory, ClassWithMemberRef.class, "foo")),
text(" "),
inline(LINK, new JavadocReference(element.getField("hey").getReference())),
text(" "),
inline(LINK, new JavadocReference(element.getField("hey").getReference()))
);
}

/**
* {@link String} {@link #String} {@link foo} {@link #foo} {@link hey} {@link #hey}
*/
private static class ClassWithMemberRef {
public void String() {}
public void foo() {}
public int hey;
}

private static JavadocText text(String text) {
return new JavadocText(text);
}
Expand Down

0 comments on commit 98d1c16

Please sign in to comment.