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

Camelcade / Perl5-IDEA / #525521547

24 May 2025 10:39AM UTC coverage: 82.232% (-0.09%) from 82.324%
#525521547

push

github

hurricup
Got rid of ContainerUtil.immutableList usages

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

52 existing lines in 15 files now uncovered.

30870 of 37540 relevant lines covered (82.23%)

0.82 hits per line

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

94.57
/plugin/core/src/main/java/com/perl5/lang/perl/idea/formatter/PerlPreFormatter.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.perl.idea.formatter;
18

19
import com.intellij.application.options.CodeStyle;
20
import com.intellij.openapi.editor.Document;
21
import com.intellij.openapi.project.Project;
22
import com.intellij.openapi.util.TextRange;
23
import com.intellij.openapi.util.text.StringUtil;
24
import com.intellij.psi.ElementManipulators;
25
import com.intellij.psi.PsiDocumentManager;
26
import com.intellij.psi.PsiElement;
27
import com.intellij.psi.TokenType;
28
import com.intellij.psi.util.PsiTreeUtil;
29
import com.intellij.psi.util.PsiUtilCore;
30
import com.perl5.lang.perl.idea.formatter.operations.*;
31
import com.perl5.lang.perl.idea.formatter.settings.PerlCodeStyleSettings;
32
import com.perl5.lang.perl.lexer.PerlElementTypes;
33
import com.perl5.lang.perl.psi.*;
34
import com.perl5.lang.perl.psi.utils.PerlElementFactory;
35
import com.perl5.lang.perl.psi.utils.PerlPsiUtil;
36
import com.perl5.lang.perl.util.PerlPackageUtil;
37
import org.jetbrains.annotations.NotNull;
38

39
import java.util.ArrayList;
40
import java.util.List;
41
import java.util.regex.Pattern;
42

43
import static com.perl5.lang.perl.idea.formatter.PerlFormattingTokenSets.COMMA_SEQUENCE_CONTAINERS_WITH_POSSIBLE_TRAILING_COMMA;
44

45

46
public class PerlPreFormatter extends PerlRecursiveVisitor implements PerlCodeStyleSettings.OptionalConstructions, PerlElementTypes {
47
  private static final Pattern ASCII_IDENTIFIER_PATTERN = Pattern.compile("[_a-zA-Z][_\\w]*");
1✔
48
  private static final Pattern ASCII_BARE_STRING_PATTERN = Pattern.compile("-?[_a-zA-Z][_\\w]*");
1✔
49

50
  protected final Project myProject;
51
  protected final PerlCodeStyleSettings myPerlSettings;
52

53
  private final List<PerlFormattingOperation> myFormattingOperations = new ArrayList<>();
1✔
54
  protected TextRange myRange;
55

56
  public PerlPreFormatter(Project project) {
1✔
57
    myProject = project;
1✔
58
    myPerlSettings = CodeStyle.getSettings(project).getCustomSettings(PerlCodeStyleSettings.class);
1✔
59
  }
1✔
60

61
  public TextRange process(PsiElement element, TextRange range) {
62
    myRange = range;
1✔
63
    final PsiDocumentManager manager = PsiDocumentManager.getInstance(myProject);
1✔
64
    final Document document = manager.getDocument(element.getContainingFile());
1✔
65
    int myDelta = 0;
1✔
66
    if (document != null) {
1✔
67
      manager.doPostponedOperationsAndUnblockDocument(document);
1✔
68

69
      try {
70
        // scan document
71
        element.accept(this);
1✔
72

73
        for (int i = myFormattingOperations.size() - 1; i >= 0; i--) {
1✔
74
          myDelta += myFormattingOperations.get(i).apply();
1✔
75
        }
76
      }
77
      finally {
78
        manager.commitDocument(document);
1✔
79
      }
80
    }
81

82
    return TextRange.create(range.getStartOffset(), range.getEndOffset() + myDelta);
1✔
83
  }
84

85
  protected void removeElement(PsiElement o) {
86
    myFormattingOperations.add(new PerlFormattingRemove(o));
1✔
87
  }
1✔
88

89
  protected void replaceElement(PsiElement what, PsiElement with) {
90
    myFormattingOperations.add(new PerlFormattingReplace(what, with));
1✔
91
  }
1✔
92

93
  protected void insertElementAfter(PsiElement element, PsiElement anchor) {
94
    myFormattingOperations.add(new PerlFormattingInsertAfter(element, anchor));
1✔
95
  }
1✔
96

97
  protected boolean isStringQuotable(PsiPerlStringBare o) {
98
    return myPerlSettings.OPTIONAL_QUOTES == FORCE && isCommaArrowAhead(o) ||
1✔
99
           myPerlSettings.OPTIONAL_QUOTES_HASH_INDEX == FORCE && isHashIndexKey(o);
1✔
100
  }
101

102
  protected boolean isSimpleScalarCast(PerlCastExpression o) {
103
    return o.getLastChild() instanceof PsiPerlScalarVariable || isNotSoSimpleScalarCast(o);
1✔
104
  }
105

106
  protected boolean isNotSoSimpleScalarCast(PerlCastExpression o) {
107
    PsiPerlScalarVariable variable = PsiTreeUtil.findChildOfType(o, PsiPerlScalarVariable.class);
1✔
108
    if (variable != null) {
1✔
109
      PsiElement statement = variable.getParent();
1✔
110
      if (statement instanceof PsiPerlStatement && statement.getChildren().length == 1 && o.equals(statement.getParent())) {
1✔
UNCOV
111
        return true;
×
112
      }
113
    }
114
    return false;
1✔
115
  }
116

117
  protected boolean isStringInHeredocQuotable(PsiPerlStringBare o) {
118
    return myPerlSettings.OPTIONAL_QUOTES_HEREDOC_OPENER == FORCE && isInHeredocOpener(o) && !isBackrefString(o);
1✔
119
  }
120

121
  protected boolean isBackrefString(PsiPerlStringBare o) {
122
    PsiElement predecessor = o.getPrevSibling();
1✔
123
    return predecessor != null && predecessor.getNode().getElementType() == OPERATOR_REFERENCE;
1✔
124
  }
125

126
  protected boolean isStringUnqutable(PerlString o) {
127
    return isStringSimple(o) && (
1✔
128
      isHashIndexKey(o) && myPerlSettings.OPTIONAL_QUOTES_HASH_INDEX == SUPPRESS ||
1✔
129
      isCommaArrowAhead(o) && myPerlSettings.OPTIONAL_QUOTES == SUPPRESS)
1✔
130
      ;
131
  }
132

133
  protected boolean isStringInHeredocUnquotable(PerlString o) {
134
    return isStringSimpleIdentifier(o) && isInHeredocOpener(o) && myPerlSettings.OPTIONAL_QUOTES_HEREDOC_OPENER == SUPPRESS;
1✔
135
  }
136

137
  @Override
138
  public void visitStringDq(@NotNull PsiPerlStringDq o) {
139
    if (!myRange.contains(o.getTextRange())) {
1✔
140
      return;
1✔
141
    }
142
    if (isStringUnqutable(o) || isStringInHeredocUnquotable(o)) {
1✔
143
      unquoteString(o);
1✔
144
    }
145
    else {
146
      super.visitStringDq(o);
1✔
147
    }
148
  }
1✔
149

150
  @Override
151
  public void visitStringSq(@NotNull PsiPerlStringSq o) {
152
    if (!myRange.contains(o.getTextRange())) {
1✔
153
      return;
1✔
154
    }
155
    if (isStringUnqutable(o)) {
1✔
156
      unquoteString(o);
1✔
157
    }
158
    else {
159
      super.visitStringSq(o);
1✔
160
    }
161
  }
1✔
162

163
  @Override
164
  public void visitStringBare(@NotNull PsiPerlStringBare o) {
165
    if (!myRange.contains(o.getTextRange())) {
1✔
166
      return;
1✔
167
    }
168
    if (isStringQuotable(o)) {
1✔
169
      replaceElement(o, PerlElementFactory.createString(myProject, "'" + ElementManipulators.getValueText(o) + "'"));
1✔
170
    }
171
    else if (isStringInHeredocQuotable(o)) {
1✔
172
      replaceElement(o, PerlElementFactory.createString(myProject, "\"" + ElementManipulators.getValueText(o) + "\""));
1✔
173
    }
174
    else {
175
      super.visitStringBare(o);
1✔
176
    }
177
  }
1✔
178

179
  @Override
180
  public void visitHashIndex(@NotNull PsiPerlHashIndex o) {
181
    if (!myRange.contains(o.getTextRange())) {
1✔
182
      return;
1✔
183
    }
184

185
    processDerefExpressionIndex(o);
1✔
186
    super.visitHashIndex(o);
1✔
187
  }
1✔
188

189
  @Override
190
  public void visitArrayIndex(@NotNull PsiPerlArrayIndex o) {
191
    if (!myRange.contains(o.getTextRange())) {
1✔
192
      return;
1✔
193
    }
194
    processDerefExpressionIndex(o);
1✔
195
    super.visitArrayIndex(o);
1✔
196
  }
1✔
197

198
  @Override
199
  public void visitPerlCastExpression(@NotNull PerlCastExpression o) {
200
    if (!myRange.contains(o.getTextRange())) {
1✔
201
      return;
1✔
202
    }
203

204
    PsiElement parent = o.getParent();
1✔
205
    boolean isSimpleScalarCast = isSimpleScalarCast(o);
1✔
206

207
    if (o instanceof PsiPerlScalarCastExpr scalarCastExpression) {
1✔
208
      boolean isInsideHashOrArrayElement = parent instanceof PsiPerlArrayElement || parent instanceof PsiPerlHashElement;
1✔
209

210
      if (myPerlSettings.OPTIONAL_DEREFERENCE_HASHREF_ELEMENT == SUPPRESS &&
1✔
211
          isInsideHashOrArrayElement &&
212
          isSimpleScalarCast) // convert $$var{key} to $var->{key}
213
      {
214
        myFormattingOperations.add(new PerlFormattingScalarDerefExpand(scalarCastExpression));
1✔
215
      }
216
      else if (!isSimpleScalarCast && myPerlSettings.OPTIONAL_DEREFERENCE_SIMPLE == SUPPRESS)    // need to convert ${var} to $var
1✔
217
      {
218
        unwrapSimpleDereference(o);
1✔
219
      }
220
      else if (isSimpleScalarCast &&
1✔
221
               !isInsideHashOrArrayElement &&
222
               myPerlSettings.OPTIONAL_DEREFERENCE_SIMPLE == FORCE)    // need to convert $$var to ${$var}
223
      {
224
        wrapSimpleDereference(o);
1✔
225
      }
226
    }
1✔
227
    else // hash and array
228
    {
229
      if (!isSimpleScalarCast && myPerlSettings.OPTIONAL_DEREFERENCE_SIMPLE == SUPPRESS)    // need to convert ${$var} to $$var
1✔
230
      {
231
        unwrapSimpleDereference(o);
1✔
232
      }
233
      else if (isSimpleScalarCast && myPerlSettings.OPTIONAL_DEREFERENCE_SIMPLE == FORCE)    // need to convert $$var to ${$var}
1✔
234
      {
235
        wrapSimpleDereference(o);
1✔
236
      }
237
    }
238
    super.visitPerlCastExpression(o);
1✔
239
  }
1✔
240

241
  /**
242
   * Wrapping reference into braces
243
   *
244
   * @param o Cast expression
245
   */
246
  public void wrapSimpleDereference(PerlCastExpression o) {
247
    PsiElement referenceVariable = o.getLastChild();
1✔
248
    if (referenceVariable instanceof PsiPerlScalarVariable scalarVariable) {
1✔
249
      myFormattingOperations.add(new PerlFormattingSimpleDereferenceWrap(o, scalarVariable));
1✔
250
    }
251
  }
1✔
252

253
  public void unwrapSimpleDereference(PerlCastExpression o) {
254
    PsiElement closeBraceElement = o.getLastChild();
1✔
255

256
    if (closeBraceElement != null && closeBraceElement.getNode().getElementType() == RIGHT_BRACE) {
1✔
UNCOV
257
      PsiElement statementElement = PerlPsiUtil.getPrevSignificantSibling(closeBraceElement);
×
UNCOV
258
      if (statementElement instanceof PsiPerlStatement) {
×
UNCOV
259
        PsiElement openBraceElement = PerlPsiUtil.getPrevSignificantSibling(statementElement);
×
UNCOV
260
        if (openBraceElement != null && openBraceElement.getNode().getElementType() == LEFT_BRACE) {
×
261
          PsiElement referenceVariable = statementElement.getFirstChild();
×
262
          if (referenceVariable instanceof PsiPerlScalarVariable scalarVariable) {
×
263
            PsiElement optionalSemi = PerlPsiUtil.getPrevSignificantSibling(referenceVariable);
×
264
            if (optionalSemi == null || optionalSemi.getNode().getElementType() == SEMICOLON && optionalSemi.getNextSibling() == null) {
×
265
              myFormattingOperations.add(new PerlFormattingSimpleDereferenceUnwrap(o, scalarVariable));
×
266
            }
267
          }
268
        }
269
      }
270
    }
271
  }
1✔
272

273
  @Override
274
  public void visitDerefExpr(@NotNull PsiPerlDerefExpr o) {
275
    if (!myRange.contains(o.getTextRange())) {
1✔
276
      return;
1✔
277
    }
278
    if (myPerlSettings.OPTIONAL_DEREFERENCE_HASHREF_ELEMENT == FORCE) {
1✔
279
      PsiElement scalarVariableElement = o.getFirstChild();
1✔
280
      if (scalarVariableElement instanceof PsiPerlScalarVariable scalarVariable) {
1✔
281
        PsiElement derefElement = PerlPsiUtil.getNextSignificantSibling(scalarVariableElement);
1✔
282
        if (derefElement != null && derefElement.getNode().getElementType() == OPERATOR_DEREFERENCE) {
1✔
283
          PsiElement probableIndexElement = PerlPsiUtil.getNextSignificantSibling(derefElement);
1✔
284

285
          if (probableIndexElement instanceof PsiPerlHashIndex || probableIndexElement instanceof PsiPerlArrayIndex) {
1✔
286
            myFormattingOperations
1✔
287
              .add(new PerlFormattingScalarDerefCollapse(scalarVariable, probableIndexElement));
1✔
288
          }
289
        }
290
      }
291
    }
292
    super.visitDerefExpr(o);
1✔
293
  }
1✔
294

295
  protected void processDerefExpressionIndex(PsiElement o) {
296
    PsiElement parent = o.getParent();
1✔
297
    PsiElement anchor = o;
1✔
298
    if (parent instanceof PsiPerlDerefExpr ||
1✔
299
        (((anchor = parent) instanceof PsiPerlHashElement || anchor instanceof PsiPerlArrayElement) &&
300
         anchor.getParent() instanceof PsiPerlDerefExpr
1✔
301
        )) {
302
      if (myPerlSettings.OPTIONAL_DEREFERENCE == FORCE) {
1✔
303
        PsiElement nextIndexElement = PerlPsiUtil.getNextSignificantSibling(anchor);
1✔
304
        if (nextIndexElement instanceof PsiPerlHashIndex || nextIndexElement instanceof PsiPerlArrayIndex) {
1✔
305
          insertElementAfter(PerlElementFactory.createDereference(myProject), anchor);
1✔
306
        }
307
      }
1✔
308
      else if (myPerlSettings.OPTIONAL_DEREFERENCE == SUPPRESS) {
1✔
309
        PsiElement potentialDereference = PerlPsiUtil.getNextSignificantSibling(anchor);
1✔
310
        if (potentialDereference != null && potentialDereference.getNode().getElementType() == OPERATOR_DEREFERENCE) {
1✔
311
          PsiElement nextIndexElement = PerlPsiUtil.getNextSignificantSibling(potentialDereference);
1✔
312
          if (nextIndexElement instanceof PsiPerlHashIndex || nextIndexElement instanceof PsiPerlArrayIndex) {
1✔
313
            removeElement(potentialDereference);
1✔
314
          }
315
        }
316
      }
317
    }
318
  }
1✔
319

320
  @Override
321
  public void visitStatementModifier(@NotNull PsiPerlStatementModifier o) {
322
    if (!myRange.contains(o.getTextRange())) {
1✔
323
      return;
1✔
324
    }
325
    PsiPerlExpr expression = PsiTreeUtil.getChildOfType(o, PsiPerlExpr.class);
1✔
326
    if (expression != null) {
1✔
327
      if (myPerlSettings.OPTIONAL_PARENTHESES == FORCE && !(expression instanceof PsiPerlParenthesisedExpr)) {
1✔
328
        myFormattingOperations.add(new PerlFormattingStatementModifierWrap(o));
1✔
329
      }
330
      else if (myPerlSettings.OPTIONAL_PARENTHESES == SUPPRESS && expression instanceof PsiPerlParenthesisedExpr) {
1✔
331
        myFormattingOperations.add(new PerlFormattingStatementModifierUnwrap(o));
1✔
332
      }
333
    }
334
    super.visitStatementModifier(o);
1✔
335
  }
1✔
336

337
  @Override
338
  public void visitNamespaceElement(@NotNull PerlNamespaceElement o) {
339
    if (!myRange.contains(o.getTextRange())) {
1✔
340
      return;
1✔
341
    }
342

343
    String elementContent = o.getNode().getText();
1✔
344

345
    if (myPerlSettings.MAIN_FORMAT == SUPPRESS && PerlPackageUtil.MAIN_NAMESPACE_FULL.equals(elementContent)) {
1✔
346
      myFormattingOperations.add(new PerlFormattingReplaceWithText(o, PerlPackageUtil.NAMESPACE_SEPARATOR));
1✔
347
    }
348
    else if (myPerlSettings.MAIN_FORMAT == FORCE && PerlPackageUtil.MAIN_NAMESPACE_SHORT.equals(elementContent)) {
1✔
349
      myFormattingOperations.add(new PerlFormattingReplaceWithText(o, PerlPackageUtil.MAIN_NAMESPACE_FULL));
1✔
350
    }
351
    else {
352
      super.visitNamespaceElement(o);
1✔
353
    }
354
  }
1✔
355

356
  @Override
357
  public void visitCommaSequenceExpr(@NotNull PsiPerlCommaSequenceExpr o) {
358
    if (!myRange.contains(o.getTextRange())) {
1✔
359
      return;
1✔
360
    }
361
    if (myPerlSettings.OPTIONAL_TRAILING_COMMA == FORCE && !hasTrailingComma(o)) {
1✔
362
      addTrailingCommaIfFollowedByTheNewLine(o);
1✔
363
    }
364
    else if (myPerlSettings.OPTIONAL_TRAILING_COMMA == SUPPRESS && hasTrailingComma(o)) {
1✔
365
      removeElement(o.getLastChild());
1✔
366
    }
367
    super.visitCommaSequenceExpr(o);
1✔
368
  }
1✔
369

370
  private boolean hasTrailingComma(@NotNull PsiPerlCommaSequenceExpr o) {
371
    return PsiUtilCore.getElementType(o.getLastChild()) == COMMA;
1✔
372
  }
373

374
  private void addTrailingCommaIfFollowedByTheNewLine(@NotNull PsiPerlCommaSequenceExpr o) {
375
    var nextSibling = o.getNextSibling();
1✔
376
    if (PsiUtilCore.getElementType(nextSibling) != TokenType.WHITE_SPACE ||
1✔
377
        !StringUtil.containsLineBreak(nextSibling.getNode().getChars())) {
1✔
378
      return;
1✔
379
    }
380
    if (COMMA_SEQUENCE_CONTAINERS_WITH_POSSIBLE_TRAILING_COMMA.contains(PsiUtilCore.getElementType(o.getParent()))) {
1✔
381
      insertElementAfter(PerlElementFactory.createComma(myProject), o.getLastChild());
1✔
382
    }
383
  }
1✔
384

385
  protected void unquoteString(PerlString o) {
386
    replaceElement(o, PerlElementFactory.createBareString(myProject, ElementManipulators.getValueText(o)));
1✔
387
  }
1✔
388

389
  protected static boolean isStringSimple(PerlString o) {
390
    return o.getFirstChild().getNextSibling() == o.getLastChild().getPrevSibling() &&
1✔
391
           // we need this because lexer unable to properly parse utf
392
           ASCII_BARE_STRING_PATTERN.matcher(ElementManipulators.getValueText(o)).matches();
1✔
393
  }
394

395
  protected static boolean isStringSimpleIdentifier(PerlString o) {
396
    return o.getFirstChild().getNextSibling() == o.getLastChild().getPrevSibling() &&
1✔
397
           ASCII_IDENTIFIER_PATTERN.matcher(ElementManipulators.getValueText(o)).matches();
1✔
398
  }
399

400
  protected static boolean isCommaArrowAhead(PsiElement o) {
401
    PsiElement nextElement = PerlPsiUtil.getNextSignificantSibling(o);
1✔
402
    return nextElement != null && nextElement.getNode().getElementType() == FAT_COMMA;
1✔
403
  }
404

405
  protected static boolean isInHeredocOpener(PerlString o) {
406
    return o.getParent() instanceof PerlHeredocOpener;
1✔
407
  }
408

409
  protected static boolean isHashIndexKey(PsiElement o) {
410
    if (!(o.getParent() instanceof PsiPerlHashIndex)) {
1✔
411
      return false;
1✔
412
    }
413
    PsiElement prevSibling = o.getPrevSibling();
1✔
414
    PsiElement nextSibling = o.getNextSibling();
1✔
415
    return prevSibling != null && prevSibling.getNode().getElementType() == LEFT_BRACE &&
1✔
416
           nextSibling != null && nextSibling.getNode().getElementType() == RIGHT_BRACE;
1✔
417
  }
418
}
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