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

Camelcade / Perl5-IDEA / #525521563

01 Jun 2025 02:15PM UTC coverage: 82.332% (+0.04%) from 82.289%
#525521563

push

github

hurricup
Bounded wildcards

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

69 existing lines in 17 files now uncovered.

30882 of 37509 relevant lines covered (82.33%)

0.82 hits per line

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

91.88
/plugin/core/src/main/java/com/perl5/lang/perl/util/PerlPackageUtil.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.util;
18

19
import com.intellij.openapi.fileTypes.FileType;
20
import com.intellij.openapi.progress.ProgressManager;
21
import com.intellij.openapi.project.Project;
22
import com.intellij.openapi.util.AtomicNotNullLazyValue;
23
import com.intellij.openapi.util.NlsSafe;
24
import com.intellij.openapi.util.Pair;
25
import com.intellij.openapi.util.TextRange;
26
import com.intellij.openapi.util.text.StringUtil;
27
import com.intellij.openapi.vfs.VfsUtil;
28
import com.intellij.openapi.vfs.VfsUtilCore;
29
import com.intellij.openapi.vfs.VirtualFile;
30
import com.intellij.psi.PsiElement;
31
import com.intellij.psi.PsiFile;
32
import com.intellij.psi.PsiManager;
33
import com.intellij.psi.search.FileTypeIndex;
34
import com.intellij.psi.search.GlobalSearchScope;
35
import com.intellij.psi.util.CachedValueProvider;
36
import com.intellij.psi.util.CachedValuesManager;
37
import com.intellij.psi.util.PsiTreeUtil;
38
import com.intellij.psi.util.PsiUtilCore;
39
import com.intellij.util.PairProcessor;
40
import com.intellij.util.Processor;
41
import com.intellij.util.SmartList;
42
import com.perl5.lang.perl.extensions.packageprocessor.PerlLibProvider;
43
import com.perl5.lang.perl.extensions.packageprocessor.PerlPackageParentsProvider;
44
import com.perl5.lang.perl.extensions.packageprocessor.PerlPackageProcessor;
45
import com.perl5.lang.perl.extensions.parser.PerlRuntimeParentsProvider;
46
import com.perl5.lang.perl.extensions.parser.PerlRuntimeParentsProviderFromArray;
47
import com.perl5.lang.perl.fileTypes.PerlFileTypePackage;
48
import com.perl5.lang.perl.idea.PerlElementPatterns;
49
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlScalarValue;
50
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlValue;
51
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlValueResolver;
52
import com.perl5.lang.perl.idea.configuration.settings.PerlSharedSettings;
53
import com.perl5.lang.perl.idea.manipulators.PerlNamespaceElementManipulator;
54
import com.perl5.lang.perl.idea.project.PerlDirectoryIndex;
55
import com.perl5.lang.perl.idea.project.PerlProjectManager;
56
import com.perl5.lang.perl.internals.PerlVersion;
57
import com.perl5.lang.perl.lexer.PerlElementTypes;
58
import com.perl5.lang.perl.psi.*;
59
import com.perl5.lang.perl.psi.impl.PerlFileImpl;
60
import com.perl5.lang.perl.psi.impl.PerlUseStatementElement;
61
import com.perl5.lang.perl.psi.stubs.globs.PerlGlobNamespaceStubIndex;
62
import com.perl5.lang.perl.psi.stubs.namespaces.PerlLightNamespaceDescendantsIndex;
63
import com.perl5.lang.perl.psi.stubs.namespaces.PerlLightNamespaceIndex;
64
import com.perl5.lang.perl.psi.stubs.namespaces.PerlNamespaceDescendantsIndex;
65
import com.perl5.lang.perl.psi.stubs.namespaces.PerlNamespaceIndex;
66
import com.perl5.lang.perl.psi.stubs.subsdefinitions.PerlCallableNamesIndex;
67
import com.perl5.lang.perl.psi.stubs.subsdefinitions.PerlLightCallableNamesIndex;
68
import com.perl5.lang.perl.psi.utils.PerlPsiUtil;
69
import org.jetbrains.annotations.*;
70

71
import java.io.File;
72
import java.util.*;
73
import java.util.concurrent.ConcurrentHashMap;
74
import java.util.function.Function;
75
import java.util.regex.Pattern;
76

77
import static com.perl5.lang.perl.util.PerlCorePackages.*;
78

79

80
public final class PerlPackageUtil implements PerlElementTypes {
81
  public static final String PROFILER_MODULE = "Devel::NYTProf";
82
  public static final String DEBUGGER_MODULE = "Devel::Camelcadedb";
83
  public static final String COVERAGE_MODULE = "Devel::Cover";
84
  public static final String TEST_HARNESS_MODULE = "Test::Harness";
85
  public static final String TAP_FORMATTER_MODULE = "TAP::Formatter::Camelcade";
86
  public static final String JSON_MODULE = "JSON";
87
  public static final String ADJUST_BLOCK = "ADJUST";
88

89
  private PerlPackageUtil() {
90
  }
91

92
  public static final String NAMESPACE_SEPARATOR = "::";
93
  public static final String DEREFERENCE_OPERATOR = "->";
94
  public static final char NAMESPACE_SEPARATOR_LEGACY = '\'';
95

96
  public static final String NAMESPACE_ANY = "*";
97
  public static final AtomicNotNullLazyValue<PerlValue> NAMESPACE_ANY_VALUE =
1✔
98
    AtomicNotNullLazyValue.createValue(() -> PerlScalarValue.create(NAMESPACE_ANY));
1✔
99

100
  public static final String __PACKAGE__ = "__PACKAGE__";
101
  public static final String PACKAGE_CARP = "Carp";
102
  public static final String PACKAGE_SCALAR_UTIL = "Scalar::Util";
103
  public static final @NonNls String PACKAGE_MOO = "Moo";
104
  public static final @NonNls String MOO_ROLE = PACKAGE_MOO + NAMESPACE_SEPARATOR + "Role";
105
  public static final String PACKAGE_CLASS_MOP_MIXIN = "Class::MOP::Mixin";
106
  public static final String PACKAGE_MOOSE = "Moose";
107
  public static final String PACKAGE_MOOSE_BASE = "Moose" + NAMESPACE_SEPARATOR;
108
  public static final String PACKAGE_MOOSE_X = PACKAGE_MOOSE + "X";
109
  public static final String PACKAGE_MOOSE_X_BASE = PACKAGE_MOOSE_X + NAMESPACE_SEPARATOR;
110
  public static final String PACKAGE_MOOSE_OBJECT = PACKAGE_MOOSE_BASE + "Object";
111
  public static final String PACKAGE_MOOSE_ROLE = PACKAGE_MOOSE_BASE + "Role";
112
  public static final String PACKAGE_MOOSE_UTIL_TYPE_CONSTRAINTS = PACKAGE_MOOSE_BASE + "Util::TypeConstraints";
113
  public static final String PACKAGE_MOOSE_X_TYPES_CHECKEDUTILEXPORTS = PACKAGE_MOOSE_X_BASE + "Types::CheckedUtilExports";
114
  public static final String PACKAGE_MOOSE_X_CLASSATTRIBUTE = PACKAGE_MOOSE_X_BASE + "ClassAttribute";
115
  public static final String PACKAGE_MOOSE_X_ROLE_PARAMETRIZIED = PACKAGE_MOOSE_X_BASE + "Role::Parameterized";
116
  public static final String PACKAGE_VARS = "vars";
117

118
  public static final Pattern PACKAGE_SEPARATOR_RE = Pattern.compile(NAMESPACE_SEPARATOR + "|" + NAMESPACE_SEPARATOR_LEGACY);
1✔
119
  public static final Pattern PACKAGE_SEPARATOR_TAIL_RE = Pattern.compile("(" + NAMESPACE_SEPARATOR + "|" +
1✔
120
                                                                          NAMESPACE_SEPARATOR_LEGACY + ")$");
121

122
  public static final Set<String> CORE_PACKAGES_ALL = new HashSet<>();
1✔
123

124
  public static final String SUPER_NAMESPACE = "SUPER";
125
  public static final String SUPER_NAMESPACE_FULL = SUPER_NAMESPACE + NAMESPACE_SEPARATOR;
126

127
  public static final String MAIN_NAMESPACE_NAME = "main";
128
  public static final String MAIN_NAMESPACE_FULL = MAIN_NAMESPACE_NAME + NAMESPACE_SEPARATOR;
129
  public static final String MAIN_NAMESPACE_SHORT = NAMESPACE_SEPARATOR;
130

131
  public static final String UNIVERSAL_NAMESPACE = "UNIVERSAL";
132

133
  public static final String CORE_NAMESPACE = "CORE";
134
  public static final String CORE_NAMESPACE_FULL = CORE_NAMESPACE + NAMESPACE_SEPARATOR;
135
  public static final String CORE_GLOBAL_NAMESPACE = CORE_NAMESPACE_FULL + "GLOBAL";
136
  public static final String DEFAULT_LIB_DIR = "lib";
137
  public static final String DEFAULT_TEST_DIR = "t";
138

139
  private static final Map<String, String> CANONICAL_NAMES_CACHE = new ConcurrentHashMap<>();
1✔
140
  private static final Map<String, String> PATH_TO_PACKAGE_NAME_MAP = new ConcurrentHashMap<>();
1✔
141
  public static final String FUNCTION_PARAMETERS = "Function::Parameters";
142

143
  static {
144
    CORE_PACKAGES_ALL.addAll(CORE_PACKAGES);
1✔
145
    CORE_PACKAGES_ALL.addAll(CORE_PACKAGES_PRAGMAS);
1✔
146
    CORE_PACKAGES_ALL.addAll(CORE_PACKAGES_DEPRECATED);
1✔
147
  }
1✔
148

149
  /**
150
   * @return true if package name is in CoreList
151
   */
152
  @Contract("null -> false")
153
  public static boolean isBuiltIn(@Nullable String packageName) {
154
    return packageName != null && CORE_PACKAGES_ALL.contains(getCanonicalNamespaceName(packageName));
1✔
155
  }
156

157
  /**
158
   * Checks if package is pragma
159
   *
160
   * @param pacakgeName package name
161
   * @return result
162
   */
163
  public static boolean isPragma(String pacakgeName) {
164
    return CORE_PACKAGES_PRAGMAS.contains(getCanonicalNamespaceName(pacakgeName));
1✔
165
  }
166

167
  /**
168
   * Checks if package is deprecated in the core or somewhere in the {@code scope}
169
   */
170
  public static boolean isDeprecated(@NotNull Project project,
171
                                     @NotNull GlobalSearchScope searchScope,
172
                                     @NotNull String packageCanonicalName) {
173
    return CORE_PACKAGES_DEPRECATED.contains(packageCanonicalName) ||
1✔
174
           !processNamespaces(packageCanonicalName, project, searchScope, it -> !it.isDeprecated());
1✔
175
  }
176

177
  @Contract("null->false")
178
  public static boolean isSUPER(@Nullable String packageName) {
179
    return SUPER_NAMESPACE.equals(packageName);
1✔
180
  }
181

182
  public static boolean isMain(String packageName) {
183
    return MAIN_NAMESPACE_NAME.equals(packageName);
1✔
184
  }
185

186
  public static boolean isCORE(String packageName) {
187
    return CORE_NAMESPACE.equals(packageName);
1✔
188
  }
189

190
  public static boolean isUNIVERSAL(String packageName) {
191
    return UNIVERSAL_NAMESPACE.equals(packageName);
1✔
192
  }
193

194
  public static @NotNull String join(@NotNull String... chunks) {
195
    return StringUtil.join(chunks, NAMESPACE_SEPARATOR);
1✔
196
  }
197

198
  /**
199
   * Make canonical package name.
200
   *
201
   * @param name package name
202
   * @return canonical package name
203
   */
204
  public static String getCanonicalNamespaceName(@NotNull String name) {
205
    String canonicalName = getCanonicalName(name);
1✔
206
    return StringUtil.startsWith(canonicalName, MAIN_NAMESPACE_FULL) ?
1✔
207
           canonicalName.substring(MAIN_NAMESPACE_FULL.length()) : canonicalName;
1✔
208
  }
209

210
  public static @NotNull String getCanonicalName(@NotNull String name) {
211
    String newName;
212

213
    if ((newName = CANONICAL_NAMES_CACHE.get(name)) != null) {
1✔
214
      return newName;
1✔
215
    }
216

217
    String originalName = name;
1✔
218

219
    name = PACKAGE_SEPARATOR_TAIL_RE.matcher(name).replaceFirst("");
1✔
220

221
    String[] chunks = PACKAGE_SEPARATOR_RE.split(name, -1);
1✔
222

223
    if (chunks.length > 0 && chunks[0].isEmpty())    // implicit main
1✔
224
    {
225
      chunks[0] = MAIN_NAMESPACE_NAME;
1✔
226
    }
227

228
    newName = StringUtil.join(chunks, NAMESPACE_SEPARATOR);
1✔
229

230
    CANONICAL_NAMES_CACHE.put(originalName, newName);
1✔
231

232
    return newName;
1✔
233
  }
234

235
  public static @NotNull PerlValue getContextType(@Nullable PsiElement element) {
236
    return PerlScalarValue.create(getContextNamespaceName(element));
1✔
237
  }
238

239
  public static @NotNull List<String> split(@Nullable String packageName) {
240
    return packageName == null ? Collections.emptyList() : StringUtil.split(getCanonicalNamespaceName(packageName), NAMESPACE_SEPARATOR);
1✔
241
  }
242

243
  @Contract("null -> null")
244
  public static @Nullable @NlsSafe Pair<@Nullable String, @Nullable String> splitNames(@Nullable @NlsSafe String fqn) {
245
    if (fqn == null || fqn.isEmpty()) {
1✔
246
      return null;
1✔
247
    }
248
    if (fqn.endsWith(NAMESPACE_SEPARATOR)) {
1✔
249
      return Pair.create(getCanonicalName(fqn), null);
1✔
250
    }
251
    var sepIndex = fqn.lastIndexOf(NAMESPACE_SEPARATOR);
1✔
252
    if (sepIndex < 0) {
1✔
253
      return Pair.create(null, fqn);
×
254
    }
255
    if (sepIndex == 0) {
1✔
256
      return Pair.create(MAIN_NAMESPACE_NAME, fqn.substring(NAMESPACE_SEPARATOR.length()));
1✔
257
    }
258
    return Pair.create(fqn.substring(0, sepIndex), fqn.substring(sepIndex + NAMESPACE_SEPARATOR.length()));
1✔
259
  }
260

261
  /**
262
   * Searching of namespace element is in. If no explicit namespaces defined, main is returned
263
   *
264
   * @param element psi element to find definition for
265
   * @return canonical package name
266
   */
267
  @Contract("null->null;!null->!null")
268
  public static String getContextNamespaceName(@Nullable PsiElement element) {
269
    if (element == null) {
1✔
270
      return null;
1✔
271
    }
272
    PerlNamespaceDefinitionElement namespaceDefinition = getContainingNamespace(element);
1✔
273

274
    if (namespaceDefinition != null &&
1✔
275
        namespaceDefinition.getNamespaceName() != null) // checking that definition is valid and got namespace
1✔
276
    {
277
      String name = namespaceDefinition.getNamespaceName();
1✔
278
      assert name != null;
1✔
279
      return name;
1✔
280
    }
281

282
    // default value
283
    PsiFile file = element.getContainingFile();
×
284
    if (file instanceof PerlFileImpl perlFile) {
×
285
      PsiElement contextParent = file.getContext();
×
286
      PsiElement realParent = file.getParent();
×
287

288
      if (contextParent != null && !contextParent.equals(realParent)) {
×
289
        return getContextNamespaceName(contextParent);
×
290
      }
291

292
      return perlFile.getNamespaceName();
×
293
    }
294
    else {
295
      return MAIN_NAMESPACE_NAME;
×
296
    }
297
  }
298

299
  public static @Nullable PerlNamespaceDefinitionElement getNamespaceContainerForElement(@Nullable PsiElement element) {
300
    if (element == null) {
1✔
301
      return null;
×
302
    }
303

304
    PerlNamespaceDefinitionElement namespaceContainer = PsiTreeUtil.getParentOfType(element, PerlNamespaceDefinitionElement.class);
1✔
305

306
    if (namespaceContainer instanceof PerlFileImpl) {
1✔
307
      PsiElement contextParent = namespaceContainer.getContext();
1✔
308
      if (contextParent != null && !contextParent.equals(namespaceContainer.getParent())) {
1✔
309
        return getNamespaceContainerForElement(contextParent);
1✔
310
      }
311
    }
312
    return namespaceContainer;
1✔
313
  }
314

315
  public static PerlNamespaceDefinitionElement getContainingNamespace(PsiElement element) {
316
    return PsiTreeUtil.getStubOrPsiParentOfType(element, PerlNamespaceDefinitionElement.class);
1✔
317
  }
318

319
  public static @NotNull List<PerlNamespaceDefinitionElement> collectNamespaceDefinitions(@NotNull Project project,
320
                                                                                          @NotNull List<String> packageNames) {
321
    ArrayList<PerlNamespaceDefinitionElement> namespaceDefinitions = new ArrayList<>();
1✔
322
    for (String packageName : packageNames) {
1✔
323
      Collection<PerlNamespaceDefinitionElement> list =
1✔
324
        getNamespaceDefinitions(project, GlobalSearchScope.projectScope(project), packageName);
1✔
325

326
      if (list.isEmpty()) {
1✔
327
        list = getNamespaceDefinitions(project, GlobalSearchScope.allScope(project), packageName);
1✔
328
      }
329

330
      namespaceDefinitions.addAll(list);
1✔
331
    }
1✔
332
    return namespaceDefinitions;
1✔
333
  }
334

335
  /**
336
   * Searching project files for namespace definitions by specific package name
337
   *
338
   * @see #processNamespaces(String, Project, GlobalSearchScope, Processor)
339
   */
340
  public static Collection<PerlNamespaceDefinitionElement> getNamespaceDefinitions(@NotNull Project project,
341
                                                                                   @NotNull GlobalSearchScope scope,
342
                                                                                   @NotNull String canonicalPackageName) {
343
    List<PerlNamespaceDefinitionElement> result = new ArrayList<>();
1✔
344
    processNamespaces(canonicalPackageName, project, scope, result::add);
1✔
345
    return result;
1✔
346
  }
347

348
  /**
349
   * Returns list of defined package names
350
   *
351
   * @param project project to search in
352
   * @return collection of package names
353
   */
354
  public static Collection<String> getKnownNamespaceNames(Project project) {
355
    Collection<String> keys = PerlNamespaceIndex.getInstance().getAllNames(project);
1✔
356
    keys.addAll(PerlLightNamespaceIndex.getInstance().getAllNames(project));
1✔
357
    return keys;
1✔
358
  }
359

360
  public static @NotNull Collection<String> getKnownNamespaceNames(@NotNull GlobalSearchScope scope) {
361
    Collection<String> keys = PerlNamespaceIndex.getInstance().getAllNames(scope);
1✔
362
    keys.addAll(PerlLightNamespaceIndex.getInstance().getAllNames(scope));
1✔
363
    return keys;
1✔
364
  }
365

366
  /**
367
   * Processes all global packages names with specific processor
368
   *
369
   * @param scope     search scope
370
   * @param processor string processor for suitable strings
371
   * @return collection of constants names
372
   */
373
  @SuppressWarnings("UnusedReturnValue")
374
  public static boolean processNamespaces(@NotNull String packageName,
375
                                          @NotNull Project project,
376
                                          @NotNull GlobalSearchScope scope,
377
                                          @NotNull Processor<? super PerlNamespaceDefinitionElement> processor) {
378
    return PerlNamespaceIndex.getInstance().processElements(project, packageName, scope, processor) &&
1✔
379
           PerlLightNamespaceIndex.getInstance().processLightElements(project, packageName, scope, processor);
1✔
380
  }
381

382
  @SuppressWarnings("UnusedReturnValue")
383
  public static boolean processChildNamespaces(@NotNull String parentPackageName,
384
                                               @NotNull Project project,
385
                                               @NotNull GlobalSearchScope scope,
386
                                               @NotNull Processor<? super PerlNamespaceDefinitionElement> processor) {
387
    return PerlNamespaceDescendantsIndex.getInstance().processElements(project, parentPackageName, scope, processor) &&
1✔
388
           PerlLightNamespaceDescendantsIndex.getInstance().processLightElements(project, parentPackageName, scope, processor);
1✔
389
  }
390

391

392
  /**
393
   * Returns list of derived classes
394
   *
395
   * @param project project to search in
396
   * @return collection of definitions
397
   */
398
  public static @NotNull List<PerlNamespaceDefinitionElement> getChildNamespaces(@NotNull Project project,
399
                                                                                 @Nullable String packageName) {
400
    if (StringUtil.isEmpty(packageName)) {
1✔
401
      return Collections.emptyList();
×
402
    }
403
    List<PerlNamespaceDefinitionElement> list = getChildNamespaces(project, packageName, GlobalSearchScope.projectScope(project));
1✔
404
    if (list.isEmpty()) {
1✔
405
      list = getChildNamespaces(project, packageName, GlobalSearchScope.allScope(project));
1✔
406
    }
407
    return list;
1✔
408
  }
409

410
  public static @NotNull List<PerlNamespaceDefinitionElement> getChildNamespaces(@NotNull Project project,
411
                                                                                 @NotNull String packageName,
412
                                                                                 @NotNull GlobalSearchScope scope) {
413
    ArrayList<PerlNamespaceDefinitionElement> elements = new ArrayList<>();
1✔
414
    processChildNamespaces(packageName, project, scope, elements::add);
1✔
415
    return elements;
1✔
416
  }
417

418
  /**
419
   * Builds package path from packageName Foo::Bar => Foo/Bar.pm
420
   *
421
   * @param packageName canonical package name
422
   * @return package path
423
   */
424
  public static String getPackagePathByName(String packageName) {
425
    return StringUtil.join(packageName.split(":+"), "/") + "." + PerlFileTypePackage.EXTENSION;
1✔
426
  }
427

428
  /**
429
   * Translates package relative name to the package name Foo/Bar.pm => Foo::Bar
430
   *
431
   * @param packagePath package relative path
432
   * @return canonical package name
433
   */
434
  public static String getPackageNameByPath(final String packagePath) {
435
    String result = PATH_TO_PACKAGE_NAME_MAP.get(packagePath);
1✔
436

437
    if (result == null) {
1✔
438
      String path = packagePath.replace("\\", "/");
1✔
439
      result = getCanonicalNamespaceName(StringUtil.join(path.replaceFirst("\\.pm$", "").split("/"), NAMESPACE_SEPARATOR));
1✔
440
      PATH_TO_PACKAGE_NAME_MAP.put(packagePath, result);
1✔
441
    }
442
    return result;
1✔
443
  }
444

445
  public static boolean processPackageFilesForPsiElement(@NotNull PsiElement element,
446
                                                         @NotNull PairProcessor<? super String, ? super VirtualFile> processor) {
447
    return processIncFilesForPsiElement(
1✔
448
      element,
449
      (file, classRoot) -> {
450
        String relativePath = VfsUtilCore.getRelativePath(file, classRoot);
1✔
451
        String packageName = getPackageNameByPath(relativePath);
1✔
452
        return processor.process(packageName, file);
1✔
453
      },
454
      PerlFileTypePackage.INSTANCE)
455
      ;
456
  }
457

458
  @SuppressWarnings("UnusedReturnValue")
459
  public static boolean processIncFilesForPsiElement(@NotNull PsiElement element,
460
                                                     @NotNull ClassRootVirtualFileProcessor processor,
461
                                                     @NotNull FileType fileType) {
462
    List<VirtualFile> incDirsForPsiElement = getIncDirsForPsiElement(element);
1✔
463
    Project project = element.getProject();
1✔
464
    Function<VirtualFile, VirtualFile> rootsComputator = PerlDirectoryIndex.getInstance(project).createRootComputator(incDirsForPsiElement);
1✔
465
    return FileTypeIndex.processFiles(
1✔
466
      fileType,
467
      virtualFile -> {
468
        ProgressManager.checkCanceled();
1✔
469
        VirtualFile incDir = rootsComputator.apply(virtualFile);
1✔
470
        return incDir == null || processor.process(virtualFile, incDir);
1✔
471
      },
472
      GlobalSearchScope.allScope(project));
1✔
473
  }
474

475
  public static void processNotOverridedMethods(final PerlNamespaceDefinitionElement namespaceDefinition,
476
                                                Processor<? super PerlSubElement> processor) {
477
    if (namespaceDefinition != null) {
1✔
478
      PsiFile containingFile = namespaceDefinition.getContainingFile();
1✔
479
      String packageName = namespaceDefinition.getNamespaceName();
1✔
480
      if (packageName == null) {
1✔
481
        return;
×
482
      }
483

484
      Set<String> namesSet = new HashSet<>();
1✔
485
      // collecting overrided
486
      for (PerlSubDefinitionElement subDefinitionBase : PsiTreeUtil.findChildrenOfType(containingFile, PerlSubDefinitionElement.class)) {
1✔
487
        if (subDefinitionBase.isValid() && StringUtil.equals(packageName, subDefinitionBase.getNamespaceName())) {
1✔
488
          namesSet.add(subDefinitionBase.getSubName());
1✔
489
        }
490
      }
1✔
491

492
      processParentClassesSubs(
1✔
493
        namespaceDefinition,
494
        namesSet,
495
        new HashSet<>(),
496
        processor
497
      );
498
    }
499
  }
1✔
500

501
  public static void processParentClassesSubs(PerlNamespaceDefinitionElement childClass,
502
                                              Set<? super String> processedSubsNames,
503
                                              Set<? super PerlNamespaceDefinitionElement> recursionMap,
504
                                              Processor<? super PerlSubElement> processor
505
  ) {
506
    if (childClass == null || recursionMap.contains(childClass)) {
1✔
507
      return;
×
508
    }
509
    recursionMap.add(childClass);
1✔
510

511
    for (PerlNamespaceDefinitionElement parentNamespace : childClass.getParentNamespaceDefinitions()) {
1✔
512
      for (PsiElement subDefinitionBase : collectNamespaceSubs(parentNamespace)) {
1✔
513
        ProgressManager.checkCanceled();
1✔
514
        String subName = ((PerlSubElement)subDefinitionBase).getSubName();
1✔
515
        if (subDefinitionBase.isValid() &&
1✔
516
            ((PerlSubElement)subDefinitionBase).isMethod() &&
1✔
517
            !processedSubsNames.contains(subName)
1✔
518
          ) {
519
          processedSubsNames.add(subName);
1✔
520
          processor.process(((PerlSubElement)subDefinitionBase));
1✔
521
        }
522
      }
1✔
523
      processParentClassesSubs(
1✔
524
        parentNamespace,
525
        processedSubsNames,
526
        recursionMap,
527
        processor
528
      );
529
    }
1✔
530
  }
1✔
531

532
  public static List<PsiElement> collectNamespaceSubs(final @NotNull PsiElement namespace) {
533
    return CachedValuesManager.getCachedValue(
1✔
534
      namespace,
535
      () -> CachedValueProvider.Result
1✔
536
        .create(PerlPsiUtil.collectNamespaceMembers(namespace, PerlSubElement.class), namespace));
1✔
537
  }
538

539
  public static @Nullable PsiFile getPackagePsiFileByPackageName(Project project, String packageName) {
540
    VirtualFile packageVirtualFile = getPackageVirtualFileByPackageName(project, packageName);
1✔
541

542
    if (packageVirtualFile != null) {
1✔
543
      return PsiManager.getInstance(project).findFile(packageVirtualFile);
×
544
    }
545

546
    return null;
1✔
547
  }
548

549
  public static @Nullable VirtualFile getPackageVirtualFileByPackageName(Project project, String packageName) {
550
    if (StringUtil.isEmpty(packageName)) {
1✔
551
      return null;
×
552
    }
553

554
    String packagePath = getPackagePathByName(packageName);
1✔
555
    for (VirtualFile classRoot : PerlProjectManager.getInstance(project).getAllLibraryRoots()) {
1✔
556
      VirtualFile targetFile = classRoot.findFileByRelativePath(packagePath);
1✔
557
      if (targetFile != null) {
1✔
558
        return targetFile;
1✔
559
      }
560
    }
1✔
561
    return null;
1✔
562
  }
563

564
  /**
565
   * Resolving canonical package name to a psi file
566
   *
567
   * @param psiFile              base file
568
   * @param canonicalPackageName package name in canonical form
569
   * @return vartual file
570
   */
571
  public static @Nullable PsiFile resolvePackageNameToPsi(@NotNull PsiFile psiFile, String canonicalPackageName) {
572
    // resolves to a psi file
573
    return resolveRelativePathToPsi(psiFile, getPackagePathByName(canonicalPackageName));
1✔
574
  }
575

576
  /**
577
   * Resolving relative path to a psi file
578
   *
579
   * @param psiFile      base file
580
   * @param relativePath relative path
581
   * @return vartual file
582
   */
583
  public static @Nullable PsiFile resolveRelativePathToPsi(@NotNull PsiFile psiFile, String relativePath) {
584
    VirtualFile targetFile = resolveRelativePathToVirtualFile(psiFile, relativePath);
1✔
585

586
    if (targetFile != null && targetFile.exists()) {
1✔
587
      return PsiManager.getInstance(psiFile.getProject()).findFile(targetFile);
1✔
588
    }
589

590
    return null;
1✔
591
  }
592

593
  /**
594
   * Resolving relative path to a virtual file
595
   *
596
   * @param psiFile      base file
597
   * @param relativePath relative path
598
   * @return vartual file
599
   */
600
  public static @Nullable VirtualFile resolveRelativePathToVirtualFile(@NotNull PsiFile psiFile, String relativePath) {
601
    if (relativePath == null) {
1✔
UNCOV
602
      return null;
×
603
    }
604
    for (VirtualFile classRoot : getIncDirsForPsiElement(psiFile)) {
1✔
605
      if (classRoot == null) {
1✔
UNCOV
606
        continue;
×
607
      }
608
      VirtualFile targetFile = classRoot.findFileByRelativePath(relativePath);
1✔
609
      if (targetFile == null) {
1✔
610
        continue;
1✔
611
      }
612
      String foundRelativePath = VfsUtilCore.getRelativePath(targetFile, classRoot);
1✔
613

614
      if (StringUtil.isNotEmpty(foundRelativePath) && StringUtil.equals(foundRelativePath, relativePath)) {
1✔
615
        return targetFile;
1✔
616
      }
UNCOV
617
    }
×
618

619
    return null;
1✔
620
  }
621

622
  /**
623
   * Returns List of lib directories including class roots, current directory and use lib ones
624
   *
625
   * @param psiElement to resolve for
626
   * @return list of lib dirs
627
   */
628
  @VisibleForTesting
629
  public static @NotNull List<VirtualFile> getIncDirsForPsiElement(@NotNull PsiElement psiElement) {
630
    PsiFile psiFile = psiElement.getContainingFile().getOriginalFile();
1✔
631
    List<VirtualFile> result = new ArrayList<>();
1✔
632

633
    for (PerlUseStatementElement useStatement : PsiTreeUtil.findChildrenOfType(psiFile, PerlUseStatementElement.class)) {
1✔
634
      PerlPackageProcessor packageProcessor = useStatement.getPackageProcessor();
1✔
635
      if (packageProcessor instanceof PerlLibProvider perlLibProvider) {
1✔
UNCOV
636
        perlLibProvider.addLibDirs(useStatement, result);
×
637
      }
638
    }
1✔
639

640
    // classpath
641
    result.addAll(PerlProjectManager.getInstance(psiElement.getProject()).getAllLibraryRoots());
1✔
642

643
    // current dir
644
    if (PerlSharedSettings.getInstance(psiFile.getProject()).getTargetPerlVersion().lesserThan(PerlVersion.V5_26)) {
1✔
645
      VirtualFile virtualFile = psiFile.getVirtualFile();
1✔
646
      if (virtualFile != null && virtualFile.getParent() != null) {
1✔
647
        result.add(virtualFile.getParent());
1✔
648
      }
649
    }
650

651
    return result;
1✔
652
  }
653

654
  /**
655
   * Checks if sequence looks like a fqn
656
   *
657
   * @param text sequence to check
658
   * @return true if it's foo::bar
659
   */
660
  public static boolean isFullQualifiedName(String text) {
661
    return text.length() > 1 && StringUtil.containsAnyChar(text, ":'");
1✔
662
  }
663

664
  /**
665
   * Returns qualified ranges for identifier, like variable name or sub_name_qualified
666
   *
667
   * @param text token text
668
   * @return pair of two ranges; first will be null if it's not qualified name
669
   */
670
  public static @NotNull Pair<TextRange, TextRange> getQualifiedRanges(@NotNull CharSequence text) {
671
    if (text.length() == 1) {
1✔
672
      return Pair.create(null, TextRange.create(0, 1));
1✔
673
    }
674

675
    int lastSeparatorOffset = StringUtil.lastIndexOfAny(text, ":'");
1✔
676

677
    if (lastSeparatorOffset < 0) {
1✔
678
      return Pair.create(null, TextRange.create(0, text.length()));
1✔
679
    }
680

681
    TextRange packageRange = PerlNamespaceElementManipulator.getRangeInString(text.subSequence(0, lastSeparatorOffset));
1✔
682

683
    TextRange nameRange;
684

685
    if (++lastSeparatorOffset < text.length()) {
1✔
686
      nameRange = TextRange.create(lastSeparatorOffset, text.length());
1✔
687
    }
688
    else {
689
      nameRange = TextRange.EMPTY_RANGE;
1✔
690
    }
691
    return Pair.create(packageRange, nameRange);
1✔
692
  }
693

694
  /**
695
   * @return the expected value of the {@code $self} passed to the method. This is either context value or value from the self hinter
696
   */
697
  public static @NotNull PerlValue getExpectedSelfValue(@NotNull PsiElement psiElement) {
698
    PsiElement run = psiElement;
1✔
699
    while (true) {
700
      PerlSelfHinterElement selfHinter = PsiTreeUtil.getParentOfType(run, PerlSelfHinterElement.class);
1✔
701
      if (selfHinter == null) {
1✔
702
        break;
1✔
703
      }
704
      PerlValue hintedType = selfHinter.getSelfType();
1✔
705
      if (!hintedType.isUnknown()) {
1✔
706
        return hintedType;
1✔
707
      }
708
      run = selfHinter;
1✔
709
    }
1✔
710
    return PerlScalarValue.create(getContextNamespaceName(psiElement));
1✔
711
  }
712

713
  /**
714
   * @return list of parent namespaces defined by different syntax constructions in the sub-tree of the {@code namespaceDefinition}
715
   */
716
  public static @NotNull List<String> collectParentNamespaceNamesFromPsi(@NotNull PerlNamespaceDefinitionElement namespaceDefinition) {
717
    String namespaceName = namespaceDefinition.getNamespaceName();
1✔
718
    if (StringUtil.isEmpty(namespaceName)) {
1✔
UNCOV
719
      return Collections.emptyList();
×
720
    }
721
    ParentNamespacesNamesCollector collector = new ParentNamespacesNamesCollector(namespaceName);
1✔
722
    PerlPsiUtil.processNamespaceStatements(namespaceDefinition, collector);
1✔
723
    collector.applyRunTimeModifiers();
1✔
724
    return collector.getParentNamespaces();
1✔
725
  }
726

727
  @Contract("null->null")
728
  public static @Nullable VirtualFile getClosestIncRoot(@Nullable PsiFile psiFile) {
729
    return psiFile == null ? null : getClosestIncRoot(psiFile.getProject(), PsiUtilCore.getVirtualFile(psiFile));
1✔
730
  }
731

732
  /**
733
   * @return innermost @INC root for a file
734
   * @apiNote this method may work wrong, because it is not accounts for dynamic lib paths, e.g. use lib
735
   */
736
  @Contract("_,null->null")
737
  public static @Nullable VirtualFile getClosestIncRoot(@NotNull Project project, @Nullable VirtualFile file) {
738
    return PerlDirectoryIndex.getInstance(project).getRoot(file);
1✔
739
  }
740

741
  /**
742
   * @return innermost @INC root for a file by path
743
   * @apiNote this method may work wrong, because it is not accounts for dynamic lib paths, e.g. use lib
744
   */
745
  public static @Nullable VirtualFile getClosestIncRoot(@NotNull Project project, @NotNull String filePath) {
UNCOV
746
    return getClosestIncRoot(project, VfsUtil.findFileByIoFile(new File(filePath), false));
×
747
  }
748

749
  public static boolean processCallables(@NotNull Project project,
750
                                         @NotNull GlobalSearchScope searchScope,
751
                                         @NotNull String canonicalName,
752
                                         @NotNull Processor<? super PerlCallableElement> processor) {
753
    if (!PerlSubUtil.processSubDefinitions(project, canonicalName, searchScope, processor)) {
1✔
754
      return false;
1✔
755
    }
756
    if (!PerlSubUtil.processSubDeclarations(project, canonicalName, searchScope, processor)) {
1✔
757
      return false;
1✔
758
    }
759
    for (PerlGlobVariableElement target : PerlGlobUtil.getGlobsDefinitions(project, canonicalName, searchScope)) {
1✔
760
      if (!processor.process(target)) {
1✔
UNCOV
761
        return false;
×
762
      }
763
    }
1✔
764
    return true;
1✔
765
  }
766

767
  public static boolean processCallablesInNamespace(@NotNull Project project,
768
                                                    @NotNull GlobalSearchScope searchScope,
769
                                                    @NotNull String packageName,
770
                                                    @NotNull Processor<? super PerlCallableElement> processor) {
771
    return PerlSubUtil.processRelatedSubsInPackage(project, searchScope, packageName, processor) &&
1✔
772
           PerlGlobNamespaceStubIndex.getInstance().processElements(project, packageName, searchScope, processor);
1✔
773
  }
774

775
  @SuppressWarnings("UnusedReturnValue")
776
  public static boolean processCallablesNamespaceNames(@NotNull PerlValueResolver resolver,
777
                                                       @NotNull String callableName,
778
                                                       @NotNull Processor<? super PerlCallableElement> processor) {
779
    var project = resolver.getProject();
1✔
780
    return
1✔
781
      PerlCallableNamesIndex.getInstance().processElements(project, callableName, resolver.getResolveScope(), processor) &&
1✔
782
      PerlLightCallableNamesIndex.getInstance().processLightElements(project, callableName, resolver.getResolveScope(), processor);
1✔
783
  }
784

785
  public interface ClassRootVirtualFileProcessor {
786
    boolean process(VirtualFile file, VirtualFile classRoot);
787
  }
788

789
  public static class ParentNamespacesNamesCollector implements Processor<PsiElement> {
790
    private final @NotNull List<String> myParentNamespaces = new SmartList<>();
1✔
791
    private final @NotNull List<PerlRuntimeParentsProvider> runtimeModifiers = new SmartList<>();
1✔
792
    private final @NotNull String myNamespaceName;
793

794
    public ParentNamespacesNamesCollector(@NotNull String namespaceName) {
1✔
795
      myNamespaceName = namespaceName;
1✔
796
    }
1✔
797

798
    @Override
799
    public boolean process(PsiElement element) {
800
      if (element instanceof PerlUseStatementElement useStatementElement) {
1✔
801
        PerlPackageProcessor processor = useStatementElement.getPackageProcessor();
1✔
802
        if (processor instanceof PerlPackageParentsProvider packageParentsProvider) {
1✔
803
          packageParentsProvider.changeParentsList(useStatementElement, myParentNamespaces);
1✔
804
        }
805
      }
1✔
806
      else if (element instanceof PerlRuntimeParentsProvider runtimeParentsProvider) {
1✔
807
        runtimeModifiers.add(runtimeParentsProvider);
1✔
808
      }
809
      else if (element.getFirstChild() instanceof PerlRuntimeParentsProvider runtimeParentsProvider) {
1✔
810
        runtimeModifiers.add(runtimeParentsProvider);
1✔
811
      }
812
      else if (PerlElementPatterns.ISA_ASSIGN_STATEMENT.accepts(element)) {
1✔
813
        PsiElement assignExpr = element.getFirstChild();
1✔
814
        if (assignExpr instanceof PsiPerlAssignExpr) {
1✔
815
          PsiPerlArrayVariable variable = PsiTreeUtil.findChildOfType(element, PsiPerlArrayVariable.class);
1✔
816

817
          if (variable != null && StringUtil.equals("ISA", variable.getName())) {
1✔
818
            PsiElement rightSide = assignExpr.getLastChild();
1✔
819
            if (rightSide != null) {
1✔
820
              String explicitPackageName = variable.getExplicitNamespaceName();
1✔
821
              if (explicitPackageName == null || StringUtil.equals(explicitPackageName, myNamespaceName)) {
1✔
822
                runtimeModifiers.add(new PerlRuntimeParentsProviderFromArray(assignExpr.getLastChild()));
1✔
823
              }
824
            }
825
          }
826
        }
827
      }
828

829
      return true;
1✔
830
    }
831

832
    public void applyRunTimeModifiers() {
833
      for (PerlRuntimeParentsProvider provider : runtimeModifiers) {
1✔
834
        provider.changeParentsList(myParentNamespaces);
1✔
835
      }
1✔
836
    }
1✔
837

838
    public @NotNull List<String> getParentNamespaces() {
839
      return myParentNamespaces;
1✔
840
    }
841
  }
842
}
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