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

Cognifide / AEM-Rules-for-SonarQube / #800

25 Oct 2024 02:25PM UTC coverage: 93.905% (-0.9%) from 94.773%
#800

push

tomasz-strzelecki-wttech
Fix SonarLint Java support

635 of 768 branches covered (82.68%)

28 of 58 new or added lines in 11 files covered. (48.28%)

3 existing lines in 2 files now uncovered.

1664 of 1772 relevant lines covered (93.91%)

0.94 hits per line

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

97.03
/src/main/java/com/vml/aemrules/htl/lex/ElementTokenizer.java
1
/*-
2
 * #%L
3
 * AEM Rules for SonarQube
4
 * %%
5
 * Copyright (C) 2015-2024 VML
6
 * %%
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *      http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 * #L%
19
 */
20
package com.vml.aemrules.htl.lex;
21

22
import org.apache.commons.lang3.StringUtils;
23
import org.sonar.plugins.html.node.Attribute;
24
import org.sonar.plugins.html.node.Node;
25
import org.sonar.plugins.html.node.TagNode;
26
import org.sonar.sslr.channel.CodeReader;
27
import org.sonar.sslr.channel.EndMatcher;
28

29
import java.util.ArrayDeque;
30
import java.util.Deque;
31
import java.util.List;
32
import java.util.Set;
33
import java.util.stream.Collectors;
34
import java.util.stream.Stream;
35

36
class ElementTokenizer extends AbstractTokenizer<List<Node>> {
37

38
    private static final EndQNameMatcher END_Q_NAME_MATCHER = new EndQNameMatcher();
1✔
39

40
    private static final EndTokenMatcher END_TOKEN_MATCHER = new EndTokenMatcher();
1✔
41

42
    private static final EndUnquotedAttributeMatcher END_UNQUOTED_ATTRIBUTE_MATCHER = new EndUnquotedAttributeMatcher();
1✔
43

44
    public ElementTokenizer(String startToken, String endToken) {
45
        super(startToken, endToken);
1✔
46
    }
1✔
47

48
    private static void nestedTag(TagNode element, CodeReader codeReader, ParseMode mode) {
49
        // found a nested tag
50
        if (mode == ParseMode.BEFORE_ATTRIBUTE_NAME) {
1✔
51
            parseNestedTag(codeReader, element);
1✔
52
        } else {
53
            codeReader.pop();
1✔
54
        }
55
    }
1✔
56

57
    /**
58
     * Parse a nested tag with HtlLexer. The nested tag is added as an attribute to its parent element.
59
     */
60
    private static void parseNestedTag(CodeReader codeReader, TagNode element) {
61
        HtlLexer nestedPageLexer = new HtlLexer();
1✔
62
        List<Node> nodeList = nestedPageLexer.nestedParse(codeReader);
1✔
63

64
        // add the nested tags as attribute.
65
        for (Node node : nodeList) {
1✔
66
            element.getAttributes().add(new Attribute(node.getCode()));
1✔
67
        }
1✔
68
    }
1✔
69

70
    private static void handleBeforeAttributeValue(CodeReader codeReader, TagNode element) {
71
        Attribute attribute;
72
        if (!element.getAttributes().isEmpty()) {
1!
73
            attribute = element.getAttributes().get(element.getAttributes().size() - 1);
1✔
74
            StringBuilder sbValue = new StringBuilder();
1✔
75
            int ch = codeReader.peek();
1✔
76

77
            if (isQuote((char) ch)) {
1!
78
                codeReader.pop();
1✔
79
                if (codeReader.peek() != ch) {
1!
80
                    popTo(codeReader, new QuoteMatcher((char) ch), sbValue);
1✔
81
                    attribute.setValue(unescapeQuotes(sbValue.toString(), (char) ch));
1✔
82
                }
83
                codeReader.pop();
1✔
84
                attribute.setQuoteChar((char) ch);
1✔
85
            } else {
NEW
86
                popTo(codeReader, END_UNQUOTED_ATTRIBUTE_MATCHER, sbValue);
×
87
                attribute.setValue(sbValue.toString().trim());
×
88
            }
89
        }
90
    }
1✔
91

92
    private static void handleBeforeAttributeName(CodeReader codeReader, TagNode element) {
93
        Attribute attribute;
94
        StringBuilder sbQName = new StringBuilder();
1✔
95
        popTo(codeReader, END_Q_NAME_MATCHER, sbQName);
1✔
96
        attribute = new Attribute(sbQName.toString().trim());
1✔
97
        attribute.setLine(codeReader.getLinePosition() + element.getStartLinePosition() - 1);
1✔
98
        element.getAttributes().add(attribute);
1✔
99
    }
1✔
100

101
    private static void handleBeforeNodeName(CodeReader codeReader, TagNode element) {
102
        StringBuilder sbNodeName = new StringBuilder();
1✔
103
        popTo(codeReader, END_TOKEN_MATCHER, sbNodeName);
1✔
104
        element.setNodeName(sbNodeName.toString());
1✔
105
    }
1✔
106

107
    /**
108
     * Unescape the quotes from the attribute value.
109
     */
110
    private static String unescapeQuotes(String value, char ch) {
111
        return StringUtils.replace(value, "\\" + ch, Character.toString(ch));
1✔
112
    }
113

114
    private static boolean isQuote(char c) {
115
        return c == '\'' || c == '"';
1!
116
    }
117

118
    @Override
119
    protected void addNode(List<Node> nodeList, Node node) {
120
        super.addNode(nodeList, node);
1✔
121
        parseToken(node);
1✔
122
    }
1✔
123

124
    @Override
125
    Node createNode() {
126
        return new TagNode();
1✔
127
    }
128

129
    private void parseToken(Node node) {
130
        TagNode element = (TagNode) node;
1✔
131

132
        CodeReader codeReader = new CodeReader(node.getCode());
1✔
133

134
        ParseMode mode = ParseMode.BEFORE_NODE_NAME;
1✔
135
        for (int ch = codeReader.peek(); ch != -1; ch = codeReader.peek()) {
1✔
136

137
            // handle white space
138
            if (Character.isWhitespace(ch)) {
1✔
139
                codeReader.pop();
1✔
140
                continue;
1✔
141
            }
142

143
            // handle special characters
144
            switch (ch) {
1✔
145
                case '=':
146
                    mode = ParseMode.BEFORE_ATTRIBUTE_VALUE;
1✔
147
                    codeReader.pop();
1✔
148
                    continue;
1✔
149
                case '<':
150
                    nestedTag(element, codeReader, mode);
1✔
151
                    continue;
1✔
152
                case '>':
153
                case '/':
154
                case '%':
155
                case '@':
156
                    codeReader.pop();
1✔
157
                    continue;
1✔
158
                default:
159
                    break;
160
            }
161

162
            mode = parseToken(mode, codeReader, element);
1✔
163
        }
164
    }
1✔
165

166
    private ParseMode parseToken(ParseMode mode, CodeReader codeReader, TagNode element) {
167
        ParseMode result = ParseMode.BEFORE_NODE_NAME;
1✔
168
        switch (mode) {
1!
169
            case BEFORE_NODE_NAME:
170
                handleBeforeNodeName(codeReader, element);
1✔
171
                result = ParseMode.BEFORE_ATTRIBUTE_NAME;
1✔
172
                break;
1✔
173
            case BEFORE_ATTRIBUTE_NAME:
174
                handleBeforeAttributeName(codeReader, element);
1✔
175
                result = ParseMode.BEFORE_ATTRIBUTE_NAME;
1✔
176
                break;
1✔
177
            case BEFORE_ATTRIBUTE_VALUE:
178
                handleBeforeAttributeValue(codeReader, element);
1✔
179
                result = ParseMode.BEFORE_ATTRIBUTE_NAME;
1✔
180
                break;
1✔
181
            default:
182
                break;
183
        }
184
        return result;
1✔
185
    }
186

187
    private enum ParseMode {
1✔
188
        BEFORE_ATTRIBUTE_NAME, BEFORE_ATTRIBUTE_VALUE, BEFORE_NODE_NAME
1✔
189
    }
190

191
    private static final class EndQNameMatcher implements EndMatcher {
192

193
        @Override
194
        public boolean match(int character) {
195
            return character == '=' || character == '>' || Character.isWhitespace(character);
1✔
196
        }
197
    }
198

199
    private static final class EndUnquotedAttributeMatcher implements EndMatcher {
200

201
        private static final Set<Character> FORBIDDEN = Stream.of(
1✔
202
                '"',
1✔
203
                '\'',
1✔
204
                '=',
1✔
205
                '<',
1✔
206
                '>',
1✔
207
                '`'
1✔
208
        ).collect(Collectors.toSet());
1✔
209

210
        @Override
211
        public boolean match(int character) {
212
            return Character.isWhitespace(character) || FORBIDDEN.contains((char) character);
×
213
        }
214
    }
215

216
    private static final class EndTokenMatcher implements EndMatcher {
217

218
        @Override
219
        public boolean match(int character) {
220
            switch (character) {
1✔
221
                case '/':
222
                case '>':
223
                    return true;
1✔
224
                default:
225
                    break;
226
            }
227
            return Character.isWhitespace(character);
1✔
228
        }
229
    }
230

231
    private static final class QuoteMatcher implements EndMatcher {
232

233
        private static final char SINGLE_QUOTE = '\'';
234
        private static final char DOUBLE_QUOTE = '"';
235
        private final Deque<Character> startChars = new ArrayDeque<>();
1✔
236
        private int previousChar;
237

238
        QuoteMatcher(char startChar) {
1✔
239
            this.startChars.addFirst(startChar);
1✔
240
        }
1✔
241

242
        @Override
243
        public boolean match(int character) {
244
            boolean result = false;
1✔
245
            if ((character == SINGLE_QUOTE || character == DOUBLE_QUOTE) && previousChar != '\\') {
1!
246
                if (startChars.peekFirst() == (char) character) {
1✔
247
                    startChars.removeFirst();
1✔
248
                } else {
249
                    startChars.addFirst((char) character);
1✔
250
                }
251
                result = startChars.isEmpty();
1✔
252
            }
253
            previousChar = character;
1✔
254
            return result;
1✔
255
        }
256
    }
257

258
}
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

© 2026 Coveralls, Inc