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 20dd5621aa..f14cfb3967 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 @@ -218,7 +218,11 @@ class ApexTreeBuilder(private val task: ParserTask, private val proc: ApexLangua }.apply { buildModifiers(node.modifiers).also { it.setParent(this) } if (node is TriggerDeclaration) { - // 1. Create a synthetic "invoke" ASTMethod for the trigger body + // 1. Add all extra methods that are defined in the trigger + node.body.filterIsInstance().forEach { it -> + buildMethodDeclaration(it, this)?.also { it.setParent(this) } + } + // 2. Create a synthetic "invoke" ASTMethod for the trigger body val invokeMethod = ASTMethod( /* name= */ "invoke", /* internalName= */ "", @@ -226,10 +230,11 @@ class ApexTreeBuilder(private val task: ParserTask, private val proc: ApexLangua /* returnType= */ "void", SourceLocation.UNKNOWN, ).also{ it.setParent(this) } - // 2. Add the expected ASTModifier child node + // 3. Add the expected ASTModifier child node buildModifiers(emptyList()).also { it.setParent(invokeMethod) } - // 3. Elide the body CompoundStatement->ASTBlockStatement - node.body.forEach { buildAndSetParent(it, parent = invokeMethod as AbstractApexNode) } + // 4. Elide the remaining body CompoundStatement->ASTBlockStatement + node.body.filterNot { it is MethodDeclaration } + .forEach { buildAndSetParent(it, parent = invokeMethod as AbstractApexNode) } } else { buildChildren(node, parent = this, exclude = { it in node.modifiers }) } diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeDumpTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeDumpTest.java index 5d3cbe3287..54294e6012 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeDumpTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeDumpTest.java @@ -86,4 +86,12 @@ class ApexTreeDumpTest extends BaseTreeDumpTest { void groupingInSoql() { doTest("GroupingInSoql"); } + + /** + * @see [apex] Seeing false-negatives on PMD 7.3.0 that were not there with 7.2.0 + */ + @Test + void triggersWithMethods() { + doTest("TriggerWithMethod"); + } } diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/ast/TriggerWithMethod.cls b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/ast/TriggerWithMethod.cls new file mode 100644 index 0000000000..9809bcf543 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/ast/TriggerWithMethod.cls @@ -0,0 +1,11 @@ +// based on https://github.com/SalesforceLabs/EmailResponseConsole/blob/master/triggers/HVEMApprovalProcessAction.trigger +trigger TriggerWithMethod on DraftEmailMessage__c (after update){ + String body = 'email body'; + + for(DraftEmailMessage__c demInstance : Trigger.New){ + sendEmail(demInstance, emailBody); + } + + private void sendEmail(DraftEmailMessage__c demInstance, String emailBody) { + } +} diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/ast/TriggerWithMethod.txt b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/ast/TriggerWithMethod.txt new file mode 100644 index 0000000000..ebbf46a1b1 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/ast/TriggerWithMethod.txt @@ -0,0 +1,37 @@ ++- ApexFile[@DefiningType = "TriggerWithMethod", @RealLoc = true] + +- UserTrigger[@DefiningType = "TriggerWithMethod", @Image = "TriggerWithMethod", @Nested = false, @RealLoc = true, @SimpleName = "TriggerWithMethod", @TargetName = "DraftEmailMessage__c", @Usages = (TriggerUsage.AFTER_UPDATE)] + +- ModifierNode[@Abstract = false, @DefiningType = "TriggerWithMethod", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false] + +- Field[@DefiningType = "TriggerWithMethod", @Image = "body", @Name = "body", @RealLoc = true, @Type = "String", @Value = "email body"] + | +- ModifierNode[@Abstract = false, @DefiningType = "TriggerWithMethod", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false] + +- Method[@Arity = 2, @CanonicalName = "sendEmail", @Constructor = false, @DefiningType = "TriggerWithMethod", @Image = "sendEmail", @RealLoc = true, @ReturnType = "void", @StaticInitializer = false] + | +- ModifierNode[@Abstract = false, @DefiningType = "TriggerWithMethod", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 2, @Override = false, @Private = true, @Protected = false, @Public = false, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false] + | +- Parameter[@DefiningType = "TriggerWithMethod", @Image = "demInstance", @RealLoc = true, @Type = "DraftEmailMessage__c"] + | | +- ModifierNode[@Abstract = false, @DefiningType = "TriggerWithMethod", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false] + | +- Parameter[@DefiningType = "TriggerWithMethod", @Image = "emailBody", @RealLoc = true, @Type = "String"] + | | +- ModifierNode[@Abstract = false, @DefiningType = "TriggerWithMethod", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false] + | +- BlockStatement[@CurlyBrace = true, @DefiningType = "TriggerWithMethod", @RealLoc = true] + +- Method[@Arity = 0, @CanonicalName = "invoke", @Constructor = false, @DefiningType = "TriggerWithMethod", @Image = "invoke", @RealLoc = false, @ReturnType = "void", @StaticInitializer = false] + +- ModifierNode[@Abstract = false, @DefiningType = "TriggerWithMethod", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false] + +- FieldDeclarationStatements[@DefiningType = "TriggerWithMethod", @RealLoc = true, @TypeArguments = (), @TypeName = "String"] + | +- ModifierNode[@Abstract = false, @DefiningType = "TriggerWithMethod", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false] + | +- FieldDeclaration[@DefiningType = "TriggerWithMethod", @Image = "body", @Name = "body", @RealLoc = true] + | +- LiteralExpression[@Boolean = false, @Decimal = false, @DefiningType = "TriggerWithMethod", @Double = false, @Image = "email body", @Integer = false, @LiteralType = LiteralType.STRING, @Long = false, @Name = null, @Null = false, @RealLoc = true, @String = true] + | +- VariableExpression[@DefiningType = "TriggerWithMethod", @Image = "body", @RealLoc = true] + | +- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false] + +- ForEachStatement[@DefiningType = "TriggerWithMethod", @RealLoc = true] + +- VariableDeclarationStatements[@DefiningType = "TriggerWithMethod", @RealLoc = true] + | +- ModifierNode[@Abstract = false, @DefiningType = "TriggerWithMethod", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false] + | +- VariableDeclaration[@DefiningType = "TriggerWithMethod", @Image = "demInstance", @RealLoc = true, @Type = "DraftEmailMessage__c"] + | +- VariableExpression[@DefiningType = "TriggerWithMethod", @Image = "demInstance", @RealLoc = true] + | +- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false] + +- VariableExpression[@DefiningType = "TriggerWithMethod", @Image = "demInstance", @RealLoc = true] + | +- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false] + +- BlockStatement[@CurlyBrace = true, @DefiningType = "TriggerWithMethod", @RealLoc = true] + | +- ExpressionStatement[@DefiningType = "TriggerWithMethod", @RealLoc = true] + | +- MethodCallExpression[@DefiningType = "TriggerWithMethod", @FullMethodName = "sendEmail", @InputParametersSize = 2, @MethodName = "sendEmail", @RealLoc = true] + | +- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false] + | +- VariableExpression[@DefiningType = "TriggerWithMethod", @Image = "demInstance", @RealLoc = true] + | | +- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false] + | +- VariableExpression[@DefiningType = "TriggerWithMethod", @Image = "emailBody", @RealLoc = true] + | +- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false] + +- TriggerVariableExpression[@DefiningType = "TriggerWithMethod", @RealLoc = true]