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

Camelcade / Perl5-IDEA / #525521555

28 May 2025 04:25PM UTC coverage: 82.274% (-0.01%) from 82.286%
#525521555

push

github

hurricup
PerlNameSuggestionProvider cleanup

8 of 9 new or added lines in 1 file covered. (88.89%)

20 existing lines in 6 files now uncovered.

30880 of 37533 relevant lines covered (82.27%)

0.82 hits per line

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

95.29
/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<? super 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
        suggestAndAddRecommendedNameForVariable(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 suggestAndAddRecommendedNameForVariable(@NotNull PerlVariableDeclarationElement declaration,
194
                                                                   @Nullable PsiElement ignoredContextElement,
195
                                                                   @NotNull Set<? super 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✔
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<? super 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<? super String> result,
283
                                             @SuppressWarnings("SameParameterValue") String initialRecommendation) {
284
    PsiElement[] children = expression.getChildren();
1✔
285
    PsiElement element = children[children.length - 1];
1✔
286
    PsiElement baseElement = children[children.length - 2];
1✔
287

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

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

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

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

323
    PerlValue subNameValue = callValue.getSubNameValue();
1✔
324

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

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

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

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

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

410
    IElementType elementType = PsiUtilCore.getElementType(element);
1✔
411

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

453
    return null;
1✔
454
  }
455

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

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

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

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

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

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

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

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

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

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

580
  private static @NotNull PerlNameSuggestionProvider getInstance() {
581
    return Objects.requireNonNull(EP_NAME.findExtension(PerlNameSuggestionProvider.class));
1✔
582
  }
583

584
  public static void suggestNames(@NotNull PerlVariableDeclarationElement variableDeclarationElement, @NotNull Set<? super String> result) {
585
    getInstance().suggestAndAddRecommendedName(variableDeclarationElement, null, result);
1✔
586
  }
1✔
587

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

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

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

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

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

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

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

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

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

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

718
    return names;
1✔
719
  }
720
}
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