diff --git a/sfge/src/main/java/com/salesforce/apex/jorje/UserTriggerWrapper.java b/sfge/src/main/java/com/salesforce/apex/jorje/UserTriggerWrapper.java index d06b274e6..e58a783e3 100644 --- a/sfge/src/main/java/com/salesforce/apex/jorje/UserTriggerWrapper.java +++ b/sfge/src/main/java/com/salesforce/apex/jorje/UserTriggerWrapper.java @@ -18,5 +18,7 @@ public void accept(JorjeNodeVisitor visitor) { @Override protected void fillProperties(Map properties) { properties.put(Schema.NAME, getName()); + properties.put(Schema.TARGET_NAME, getNode().getTargetName().get(0).getValue()); + properties.put(Schema.USAGES, getNode().getUsages().toString()); } } diff --git a/sfge/src/main/java/com/salesforce/graph/Schema.java b/sfge/src/main/java/com/salesforce/graph/Schema.java index 1c839a6f9..4915f0479 100644 --- a/sfge/src/main/java/com/salesforce/graph/Schema.java +++ b/sfge/src/main/java/com/salesforce/graph/Schema.java @@ -62,10 +62,12 @@ public class Schema { public static final String STATIC_CONSTRUCTOR_CANONICAL_NAME = ""; public static final String SUPER_CLASS_NAME = "SuperClassName"; public static final String SUPER_INTERFACE_NAME = "SuperInterfaceName"; + public static final String TARGET_NAME = "TargetName"; public static final String TYPE = "Type"; /** Contains type for statements such as MyClass.class */ public static final String TYPE_REF = "TypeRef"; + public static final String USAGES = "Usages"; public static final String VALUE = "Value"; public static final String VIRTUAL = "Virtual"; public static final String QUERY = "Query"; diff --git a/sfge/src/main/java/com/salesforce/graph/build/CaseSafePropertyUtil.java b/sfge/src/main/java/com/salesforce/graph/build/CaseSafePropertyUtil.java index ec2e4a4f6..a257cf37e 100644 --- a/sfge/src/main/java/com/salesforce/graph/build/CaseSafePropertyUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/build/CaseSafePropertyUtil.java @@ -47,7 +47,8 @@ public class CaseSafePropertyUtil { Schema.NAME, Schema.RETURN_TYPE, Schema.SUPER_CLASS_NAME, - Schema.SUPER_INTERFACE_NAME); + Schema.SUPER_INTERFACE_NAME, + Schema.TARGET_NAME); static void addCaseSafeProperty( GraphTraversal traversal, String property, Object value) { diff --git a/sfge/src/main/java/com/salesforce/graph/ops/GraphUtil.java b/sfge/src/main/java/com/salesforce/graph/ops/GraphUtil.java index 38fee0829..c049f4a30 100644 --- a/sfge/src/main/java/com/salesforce/graph/ops/GraphUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/ops/GraphUtil.java @@ -194,7 +194,7 @@ private static Optional loadFile(Path path) throws I String pathString = path.toString(); final ProgressListener progressListener = ProgressListenerProvider.get(); - if (!pathString.toLowerCase(Locale.ROOT).endsWith(".cls")) { + if (!isGraphablePath(pathString)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Skipping file. path=" + pathString); } @@ -215,6 +215,11 @@ private static Optional loadFile(Path path) throws I } } + private static boolean isGraphablePath(String pathString) { + String lcPathString = pathString.toLowerCase(Locale.ROOT); + return lcPathString.endsWith(".cls") || lcPathString.endsWith(".trigger"); + } + private GraphUtil() {} private static final class SourceFileVisitor extends SimpleFileVisitor { diff --git a/sfge/src/main/java/com/salesforce/rules/UnusedMethodRule.java b/sfge/src/main/java/com/salesforce/rules/UnusedMethodRule.java index a4166297e..72bf269a8 100644 --- a/sfge/src/main/java/com/salesforce/rules/UnusedMethodRule.java +++ b/sfge/src/main/java/com/salesforce/rules/UnusedMethodRule.java @@ -110,6 +110,14 @@ public List getEligibleMethods(GraphTraversalSource g) { NodeType.METHOD, Schema.NAME, ASTConstants.PROPERTY_METHOD_PREFIX)) + // Triggers technically have methods. We should ignore those. + // TODO: Once triggers are explicit sources, this line is + // technically unnecessary. + .where( + __.out(Schema.PARENT) + .hasLabel(NodeType.USER_TRIGGER) + .count() + .is(P.eq(0))) // Abstract methods must be implemented by all concrete child // classes. This rule can detect whether those concrete // implementations are used, and another rule detects abstract diff --git a/sfge/src/test/java/com/salesforce/graph/build/CustomerApexVertexBuilderTest.java b/sfge/src/test/java/com/salesforce/graph/build/CustomerApexVertexBuilderTest.java index 73a6d8ebd..11e7d6f46 100644 --- a/sfge/src/test/java/com/salesforce/graph/build/CustomerApexVertexBuilderTest.java +++ b/sfge/src/test/java/com/salesforce/graph/build/CustomerApexVertexBuilderTest.java @@ -3,6 +3,7 @@ import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.has; import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.salesforce.TestUtil; import com.salesforce.apex.jorje.ASTConstants.NodeType; @@ -12,10 +13,7 @@ import com.salesforce.graph.cache.VertexCacheProvider; import com.salesforce.graph.vertex.MethodVertex; import com.salesforce.graph.vertex.SFVertexFactory; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.Scope; @@ -85,6 +83,53 @@ public void testSimple() { assertEquals("SimpleSource", properties.get(Schema.FILE_NAME)); } + /** + * Verifies that the special properties associated with {@link + * com.salesforce.graph.vertex.UserTriggerVertex} are set. + */ + @Test + public void testTriggers() { + // spotless:off + String triggerSource = + "trigger AccountBefore on Account (before insert, before update) {\n" + + " if (Trigger.isInsert) {\n" + + " System.debug('Some debug-worthy message');\n" + + " }\n" + + "}\n"; + // spotless:on + + AstNodeWrapper triggerComp = JorjeUtil.compileApexFromString(triggerSource); + Util.CompilationDescriptor triggerDesc = + new Util.CompilationDescriptor("TestCode1", triggerComp); + + CustomerApexVertexBuilder vertexBuilder = + new CustomerApexVertexBuilder(g, Collections.singletonList(triggerDesc)); + vertexBuilder.build(); + + // Validate that a vertex was created for the trigger. + Map triggerVertex = + g.V().hasLabel(NodeType.USER_TRIGGER).elementMap().next(); + assertEquals("AccountBefore", triggerVertex.get(Schema.NAME)); + assertEquals("AccountBefore", triggerVertex.get(Schema.DEFINING_TYPE)); + assertEquals("Account", triggerVertex.get(Schema.TARGET_NAME)); + String rawUsages = (String) triggerVertex.get(Schema.USAGES); + Set usages = + new HashSet<>( + Arrays.asList(rawUsages.substring(1, rawUsages.length() - 1).split(", "))); + assertTrue(usages.contains("BEFORE INSERT")); + assertTrue(usages.contains("BEFORE UPDATE")); + // validate that the trigger's contents were added as an `invoke()` method. + Map invokeVertex = + g.V() + .hasLabel(NodeType.USER_TRIGGER) + .out(Schema.CHILD) + .hasLabel(NodeType.METHOD) + .has(Schema.NAME, "invoke") + .elementMap() + .next(); + assertEquals("AccountBefore", invokeVertex.get(Schema.DEFINING_TYPE)); + } + @Test public void testCaseSafeProperties() { String interfaceSource = diff --git a/sfge/src/test/java/com/salesforce/graph/ops/GraphUtilTest.java b/sfge/src/test/java/com/salesforce/graph/ops/GraphUtilTest.java index 428009941..adb99061f 100644 --- a/sfge/src/test/java/com/salesforce/graph/ops/GraphUtilTest.java +++ b/sfge/src/test/java/com/salesforce/graph/ops/GraphUtilTest.java @@ -1,9 +1,14 @@ package com.salesforce.graph.ops; import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import com.salesforce.TestUtil; +import com.salesforce.apex.jorje.ASTConstants; +import com.salesforce.graph.Schema; import java.io.File; +import java.util.Map; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; @@ -20,6 +25,21 @@ public void setup() { this.g = TestUtil.getUniqueGraph(); } + @Test + public void verifyTriggersAddedToGraph(TestInfo testInfo) { + try { + TestUtil.compileTestFiles(g, testInfo); + } catch (GraphUtil.GraphLoadException ex) { + fail("failed to create graph"); + } + + Map triggerVertex = + g.V().hasLabel(ASTConstants.NodeType.USER_TRIGGER).elementMap().next(); + assertEquals("AccountBefore", triggerVertex.get(Schema.NAME)); + assertEquals("AccountBefore", triggerVertex.get(Schema.DEFINING_TYPE)); + assertEquals("Account", triggerVertex.get(Schema.TARGET_NAME)); + } + /** Verify that a class which is defined in multiple directories results in an exception. */ @Test public void testDuplicateUserClassesThrowsException(TestInfo testInfo) { diff --git a/sfge/src/test/java/com/salesforce/rules/unusedmethod/IneligibleMethodExclusionTest.java b/sfge/src/test/java/com/salesforce/rules/unusedmethod/IneligibleMethodExclusionTest.java index ef403789f..9bf4a7425 100644 --- a/sfge/src/test/java/com/salesforce/rules/unusedmethod/IneligibleMethodExclusionTest.java +++ b/sfge/src/test/java/com/salesforce/rules/unusedmethod/IneligibleMethodExclusionTest.java @@ -211,6 +211,21 @@ public void emailHandlerMethod_expectNoAnalysis() { assertMethodIneligibility(sourceCode, "MyClass", "handleInboundEmail", 2); } + /** Triggers are entrypoints, and should count as used. */ + @Test + public void trigger_expectNoAnalysis() { + // spotless:off + String sourceCode = + "trigger AccountBefore on Account (before insert) {\n" + + " if (Trigger.isBefore) {\n" + + " System.debug('asdf');\n" + + " }\n" + + "}\n"; + // spotless:on + assertMethodIneligibility( + sourceCode, new String[] {"AccountBefore"}, new String[] {"invoke"}, new int[] {1}); + } + /* =============== SECTION 4: PROPERTY GETTERS AND SETTERS =============== */ // REASONING: Public setters are often used by Visualforce, and private setters are often // declared to prevent a variable from being modified entirely. diff --git a/sfge/src/test/resources/test-files/com/salesforce/graph/ops/GraphUtilTest/verifyTriggersAddedToGraph/triggers/AccountBefore.trigger b/sfge/src/test/resources/test-files/com/salesforce/graph/ops/GraphUtilTest/verifyTriggersAddedToGraph/triggers/AccountBefore.trigger new file mode 100644 index 000000000..0c419baa3 --- /dev/null +++ b/sfge/src/test/resources/test-files/com/salesforce/graph/ops/GraphUtilTest/verifyTriggersAddedToGraph/triggers/AccountBefore.trigger @@ -0,0 +1,5 @@ +trigger AccountBefore on Account (before insert, before update) { + if (Trigger.isInsert) { + System.debug('asdfasdf'); + } +}