Implement ApexQualifiableNode for ASTUserEnum.

Include enum in qualified name. Properly handle case where enum is root node.

Fixes null deref in ApexQualifiedName.toString() for built-in ASTMethods in enum types.
Reference: https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_enum.htm

Add tests of qualified and unqualified enums.

Change-Id: I691795d40a66f3d3335ab72ad43de7055a6aee31
This commit is contained in:
Aaron Hurst
2022-08-12 19:10:54 +00:00
parent 2977bd8764
commit fbeb071d08
3 changed files with 82 additions and 11 deletions

View File

@@ -8,7 +8,9 @@ import net.sourceforge.pmd.annotation.InternalApi;
import apex.jorje.semantic.ast.compilation.UserEnum;
public class ASTUserEnum extends ApexRootNode<UserEnum> {
public class ASTUserEnum extends ApexRootNode<UserEnum> implements ApexQualifiableNode {
private ApexQualifiedName qname;
@Deprecated
@InternalApi
@@ -30,4 +32,20 @@ public class ASTUserEnum extends ApexRootNode<UserEnum> {
public ASTModifierNode getModifiers() {
return getFirstChildOfType(ASTModifierNode.class);
}
@Override
public ApexQualifiedName getQualifiedName() {
if (qname == null) {
ASTUserClass parent = this.getFirstParentOfType(ASTUserClass.class);
if (parent != null) {
qname = ApexQualifiedName.ofNestedEnum(parent.getQualifiedName(), this);
} else {
qname = ApexQualifiedName.ofOuterEnum(this);
}
}
return qname;
}
}

View File

@@ -148,6 +148,20 @@ public final class ApexQualifiedName implements QualifiedName {
}
static ApexQualifiedName ofOuterEnum(ASTUserEnum astUserEnum) {
String ns = astUserEnum.getNamespace();
String[] classes = {astUserEnum.getImage()};
return new ApexQualifiedName(StringUtils.isEmpty(ns) ? "c" : ns, classes, null);
}
static ApexQualifiedName ofNestedEnum(ApexQualifiedName parent, ASTUserEnum astUserEnum) {
String[] classes = Arrays.copyOf(parent.classes, parent.classes.length + 1);
classes[classes.length - 1] = astUserEnum.getImage();
return new ApexQualifiedName(parent.nameSpace, classes, null);
}
private static String getOperationString(ASTMethod node) {
StringBuilder sb = new StringBuilder();
sb.append(node.getImage()).append('(');
@@ -171,18 +185,28 @@ public final class ApexQualifiedName implements QualifiedName {
static ApexQualifiedName ofMethod(ASTMethod node) {
ASTUserClassOrInterface<?> parent = node.getFirstParentOfType(ASTUserClassOrInterface.class);
if (parent == null) {
ASTUserTrigger trigger = node.getFirstParentOfType(ASTUserTrigger.class);
String ns = trigger.getNamespace();
String targetObj = trigger.getTargetName();
return new ApexQualifiedName(StringUtils.isEmpty(ns) ? "c" : ns, new String[]{"trigger", targetObj}, trigger.getImage()); // uses a reserved word as a class name to prevent clashes
} else {
ApexQualifiedName baseName = parent.getQualifiedName();
// Check first, as enum must be innermost potential parent
ASTUserEnum enumParent = node.getFirstParentOfType(ASTUserEnum.class);
if (enumParent != null) {
ApexQualifiedName baseName = enumParent.getQualifiedName();
return new ApexQualifiedName(baseName.nameSpace, baseName.classes, getOperationString(node));
}
ASTUserClassOrInterface<?> classParent = node.getFirstParentOfType(ASTUserClassOrInterface.class);
if (classParent != null) {
ApexQualifiedName baseName = classParent.getQualifiedName();
return new ApexQualifiedName(baseName.nameSpace, baseName.classes, getOperationString(node));
}
ASTUserTrigger triggerParent = node.getFirstParentOfType(ASTUserTrigger.class);
if (triggerParent != null) {
String ns = triggerParent.getNamespace();
String targetObj = triggerParent.getTargetName();
return new ApexQualifiedName(StringUtils.isEmpty(ns) ? "c" : ns, new String[]{"trigger", targetObj}, triggerParent.getImage()); // uses a reserved word as a class name to prevent clashes
}
throw new UnsupportedOperationException();
}
}

View File

@@ -8,6 +8,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
@@ -97,4 +98,32 @@ public class ApexQualifiedNameTest extends ApexParserTestBase {
assertEquals("c__trigger.Account#myAccountTrigger", m.getQualifiedName().toString());
}
}
@Test
public void testUnqualifiedEnum() {
ApexNode<Compilation> root = parse("public enum primaryColor { RED, YELLOW, BLUE }");
ApexQualifiedName enumQName = ASTUserEnum.class.cast(root).getQualifiedName();
List<ASTMethod> methods = root.findDescendantsOfType(ASTMethod.class);
assertEquals("c__primaryColor", enumQName.toString());
for (ASTMethod m : methods) {
assertTrue(m.getQualifiedName().toString().startsWith("c__primaryColor#"));
}
}
@Test
public void testQualifiedEnum() {
ApexNode<Compilation> root = parse("public class Outer { public enum Inner { OK } }");
ASTUserEnum enumNode = root.getFirstDescendantOfType(ASTUserEnum.class);
ApexQualifiedName enumQName = enumNode.getQualifiedName();
List<ASTMethod> methods = enumNode.findDescendantsOfType(ASTMethod.class);
assertEquals("c__Outer.Inner", enumQName.toString());
for (ASTMethod m : methods) {
assertTrue(m.getQualifiedName().toString().startsWith("c__Outer.Inner#"));
}
}
}