Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[apex] Update summit-ast and apex-parser #4977

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/pages/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ This is a {{ site.pmd.release_type }} release.
### 🚀 New and noteworthy

### 🐛 Fixed Issues
* apex
* [#4922](https://github.com/pmd/pmd/issues/4922): \[apex] SOQL syntax error with TYPEOF in sub-query
* [#5055](https://github.com/pmd/pmd/issues/5055): \[apex] SOSL syntax error with WITH USER_MODE or WITH SYSTEM_MODE

### 🚨 API Changes

Expand Down
9 changes: 4 additions & 5 deletions pmd-apex/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,15 @@
<dependency>
<groupId>com.google.summit</groupId>
<artifactId>summit-ast</artifactId>
<version>2.2.0</version>
<version>2.2.1-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>com.github.nawforce</groupId>
<artifactId>apexlink</artifactId>
<version>2.3.7</version>
<groupId>io.github.apex-dev-tools</groupId>
<artifactId>apex-ls_2.13</artifactId>
<version>5.2.0</version>
</dependency>


<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import net.sourceforge.pmd.lang.document.TextRegion;

import com.google.summit.ast.CompilationUnit;
import com.nawforce.pkgforce.api.Issue;
import io.github.apexdevtools.api.Issue;

public final class ASTApexFile extends AbstractApexNode.Single<CompilationUnit> implements RootNode {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ public String getCanonicalQuery() {
return canoncialQuery;
}

private static String convertToCanonicalQuery(String rawQuery) {
static String convertToCanonicalQuery(String rawQuery) {
// note: this is a very crude way. At some point, the parsed query should be
// provided by Summit AST. The Apex Parser already parses the SOQL/SOSL queries.
String query = rawQuery;
query = query.replaceAll("(?i)\\bselect\\b", "SELECT");
query = query.replaceAll("(?i)\\bfrom\\b", "FROM");
query = query.replaceAll("(?i)\\bupdate\\b", "UPDATE");
query = query.replaceAll("(?i)\\bwhere\\b", "WHERE");
query = query.replaceAll("(?i)\\bgroup\\b+by\\b", "GROUP BY");
query = query.replaceAll("(?i)\\bgroup\\b\\s+\\bby\\b", "GROUP BY");
query = query.replaceAll("(?i)\\band\\b", "AND");
query = query.replaceAll("(?i)\\bor\\b", "OR");
query = query.replaceAll("(?i)\\bnot\\b", "NOT");
Expand Down Expand Up @@ -78,6 +78,22 @@ private static String convertToCanonicalQuery(String rawQuery) {
query = query.replaceAll("(?i)\\bDISTANCE\\(", "DISTANCE(");
query = query.replaceAll("(?i)\\bconverttimezone\\(", "CONVERTTIMEZONE(");

// sosl keywords
query = query.replaceAll("(?i)\\bfind\\b", "FIND");
query = query.replaceAll("(?i)\\bin\\b", "IN");
query = query.replaceAll("(?i)\\breturning\\b", "RETURNING");
query = query.replaceAll("(?i)\\bassign\\b", "ASSIGN");
query = query.replaceAll("(?i)\\btarget_length\\b", "TARGET_LENGTH");
query = query.replaceAll("(?i)\\bmetadata\\b", "METADATA");
query = query.replaceAll("(?i)\\blimit\\b", "LIMIT");
query = query.replaceAll("(?i)\\bwith\\b\\s+\\bdivision\\b", "WITH DIVISION");
query = query.replaceAll("(?i)\\bwith\\b\\s+\\bdata\\b\\s+\\bcategory\\b", "WITH DATA CATEGORY");
query = query.replaceAll("(?i)\\bwith\\b\\s+\\bsnippet\\b", "WITH SNIPPET");
query = query.replaceAll("(?i)\\bwith\\b\\s+\\bnetwork\\b", "WITH NETWORK");
query = query.replaceAll("(?i)\\bwith\\b\\s+\\bpricebookid\\b", "WITH PRICEBOOKID");
query = query.replaceAll("(?i)\\bwith\\b\\s+\\buser_mode\\b", "WITH USER_MODE");
query = query.replaceAll("(?i)\\bwith\\b\\s+\\bsystem_mode\\b", "WITH SYSTEM_MODE");

// replace all binding variables with :tmpVar1 :tmpVar2...
Pattern bindingVarPattern = Pattern.compile(":[^ :]+\\b");
int tmpVarCounter = 1;
Expand All @@ -89,6 +105,6 @@ private static String convertToCanonicalQuery(String rawQuery) {
}
matcher.appendTail(normalizedVars);

return normalizedVars.toString();
return normalizedVars.toString().trim();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,35 @@

package net.sourceforge.pmd.lang.apex.ast;

import static net.sourceforge.pmd.lang.apex.ast.ASTSoqlExpression.convertToCanonicalQuery;

import com.google.summit.ast.expression.SoslExpression;

public final class ASTSoslExpression extends AbstractApexNode.Single<SoslExpression> {
private final String canoncialQuery;

ASTSoslExpression(SoslExpression soslExpression) {
super(soslExpression);
canoncialQuery = convertToCanonicalQuery(soslExpression.getQuery());
}


@Override
protected <P, R> R acceptApexVisitor(ApexVisitor<? super P, ? extends R> visitor, P data) {
return visitor.visit(this, data);
}

/**
* Returns the raw query as it appears in the source code.
*/
public String getQuery() {
return node.getQuery();
}

/**
* Returns the query with the SOSL keywords normalized as uppercase.
*/
public String getCanonicalQuery() {
return canoncialQuery;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import net.sourceforge.pmd.lang.document.TextDocument;
import net.sourceforge.pmd.lang.document.TextRegion;

import com.nawforce.apexparser.ApexLexer;
import io.github.apexdevtools.apexparser.ApexLexer;

@InternalApi
final class ApexCommentBuilder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ class ApexTreeBuilder(private val task: ParserTask, private val proc: ApexLangua
// 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 AbstractApexNode)
node.body.forEach { buildChildren(it, parent = invokeMethod as AbstractApexNode) }
} else {
buildChildren(node, parent = this, exclude = { it in node.modifiers })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import net.sourceforge.pmd.cpd.TokenFactory;
import net.sourceforge.pmd.lang.document.TextDocument;

import com.nawforce.apexparser.ApexLexer;
import io.github.apexdevtools.apexparser.ApexLexer;

public class ApexCpdLexer implements CpdLexer {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
import net.sourceforge.pmd.lang.apex.ApexLanguageProperties;

import com.nawforce.apexlink.api.Org;
import com.nawforce.pkgforce.api.Issue;
import com.nawforce.pkgforce.diagnostics.LoggerOps;
import io.github.apexdevtools.api.Issue;

/**
* Stores multi-file analysis data. The 'Org' here is the primary ApexLink structure for maintaining information
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;

import com.nawforce.pkgforce.diagnostics.UNUSED_CATEGORY;

public class UnusedMethodRule extends AbstractApexRule {

@Override
public Object visit(ASTMethod node, Object data) {
// Check if any 'Unused' Issues align with this method
node.getRoot().getGlobalIssues().stream()
.filter(issue -> "Unused".equals(issue.category()))
.filter(issue -> issue.rule().name().equals(UNUSED_CATEGORY.name()))
.filter(issue -> issue.fileLocation().startLineNumber() == node.getBeginLine())
.filter(issue -> issue.fileLocation().endLineNumber() <= node.getBeginLine())
.forEach(issue -> asCtx(data).addViolation(node));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
import org.antlr.v4.runtime.Token;
import org.junit.jupiter.api.Test;

import com.nawforce.apexparser.ApexLexer;
import com.nawforce.apexparser.ApexParser;

import io.github.apexdevtools.apexparser.ApexLexer;
import io.github.apexdevtools.apexparser.ApexParser;

/**
* This is an exploration test for {@link ApexLexer}.
Expand Down Expand Up @@ -46,7 +45,7 @@ void testLexer() {
void testParser() {
CharStream in = CharStreams.fromString(CODE);
ApexLexer lexer = new ApexLexer(in);
ApexParser parser = new com.nawforce.apexparser.ApexParser(new CommonTokenStream(lexer));
ApexParser parser = new ApexParser(new CommonTokenStream(lexer));
ApexParser.CompilationUnitContext compilationUnit = parser.compilationUnit();
assertNotNull(compilationUnit);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,20 @@ void innerClassLocations() {
void nullCoalescingOperator() {
doTest("NullCoalescingOperator");
}

/**
* @see <a href="https://github.com/pmd/pmd/issues/4922">[apex] TYPEOF in sub-query throws error #4922</a>
*/
@Test
void typeOfSubQuery() {
doTest("TypeofTest");
}

/**
* @see <a href="https://github.com/google/summit-ast/issues/53">Fail to parses SOSL with WITH USER_MODE or WITH SYSTEM_MODE #53</a>
*/
@Test
void soslWithUsermode() {
doTest("SoslWithUsermode");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import net.sourceforge.pmd.reporting.Report;
import net.sourceforge.pmd.reporting.RuleViolation;

import com.nawforce.pkgforce.path.PathFactory;
import com.nawforce.pkgforce.path.PathLike;
import com.nawforce.runtime.platform.Environment;
import scala.Option;
Expand All @@ -50,7 +49,7 @@ private void assertViolation(RuleViolation violation, String fileName, int lineN
}

private Report runRule(Path testProjectDir) throws IOException {
Option<PathLike> pathLikeOption = Option.apply(PathFactory.apply(tempDir.toString()));
Option<PathLike> pathLikeOption = Option.apply(new com.nawforce.runtime.platform.Path(tempDir));
Option<Option<PathLike>> cachDirOption = Option.apply(pathLikeOption);
Environment.setCacheDirOverride(cachDirOption);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// https://github.com/pmd/pmd/issues/5055
// https://github.com/google/summit-ast/issues/53

public inherited sharing class SoslWithUsermode {

public static String example() {
String SecondarySearchList = 'test';

List<List<SObject>> accountList = [
find :SecondarySearchList
in name fields
returning Account(Id, Account.Name where ID = '' limit 100)
with user_mode
];
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
+- ApexFile[@DefiningType = "SoslWithUsermode", @RealLoc = true]
+- UserClass[@DefiningType = "SoslWithUsermode", @Image = "SoslWithUsermode", @InterfaceNames = (), @RealLoc = true, @SimpleName = "SoslWithUsermode", @SuperClassName = ""]
+- ModifierNode[@Abstract = false, @DefiningType = "SoslWithUsermode", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = true, @Modifiers = 1, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
+- Method[@Arity = 0, @CanonicalName = "example", @Constructor = false, @DefiningType = "SoslWithUsermode", @Image = "example", @RealLoc = true, @ReturnType = "String", @StaticInitializer = false]
+- ModifierNode[@Abstract = false, @DefiningType = "SoslWithUsermode", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 9, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = true, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
+- BlockStatement[@CurlyBrace = true, @DefiningType = "SoslWithUsermode", @RealLoc = true]
+- VariableDeclarationStatements[@DefiningType = "SoslWithUsermode", @RealLoc = true]
| +- ModifierNode[@Abstract = false, @DefiningType = "SoslWithUsermode", @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 = "SoslWithUsermode", @Image = "SecondarySearchList", @RealLoc = true, @Type = "String"]
| +- LiteralExpression[@Boolean = false, @Decimal = false, @DefiningType = "SoslWithUsermode", @Double = false, @Image = "test", @Integer = false, @LiteralType = LiteralType.STRING, @Long = false, @Name = null, @Null = false, @RealLoc = true, @String = true]
| +- VariableExpression[@DefiningType = "SoslWithUsermode", @Image = "SecondarySearchList", @RealLoc = true]
| +- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false]
+- VariableDeclarationStatements[@DefiningType = "SoslWithUsermode", @RealLoc = true]
+- ModifierNode[@Abstract = false, @DefiningType = "SoslWithUsermode", @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 = "SoslWithUsermode", @Image = "accountList", @RealLoc = true, @Type = "List<List<SObject>>"]
+- SoslExpression[@CanonicalQuery = "FIND :tmpVar1\n IN name fields\n RETURNING Account(Id, Account.Name WHERE ID = \'\' LIMIT 100)\n WITH USER_MODE", @DefiningType = "SoslWithUsermode", @Query = "\n find :SecondarySearchList\n in name fields\n returning Account(Id, Account.Name where ID = \'\' limit 100)\n with user_mode\n ", @RealLoc = true]
| +- BindExpressions[@DefiningType = "SoslWithUsermode", @RealLoc = true]
| +- VariableExpression[@DefiningType = "SoslWithUsermode", @Image = "SecondarySearchList", @RealLoc = true]
| +- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false]
+- VariableExpression[@DefiningType = "SoslWithUsermode", @Image = "accountList", @RealLoc = true]
+- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false]
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// https://github.com/pmd/pmd/issues/4922

public class TypeofTest {
static void test() {
Case c = [
SELECT
Id,
Subject,
Description,
TYPEOF Owner
WHEN Group THEN Name
WHEN User THEN Name
END,
(
SELECT
Id,
Subject,
Description,
TYPEOF Owner
WHEN Group THEN Name
WHEN User THEN Name
END
FROM Cases
)
FROM Case
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
+- ApexFile[@DefiningType = "TypeofTest", @RealLoc = true]
+- UserClass[@DefiningType = "TypeofTest", @Image = "TypeofTest", @InterfaceNames = (), @RealLoc = true, @SimpleName = "TypeofTest", @SuperClassName = ""]
+- ModifierNode[@Abstract = false, @DefiningType = "TypeofTest", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 1, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
+- Method[@Arity = 0, @CanonicalName = "test", @Constructor = false, @DefiningType = "TypeofTest", @Image = "test", @RealLoc = true, @ReturnType = "void", @StaticInitializer = false]
+- ModifierNode[@Abstract = false, @DefiningType = "TypeofTest", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 8, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = true, @Static = true, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
+- BlockStatement[@CurlyBrace = true, @DefiningType = "TypeofTest", @RealLoc = true]
+- VariableDeclarationStatements[@DefiningType = "TypeofTest", @RealLoc = true]
+- ModifierNode[@Abstract = false, @DefiningType = "TypeofTest", @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 = "TypeofTest", @Image = "c", @RealLoc = true, @Type = "Case"]
+- SoqlExpression[@CanonicalQuery = "SELECT\n\t\t\t\tId,\n\t\t\t\tSubject,\n\t\t\t\tDescription,\n\t\t\t\tTYPEOF Owner\n\t\t\t\t\tWHEN Group THEN Name\n\t\t\t\t\tWHEN User THEN Name\n\t\t\t\tEND,\n\t\t\t\t(\n\t\t\t\t\tSELECT\n\t\t\t\t\t\tId,\n\t\t\t\t\t\tSubject,\n\t\t\t\t\t\tDescription,\n\t\t\t\t\t\tTYPEOF Owner\n\t\t\t\t\t\t\tWHEN Group THEN Name\n\t\t\t\t\t\t\tWHEN User THEN Name\n\t\t\t\t\t\tEND\n\t\t\t\t\tFROM Cases\n\t\t\t\t)\n\t\t\tFROM Case", @DefiningType = "TypeofTest", @Query = "SELECT\n\t\t\t\tId,\n\t\t\t\tSubject,\n\t\t\t\tDescription,\n\t\t\t\tTYPEOF Owner\n\t\t\t\t\tWHEN Group THEN Name\n\t\t\t\t\tWHEN User THEN Name\n\t\t\t\tEND,\n\t\t\t\t(\n\t\t\t\t\tSELECT\n\t\t\t\t\t\tId,\n\t\t\t\t\t\tSubject,\n\t\t\t\t\t\tDescription,\n\t\t\t\t\t\tTYPEOF Owner\n\t\t\t\t\t\t\tWHEN Group THEN Name\n\t\t\t\t\t\t\tWHEN User THEN Name\n\t\t\t\t\t\tEND\n\t\t\t\t\tFROM Cases\n\t\t\t\t)\n\t\t\tFROM Case", @RealLoc = true]
+- VariableExpression[@DefiningType = "TypeofTest", @Image = "c", @RealLoc = true]
+- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>50.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,10 @@ trigger CaseAssignLevel on CaseAssignLevel__c (after delete, after insert, after

<test-code>
<description>#768 NPE caused by exception in ApexQualifiedName.ofMethod because of trigger</description>
<rule-property name="methodReportLevel">1</rule-property>
<expected-problems>1</expected-problems>
<expected-messages>
<message>The trigger 'CaseAssignLevel' has a cyclomatic complexity of 12.</message>
<message>The trigger 'CaseAssignLevel' has a cyclomatic complexity of 9.</message>
</expected-messages>
<code-ref id="trigger"/>
</test-code>
Expand Down