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

Camelcade / Perl5-IDEA / #525521553

27 May 2025 10:24AM UTC coverage: 82.286% (-0.03%) from 82.32%
#525521553

push

github

hurricup
Removed redundant implementations, moved up to the abstract class

1 of 1 new or added line in 1 file covered. (100.0%)

128 existing lines in 30 files now uncovered.

30886 of 37535 relevant lines covered (82.29%)

0.82 hits per line

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

95.59
/plugin/core/src/main/java/com/perl5/lang/perl/idea/refactoring/PerlNameSuggestionProvider.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.refactoring;
18

19
import com.intellij.openapi.diagnostic.Logger;
20
import com.intellij.openapi.util.NotNullLazyValue;
21
import com.intellij.openapi.util.Ref;
22
import com.intellij.openapi.util.io.FileUtil;
23
import com.intellij.openapi.util.text.StringUtil;
24
import com.intellij.psi.ElementManipulator;
25
import com.intellij.psi.ElementManipulators;
26
import com.intellij.psi.PsiElement;
27
import com.intellij.psi.codeStyle.SuggestedNameInfo;
28
import com.intellij.psi.tree.IElementType;
29
import com.intellij.psi.tree.TokenSet;
30
import com.intellij.psi.util.PsiTreeUtil;
31
import com.intellij.psi.util.PsiUtilCore;
32
import com.intellij.refactoring.rename.NameSuggestionProvider;
33
import com.intellij.util.ObjectUtils;
34
import com.intellij.util.containers.ContainerUtil;
35
import com.perl5.lang.perl.PerlLanguage;
36
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlCallValue;
37
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlValue;
38
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlValuesManager;
39
import com.perl5.lang.perl.idea.intellilang.PerlInjectionMarkersService;
40
import com.perl5.lang.perl.lexer.PerlElementTypes;
41
import com.perl5.lang.perl.lexer.PerlTokenSets;
42
import com.perl5.lang.perl.parser.PerlParserUtil;
43
import com.perl5.lang.perl.psi.*;
44
import com.perl5.lang.perl.psi.PerlAssignExpression.PerlAssignValueDescriptor;
45
import com.perl5.lang.perl.psi.impl.PerlSubCallElement;
46
import com.perl5.lang.perl.psi.mixins.PerlStatementMixin;
47
import com.perl5.lang.perl.psi.properties.PerlLexicalScope;
48
import com.perl5.lang.perl.psi.utils.PerlResolveUtil;
49
import com.perl5.lang.perl.psi.utils.PerlVariableType;
50
import com.perl5.lang.perl.util.PerlPackageUtil;
51
import org.jetbrains.annotations.Contract;
52
import org.jetbrains.annotations.NotNull;
53
import org.jetbrains.annotations.Nullable;
54

55
import java.io.File;
56
import java.util.*;
57
import java.util.function.Function;
58
import java.util.stream.Collectors;
59

60
import static com.perl5.lang.perl.lexer.PerlTokenSets.*;
61
import static com.perl5.lang.perl.parser.PerlElementTypesGenerated.*;
62

63

64
public class PerlNameSuggestionProvider implements NameSuggestionProvider {
1✔
65
  private static final Logger LOG = Logger.getInstance(PerlNameSuggestionProvider.class);
1✔
66
  private static final String EXPRESSION = "expression";
67
  private static final String ANON = "anon";
68
  private static final String REFERENCE = "reference";
69
  private static final String REF = "ref";
70
  private static final String SCALAR = "scalar";
71
  private static final String VALUE = "value";
72
  private static final List<String> SCALAR_BASE_NAMES = Arrays.asList(SCALAR, VALUE);
1✔
73
  private static final String ARRAY = "array";
74
  private static final String ANON_ARRAY = Objects.requireNonNull(join(ANON, ARRAY));
1✔
75
  private static final String ARRAY_REF = Objects.requireNonNull(join(ARRAY, REF));
1✔
76
  private static final String LIST = "list";
77
  private static final List<String> ARRAY_BASE_NAMES = Arrays.asList(ARRAY, LIST);
1✔
78
  private static final List<String> ANON_ARRAY_BASE_NAMES = ContainerUtil.append(ARRAY_BASE_NAMES, ANON_ARRAY, ARRAY_REF);
1✔
79
  private static final String HASH = "hash";
80
  private static final String ANON_HASH = Objects.requireNonNull(join(ANON, HASH));
1✔
81
  private static final String HASH_REF = Objects.requireNonNull(join(HASH, REF));
1✔
82
  private static final String MAP = "map";
83
  private static final String DICT = "dict";
84
  private static final List<String> HASH_BASE_NAMES = Arrays.asList(HASH, MAP, DICT);
1✔
85
  private static final List<String> ANON_HASH_BASE_NAMES = ContainerUtil.append(HASH_BASE_NAMES, ANON_HASH, HASH_REF);
1✔
86
  private static final String NUMBER = "number";
87
  private static final String STRING = "string";
88
  private static final String COMMAND_OUTPUT = "command_output";
89
  private static final String PATTERN = "pattern";
90
  private static final String REGEX = "regex";
91
  private static final List<String> REGEX_BASE_NAMES = Arrays.asList(PATTERN, REGEX);
1✔
92
  private static final String PACKAGE = "package";
93
  private static final String NAMESPACE = "namespace";
94
  private static final String MODULE = "module";
95
  private static final List<String> PACKAGE_BASE_NAMES = Arrays.asList(PACKAGE, NAMESPACE, MODULE);
1✔
96
  private static final int PACKAGE_CHUNKS_TO_USE = 3;
97
  private static final String PATH = "path";
98
  private static final String PATH_TO = "path_to";
99
  private static final String ABSOLUTE_PATH_TO = "absolute_path_to";
100
  private static final String RELATIVE_PATH_TO = "relative_path_to";
101
  private static final String PATH_NAME = "pathname";
102
  private static final List<String> BASE_PATH_NAMES = Arrays.asList(PATH, PATH_NAME);
1✔
103
  private static final int FILE_CHUNKS_TO_USE = 2;
104
  private static final String ELEMENT = "element";
105
  private static final String ITEM = "item";
106
  private static final String SLICE = "slice";
107
  private static final int MAX_GENERATED_NAME_LENGTH = 40;
108
  private static final String SUB = "sub";
109
  private static final String ANON_SUB = Objects.requireNonNull(join(ANON, SUB));
1✔
110
  private static final String LAMBDA = "lambda";
111
  private static final String CODE_REF = Objects.requireNonNull(join("code", REF));
1✔
112
  private static final String CODE_REFERENCE = Objects.requireNonNull(join("code", REFERENCE));
1✔
113
  private static final List<String> BASE_ANON_SUB_NAMES = Arrays.asList(ANON_SUB, LAMBDA, CODE_REF, CODE_REFERENCE);
1✔
114
  private static final String RESULT = "result";
115
  private static final String DO_RESULT = "do_result";
116
  private static final String EVAL_RESULT = "eval_result";
117
  private static final String SORTED = "sorted";
118
  private static final String MAPPED = "mapped";
119
  private static final String GREPPED = "filtered";
120
  private static final String STRING_LIST_NAME = "string_list";
121

122
  private static final NotNullLazyValue<Map<IElementType, String>> FIXED_NAMES = NotNullLazyValue.createValue(() -> {
1✔
123
    Map<IElementType, String> namesMap = new HashMap<>();
1✔
124
    namesMap.put(STRING_LIST, STRING_LIST_NAME);
1✔
125
    namesMap.put(COMMA_SEQUENCE_EXPR, LIST);
1✔
126
    namesMap.put(SUB_EXPR, CODE_REF);
1✔
127
    namesMap.put(DO_BLOCK_EXPR, DO_RESULT);
1✔
128
    namesMap.put(EVAL_EXPR, EVAL_RESULT);
1✔
129
    namesMap.put(PerlElementTypes.ANON_ARRAY, ANON_ARRAY);
1✔
130
    namesMap.put(PerlElementTypes.ANON_HASH, ANON_HASH);
1✔
131
    namesMap.put(NUMBER_CONSTANT, NUMBER);
1✔
132
    return Collections.unmodifiableMap(namesMap);
1✔
133
  });
134
  private static final NotNullLazyValue<TokenSet> ELEMENTS_WITH_BASE_NAMES = NotNullLazyValue.createValue(() -> TokenSet.orSet(
1✔
135
    TokenSet.create(FIXED_NAMES.get().keySet().toArray(IElementType.EMPTY_ARRAY)),
1✔
136
    PerlTokenSets.CAST_EXPRESSIONS,
137
    PerlTokenSets.SLICES,
138
    REGEX_OPERATIONS
139
  ));
140

141
  @Override
142
  public @Nullable SuggestedNameInfo getSuggestedNames(@Nullable PsiElement targetElement,
143
                                                       @Nullable PsiElement contextElement,
144
                                                       @NotNull Set<String> result) {
145
    suggestAndAddRecommendedName(targetElement, contextElement, result);
1✔
146
    return SuggestedNameInfo.NULL_INFO;
1✔
147
  }
148

149
  @Contract("null, _, _ -> null")
150
  private void suggestAndAddRecommendedName(@Nullable PsiElement targetElement,
151
                                            @Nullable PsiElement contextElement,
152
                                            @NotNull Set<String> result) {
153
    if (targetElement == null || !targetElement.getLanguage().isKindOf(PerlLanguage.INSTANCE)) {
1✔
154
      return;
×
155
    }
156

157
    String recommendedName = null;
1✔
158
    Set<String> suggestedNames = new LinkedHashSet<>();
1✔
159

160
    if (targetElement instanceof PerlHeredocOpener) {
1✔
161
      result.addAll(PerlInjectionMarkersService.getInstance(targetElement.getProject()).getSupportedMarkers());
1✔
162
    }
163
    else if (targetElement instanceof PerlVariableDeclarationElement variableDeclarationElement) {
1✔
164
      PerlVariable declaredVariable = variableDeclarationElement.getVariable();
1✔
165
      switch (declaredVariable) {
1✔
166
        case PsiPerlScalarVariable ignored -> suggestedNames.addAll(SCALAR_BASE_NAMES);
1✔
167
        case PsiPerlArrayVariable ignored -> suggestedNames.addAll(ARRAY_BASE_NAMES);
1✔
168
        case PsiPerlHashVariable ignored -> suggestedNames.addAll(HASH_BASE_NAMES);
1✔
169
        default -> {
170
        }
171
      }
172
      recommendedName = adjustNamesToBeUniqueFor(
1✔
173
        targetElement,
174
        variableDeclarationElement.getActualType(),
1✔
175
        declaredVariable.getName(),
1✔
176
        suggestAndAddRecommendedName(variableDeclarationElement, contextElement, suggestedNames),
1✔
177
        suggestedNames
178
      );
179
    }
180

181
    result.addAll(suggestedNames);
1✔
182
    if (recommendedName == null) {
1✔
183
      if (!suggestedNames.isEmpty()) {
1✔
184
        suggestedNames.iterator().next();
1✔
185
      }
186
    }
187
  }
1✔
188

189
  /**
190
   * Attempts for figure out a variable name from {@code declaration} and/or {@code contextElement}. Adds suggestions to the {@code result}
191
   * and return recommended name if any
192
   */
193
  private @Nullable String suggestAndAddRecommendedName(@NotNull PerlVariableDeclarationElement declaration,
194
                                                        @Nullable PsiElement contextElement,
195
                                                        @NotNull Set<String> result) {
196
    PerlAssignExpression assignmentExpression = PerlAssignExpression.getAssignmentExpression(declaration);
1✔
197
    if (assignmentExpression == null) {
1✔
198
      return null;
1✔
199
    }
200
    PerlAssignValueDescriptor rightSideDescriptor = assignmentExpression.getRightPartOfAssignment(declaration);
1✔
201
    if (rightSideDescriptor == null || rightSideDescriptor.getStartIndex() != 0) {
1✔
202
      return null;
1✔
203
    }
204
    List<PsiElement> elements = rightSideDescriptor.getElements();
1✔
205
    if (elements.isEmpty()) {
1✔
UNCOV
206
      return null;
×
207
    }
208
    // fixme array_element if index is more than one
209
    // fixme we could analyze all elements
210
    return suggestAndAddRecommendedName(elements.getFirst(), result);
1✔
211
  }
212

213
  private @Nullable String suggestAndAddRecommendedName(@Nullable PsiElement expression, @NotNull Set<String> result) {
214
    if (expression instanceof PsiPerlParenthesisedExpr parenthesisedExpr) {
1✔
215
      return suggestAndAddRecommendedName(parenthesisedExpr.getExpr(), result);
1✔
216
    }
217
    String recommendation = null;
1✔
218
    IElementType expressionType = PsiUtilCore.getElementType(expression);
1✔
219
    if (STRINGS.contains(expressionType)) {
1✔
220
      recommendation = suggestAndAddNameForString((PerlString)expression, result);
1✔
221
    }
222
    else if (expressionType == HASH_ELEMENT) {
1✔
223
      String resultString = Objects.requireNonNull(join(HASH, ELEMENT));
1✔
224
      result.add(resultString);
1✔
225
      result.add(join(HASH, ITEM));
1✔
226
      recommendation = suggestNamesForElements(((PsiPerlHashElement)expression).getExpr(),
1✔
227
                                               false, ((PsiPerlHashElement)expression).getHashIndex().getExpr(),
1✔
228
                                               result,
229
                                               resultString);
230
    }
1✔
231
    else if (expressionType == ARRAY_ELEMENT) {
1✔
232
      String resultString = Objects.requireNonNull(join(ARRAY, ELEMENT));
1✔
233
      result.add(resultString);
1✔
234
      result.add(join(ARRAY, ITEM));
1✔
235
      recommendation = suggestNamesForElements(((PsiPerlArrayElement)expression).getExpr(),
1✔
236
                                               false, ((PsiPerlArrayElement)expression).getArrayIndex().getExpr(),
1✔
237
                                               result,
238
                                               resultString);
239
    }
1✔
240
    else if (expressionType == DEREF_EXPR) {
1✔
241
      recommendation = suggestAndGetForDereference(expression, result, null);
1✔
242
    }
243
    else if (expressionType == GREP_EXPR) {
1✔
244
      recommendation = suggestAndGetGrepMapSortNames(expression, GREPPED, result);
1✔
245
    }
246
    else if (expressionType == MAP_EXPR) {
1✔
247
      recommendation = suggestAndGetGrepMapSortNames(expression, MAPPED, result);
1✔
248
    }
249
    else if (expressionType == SORT_EXPR) {
1✔
250
      recommendation = suggestAndGetGrepMapSortNames(expression, SORTED, result);
1✔
251
    }
252
    else if (expressionType == SUB_CALL) {
1✔
253
      recommendation = suggestAndGetForCall((PerlSubCallElement)expression, result, null);
1✔
254
    }
255
    else if (expressionType == SUB_EXPR) {
1✔
256
      result.addAll(BASE_ANON_SUB_NAMES);
1✔
257
    }
258
    else if (REGEX_OPERATIONS.contains(expressionType)) {
1✔
259
      result.addAll(REGEX_BASE_NAMES);
1✔
260
    }
261
    else if (expressionType == PerlElementTypes.ANON_HASH) {
1✔
262
      result.addAll(ANON_HASH_BASE_NAMES);
1✔
263
    }
264
    else if (expressionType == PerlElementTypes.ANON_ARRAY) {
1✔
265
      result.addAll(ANON_ARRAY_BASE_NAMES);
1✔
266
    }
267

268
    if (ELEMENTS_WITH_BASE_NAMES.get().contains(expressionType)) {
1✔
269
      recommendation = getBaseName(expression);
1✔
270
    }
271
    /*
272
    qw/list/
273
    (1,2,3)
274
    for my $var (@array){
275
    }
276
    */
277
    ContainerUtil.addIfNotNull(result, recommendation);
1✔
278
    return recommendation;
1✔
279
  }
280

281
  private String suggestAndGetForDereference(@NotNull PsiElement expression,
282
                                             @NotNull Set<String> result, String recommendation) {
283
    PsiElement[] children = expression.getChildren();
1✔
284
    PsiElement element = children[children.length - 1];
1✔
285
    PsiElement baseElement = children[children.length - 2];
1✔
286

287
    if (element instanceof PsiPerlHashIndex hashIndex) {
1✔
288
      recommendation = Objects.requireNonNull(join(HASH, ELEMENT));
1✔
289
      result.add(recommendation);
1✔
290
      result.add(join(HASH, ITEM));
1✔
291

292
      recommendation = suggestNamesForElements(baseElement, true, hashIndex.getExpr(), result, recommendation);
1✔
293
    }
294
    else if (element instanceof PsiPerlArrayIndex arrayIndex) {
1✔
295
      recommendation = Objects.requireNonNull(join(ARRAY, ELEMENT));
1✔
296
      result.add(recommendation);
1✔
297
      result.add(join(ARRAY, ITEM));
1✔
298

299
      recommendation = suggestNamesForElements(baseElement, true, arrayIndex.getExpr(), result, recommendation);
1✔
300
    }
301
    else if (element instanceof PerlSubCallElement subCallElement) {
1✔
302
      recommendation = suggestAndGetForCall(subCallElement, result, recommendation);
1✔
303
    }
304
    else if (element instanceof PsiPerlParenthesisedCallArguments) {
1✔
305
      recommendation = join(getBaseName(baseElement), RESULT);
1✔
306
    }
307
    return recommendation;
1✔
308
  }
309

310
  private static String suggestAndGetForCall(@NotNull PerlSubCallElement subCall, @NotNull Set<String> result, String recommendation) {
311
    PsiPerlMethod method = subCall.getMethod();
1✔
312
    if (method == null) {
1✔
313
      return recommendation;
×
314
    }
315
    PerlCallValue callValue = PerlCallValue.from(method);
1✔
316
    if (callValue == null) {
1✔
UNCOV
317
      return recommendation;
×
318
    }
319

320
    PerlValue subNameValue = callValue.getSubNameValue();
1✔
321

322
    if (subNameValue.canRepresentSubName("new")) {
1✔
323
      PerlValue namespaceNameValue = callValue.getNamespaceNameValue();
1✔
324
      Collection<String> variantsFromObjectClass = getVariantsFromPerlValueNamespaces(method, namespaceNameValue);
1✔
325
      result.addAll(variantsFromObjectClass);
1✔
326
      if (!variantsFromObjectClass.isEmpty()) {
1✔
327
        recommendation = variantsFromObjectClass.iterator().next();
1✔
328
      }
329
    }
1✔
330
    else {
331
      Ref<String> recommendationRef = Ref.create(recommendation);
1✔
332
      for (String subName : subNameValue.resolve(method).getSubNames()) {
1✔
333
        String normalizedName = join(subName.replaceAll("^(_+|get_*|set_*)", ""));
1✔
334
        if (StringUtil.isNotEmpty(normalizedName)) {
1✔
335
          result.add(normalizedName);
1✔
336
          recommendationRef.set(normalizedName);
1✔
337
        }
338
      }
1✔
339
      recommendation = recommendationRef.get();
1✔
340
      result.addAll(getVariantsFromEntityValueNamespaces(subCall));
1✔
341
    }
342
    return recommendation;
1✔
343
  }
344

345
  private String suggestAndGetGrepMapSortNames(@Nullable PsiElement expression,
346
                                               @NotNull String prefix,
347
                                               @NotNull Set<String> result) {
348
    String recommendedName = join(prefix, VALUE);
1✔
349
    result.add(recommendedName);
1✔
350
    String fullName = getBaseName(expression);
1✔
351
    if (StringUtil.isNotEmpty(fullName)) {
1✔
352
      recommendedName = fullName;
1✔
353
      result.add(fullName);
1✔
354
    }
355
    return recommendedName;
1✔
356
  }
357

358
  private static String suggestAndAddNameForString(@NotNull PerlString expression, @NotNull Set<String> result) {
359
    result.add(STRING);
1✔
360
    if (expression instanceof PsiPerlStringXq) {
1✔
361
      result.add(COMMAND_OUTPUT);
1✔
362
      return COMMAND_OUTPUT;
1✔
363
    }
364
    String recommendedString = STRING;
1✔
365
    if (expression.getChildren().length != 0) {
1✔
366
      return recommendedString;
1✔
367
    }
368
    String nameFromManipulator = getNameFromManipulator(expression);
1✔
369
    if (nameFromManipulator != null) {
1✔
370
      result.add(nameFromManipulator);
1✔
371
      recommendedString = nameFromManipulator;
1✔
372
    }
373

374
    String valueText = ElementManipulators.getValueText(expression);
1✔
375
    if (PerlString.looksLikePackage(valueText)) {
1✔
376
      result.addAll(PACKAGE_BASE_NAMES);
1✔
377
      List<String> namespaceVariants = getVariantsFromNamespaceName(valueText);
1✔
378
      result.addAll(namespaceVariants);
1✔
379
      if (!namespaceVariants.isEmpty() && STRING.equals(recommendedString)) {
1✔
UNCOV
380
        recommendedString = namespaceVariants.getFirst();
×
381
      }
382
    }
1✔
383
    else{
384
      String independentPath = FileUtil.toSystemIndependentName(valueText);
1✔
385
      if (PerlString.looksLikePath(independentPath)) {
1✔
386
        result.addAll(BASE_PATH_NAMES);
1✔
387
        List<String> pathVariants = getVariantsFromPath(independentPath);
1✔
388
        result.addAll(pathVariants);
1✔
389
        if (!pathVariants.isEmpty() && STRING.equals(recommendedString)) {
1✔
390
          recommendedString = pathVariants.getFirst();
1✔
391
        }
392
      }
393
    }
394
    return recommendedString;
1✔
395
  }
396

397
  @Contract("null->null")
398
  private static @Nullable String getBaseName(@Nullable PsiElement element) {
399
    if (element == null) {
1✔
UNCOV
400
      return null;
×
401
    }
402
    String nameFromKey = getNameFromManipulator(element);
1✔
403
    if (StringUtil.isNotEmpty(nameFromKey)) {
1✔
404
      return nameFromKey;
1✔
405
    }
406

407
    IElementType elementType = PsiUtilCore.getElementType(element);
1✔
408

409
    String fixedName = FIXED_NAMES.get().get(elementType);
1✔
410
    if (fixedName != null) {
1✔
411
      return fixedName;
1✔
412
    }
413
    else if (elementType == GREP_EXPR) {
1✔
414
      return join(GREPPED, getBaseName(((PerlGrepExpr)element).getExpr()));
1✔
415
    }
416
    else if (elementType == MAP_EXPR) {
1✔
417
      return join(MAPPED, getBaseName(((PerlMapExpr)element).getExpr()));
1✔
418
    }
419
    else if (elementType == SORT_EXPR) {
1✔
420
      return join(SORTED, getBaseName(((PerlSortExpr)element).getTarget()));
1✔
421
    }
422
    else if (elementType == ARRAY_SLICE) {
1✔
423
      return join(getBaseName(((PsiPerlArraySlice)element).getExpr()), SLICE);
1✔
424
    }
425
    else if (elementType == HASH_SLICE) {
1✔
426
      return join(getBaseName(((PsiPerlHashSlice)element).getExpr()), SLICE);
1✔
427
    }
428
    else if (BLOCK_LIKE_CONTAINERS.contains(elementType)) {
1✔
429
      PsiElement[] blockChildren = element.getChildren();
1✔
430
      if (blockChildren.length == 1) {
1✔
431
        PsiElement perlStatement = blockChildren[0];
1✔
432
        if (perlStatement instanceof PerlStatementMixin statementMixin &&
1✔
433
            !statementMixin.hasModifier()) {
1✔
434
          return getBaseName(((PsiPerlStatement)perlStatement).getExpr());
1✔
435
        }
436
      }
437
    }
1✔
438
    else if (REGEX_OPERATIONS.contains(elementType)) {
1✔
439
      return PATTERN;
1✔
440
    }
441
    else if (VARIABLES.contains(elementType)) {
1✔
442
      return join(((PerlVariable)element).getName());
1✔
443
    }
444
    else if (CAST_EXPRESSIONS.contains(elementType)) {
1✔
445
      PsiPerlExpr targetExpr = ((PerlCastExpression)element).getExpr();
1✔
446
      PsiPerlBlock targetBlock = ((PerlCastExpression)element).getBlock();
1✔
447
      return derefName(getBaseName(targetExpr == null ? targetBlock : targetExpr));
1✔
448
    }
449

450
    return null;
1✔
451
  }
452

453
  /**
454
   * Suggests names for hash/array elements
455
   *
456
   * @param baseExpr       variable from index expression
457
   * @param derefBase      if true, base name going to be stripped from ref/reference
458
   * @param indexExpr      expressions from index expression
459
   * @param result         result to add recommendations to
460
   * @param recommendation base recommendation name
461
   * @return recommended name
462
   */
463
  private static @NotNull String suggestNamesForElements(@Nullable PsiElement baseExpr,
464
                                                         boolean derefBase,
465
                                                         @Nullable PsiElement indexExpr,
466
                                                         @NotNull Set<String> result,
467
                                                         @NotNull String recommendation) {
468
    String singularName = null;
1✔
469
    String baseExprName = getBaseName(baseExpr);
1✔
470
    if (derefBase) {
1✔
471
      baseExprName = derefName(baseExprName);
1✔
472
    }
473

474
    if (StringUtil.isNotEmpty(baseExprName)) {
1✔
475
      singularName = ObjectUtils.notNull(StringUtil.unpluralize(baseExprName), baseExprName);
1✔
476
      String variableNameElement = join(singularName, ELEMENT);
1✔
477
      if (StringUtil.isNotEmpty(variableNameElement)) {
1✔
478
        result.add(variableNameElement);
1✔
479
        recommendation = variableNameElement;
1✔
480
      }
481
      ContainerUtil.addIfNotNull(result, join(singularName, ITEM));
1✔
482
    }
483

484
    String nameFromKey = getBaseName(indexExpr);
1✔
485
    if (indexExpr instanceof PerlString) {
1✔
486
      if (StringUtil.isEmpty(nameFromKey)) {
1✔
487
        LOG.debug("Unable to extract name from: ", indexExpr, "; '", indexExpr.getText(), "'");
1✔
488
        recommendation = "element";
1✔
489
      }
490
      else {
491
        recommendation = nameFromKey;
1✔
492
      }
493
    }
494

495
    if (StringUtil.isNotEmpty(nameFromKey) && !(indexExpr instanceof PsiPerlNumberConstant)) {
1✔
496
      result.add(nameFromKey);
1✔
497
      if (StringUtil.isNotEmpty(singularName)) {
1✔
498
        String join = join(singularName, nameFromKey);
1✔
499
        ContainerUtil.addIfNotNull(result, join);
1✔
500
      }
501
    }
502
    return recommendation;
1✔
503
  }
504

505
  private static @NotNull Collection<String> getVariantsFromEntityValueNamespaces(@Nullable PsiElement contextElement) {
506
    if (contextElement == null) {
1✔
UNCOV
507
      return Collections.emptyList();
×
508
    }
509
    return getVariantsFromPerlValueNamespaces(contextElement, PerlValuesManager.from(contextElement));
1✔
510
  }
511

512
  private static @NotNull Collection<String> getVariantsFromPerlValueNamespaces(@NotNull PsiElement contextElement,
513
                                                                                @Nullable PerlValue perlValue) {
514
    if (PerlValuesManager.isUnknown(perlValue)) {
1✔
UNCOV
515
      return Collections.emptyList();
×
516
    }
517
    Set<String> result = new LinkedHashSet<>();
1✔
518
    perlValue.resolve(contextElement).getNamespaceNames().forEach(it -> result.addAll(getVariantsFromNamespaceName(it)));
1✔
519
    return result;
1✔
520
  }
521

522
  private static @NotNull List<String> getVariantsFromNamespaceName(@Nullable String packageName) {
523
    if (StringUtil.isEmptyOrSpaces(packageName)) {
1✔
524
      return Collections.emptyList();
×
525
    }
526
    List<String> packageChunks = PerlPackageUtil.split(packageName);
1✔
527
    if (packageChunks.isEmpty()) {
1✔
UNCOV
528
      return Collections.emptyList();
×
529
    }
530
    int chunksNumber = Math.min(packageChunks.size(), PACKAGE_CHUNKS_TO_USE);
1✔
531
    List<String> result = new ArrayList<>(chunksNumber);
1✔
532
    for (int i = 1; i <= chunksNumber; i++) {
1✔
533
      String baseName = join(packageChunks.subList(chunksNumber - i, chunksNumber));
1✔
534
      ContainerUtil.addIfNotNull(result, baseName);
1✔
535
    }
536
    return result;
1✔
537
  }
538

539
  private static @NotNull List<String> getVariantsFromPath(@NotNull String pathName) {
540
    File file = new File(pathName);
1✔
541
    String fileName = file.getName();
1✔
542
    if (StringUtil.isEmptyOrSpaces(fileName)) {
1✔
UNCOV
543
      return Collections.emptyList();
×
544
    }
545

546
    List<String> fileNameParts = StringUtil.split(StringUtil.trimLeading(fileName, '.'), ".");
1✔
547
    int chunksNumber = Math.min(fileNameParts.size(), FILE_CHUNKS_TO_USE);
1✔
548
    List<String> result = new ArrayList<>();
1✔
549
    for (int i = 1; i <= chunksNumber; i++) {
1✔
550
      String baseFileName = join(fileNameParts.subList(0, i));
1✔
551
      if (baseFileName != null) {
1✔
552
        result.add(baseFileName);
1✔
553
        if (file.isAbsolute()) {
1✔
554
          ContainerUtil.addIfNotNull(result, join(ABSOLUTE_PATH_TO, baseFileName));
1✔
555
        }
556
        else {
557
          ContainerUtil.addIfNotNull(result, join(RELATIVE_PATH_TO, baseFileName));
1✔
558
        }
559
        ContainerUtil.addIfNotNull(result, join(PATH_TO, baseFileName));
1✔
560
      }
561
    }
562
    return result;
1✔
563
  }
564

565
  /**
566
   * Fills {@code result} with names suggested for the {@code targetElement} and returns the recommended name
567
   */
568
  public static @NotNull String getRecommendedName(@NotNull PsiElement expression,
569
                                                   @NotNull PsiElement contextElement,
570
                                                   @NotNull PerlVariableType variableType) {
571
    String suggestedName = getInstance().suggestAndAddRecommendedName(expression, new LinkedHashSet<>());
1✔
572
    return StringUtil.notNullize(
1✔
573
      adjustNamesToBeUniqueFor(contextElement, variableType, null, suggestedName, Collections.emptySet()),
1✔
574
      EXPRESSION);
575
  }
576

577
  private static @NotNull PerlNameSuggestionProvider getInstance() {
578
    return Objects.requireNonNull(EP_NAME.findExtension(PerlNameSuggestionProvider.class));
1✔
579
  }
580

581
  public static void suggestNames(@NotNull PerlVariableDeclarationElement variableDeclarationElement, @NotNull Set<String> result) {
582
    getInstance().suggestAndAddRecommendedName((PsiElement)variableDeclarationElement, null, result);
1✔
583
  }
1✔
584

585
  @Contract("null->null")
586
  private static @Nullable String join(@Nullable String @Nullable ... source) {
587
    return source == null ? null : join(Arrays.asList(source));
1✔
588
  }
589

590
  private static @Nullable String join(@NotNull List<String> source) {
591
    var result = source.stream()
1✔
592
      .filter(chunk -> !StringUtil.isEmptyOrSpaces(chunk))
1✔
593
      .collect(Collectors.joining("_"))
1✔
594
      .toLowerCase(Locale.getDefault());
1✔
595
    return validateName(result);
1✔
596
  }
597

598
  private static @Nullable String spacedToSnakeCase(@NotNull String source) {
599
    return join(source.toLowerCase(Locale.getDefault()).split("\\W+"));
1✔
600
  }
601

602
  @Contract("null->null")
603
  private static @Nullable String validateName(@Nullable String name) {
604
    return StringUtil.isNotEmpty(name) && name.length() <= MAX_GENERATED_NAME_LENGTH && PerlParserUtil.isIdentifier(name) ? name : null;
1✔
605
  }
606

607
  @Contract("null->null")
608
  private static @Nullable String getNameFromManipulator(@Nullable PsiElement element) {
609
    if (element == null) {
1✔
UNCOV
610
      return null;
×
611
    }
612
    ElementManipulator<PsiElement> manipulator = ElementManipulators.getManipulator(element);
1✔
613
    if (manipulator == null) {
1✔
614
      return null;
1✔
615
    }
616
    return spacedToSnakeCase(ElementManipulators.getValueText(element));
1✔
617
  }
618

619
  @Contract("null->null")
620
  private static @Nullable String derefName(@Nullable String name) {
621
    return removeChunk(removeChunk(name, REFERENCE + "s?"), REF + "s?");
1✔
622
  }
623

624
  /**
625
   * @return original {@code name} with {@code chunk} removed. Chunk removed with underscore prefix
626
   */
627
  @Contract("null,_->null")
628
  private static @Nullable String removeChunk(@Nullable String name, @NotNull String chunk) {
629
    if (name == null) {
1✔
630
      return null;
1✔
631
    }
632
    return validateName(name.replaceAll("_+" + chunk + "_+", "_").replaceAll("(_+" + chunk + "$|^" + chunk + "_+)", ""));
1✔
633
  }
634

635
  /**
636
   * Collects existing variables names for the {@code contextElement} and processing names from the {@code names} by adding
637
   * a number if variable of {@code variableType} with original name exists in scope. Modifying passed names.
638
   *
639
   * @return potentially adjusted {@code recommendedName}
640
   */
641
  @Contract("_,_,_,null,_->null")
642
  public static @Nullable String adjustNamesToBeUniqueFor(@NotNull PsiElement contextElement,
643
                                                          @NotNull PerlVariableType variableType,
644
                                                          @Nullable String currentName,
645
                                                          @Nullable String recommendedName,
646
                                                          @NotNull Set<String> names) {
647
    if (names.isEmpty() && recommendedName == null) {
1✔
648
      return null;
1✔
649
    }
650
    Set<String> existingNames = collectExistingNames(contextElement, variableType);
1✔
651
    Function<String, String> fun = originalName -> {
1✔
652
      if (originalName == null) {
1✔
653
        return null;
1✔
654
      }
655
      if (!existingNames.contains(originalName) || originalName.equals(currentName)) {
1✔
656
        return originalName;
1✔
657
      }
658
      else {
659
        for (int i = 1; i < Integer.MAX_VALUE; i++) {
1✔
660
          String adjustedName = originalName + i;
1✔
661
          if (!existingNames.contains(adjustedName)) {
1✔
662
            return adjustedName;
1✔
663
          }
664
        }
UNCOV
665
        return null;
×
666
      }
667
    };
668
    if (!names.isEmpty()) {
1✔
669
      LinkedHashSet<String> result = new LinkedHashSet<>();
1✔
670
      names.forEach(it -> result.add(fun.apply(it)));
1✔
671
      names.clear();
1✔
672
      names.addAll(result);
1✔
673
    }
674
    return fun.apply(recommendedName);
1✔
675
  }
676

677
  /**
678
   * Traverses a subtree after a baseElement and walk ups from the baseElement, collecting all variable names.
679
   */
680
  public static @NotNull Set<String> collectExistingNames(@Nullable PsiElement baseElement, @NotNull PerlVariableType variableType) {
681
    if (baseElement == null) {
1✔
UNCOV
682
      return Collections.emptySet();
×
683
    }
684
    Set<String> names = new HashSet<>();
1✔
685
    PsiElement closestScope = PsiTreeUtil.getParentOfType(baseElement, PerlLexicalScope.class, false);
1✔
686
    if (closestScope == null) {
1✔
UNCOV
687
      LOG.error("Unable to find scope for an " + baseElement);
×
688
    }
689
    else {
690
      PsiElement[] children = closestScope.getChildren();
1✔
691
      PerlRecursiveVisitor recursiveVisitor = new PerlRecursiveVisitor() {
1✔
692
        @Override
693
        public void visitPerlVariable(@NotNull PerlVariable o) {
694
          if (o.getActualType() == variableType) {
1✔
695
            ContainerUtil.addIfNotNull(names, o.getName());
1✔
696
          }
697
        }
1✔
698
      };
699
      for (int i = children.length - 1; i >= 0; i--) {
1✔
700
        PsiElement child = children[i];
1✔
701
        child.accept(recursiveVisitor);
1✔
702
        if (PsiTreeUtil.isAncestor(child, baseElement, false)) {
1✔
703
          break;
1✔
704
        }
705
      }
706
    }
707

708
    PerlResolveUtil.treeWalkUp(baseElement, (element, __) -> {
1✔
709
      if (element instanceof PerlVariable perlVariable && perlVariable.getActualType() == variableType) {
1✔
710
        ContainerUtil.addIfNotNull(names, perlVariable.getName());
1✔
711
      }
712
      return true;
1✔
713
    });
714

715
    return names;
1✔
716
  }
717
}
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