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

Camelcade / Perl5-IDEA / #525521819

12 Apr 2026 11:03AM UTC coverage: 76.189% (+0.1%) from 76.061%
#525521819

push

github

hurricup
[qodana] Suppressed a warning on the api method

14764 of 22542 branches covered (65.5%)

Branch coverage included in aggregate %.

31091 of 37644 relevant lines covered (82.59%)

0.83 hits per line

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

88.64
/plugin/common/src/main/java/com/perl5/lang/perl/idea/formatter/blocks/PerlFormattingBlock.java
1
/*
2
 * Copyright 2015-2026 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.blocks;
18

19
import com.intellij.formatting.*;
20
import com.intellij.lang.ASTNode;
21
import com.intellij.openapi.util.AtomicNotNullLazyValue;
22
import com.intellij.openapi.util.TextRange;
23
import com.intellij.psi.TokenType;
24
import com.intellij.psi.formatter.common.AbstractBlock;
25
import com.intellij.psi.tree.IElementType;
26
import com.intellij.psi.tree.TokenSet;
27
import com.intellij.psi.util.PsiUtilCore;
28
import com.perl5.lang.perl.idea.formatter.PurePerlFormattingContext;
29
import com.perl5.lang.perl.parser.PerlParserUtil;
30
import org.jetbrains.annotations.NotNull;
31
import org.jetbrains.annotations.Nullable;
32

33
import java.util.ArrayList;
34
import java.util.Collections;
35
import java.util.List;
36

37
import static com.perl5.lang.perl.lexer.PerlTokenSets.HEREDOC_BODIES_TOKENSET;
38
import static com.perl5.lang.perl.lexer.PerlTokenSets.TRANSPARENT_ELEMENT_TYPES;
39
import static com.perl5.lang.perl.parser.PerlElementTypesGenerated.*;
40

41

42
public class PerlFormattingBlock extends AbstractBlock implements PerlAstBlock {
43
  /**
44
   * Composite elements that should be treated as leaf elements, no children
45
   */
46
  public static final TokenSet LEAF_ELEMENTS =
1✔
47
    TokenSet.create(
1✔
48
      POD,
49
      PerlParserUtil.DUMMY_BLOCK
50
    );
51

52
  protected final @NotNull PurePerlFormattingContext myContext;
53
  private Indent myIndent;
54
  private Boolean myIsIncomplete;
55
  private final AtomicNotNullLazyValue<List<Block>> mySubBlocksProvider = AtomicNotNullLazyValue.createValue(
1✔
56
    () -> List.copyOf(buildSubBlocks())
1✔
57
  );
58

59
  public PerlFormattingBlock(@NotNull ASTNode node, @NotNull PurePerlFormattingContext context) {
60
    super(node, context.getWrap(node), context.getAlignment(node));
1✔
61
    myContext = context;
1✔
62
    myIndent = context.getNodeIndent(node);
1✔
63
  }
1✔
64

65
  @Override
66
  public void setIndent(@Nullable Indent indent) {
67
    myIndent = indent;
1✔
68
  }
1✔
69

70
  @Override
71
  protected final @NotNull List<Block> buildChildren() {
72
    if (isLeaf()) {
1✔
73
      return Collections.emptyList();
1!
74
    }
75
    return mySubBlocksProvider.getValue();
1!
76
  }
77

78
  protected @NotNull List<Block> buildSubBlocks() {
79
    ASTNode run = myNode.getFirstChildNode();
1✔
80
    if (run == null) {
1!
81
      return Collections.emptyList();
×
82
    }
83

84
    if (getElementType() == PERL_REGEX) {
1✔
85
      return buildRegexpSubBlocks();
1!
86
    }
87
    List<Block> blocks = new ArrayList<>();
1✔
88
    buildSubBlocksForNode(blocks, myNode);
1✔
89
    return processSubBlocks(blocks.isEmpty() ? Collections.emptyList() : blocks);
1✔
90
  }
91

92
  private void buildSubBlocksForNode(@NotNull List<? super Block> blocks, @NotNull ASTNode node) {
93
    TextRange formattedRange = myContext.getTextRange();
1✔
94

95
    ASTNode run = node.getFirstChildNode();
1✔
96
    int startOffset = run.getStartOffset();
1✔
97

98
    ASTNode lastCoverableNode = null;
1✔
99
    while (!run.getTextRange().intersects(formattedRange)) {
1✔
100
      if (shouldCreateSubBlockFor(run)) {
1✔
101
        lastCoverableNode = run;
1✔
102
      }
103
      run = run.getTreeNext();
1✔
104
    }
105

106
    if (lastCoverableNode != null) {
1✔
107
      while (TRANSPARENT_ELEMENT_TYPES.contains(PsiUtilCore.getElementType(lastCoverableNode)) &&
1!
108
             lastCoverableNode.getLastChildNode() != null) {
×
109
        lastCoverableNode = lastCoverableNode.getLastChildNode();
×
110
      }
111
      blocks.add(new PerlTextRangeBasedBlock(TextRange.create(startOffset, lastCoverableNode.getTextRange().getEndOffset())));
1✔
112
    }
113

114
    for (; run != null && run.getTextRange().intersects(formattedRange); run = run.getTreeNext()) {
1✔
115
      if (TRANSPARENT_ELEMENT_TYPES.contains(PsiUtilCore.getElementType(run))) {
1✔
116
        buildSubBlocksForNode(blocks, run);
1✔
117
      }
118
      else if (shouldCreateSubBlockFor(run)) {
1✔
119
        blocks.add(createBlock(run));
1✔
120
      }
121
    }
122

123
    if (run != null) {
1✔
124
      blocks.add(new PerlTextRangeBasedBlock(TextRange.create(run.getStartOffset(), node.getTextRange().getEndOffset())));
1✔
125
    }
126
  }
1✔
127

128
  protected @NotNull List<Block> buildRegexpSubBlocks() {
129
    ASTNode run = myNode.getFirstChildNode();
1✔
130
    if (run == null) {
1!
131
      return Collections.emptyList();
×
132
    }
133

134
    List<Block> result = new ArrayList<>();
1✔
135
    int startOffset = -1;
1✔
136
    while (run != null) {
1✔
137
      IElementType elementType = PsiUtilCore.getElementType(run);
1✔
138
      if (elementType == TokenType.WHITE_SPACE || elementType == REGEX_TOKEN || elementType == COMMENT_LINE) {
1✔
139
        if (startOffset < 0) {
1✔
140
          startOffset = run.getStartOffset();
1✔
141
        }
142
      }
143
      else {
144
        if (startOffset >= 0) {
1✔
145
          result.add(new PerlTextRangeBasedBlock(TextRange.create(startOffset, run.getStartOffset())));
1✔
146
          startOffset = -1;
1✔
147
        }
148
        if (shouldCreateSubBlockFor(run)) {
1!
149
          result.add(createBlock(run));
1✔
150
        }
151
      }
152
      run = run.getTreeNext();
1✔
153
    }
1✔
154

155
    if (startOffset >= 0) {
1✔
156
      result.add(new PerlTextRangeBasedBlock(TextRange.create(startOffset, myNode.getStartOffset() + myNode.getTextLength())));
1✔
157
    }
158

159
    return result;
1!
160
  }
161

162
  private @NotNull List<Block> processSubBlocks(@NotNull List<Block> rawBlocks) {
163
    IElementType elementType = getElementType();
1✔
164
    if (elementType == SIGNATURE_ELEMENT && rawBlocks.size() == 1) {
1✔
165
      while (rawBlocks.size() == 1) {
1✔
166
        rawBlocks = rawBlocks.getFirst().getSubBlocks();
1✔
167
      }
168
      return rawBlocks;
1!
169
    }
170
    else if (elementType == COMMA_SEQUENCE_EXPR) {
1✔
171
      return processCommaSequenceBlocks(rawBlocks);
1✔
172
    }
173
    return rawBlocks;
1!
174
  }
175

176
  private @NotNull List<Block> processCommaSequenceBlocks(@NotNull List<? extends Block> rawBlocks) {
177
    List<Block> result = new ArrayList<>();
1✔
178
    List<Block> blocksToGroup = new ArrayList<>();
1✔
179
    boolean[] hasFatComma = new boolean[]{false};
1✔
180
    Runnable blocksDispatcher = () -> {
1✔
181
      if (blocksToGroup.isEmpty()) {
1✔
182
        return;
1✔
183
      }
184
      if (hasFatComma[0]) {
1✔
185
        result.add(new PerlSyntheticBlock(this, blocksToGroup, null, null, myContext));
1✔
186
        hasFatComma[0] = false;
1✔
187
      }
188
      else {
189
        result.addAll(blocksToGroup);
1✔
190
      }
191
      blocksToGroup.clear();
1✔
192
    };
1✔
193

194
    for (Block block : rawBlocks) {
1✔
195
      IElementType blockType = ASTBlock.getElementType(block);
1✔
196
      if ((blockType == null || blockType == COMMA)) {
1✔
197
        blocksToGroup.add(block);
1✔
198
        blocksDispatcher.run();
1✔
199
      }
200
      else if (blocksToGroup.isEmpty() && (blockType == COMMENT_LINE || blockType == COMMENT_ANNOTATION)) {
1!
201
        result.add(block);
1✔
202
      }
203
      else {
204
        blocksToGroup.add(block);
1✔
205
        hasFatComma[0] |= blockType == FAT_COMMA;
1✔
206
      }
207
    }
1✔
208
    blocksDispatcher.run();
1✔
209

210
    return result;
1!
211
  }
212

213
  protected PerlFormattingBlock createBlock(@NotNull ASTNode node) {
214
    if (HEREDOC_BODIES_TOKENSET.contains(PsiUtilCore.getElementType(node))) {
1✔
215
      return new PerlHeredocFormattingBlock(node, myContext);
1✔
216
    }
217
    return new PerlFormattingBlock(node, myContext);
1✔
218
  }
219

220
  @Override
221
  public @Nullable Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
222
    return myContext.getSpacing(this, child1, child2);
1✔
223
  }
224

225
  @Override
226
  public boolean isLeaf() {
227
    return myNode.getFirstChildNode() == null || LEAF_ELEMENTS.contains(myNode.getElementType());
1!
228
  }
229

230
  @Override
231
  public Indent getIndent() {
232
    return myIndent;
1✔
233
  }
234

235
  @Override
236
  protected final @Nullable Indent getChildIndent() {
237
    throw new IllegalArgumentException("Formatting context must be used for this");
×
238
  }
239

240
  @Override
241
  public final @NotNull ChildAttributes getChildAttributes(int newChildIndex) {
242
    return myContext.getChildAttributes(this, newChildIndex);
1!
243
  }
244

245
  @Override
246
  public final boolean isIncomplete() {
247
    if (myIsIncomplete == null) {
1✔
248
      myIsIncomplete = myContext.isIncomplete(this);
1✔
249
      if (myIsIncomplete == null) {
1✔
250
        myIsIncomplete = super.isIncomplete();
1✔
251
      }
252
    }
253
    return myIsIncomplete;
1✔
254
  }
255

256
  protected boolean shouldCreateSubBlockFor(ASTNode node) {
257
    IElementType elementType = PsiUtilCore.getElementType(node);
1✔
258
    return elementType != TokenType.WHITE_SPACE && !node.getText().isEmpty() &&
1✔
259
           !(HEREDOC_BODIES_TOKENSET.contains(elementType) && node.getTextLength() == 1);
1✔
260
  }
261
}
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