diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md
index bb8cef8407..24ed6f31bd 100644
--- a/docs/pages/release_notes.md
+++ b/docs/pages/release_notes.md
@@ -41,6 +41,7 @@ Note that XPath 1.0 support, the default XPath version, is deprecated since PMD
* apex
* [#2210](https://github.com/pmd/pmd/issues/2210): \[apex] ApexCRUDViolation: Support WITH SECURITY_ENFORCED
+ * [#2358](https://github.com/pmd/pmd/issues/2358): \[apex] Invalid Apex in Cognitive Complexity tests
### API Changes
@@ -106,6 +107,7 @@ implementations, and their corresponding Parser if it exists (in the same packag
* [#2312](https://github.com/pmd/pmd/pull/2312): \[apex] Update ApexCRUDViolation Rule - [Joshua S Arquilevich](https://github.com/jarquile)
* [#2314](https://github.com/pmd/pmd/pull/2314): \[doc] maven integration - Add version to plugin - [Pham Hai Trung](https://github.com/gpbp)
* [#2353](https://github.com/pmd/pmd/pull/2353): \[plsql] xmlforest with optional AS - [Piotr Szymanski](https://github.com/szyman23)
+* [#2383](https://github.com/pmd/pmd/pull/2383): \[apex] Fix invalid apex in documentation - [Gwilym Kuiper](https://github.com/gwilymatgearset)
{% endtocmaker %}
diff --git a/pmd-apex/src/main/resources/category/apex/bestpractices.xml b/pmd-apex/src/main/resources/category/apex/bestpractices.xml
index 4c43789af4..9d2913a771 100644
--- a/pmd-apex/src/main/resources/category/apex/bestpractices.xml
+++ b/pmd-apex/src/main/resources/category/apex/bestpractices.xml
@@ -24,7 +24,7 @@ improves the readability of test output.
@@ -106,12 +106,12 @@ Apex unit tests should not use @isTest(seeAllData=true) because it opens up the
diff --git a/pmd-apex/src/main/resources/category/apex/design.xml b/pmd-apex/src/main/resources/category/apex/design.xml
index bef21cd3f7..da9bad6e11 100644
--- a/pmd-apex/src/main/resources/category/apex/design.xml
+++ b/pmd-apex/src/main/resources/category/apex/design.xml
@@ -121,7 +121,10 @@ public class Foo {
if (a.Phone == null) { // +1
a.Phone = phone;
update a;
+ return true;
}
+
+ return false;
}
// Has a cognitive complexity of 5
@@ -189,7 +192,7 @@ same datatype. These situations usually denote the need for new objects to wrap
@@ -165,11 +165,11 @@ Empty If Statement finds instances where a condition is checked but nothing is d
@@ -199,9 +199,9 @@ Empty block statements serve no purpose and should be removed.
Example:
+ *
+ * ExpressionPrinter printer = new ExpressionPrinter();
+ * printer.visit(query.xpathExpression.getInternalExpression());
+ *
*/
-public class ExpressionPrinter extends Visitor {
+public class ExpressionPrinter extends SaxonExprVisitor {
private int depth = 0;
private void print(String s) {
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/RuleChainAnalyzer.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/RuleChainAnalyzer.java
index d621878023..9c9bcdb855 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/RuleChainAnalyzer.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/RuleChainAnalyzer.java
@@ -13,6 +13,7 @@ import net.sf.saxon.Configuration;
import net.sf.saxon.expr.AxisExpression;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.FilterExpression;
+import net.sf.saxon.expr.LazyExpression;
import net.sf.saxon.expr.PathExpression;
import net.sf.saxon.expr.RootExpression;
import net.sf.saxon.om.Axis;
@@ -33,17 +34,22 @@ import net.sf.saxon.type.Type;
* DocumentSorter expression is removed. The sorting of the resulting nodes needs to be done
* after all (sub)expressions have been executed.
*/
-public class RuleChainAnalyzer extends Visitor {
+public class RuleChainAnalyzer extends SaxonExprVisitor {
private final Configuration configuration;
private String rootElement;
private boolean rootElementReplaced;
+ private boolean insideLazyExpression;
+ private boolean foundPathInsideLazy;
public RuleChainAnalyzer(Configuration currentConfiguration) {
this.configuration = currentConfiguration;
}
public String getRootElement() {
- return rootElement;
+ if (!foundPathInsideLazy && rootElementReplaced) {
+ return rootElement;
+ }
+ return null;
}
@Override
@@ -55,7 +61,7 @@ public class RuleChainAnalyzer extends Visitor {
@Override
public Expression visit(PathExpression e) {
- if (rootElement == null) {
+ if (!insideLazyExpression && rootElement == null) {
Expression result = super.visit(e);
if (rootElement != null && !rootElementReplaced) {
if (result instanceof PathExpression) {
@@ -79,6 +85,9 @@ public class RuleChainAnalyzer extends Visitor {
}
return result;
} else {
+ if (insideLazyExpression) {
+ foundPathInsideLazy = true;
+ }
return super.visit(e);
}
}
@@ -96,6 +105,15 @@ public class RuleChainAnalyzer extends Visitor {
return super.visit(e);
}
+ @Override
+ public Expression visit(LazyExpression e) {
+ boolean prevCtx = insideLazyExpression;
+ insideLazyExpression = true;
+ Expression result = super.visit(e);
+ insideLazyExpression = prevCtx;
+ return result;
+ }
+
public static Comparator documentOrderComparator() {
return net.sourceforge.pmd.lang.rule.xpath.internal.DocumentSorter.INSTANCE;
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/Visitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/SaxonExprVisitor.java
similarity index 79%
rename from pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/Visitor.java
rename to pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/SaxonExprVisitor.java
index 088724df7e..d1dc8dd873 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/Visitor.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/SaxonExprVisitor.java
@@ -5,8 +5,10 @@
package net.sourceforge.pmd.lang.rule.xpath.internal;
import net.sf.saxon.expr.AxisExpression;
+import net.sf.saxon.expr.BooleanExpression;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.FilterExpression;
+import net.sf.saxon.expr.LazyExpression;
import net.sf.saxon.expr.LetExpression;
import net.sf.saxon.expr.PathExpression;
import net.sf.saxon.expr.QuantifiedExpression;
@@ -14,7 +16,7 @@ import net.sf.saxon.expr.RootExpression;
import net.sf.saxon.expr.VennExpression;
import net.sf.saxon.sort.DocumentSorter;
-abstract class Visitor {
+abstract class SaxonExprVisitor {
public Expression visit(DocumentSorter e) {
Expression base = visit(e.getBaseExpression());
return new DocumentSorter(base);
@@ -63,6 +65,18 @@ abstract class Visitor {
return result;
}
+ public Expression visit(LazyExpression e) {
+ Expression base = visit(e.getBaseExpression());
+ return LazyExpression.makeLazyExpression(base);
+ }
+
+ public Expression visit(BooleanExpression e) {
+ final Expression[] operands = e.getOperands();
+ Expression operand0 = visit(operands[0]);
+ Expression operand1 = visit(operands[1]);
+ return new BooleanExpression(operand0, e.getOperator(), operand1);
+ }
+
public Expression visit(Expression expr) {
Expression result;
if (expr instanceof DocumentSorter) {
@@ -81,6 +95,10 @@ abstract class Visitor {
result = visit((QuantifiedExpression) expr);
} else if (expr instanceof LetExpression) {
result = visit((LetExpression) expr);
+ } else if (expr instanceof LazyExpression) {
+ result = visit((LazyExpression) expr);
+ } else if (expr instanceof BooleanExpression) {
+ result = visit((BooleanExpression) expr);
} else {
result = expr;
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/SplitUnions.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/SplitUnions.java
index 20120c65ef..7d45b544b3 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/SplitUnions.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/SplitUnions.java
@@ -17,7 +17,7 @@ import net.sf.saxon.expr.VennExpression;
*
* E.g. "//A | //B | //C" will result in 3 expressions "//A", "//B", and "//C".
*/
-class SplitUnions extends Visitor {
+class SplitUnions extends SaxonExprVisitor {
private List expressions = new ArrayList<>();
@Override
diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java
index 0dc68954ab..ae4643c8d6 100644
--- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java
+++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java
@@ -17,10 +17,14 @@ import net.sourceforge.pmd.lang.ast.DummyNode;
import net.sourceforge.pmd.lang.ast.DummyRoot;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.ParseException;
+import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler;
import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor;
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
import net.sourceforge.pmd.lang.rule.impl.DefaultRuleViolationFactory;
+import net.sf.saxon.expr.XPathContext;
+import net.sf.saxon.sxpath.IndependentContext;
+
/**
* Dummy language used for testing PMD.
*/
@@ -67,6 +71,26 @@ public class DummyLanguageModule extends BaseLanguageModule {
super(DummyAstStages.class);
}
+ public static class TestFunctions {
+ public static boolean typeIs(final XPathContext context, final String fullTypeName) {
+ return false;
+ }
+ }
+
+ @Override
+ public XPathHandler getXPathHandler() {
+ return new DefaultASTXPathHandler() {
+ @Override
+ public void initialize(IndependentContext context) {
+ super.initialize(context, LanguageRegistry.getLanguage(DummyLanguageModule.NAME), TestFunctions.class);
+ }
+
+ @Override
+ public void initialize() {
+ }
+ };
+ }
+
@Override
public RuleViolationFactory getRuleViolationFactory() {
return new RuleViolationFactory();
diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQueryTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQueryTest.java
index c2c48be729..93b7d4e3a3 100644
--- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQueryTest.java
+++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQueryTest.java
@@ -18,7 +18,6 @@ import net.sourceforge.pmd.properties.PropertyDescriptor;
import net.sourceforge.pmd.properties.PropertyFactory;
import net.sf.saxon.expr.Expression;
-import net.sf.saxon.expr.XPathContext;
public class SaxonXPathRuleQueryTest {
@@ -64,10 +63,42 @@ public class SaxonXPathRuleQueryTest {
assertExpression("((((/)/descendant::element(dummyNode, xs:anyType))[QuantifiedExpression(Atomizer(attribute::attribute(Test2, xs:anyAtomicType)), ($qq:qq1741979653 singleton eq true()))])[QuantifiedExpression(Atomizer(attribute::attribute(Test1, xs:anyAtomicType)), ($qq:qq1529060733 singleton eq false()))])", query.nodeNameToXPaths.get(SaxonXPathRuleQuery.AST_ROOT).get(0));
}
- public static class TestFunctions {
- public static boolean typeIs(final XPathContext context, final String fullTypeName) {
- return false;
- }
+ @Test
+ public void ruleChainVisitsCustomFunctions() {
+ SaxonXPathRuleQuery query = createQuery("//dummyNode[pmd-dummy:typeIs(@Image)]");
+ List ruleChainVisits = query.getRuleChainVisits();
+ Assert.assertEquals(1, ruleChainVisits.size());
+ Assert.assertTrue(ruleChainVisits.contains("dummyNode"));
+ Assert.assertEquals(2, query.nodeNameToXPaths.size());
+ assertExpression("(self::node()[pmd-dummy:typeIs(CardinalityChecker(ItemChecker(UntypedAtomicConverter(Atomizer(attribute::attribute(Image, xs:anyAtomicType))))))])", query.nodeNameToXPaths.get("dummyNode").get(0));
+ assertExpression("DocumentSorter((((/)/descendant-or-self::node())/(child::element(dummyNode, xs:anyType)[pmd-dummy:typeIs(CardinalityChecker(ItemChecker(UntypedAtomicConverter(Atomizer(attribute::attribute(Image, xs:anyAtomicType))))))])))", query.nodeNameToXPaths.get(SaxonXPathRuleQuery.AST_ROOT).get(0));
+ }
+
+ /**
+ * If a query contains another unbounded path expression other than the first one, it must be
+ * excluded from rule chain execution. Saxon itself optimizes this quite good already.
+ */
+ @Test
+ public void ruleChainVisitsUnboundedPathExpressions() {
+ SaxonXPathRuleQuery query = createQuery("//dummyNode[//ClassOrInterfaceType]");
+ List ruleChainVisits = query.getRuleChainVisits();
+ Assert.assertEquals(0, ruleChainVisits.size());
+ Assert.assertEquals(1, query.nodeNameToXPaths.size());
+ assertExpression("LetExpression(LazyExpression(((/)/descendant::element(ClassOrInterfaceType, xs:anyType))), (((/)/descendant::element(dummyNode, xs:anyType))[$zz:zz771775563]))", query.nodeNameToXPaths.get(SaxonXPathRuleQuery.AST_ROOT).get(0));
+
+ // second sample, more complex
+ query = createQuery("//dummyNode[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType]]");
+ ruleChainVisits = query.getRuleChainVisits();
+ Assert.assertEquals(0, ruleChainVisits.size());
+ Assert.assertEquals(1, query.nodeNameToXPaths.size());
+ assertExpression("LetExpression(LazyExpression(((/)/descendant::element(ClassOrInterfaceType, xs:anyType))), (((/)/descendant::element(dummyNode, xs:anyType))[(ancestor::element(ClassOrInterfaceDeclaration, xs:anyType)[$zz:zz106374177])]))", query.nodeNameToXPaths.get(SaxonXPathRuleQuery.AST_ROOT).get(0));
+
+ // third example, with boolean expr
+ query = createQuery("//dummyNode[//ClassOrInterfaceType or //OtherNode]");
+ ruleChainVisits = query.getRuleChainVisits();
+ Assert.assertEquals(0, ruleChainVisits.size());
+ Assert.assertEquals(1, query.nodeNameToXPaths.size());
+ assertExpression("LetExpression(LazyExpression((((/)/descendant::element(ClassOrInterfaceType, xs:anyType)) or ((/)/descendant::element(OtherNode, xs:anyType)))), (((/)/descendant::element(dummyNode, xs:anyType))[$zz:zz1364913072]))", query.nodeNameToXPaths.get(SaxonXPathRuleQuery.AST_ROOT).get(0));
}
@Test
diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt
index 31e772b928..4cc5f2b087 100644
--- a/pmd-java/etc/grammar/Java.jjt
+++ b/pmd-java/etc/grammar/Java.jjt
@@ -924,7 +924,7 @@ void RecordBodyDeclaration() #void :
private void RecordCtorLookahead() #void:
{}
{
- Modifiers() [ TypeParameters() ] ("throws" | "{")
+ Modifiers() "{"
}
void RecordConstructorDeclaration():
@@ -933,7 +933,6 @@ void RecordConstructorDeclaration():
}
{
modifiers = Modifiers() { jjtThis.setModifiers(modifiers); }
- [TypeParameters()]
{ jjtThis.setImage(token.image); }
Block()
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRecordConstructorDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRecordConstructorDeclaration.java
index cebb53fc4c..a810da9a15 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRecordConstructorDeclaration.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRecordConstructorDeclaration.java
@@ -14,7 +14,6 @@ import net.sourceforge.pmd.annotation.Experimental;
*
* RecordConstructorDeclaration ::= ({@linkplain ASTAnnotation Annotation})*
* RecordModifiers
- * {@linkplain ASTTypeParameters TypeParameters}?
* <IDENTIFIER>
* {@link ASTBlock Block}
*