• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / #3785

pending completion
#3785

push

github actions

web-flow
Merge pull request #4362 from Monits/xpath-properties-rulechain

[core] Allow more XPath rules to use the rulechain

25 of 25 new or added lines in 3 files covered. (100.0%)

67066 of 127687 relevant lines covered (52.52%)

0.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.35
/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/SaxonExprTransformations.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5

6
package net.sourceforge.pmd.lang.rule.xpath.internal;
7

8
import java.util.Collections;
9

10
import net.sf.saxon.expr.AxisExpression;
11
import net.sf.saxon.expr.Expression;
12
import net.sf.saxon.expr.FilterExpression;
13
import net.sf.saxon.expr.LetExpression;
14
import net.sf.saxon.expr.RootExpression;
15
import net.sf.saxon.expr.SlashExpression;
16
import net.sf.saxon.om.AxisInfo;
17
import net.sf.saxon.pattern.AnyNodeTest;
18
import net.sf.saxon.pattern.NodeTest;
19

20
/**
21
 * Utilities to transform saxon expression trees.
22
 */
23
final class SaxonExprTransformations {
24

25
    private SaxonExprTransformations() {
26
        // utility class
27
    }
28

29
    private static final SaxonExprVisitor FILTER_HOISTER = new SaxonExprVisitor() {
1✔
30

31
        @Override
32
        public Expression visit(SlashExpression e) {
33
            Expression left = super.visit(e.getLhsExpression());
1✔
34
            Expression right = super.visit(e.getRhsExpression());
1✔
35
            if (right instanceof FilterExpression) {
1✔
36
                Expression middle = ((FilterExpression) right).getBase();
×
37
                Expression filter = ((FilterExpression) right).getFilter();
×
38
                return new FilterExpression(new SlashExpression(left, middle), filter);
×
39
            }
40
            return super.visit(e);
1✔
41
        }
42
    };
43

44
    private static final SaxonExprVisitor ROOT_REDUCER = new SaxonExprVisitor() {
1✔
45

46
        @Override
47
        public Expression visit(SlashExpression e) {
48
            Expression left = super.visit(e.getLhsExpression());
1✔
49
            Expression right = super.visit(e.getRhsExpression());
1✔
50

51
            if (right instanceof AxisExpression
1✔
52
                && ((AxisExpression) right).getAxis() == AxisInfo.CHILD
1✔
53
                && left instanceof SlashExpression) {
54

55
                Expression leftLeft = ((SlashExpression) left).getLhsExpression();
1✔
56
                Expression leftRight = ((SlashExpression) left).getRhsExpression();
1✔
57

58
                if (leftLeft instanceof RootExpression && leftRight instanceof AxisExpression) {
1✔
59
                    if (((AxisExpression) leftRight).getAxis() == AxisInfo.DESCENDANT_OR_SELF
1✔
60
                        && isAnyNode(((AxisExpression) leftRight).getNodeTest())) {
×
61
                        // ok!
62
                        left = leftLeft; // the root expression
×
63
                        right = new AxisExpression(AxisInfo.DESCENDANT, ((AxisExpression) right).getNodeTest());
×
64
                    }
65
                }
66
            }
67

68
            return new SlashExpression(left, right);
1✔
69
        }
70

71
        private boolean isAnyNode(NodeTest nodeTest) {
72
            return nodeTest == null || nodeTest instanceof AnyNodeTest;
×
73
        }
74
    };
75

76
    /**
77
     * Turn {@code a/(b[c])} into {@code (a/b)[c]}. This is to somewhat
78
     * normalize the expression as Saxon parses this inconsistently.
79
     */
80
    static Expression hoistFilters(Expression expression) {
81
        return FILTER_HOISTER.visit(expression);
1✔
82
    }
83

84
    /**
85
     * Turn {@code ((root)/descendant-or-self::node())/child::someTest}
86
     * into {@code ((root)/descendant::someTest)}. The latter is the pattern
87
     * detected by the rulechain analyser.
88
     */
89
    static Expression reduceRoot(Expression expression) {
90
        return ROOT_REDUCER.visit(expression);
1✔
91
    }
92

93
    /**
94
     * Splits a venn expression with the union operator into single expressions.
95
     *
96
     * <p>E.g. "//A | //B | //C" will result in 3 expressions "//A", "//B", and "//C".
97
     * 
98
     * This split will skip into any top-level lets. So, for "let $a := e1 in (e2 | e3)"
99
     * this will return the subexpression e2 and e3. To ensure the splits are actually equivalent
100
     * you will have to call {@link #copyTopLevelLets(Expression, Expression)} on each subexpression
101
     * to turn them back into "let $a := e1 in e2" and "let $a := e1 in e3" respectively.
102
     */
103
    static Iterable<Expression> splitUnions(Expression expr) {
104
        SplitUnions unions = new SplitUnions();
1✔
105
        unions.visit(expr);
1✔
106
        if (unions.getExpressions().isEmpty()) {
1✔
107
            return Collections.singletonList(expr);
1✔
108
        }
109
        return unions.getExpressions();
1✔
110
    }
111

112
    /**
113
     * Wraps a given subexpression in all top-level lets from the original.
114
     * If the subexpression matches the original, then nothing is done.
115
     * 
116
     * @param subexpr The subexpression that has been manipulated.
117
     * @param original The original expression from which it was obtained by calling {@link #splitUnions(Expression)}.
118
     * @return The subexpression, wrapped in a copy of all top-level let expression from the original.
119
     */
120
    static Expression copyTopLevelLets(Expression subexpr, Expression original) {
121
        if (!(original instanceof LetExpression)) {
1✔
122
            return subexpr;
1✔
123
        }
124

125
        // Does it need them? Or is it already the same variable under the same assignment?
126
        if (subexpr instanceof LetExpression) {
1✔
127
            final LetExpression letSubexpr = (LetExpression) subexpr;
1✔
128
            final LetExpression letOriginal = (LetExpression) original;
1✔
129
            if (letOriginal.getVariableQName().equals(letSubexpr.getVariableQName())
1✔
130
                    && letSubexpr.getSequence().toString().equals(letOriginal.getSequence().toString())) {
1✔
131
                return subexpr;
1✔
132
            }
133
        }
134
        
135
        final SaxonExprVisitor topLevelLetCopier = new SaxonExprVisitor() {
1✔
136
            
137
            @Override
138
            public Expression visit(LetExpression e) {
139
                // keep copying
140
                if (e.getAction() instanceof LetExpression) {
1✔
141
                    return super.visit(e);
×
142
                }
143
                
144
                // Manually craft the inner-most LetExpression
145
                Expression sequence = visit(e.getSequence());
1✔
146
                LetExpression result = new LetExpression();
1✔
147
                result.setAction(subexpr);
1✔
148
                result.setSequence(sequence);
1✔
149
                result.setVariableQName(e.getVariableQName());
1✔
150
                result.setRequiredType(e.getRequiredType());
1✔
151
                result.setSlotNumber(e.getLocalSlotNumber());
1✔
152
                return result;
1✔
153
            }
154
        };
155
        
156
        if (original instanceof LetExpression) {
1✔
157
            return topLevelLetCopier.visit(original);
1✔
158
        }
159
        
160
        return subexpr;
×
161
    }
162
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc