diff --git a/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java b/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java index 37a144e2f..9632bb1d6 100644 --- a/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java +++ b/sfge/src/main/java/com/salesforce/graph/ops/MethodUtil.java @@ -178,8 +178,8 @@ public static List getNamespaceAccessibleMethods( } /** - * Returns non-test methods in the target files with a @RemoteAction annotation. An empty - * list implicitly includes all files. + * Returns non-test methods in the target files with a @RemoteAction annotation. An empty list + * implicitly includes all files. */ public static List getRemoteActionMethods( GraphTraversalSource g, List targetFiles) { @@ -229,6 +229,34 @@ private static GraphTraversal rootMethodTraversal( .not(has(Schema.IS_TEST, true)); } + /** + * Returns non-test methods in the target files whose modifier scope is `global`. An empty list + * implicitly includes all files. + */ + public static List getGlobalMethods( + GraphTraversalSource g, List targetFiles) { + // Get all methods in the target files. + return SFVertexFactory.loadVertices( + g, + rootMethodTraversal(g, targetFiles) + .filter( + __.and( + // If a method has at least one block statement, then it is + // definitely actually declared, as + // opposed to being an implicit method. + out(Schema.CHILD) + .hasLabel(NodeType.BLOCK_STATEMENT) + .count() + .is(P.gte(1)), + // We only want global methods. + out(Schema.CHILD) + .hasLabel(NodeType.MODIFIER_NODE) + .has(Schema.GLOBAL, true), + // Ignore any standard methods, otherwise will get a ton of + // extra results. + __.not(__.has(Schema.IS_STANDARD, true))))); + } + /** * Returns all non-test public- and global-scoped methods in controllers referenced by * VisualForce pages, filtered by target file list. An empty list implicitly includes all files. diff --git a/sfge/src/main/java/com/salesforce/rules/RuleUtil.java b/sfge/src/main/java/com/salesforce/rules/RuleUtil.java index b0b7c6ec0..5e088f338 100644 --- a/sfge/src/main/java/com/salesforce/rules/RuleUtil.java +++ b/sfge/src/main/java/com/salesforce/rules/RuleUtil.java @@ -65,6 +65,8 @@ public static List getPathEntryPoints( methods.addAll(MethodUtil.getRemoteActionMethods(g, fileLevelTargets)); // ...and PageReference methods... methods.addAll(MethodUtil.getPageReferenceMethods(g, fileLevelTargets)); + // ...and global-exposed methods... + methods.addAll(MethodUtil.getGlobalMethods(g, fileLevelTargets)); // ...and exposed methods on VF controllers. methods.addAll(MethodUtil.getExposedControllerMethods(g, fileLevelTargets)); } diff --git a/sfge/src/test/java/com/salesforce/graph/ops/MethodUtilTest.java b/sfge/src/test/java/com/salesforce/graph/ops/MethodUtilTest.java index 051ed13a0..52336ec4f 100644 --- a/sfge/src/test/java/com/salesforce/graph/ops/MethodUtilTest.java +++ b/sfge/src/test/java/com/salesforce/graph/ops/MethodUtilTest.java @@ -389,4 +389,43 @@ public void testGetMethodsWithAnnotation(String annotation) { MatcherAssert.assertThat(excludedName, excludedMethod.isTest(), equalTo(true)); } } + + @Test + public void testGetGlobalMethods() { + String[] sourceCode = { + "public class MyClass {\n" + + " global static void foo() {\n" + + " }\n" + + " global static testMethod void shouldBeExcludedByModifier() {\n" + + " }\n" + + " @isTest\n" + + " global static void shouldBeExcludedByAnnotation() {\n" + + " }\n" + + " public static void bar() {\n" + + " }\n" + + "}\n", + "@isTest\n" + + "public class MyTestClass {\n" + + " public static void foo() {\n" + + " }\n" + + "}\n", + }; + + TestUtil.buildGraph(g, sourceCode); + + List methods = MethodUtil.getGlobalMethods(g, new ArrayList<>()); + // The `foo` method should be included because it's declared as global. + MatcherAssert.assertThat(methods, hasSize(equalTo(1))); + MatcherAssert.assertThat(methods.get(0).getName(), equalTo("foo")); + + for (String excludedName : + new String[] {"shouldBeExcludedByModifier", "shouldBeExcludedByAnnotation"}) { + MethodVertex excludedMethod = + SFVertexFactory.load( + g, + g.V().hasLabel(ASTConstants.NodeType.METHOD) + .has(Schema.NAME, excludedName)); + MatcherAssert.assertThat(excludedName, excludedMethod.isTest(), equalTo(true)); + } + } } diff --git a/sfge/src/test/java/com/salesforce/rules/RuleUtilTest.java b/sfge/src/test/java/com/salesforce/rules/RuleUtilTest.java index 0479775a4..e55890da4 100644 --- a/sfge/src/test/java/com/salesforce/rules/RuleUtilTest.java +++ b/sfge/src/test/java/com/salesforce/rules/RuleUtilTest.java @@ -5,8 +5,7 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.IsNull.nullValue; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; import com.salesforce.TestUtil; import com.salesforce.graph.Schema; @@ -63,6 +62,40 @@ public void getPathEntryPoints_includesAnnotatedMethods(String annotation) { assertEquals("annotatedMethod", firstVertex.getName()); } + @Test + public void getPathEntryPoints_includesGlobalMethods() { + String sourceCode = + "public class Foo {\n" + + " global static void globalStaticMethod() {\n" + + " }\n" + + " global void globalInstanceMethod() {\n" + + " }\n" + + " public static void publicStaticMethod() {\n" + + " }\n" + + "}\n"; + TestUtil.buildGraph(g, sourceCode, true); + + List entryPoints = RuleUtil.getPathEntryPoints(g); + + MatcherAssert.assertThat(entryPoints, hasSize(equalTo(2))); + boolean staticMethodFound = false; + boolean instanceMethodFound = false; + for (MethodVertex entrypoint : entryPoints) { + switch (entrypoint.getName()) { + case "globalStaticMethod": + staticMethodFound = true; + break; + case "globalInstanceMethod": + instanceMethodFound = true; + break; + default: + fail("Unexpected method " + entrypoint.getName()); + } + } + assertTrue(staticMethodFound); + assertTrue(instanceMethodFound); + } + @Test public void getPathEntryPoints_includesPageReferenceMethods() { String sourceCode =