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

Camelcade / Perl5-IDEA / #525521660

24 Aug 2025 01:28PM UTC coverage: 75.89% (-6.3%) from 82.227%
#525521660

push

github

hurricup
Migrated coverage reporting to https://github.com/nbaztec/coveralls-jacoco-gradle-plugin

See: https://github.com/kt3k/coveralls-gradle-plugin/issues/119

14751 of 22639 branches covered (65.16%)

Branch coverage included in aggregate %.

31091 of 37767 relevant lines covered (82.32%)

0.82 hits per line

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

92.37
/tt2/common/src/main/java/com/perl5/lang/tt2/lexer/TemplateToolkitLexer.java
1
/*
2
 * Copyright 2015-2025 Alexandr Evstigneev
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.perl5.lang.tt2.lexer;
18

19
import com.intellij.openapi.project.Project;
20
import com.intellij.openapi.util.text.StringUtil;
21
import com.intellij.psi.tree.IElementType;
22
import com.perl5.lang.perl.lexer.PerlLexerWithCustomStates;
23
import com.perl5.lang.tt2.idea.settings.TemplateToolkitSettings;
24

25
import java.io.IOException;
26

27
import static com.perl5.lang.tt2.parser.TemplateToolkitElementTypesGenerated.*;
28

29

30
public class TemplateToolkitLexer extends TemplateToolkitLexerGenerated implements PerlLexerWithCustomStates {
1✔
31
  private static final String CHOMP_MODIFIERS = "-+=~";
32
  private final TemplateToolkitSettings mySettings;
33
  private int customState = 0;
1✔
34
  private boolean isEscaped = false;
1✔
35

36
  public TemplateToolkitLexer(Project project) {
37
    super(null);
1✔
38
    mySettings = project == null ? null : TemplateToolkitSettings.getInstance(project);
1!
39
  }
1✔
40

41
  @Override
42
  public IElementType perlAdvance() throws IOException {
43
    CharSequence buffer = getBuffer();
1✔
44
    int tokenStart = getNextTokenStart();
1✔
45
    int bufferEnd = getBufferEnd();
1✔
46
    int currentCustomState = getCustomState();
1✔
47
    int currentState = getRealLexicalState();
1✔
48

49
    if (bufferEnd == 0 || tokenStart >= bufferEnd) {
1✔
50
      return super.perlAdvance();
1✔
51
    }
52
    else if (currentCustomState == LEX_HTML) {
1✔
53
      int offset = tokenStart;
1✔
54
      boolean blockStart = false;
1✔
55
      for (; offset < bufferEnd; offset++) {
1✔
56
        if (isBufferAtString(buffer, offset, getStartTag())) {
1✔
57
          blockStart = true;
1✔
58
          break;
1✔
59
        }
60
        else if (isOutlineOpener(buffer, offset)) {
1✔
61
          break;
1✔
62
        }
63
      }
64

65
      if (offset > tokenStart) {
1✔
66
        pushPreparsedSpaceOrToken(tokenStart, offset, TT2_HTML);
1✔
67
      }
68

69
      if (blockStart) {
1✔
70
        int openTagLength = getStartTag().length();
1✔
71

72
        if (offset + openTagLength < bufferEnd && StringUtil.containsChar(CHOMP_MODIFIERS, buffer.charAt(offset + openTagLength))) {
1✔
73
          openTagLength++;
1✔
74
        }
75

76
        pushPreparsedToken(offset, offset + openTagLength, TT2_OPEN_TAG);
1✔
77
        int nextCharOffset = offset + openTagLength;
1✔
78

79
        if (nextCharOffset < bufferEnd && buffer.charAt(nextCharOffset) == '#') {
1✔
80
          int blockCommentEnd = nextCharOffset + 1;
1✔
81
          String endTag = getEndTag();
1✔
82
          while (blockCommentEnd < bufferEnd && !isBufferAtString(buffer, blockCommentEnd, endTag)) {
1!
83
            blockCommentEnd++;
1✔
84
          }
85
          pushPreparsedToken(nextCharOffset, blockCommentEnd, LINE_COMMENT);
1✔
86
        }
87
        setCustomState(LEX_TEMPLATE_BLOCK);
1✔
88
      }
1✔
89
      else if (offset < bufferEnd) {
1✔
90
        pushPreparsedToken(offset, offset + getOutlineTag().length(), TT2_OUTLINE_TAG);
1✔
91
        setCustomState(LEX_TEMPLATE_LINE);
1✔
92
      }
93

94
      assert !myPreparsedTokensList.isEmpty();
1!
95
      return getPreParsedToken();
1✔
96
    }
97
    else if (currentCustomState == LEX_TEMPLATE_BLOCK) {
1✔
98
      int closeTagLength;
99
      if (currentState != LEX_DQ_STRING &&
1✔
100
          currentState != LEX_SQ_STRING &&
101
          (closeTagLength = checkCloseTagAndGetLength(buffer, tokenStart, bufferEnd)) > 0) {
1✔
102
        endTemplate(tokenStart, closeTagLength);
1✔
103
        return TT2_CLOSE_TAG;
1✔
104
      }
105
      else if (isLineComment(buffer, tokenStart, bufferEnd)) {
1✔
106
        return lexLineComment(buffer, tokenStart, bufferEnd);
1✔
107
      }
108
    }
109
    else if (currentCustomState == LEX_TEMPLATE_LINE) {
1!
110
      if (buffer.charAt(tokenStart) == '\n') {
1✔
111
        endTemplate(tokenStart, 1);
1✔
112
        return TT2_HARD_NEWLINE;
1✔
113
      }
114
      else if (isLineComment(buffer, tokenStart, bufferEnd)) {
1✔
115
        return lexLineComment(buffer, tokenStart, bufferEnd);
1✔
116
      }
117
    }
118

119
    IElementType result = super.perlAdvance();
1✔
120

121
    if (currentCustomState == LEX_TEMPLATE_BLOCK || currentCustomState == LEX_TEMPLATE_LINE) {
1!
122
      currentState = getRealLexicalState();
1✔
123
      if (currentState == LEX_DQ_STRING) {
1✔
124
        if (result == TT2_DQ && !isEscaped) {
1✔
125
          popState();
1✔
126
          result = TT2_DQ_CLOSE;
1✔
127
        }
128
        else {
129
          isEscaped = !isEscaped && result == TT2_ESCAPE;
1✔
130
          result = TT2_STRING_CONTENT;
1✔
131
        }
132
      }
133
      else if (currentState == LEX_SQ_STRING) {
1✔
134
        if (result == TT2_SQ && !isEscaped) {
1✔
135
          popState();
1✔
136
          result = TT2_SQ_CLOSE;
1✔
137
        }
138
        else {
139
          isEscaped = !isEscaped && result == TT2_ESCAPE;
1✔
140
          result = TT2_STRING_CONTENT;
1✔
141
        }
142
      }
143
      else if (result == TT2_SQ) {
1✔
144
        pushState();
1✔
145
        yybegin(LEX_SQ_STRING);
1✔
146
        isEscaped = false;
1✔
147
        result = TT2_SQ_OPEN;
1✔
148
      }
149
      else if (result == TT2_DQ) {
1✔
150
        pushState();
1✔
151
        yybegin(LEX_DQ_STRING);
1✔
152
        isEscaped = false;
1✔
153
        result = TT2_DQ_OPEN;
1✔
154
      }
155
    }
156

157
    return result;
1✔
158
  }
159

160
  /**
161
   * Checks if lexer is at close tag with possible chomp modifier and returns close tag length
162
   *
163
   * @param buffer    chars buffer
164
   * @param offset    current offset
165
   * @param bufferEnd buffer end
166
   * @return close tag length or -1 of we are not at it
167
   */
168
  protected int checkCloseTagAndGetLength(CharSequence buffer, int offset, int bufferEnd) {
169
    if (offset >= bufferEnd) {
1!
170
      return -1;
×
171
    }
172

173
    String endTag = getEndTag();
1✔
174
    if (isBufferAtString(buffer, offset, endTag)) {
1✔
175
      return endTag.length();
1✔
176
    }
177

178
    if (StringUtil.containsChar(CHOMP_MODIFIERS, buffer.charAt(offset)) && isBufferAtString(buffer, offset + 1, endTag)) {
1✔
179
      return endTag.length() + 1;
1✔
180
    }
181
    return -1;
1✔
182
  }
183

184
  protected boolean isLineComment(CharSequence buffer, int offset, int bufferEnd) {
185
    int currentState = getRealLexicalState();
1✔
186
    return currentState != LEX_DQ_STRING &&
1✔
187
           currentState != LEX_SQ_STRING &&
188
           getLastTokenType() != TT2_OPEN_TAG &&
1!
189
           offset < bufferEnd &&
190
           buffer.charAt(offset) == '#';
1✔
191
  }
192

193
  @SuppressWarnings("SameReturnValue")
194
  protected IElementType lexLineComment(CharSequence buffer, int offset, int bufferEnd) {
195
    int endOffset = offset;
1✔
196
    String endTag = getEndTag();
1✔
197
    boolean isTemplateLine = getCustomState() == LEX_TEMPLATE_LINE;
1✔
198

199
    while (endOffset < bufferEnd) {
1!
200
      if (buffer.charAt(endOffset) == '\n' || !isTemplateLine && isBufferAtString(buffer, endOffset, endTag)) {
1✔
201
        break;
1✔
202
      }
203
      endOffset++;
1✔
204
    }
205
    setTokenStart(offset);
1✔
206
    setTokenEnd(endOffset);
1✔
207

208
    return LINE_COMMENT;
1✔
209
  }
210

211
  protected void endTemplate(int tokenStart, int tokenLength) {
212
    setTokenStart(tokenStart);
1✔
213
    setTokenEnd(tokenStart + tokenLength);
1✔
214
    setCustomState(LEX_HTML);
1✔
215

216
    int currentState = getRealLexicalState();
1✔
217
    if (currentState == LEX_SQ_STRING || currentState == LEX_DQ_STRING) {
1!
218
      popState();
×
219
    }
220
  }
1✔
221

222
  @Override
223
  public int getCustomState() {
224
    return customState;
1✔
225
  }
226

227
  @Override
228
  public void setCustomState(int newState) {
229
    customState = newState;
1✔
230
  }
1✔
231

232
  @Override
233
  public int getInitialCustomState() {
234
    return LEX_HTML;
1✔
235
  }
236

237
  protected String getStartTag() {
238
    return mySettings == null ? TemplateToolkitSettings.DEFAULT_START_TAG : mySettings.START_TAG;
1!
239
  }
240

241
  protected String getEndTag() {
242
    return mySettings == null ? TemplateToolkitSettings.DEFAULT_END_TAG : mySettings.END_TAG;
1!
243
  }
244

245
  protected String getOutlineTag() {
246
    return mySettings == null ? TemplateToolkitSettings.DEFAULT_OUTLINE_TAG : mySettings.OUTLINE_TAG;
1!
247
  }
248

249
  protected boolean isOutlineEnabled() {
250
    return StringUtil.isNotEmpty(getOutlineTag());
1✔
251
  }
252

253
  protected boolean isOutlineOpener(CharSequence buffer, int offset) {
254
    return isOutlineEnabled() && !(offset > 0 && buffer.charAt(offset - 1) != '\n') && isBufferAtString(buffer, offset, getOutlineTag());
1!
255
  }
256

257
  protected boolean isAnycaseEnabled() {
258
    return mySettings != null && mySettings.ENABLE_ANYCASE;
1!
259
  }
260

261
  @Override
262
  public IElementType parseIdentifier() {
263
    String identifier = yytext().toString();
1✔
264

265
    // check for operator
266
    IElementType tokenType = TemplateToolkitSyntaxElements.TEXT_OPERATORS.get(identifier);
1✔
267

268
    if (tokenType == null) {
1✔
269
      // check for derictive
270
      if (isAnycaseEnabled()) {
1!
271
        identifier = identifier.toUpperCase();
×
272
      }
273

274
      tokenType = TemplateToolkitSyntaxElements.KEYWORDS.get(identifier);
1✔
275
    }
276

277
    return tokenType == null ? TT2_IDENTIFIER : tokenType;
1✔
278
  }
279
}
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