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

Camelcade / Perl5-IDEA / #525521519

21 Apr 2025 01:57PM UTC coverage: 82.17% (+0.01%) from 82.156%
#525521519

push

github

hurricup
CAMELCADE-22634 Cleanup

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

102 existing lines in 15 files now uncovered.

30868 of 37566 relevant lines covered (82.17%)

0.82 hits per line

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

91.54
/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✔
UNCOV
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) {
×
UNCOV
285
      PsiElement contextParent = file.getContext();
×
286
      PsiElement realParent = file.getParent();
×
287

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

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

299
  public static @Nullable PerlNamespaceDefinitionElement getNamespaceContainerForElement(@Nullable PsiElement element) {
300
    if (element == null) {
1✔
UNCOV
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<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✔
UNCOV
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<String, 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<PerlSubElement> processor) {
477
    if (namespaceDefinition != null) {
1✔
478
      PsiFile containingFile = namespaceDefinition.getContainingFile();
1✔
479
      String packageName = namespaceDefinition.getNamespaceName();
1✔
480
      if (packageName == null) {
1✔
UNCOV
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<String> processedSubsNames,
503
                                              Set<PerlNamespaceDefinitionElement> recursionMap,
504
                                              Processor<PerlSubElement> processor
505
  ) {
506
    if (childClass == null || recursionMap.contains(childClass)) {
1✔
UNCOV
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✔
UNCOV
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✔
UNCOV
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 canonical package to a virtual file
578
   *
579
   * @param psiFile              base file
580
   * @param canonicalPackageName package name in canonical form
581
   * @return vartual file
582
   */
583
  public static @Nullable VirtualFile resolvePackageNameToVirtualFile(@NotNull PsiFile psiFile, String canonicalPackageName) {
584
    // resolves to a psi file
UNCOV
585
    return resolveRelativePathToVirtualFile(psiFile, getPackagePathByName(canonicalPackageName));
×
586
  }
587

588
  /**
589
   * Resolving relative path to a psi file
590
   *
591
   * @param psiFile      base file
592
   * @param relativePath relative path
593
   * @return vartual file
594
   */
595
  public static @Nullable PsiFile resolveRelativePathToPsi(@NotNull PsiFile psiFile, String relativePath) {
596
    VirtualFile targetFile = resolveRelativePathToVirtualFile(psiFile, relativePath);
1✔
597

598
    if (targetFile != null && targetFile.exists()) {
1✔
599
      return PsiManager.getInstance(psiFile.getProject()).findFile(targetFile);
1✔
600
    }
601

602
    return null;
1✔
603
  }
604

605
  /**
606
   * Resolving relative path to a virtual file
607
   *
608
   * @param psiFile      base file
609
   * @param relativePath relative path
610
   * @return vartual file
611
   */
612
  public static @Nullable VirtualFile resolveRelativePathToVirtualFile(@NotNull PsiFile psiFile, String relativePath) {
613
    if (relativePath == null) {
1✔
UNCOV
614
      return null;
×
615
    }
616
    for (VirtualFile classRoot : getIncDirsForPsiElement(psiFile)) {
1✔
617
      if (classRoot == null) {
1✔
UNCOV
618
        continue;
×
619
      }
620
      VirtualFile targetFile = classRoot.findFileByRelativePath(relativePath);
1✔
621
      if (targetFile == null) {
1✔
622
        continue;
1✔
623
      }
624
      String foundRelativePath = VfsUtilCore.getRelativePath(targetFile, classRoot);
1✔
625

626
      if (StringUtil.isNotEmpty(foundRelativePath) && StringUtil.equals(foundRelativePath, relativePath)) {
1✔
627
        return targetFile;
1✔
628
      }
UNCOV
629
    }
×
630

631
    return null;
1✔
632
  }
633

634
  /**
635
   * Returns List of lib directories including class roots, current directory and use lib ones
636
   *
637
   * @param psiElement to resolve for
638
   * @return list of lib dirs
639
   */
640
  @VisibleForTesting
641
  public static @NotNull List<VirtualFile> getIncDirsForPsiElement(@NotNull PsiElement psiElement) {
642
    PsiFile psiFile = psiElement.getContainingFile().getOriginalFile();
1✔
643
    List<VirtualFile> result = new ArrayList<>();
1✔
644

645
    for (PerlUseStatementElement useStatement : PsiTreeUtil.findChildrenOfType(psiFile, PerlUseStatementElement.class)) {
1✔
646
      PerlPackageProcessor packageProcessor = useStatement.getPackageProcessor();
1✔
647
      if (packageProcessor instanceof PerlLibProvider perlLibProvider) {
1✔
UNCOV
648
        perlLibProvider.addLibDirs(useStatement, result);
×
649
      }
650
    }
1✔
651

652
    // classpath
653
    result.addAll(PerlProjectManager.getInstance(psiElement.getProject()).getAllLibraryRoots());
1✔
654

655
    // current dir
656
    if (PerlSharedSettings.getInstance(psiFile.getProject()).getTargetPerlVersion().lesserThan(PerlVersion.V5_26)) {
1✔
657
      VirtualFile virtualFile = psiFile.getVirtualFile();
1✔
658
      if (virtualFile != null && virtualFile.getParent() != null) {
1✔
659
        result.add(virtualFile.getParent());
1✔
660
      }
661
    }
662

663
    return result;
1✔
664
  }
665

666
  /**
667
   * Checks if sequence looks like a fqn
668
   *
669
   * @param text sequence to check
670
   * @return true if it's foo::bar
671
   */
672
  public static boolean isFullQualifiedName(String text) {
673
    return text.length() > 1 && StringUtil.containsAnyChar(text, ":'");
1✔
674
  }
675

676
  /**
677
   * Returns qualified ranges for identifier, like variable name or sub_name_qualified
678
   *
679
   * @param text token text
680
   * @return pair of two ranges; first will be null if it's not qualified name
681
   */
682
  public static @NotNull Pair<TextRange, TextRange> getQualifiedRanges(@NotNull CharSequence text) {
683
    if (text.length() == 1) {
1✔
684
      return Pair.create(null, TextRange.create(0, 1));
1✔
685
    }
686

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

689
    if (lastSeparatorOffset < 0) {
1✔
690
      return Pair.create(null, TextRange.create(0, text.length()));
1✔
691
    }
692

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

695
    TextRange nameRange;
696

697
    if (++lastSeparatorOffset < text.length()) {
1✔
698
      nameRange = TextRange.create(lastSeparatorOffset, text.length());
1✔
699
    }
700
    else {
701
      nameRange = TextRange.EMPTY_RANGE;
1✔
702
    }
703
    return Pair.create(packageRange, nameRange);
1✔
704
  }
705

706
  /**
707
   * @return the expected value of the {@code $self} passed to the method. This is either context value or value from the self hinter
708
   */
709
  public static @NotNull PerlValue getExpectedSelfValue(@NotNull PsiElement psiElement) {
710
    PsiElement run = psiElement;
1✔
711
    while (true) {
712
      PerlSelfHinterElement selfHinter = PsiTreeUtil.getParentOfType(run, PerlSelfHinterElement.class);
1✔
713
      if (selfHinter == null) {
1✔
714
        break;
1✔
715
      }
716
      PerlValue hintedType = selfHinter.getSelfType();
1✔
717
      if (!hintedType.isUnknown()) {
1✔
718
        return hintedType;
1✔
719
      }
720
      run = selfHinter;
1✔
721
    }
1✔
722
    return PerlScalarValue.create(getContextNamespaceName(psiElement));
1✔
723
  }
724

725
  /**
726
   * @return list of parent namespaces defined by different syntax constructions in the sub-tree of the {@code namespaceDefinition}
727
   */
728
  public static @NotNull List<String> collectParentNamespaceNamesFromPsi(@NotNull PerlNamespaceDefinitionElement namespaceDefinition) {
729
    String namespaceName = namespaceDefinition.getNamespaceName();
1✔
730
    if (StringUtil.isEmpty(namespaceName)) {
1✔
UNCOV
731
      return Collections.emptyList();
×
732
    }
733
    ParentNamespacesNamesCollector collector = new ParentNamespacesNamesCollector(namespaceName);
1✔
734
    PerlPsiUtil.processNamespaceStatements(namespaceDefinition, collector);
1✔
735
    collector.applyRunTimeModifiers();
1✔
736
    return collector.getParentNamespaces();
1✔
737
  }
738

739
  @Contract("null->null")
740
  public static @Nullable VirtualFile getClosestIncRoot(@Nullable PsiFile psiFile) {
741
    return psiFile == null ? null : getClosestIncRoot(psiFile.getProject(), PsiUtilCore.getVirtualFile(psiFile));
1✔
742
  }
743

744
  /**
745
   * @return innermost @INC root for a file
746
   * @apiNote this method may work wrong, because it is not accounts for dynamic lib paths, e.g. use lib
747
   */
748
  @Contract("_,null->null")
749
  public static @Nullable VirtualFile getClosestIncRoot(@NotNull Project project, @Nullable VirtualFile file) {
750
    return PerlDirectoryIndex.getInstance(project).getRoot(file);
1✔
751
  }
752

753
  /**
754
   * @return innermost @INC root for a file by path
755
   * @apiNote this method may work wrong, because it is not accounts for dynamic lib paths, e.g. use lib
756
   */
757
  public static @Nullable VirtualFile getClosestIncRoot(@NotNull Project project, @NotNull String filePath) {
UNCOV
758
    return getClosestIncRoot(project, VfsUtil.findFileByIoFile(new File(filePath), false));
×
759
  }
760

761
  public static boolean processCallables(@NotNull Project project,
762
                                         @NotNull GlobalSearchScope searchScope,
763
                                         @NotNull String canonicalName,
764
                                         @NotNull Processor<? super PerlCallableElement> processor) {
765
    if (!PerlSubUtil.processSubDefinitions(project, canonicalName, searchScope, processor::process)) {
1✔
766
      return false;
1✔
767
    }
768
    if (!PerlSubUtil.processSubDeclarations(project, canonicalName, searchScope, processor::process)) {
1✔
769
      return false;
1✔
770
    }
771
    for (PerlGlobVariableElement target : PerlGlobUtil.getGlobsDefinitions(project, canonicalName, searchScope)) {
1✔
772
      if (!processor.process(target)) {
1✔
UNCOV
773
        return false;
×
774
      }
775
    }
1✔
776
    return true;
1✔
777
  }
778

779
  public static boolean processCallablesInNamespace(@NotNull Project project,
780
                                                    @NotNull GlobalSearchScope searchScope,
781
                                                    @NotNull String packageName,
782
                                                    @NotNull Processor<? super PerlCallableElement> processor) {
783
    return PerlSubUtil.processRelatedSubsInPackage(project, searchScope, packageName, processor) &&
1✔
784
           PerlGlobNamespaceStubIndex.getInstance().processElements(project, packageName, searchScope, processor);
1✔
785
  }
786

787
  public static boolean processCallablesNamespaceNames(@NotNull PerlValueResolver resolver,
788
                                                       @NotNull String callableName,
789
                                                       @NotNull Processor<? super PerlCallableElement> processor) {
790
    var project = resolver.getProject();
1✔
791
    return
1✔
792
      PerlCallableNamesIndex.getInstance().processElements(project, callableName, resolver.getResolveScope(), processor) &&
1✔
793
      PerlLightCallableNamesIndex.getInstance().processLightElements(project, callableName, resolver.getResolveScope(), processor);
1✔
794
  }
795

796
  public interface ClassRootVirtualFileProcessor {
797
    boolean process(VirtualFile file, VirtualFile classRoot);
798
  }
799

800
  public static class ParentNamespacesNamesCollector implements Processor<PsiElement> {
801
    private final @NotNull List<String> myParentNamespaces = new SmartList<>();
1✔
802
    private final @NotNull List<PerlRuntimeParentsProvider> runtimeModifiers = new SmartList<>();
1✔
803
    private final @NotNull String myNamespaceName;
804

805
    public ParentNamespacesNamesCollector(@NotNull String namespaceName) {
1✔
806
      myNamespaceName = namespaceName;
1✔
807
    }
1✔
808

809
    @Override
810
    public boolean process(PsiElement element) {
811
      if (element instanceof PerlUseStatementElement useStatementElement) {
1✔
812
        PerlPackageProcessor processor = useStatementElement.getPackageProcessor();
1✔
813
        if (processor instanceof PerlPackageParentsProvider packageParentsProvider) {
1✔
814
          packageParentsProvider.changeParentsList((PerlUseStatementElement)element, myParentNamespaces);
1✔
815
        }
816
      }
1✔
817
      else if (element instanceof PerlRuntimeParentsProvider runtimeParentsProvider) {
1✔
818
        runtimeModifiers.add(runtimeParentsProvider);
1✔
819
      }
820
      else if (element.getFirstChild() instanceof PerlRuntimeParentsProvider runtimeParentsProvider) {
1✔
821
        runtimeModifiers.add(runtimeParentsProvider);
1✔
822
      }
823
      else if (PerlElementPatterns.ISA_ASSIGN_STATEMENT.accepts(element)) {
1✔
824
        PsiElement assignExpr = element.getFirstChild();
1✔
825
        if (assignExpr instanceof PsiPerlAssignExpr) {
1✔
826
          PsiPerlArrayVariable variable = PsiTreeUtil.findChildOfType(element, PsiPerlArrayVariable.class);
1✔
827

828
          if (variable != null && StringUtil.equals("ISA", variable.getName())) {
1✔
829
            PsiElement rightSide = assignExpr.getLastChild();
1✔
830
            if (rightSide != null) {
1✔
831
              String explicitPackageName = variable.getExplicitNamespaceName();
1✔
832
              if (explicitPackageName == null || StringUtil.equals(explicitPackageName, myNamespaceName)) {
1✔
833
                runtimeModifiers.add(new PerlRuntimeParentsProviderFromArray(assignExpr.getLastChild()));
1✔
834
              }
835
            }
836
          }
837
        }
838
      }
839

840
      return true;
1✔
841
    }
842

843
    public void applyRunTimeModifiers() {
844
      for (PerlRuntimeParentsProvider provider : runtimeModifiers) {
1✔
845
        provider.changeParentsList(myParentNamespaces);
1✔
846
      }
1✔
847
    }
1✔
848

849
    public @NotNull List<String> getParentNamespaces() {
850
      return myParentNamespaces;
1✔
851
    }
852
  }
853
}
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