Skip to content
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 @@ -18,5 +18,7 @@ public void accept(JorjeNodeVisitor visitor) {
@Override
protected void fillProperties(Map<String, Object> properties) {
properties.put(Schema.NAME, getName());
properties.put(Schema.TARGET_NAME, getNode().getTargetName().get(0).getValue());
properties.put(Schema.USAGES, getNode().getUsages().toString());
}
}
2 changes: 2 additions & 0 deletions sfge/src/main/java/com/salesforce/graph/Schema.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,12 @@ public class Schema {
public static final String STATIC_CONSTRUCTOR_CANONICAL_NAME = "<clinit>";
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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vertex, Vertex> traversal, String property, Object value) {
Expand Down
7 changes: 6 additions & 1 deletion sfge/src/main/java/com/salesforce/graph/ops/GraphUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ private static Optional<Util.CompilationDescriptor> 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);
}
Expand All @@ -215,6 +215,11 @@ private static Optional<Util.CompilationDescriptor> 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<Path> {
Expand Down
8 changes: 8 additions & 0 deletions sfge/src/main/java/com/salesforce/rules/UnusedMethodRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ public List<MethodVertex> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<Object, Object> 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<String> 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<Object, Object> 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 =
Expand Down
20 changes: 20 additions & 0 deletions sfge/src/test/java/com/salesforce/graph/ops/GraphUtilTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Object, Object> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
trigger AccountBefore on Account (before insert, before update) {
if (Trigger.isInsert) {
System.debug('asdfasdf');
}
}