From 62080c69f247323770b82757c95cdf9a7a3b172b Mon Sep 17 00:00:00 2001 From: Aaron Hurst Date: Thu, 1 Dec 2022 22:47:36 +0000 Subject: [PATCH] Refactor ASTMethod to support synthetic definitions, and add a synthetic "invoke" method inside triggers. Change-Id: I21344a36c9795deffd9c62aa0e768eb6f6742796 --- .../pmd/lang/apex/ast/ASTMethod.java | 92 +++++++++++++------ .../pmd/lang/apex/ast/ApexQualifiedName.java | 10 +- .../pmd/lang/apex/ast/ApexTreeBuilder.kt | 19 +++- 3 files changed, 86 insertions(+), 35 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java index 7503b253f9..70a3e2167f 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java @@ -7,11 +7,24 @@ package net.sourceforge.pmd.lang.apex.ast; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSignature; import net.sourceforge.pmd.lang.ast.SignedNode; +import net.sourceforge.pmd.lang.ast.SourceCodePositioner; +import java.util.List; +import java.util.stream.Collectors; + +import com.google.summit.ast.SourceLocation; import com.google.summit.ast.declaration.MethodDeclaration; -public class ASTMethod extends AbstractApexNode.Single implements ApexQualifiableNode, - SignedNode, CanSuppressWarnings { +public class ASTMethod extends AbstractApexNode implements ApexQualifiableNode, + SignedNode, CanSuppressWarnings { + + // Store the details instead of wrapping a com.google.summit.ast.Node. + // This is to allow synthetic ASTMethod nodes. + // An example is the trigger `invoke` method. + private String name; + private List parameterTypes; + private String returnType; + private SourceLocation sourceLocation; /** * Internal name used by constructors. @@ -23,8 +36,34 @@ public class ASTMethod extends AbstractApexNode.Single implem */ public static final String STATIC_INIT_ID = ""; - ASTMethod(MethodDeclaration method) { - super(method); + public ASTMethod( + String name, + List parameterTypes, + String returnType, + SourceLocation sourceLocation) + { + this.name = name; + this.parameterTypes = parameterTypes; + this.returnType = returnType; + this.sourceLocation = sourceLocation; + } + + public static ASTMethod fromNode(MethodDeclaration node) { + + String name = node.getId().getString(); + if (node.isAnonymousInitializationCode()) { + name = STATIC_INIT_ID; + } else if (node.isConstructor()) { + name = CONSTRUCTOR_ID; + } + + return new ASTMethod( + name, + node.getParameterDeclarations().stream() + .map(p -> caseNormalizedTypeIfPrimitive(p.getType().asCodeString())) + .collect(Collectors.toList()), + caseNormalizedTypeIfPrimitive(node.getReturnType().asCodeString()), + node.getSourceLocation()); } @Override @@ -32,39 +71,41 @@ public class ASTMethod extends AbstractApexNode.Single implem return visitor.visit(this, data); } - /** - * Returns the name of the method, converting the parser name to the internal PMD name as - * needed. - */ - private String getName() { - if (node.isAnonymousInitializationCode()) { - return STATIC_INIT_ID; - } else if (node.isConstructor()) { - return CONSTRUCTOR_ID; - } else if (getParent() instanceof ASTProperty) { - return ASTProperty.formatAccessorName((ASTProperty) getParent()); + @Override + void calculateLineNumbers(SourceCodePositioner positioner) { + setLineNumbers(sourceLocation); + } + + @Override + public boolean hasRealLoc() { + return !sourceLocation.isUnknown(); + } + + @Override + public String getLocation() { + if (hasRealLoc()) { + return String.valueOf(sourceLocation); } else { - return node.getId().getString(); + return "no location"; } } @Override public String getImage() { - if (node.isConstructor()) { + if (isConstructor()) { ApexRootNode rootNode = getFirstParentOfType(ApexRootNode.class); if (rootNode != null) { return rootNode.node.getId().getString(); } } - return getName(); + return getCanonicalName(); } public String getCanonicalName() { - if (node.isConstructor()) { - return CONSTRUCTOR_ID; - } else { - return getName(); + if (getParent() instanceof ASTProperty) { + return ASTProperty.formatAccessorName((ASTProperty) getParent()); } + return name; } @Override @@ -134,7 +175,6 @@ public class ASTMethod extends AbstractApexNode.Single implem return ApexQualifiedName.ofMethod(this); } - @Override public ApexOperationSignature getSignature() { return ApexOperationSignature.of(this); @@ -153,7 +193,7 @@ public class ASTMethod extends AbstractApexNode.Single implem } public boolean isConstructor() { - return node.isConstructor(); + return name.equals(CONSTRUCTOR_ID); } public ASTModifierNode getModifiers() { @@ -167,10 +207,10 @@ public class ASTMethod extends AbstractApexNode.Single implem * If the type is a primitive, its case will be normalized. */ public String getReturnType() { - return caseNormalizedTypeIfPrimitive(node.getReturnType().asCodeString()); + return returnType; } public int getArity() { - return node.getParameterDeclarations().size(); + return parameterTypes.size(); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java index 9d3c3e553d..489283db4c 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java @@ -13,9 +13,6 @@ import org.apache.commons.lang3.StringUtils; import net.sourceforge.pmd.lang.ast.QualifiedName; -import com.google.summit.ast.TypeRef; -import com.google.summit.ast.declaration.ParameterDeclaration; - /** * Qualified name of an apex class or method. * @@ -172,15 +169,14 @@ public final class ApexQualifiedName implements QualifiedName { StringBuilder sb = new StringBuilder(); sb.append(node.getImage()).append('('); - List paramTypes = node.node.getParameterDeclarations().stream() - .map(ParameterDeclaration::getType) + List paramTypes = node.findChildrenOfType(ASTParameter.class).stream() + .map(ASTParameter::getType) .collect(Collectors.toList()); if (!paramTypes.isEmpty()) { for (int i = 0; i < paramTypes.size(); i++) { sb.append(i > 0 ? ", " : ""); - sb.append(AbstractApexNode.caseNormalizedTypeIfPrimitive( - paramTypes.get(i).asCodeString())); + sb.append(paramTypes.get(i)); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.kt b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.kt index 70f2be4177..d03437c64a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.kt +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.kt @@ -14,6 +14,7 @@ import net.sourceforge.pmd.lang.ast.SourceCodePositioner import com.google.summit.ast.CompilationUnit import com.google.summit.ast.Identifier import com.google.summit.ast.Node +import com.google.summit.ast.SourceLocation import com.google.summit.ast.TypeRef import com.google.summit.ast.declaration.ClassDeclaration import com.google.summit.ast.declaration.EnumDeclaration @@ -222,7 +223,21 @@ class ApexTreeBuilder(val sourceCode: String, val parserOptions: ApexParserOptio is TriggerDeclaration -> ASTUserTrigger(node) }.apply { buildModifiers(node.modifiers).also { it.setParent(this) } - buildChildren(node, parent = this, exclude = { it in node.modifiers }) + if (node is TriggerDeclaration) { + // 1. Create a synthetic "invoke" ASTMethod for the trigger body + val invokeMethod = ASTMethod( + /* name= */ "invoke", + /* parameterTypes= */ emptyList(), + /* returnType= */ "void", + SourceLocation.UNKNOWN, + ).also{ it.setParent(this) } + // 2. Add the expected ASTModifier child node + buildModifiers(emptyList()).also { it.setParent(invokeMethod) } + // 3. Elide the body CompoundStatement->ASTBlockStatement + buildChildren(node.body, parent = invokeMethod as ApexNode<*>) + } else { + buildChildren(node, parent = this, exclude = { it in node.modifiers }) + } } /** Builds an [ASTMethod] wrapper for the [MethodDeclaration] node. */ @@ -231,7 +246,7 @@ class ApexTreeBuilder(val sourceCode: String, val parserOptions: ApexParserOptio node.isAnonymousInitializationCode() && !node.hasKeyword(Keyword.STATIC) -> build(node.body, parent) else -> { - ASTMethod(node).apply { + ASTMethod.fromNode(node).apply { buildModifiers(node.modifiers).also { it.setParent(this) } buildChildren(node, parent = this, exclude = { it in node.modifiers }) }