Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

review: fix: Extend support for member references without a leading # and java modules #5867

Merged
merged 1 commit into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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