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

Camelcade / Perl5-IDEA / #525521635

20 Jul 2025 03:45PM UTC coverage: 82.266% (-0.09%) from 82.355%
#525521635

push

github

hurricup
Build 252.23892.248

30936 of 37605 relevant lines covered (82.27%)

0.82 hits per line

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

91.37
/plugin/backend/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.text.StringUtil;
23
import com.intellij.openapi.vfs.VfsUtil;
24
import com.intellij.openapi.vfs.VfsUtilCore;
25
import com.intellij.openapi.vfs.VirtualFile;
26
import com.intellij.psi.PsiElement;
27
import com.intellij.psi.PsiFile;
28
import com.intellij.psi.PsiManager;
29
import com.intellij.psi.search.FileTypeIndex;
30
import com.intellij.psi.search.GlobalSearchScope;
31
import com.intellij.psi.util.CachedValueProvider;
32
import com.intellij.psi.util.CachedValuesManager;
33
import com.intellij.psi.util.PsiTreeUtil;
34
import com.intellij.psi.util.PsiUtilCore;
35
import com.intellij.util.PairProcessor;
36
import com.intellij.util.Processor;
37
import com.perl5.lang.perl.extensions.packageprocessor.PerlLibProvider;
38
import com.perl5.lang.perl.extensions.packageprocessor.PerlPackageProcessor;
39
import com.perl5.lang.perl.fileTypes.PerlFileTypePackage;
40
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlValueResolver;
41
import com.perl5.lang.perl.idea.configuration.settings.PerlSharedSettings;
42
import com.perl5.lang.perl.idea.project.PerlDirectoryIndex;
43
import com.perl5.lang.perl.idea.project.PerlProjectManager;
44
import com.perl5.lang.perl.internals.PerlVersion;
45
import com.perl5.lang.perl.lexer.PerlElementTypes;
46
import com.perl5.lang.perl.psi.*;
47
import com.perl5.lang.perl.psi.impl.PerlFileImpl;
48
import com.perl5.lang.perl.psi.impl.PerlUseStatementElement;
49
import com.perl5.lang.perl.psi.stubs.globs.PerlGlobNamespaceStubIndex;
50
import com.perl5.lang.perl.psi.stubs.namespaces.PerlLightNamespaceDescendantsIndex;
51
import com.perl5.lang.perl.psi.stubs.namespaces.PerlLightNamespaceIndex;
52
import com.perl5.lang.perl.psi.stubs.namespaces.PerlNamespaceDescendantsIndex;
53
import com.perl5.lang.perl.psi.stubs.namespaces.PerlNamespaceIndex;
54
import com.perl5.lang.perl.psi.stubs.subsdefinitions.PerlCallableNamesIndex;
55
import com.perl5.lang.perl.psi.stubs.subsdefinitions.PerlLightCallableNamesIndex;
56
import com.perl5.lang.perl.psi.utils.PerlPsiUtil;
57
import org.jetbrains.annotations.Contract;
58
import org.jetbrains.annotations.NotNull;
59
import org.jetbrains.annotations.Nullable;
60
import org.jetbrains.annotations.VisibleForTesting;
61

62
import java.io.File;
63
import java.util.*;
64
import java.util.function.Function;
65

66

67
public final class PerlPackageUtil implements PerlElementTypes {
68

69
  private PerlPackageUtil() {
70
  }
71

72
  public static @Nullable PerlNamespaceDefinitionElement getNamespaceContainerForElement(@Nullable PsiElement element) {
73
    if (element == null) {
1✔
74
      return null;
×
75
    }
76

77
    PerlNamespaceDefinitionElement namespaceContainer = PsiTreeUtil.getParentOfType(element, PerlNamespaceDefinitionElement.class);
1✔
78

79
    if (namespaceContainer instanceof PerlFileImpl) {
1✔
80
      PsiElement contextParent = namespaceContainer.getContext();
1✔
81
      if (contextParent != null && !contextParent.equals(namespaceContainer.getParent())) {
1✔
82
        return getNamespaceContainerForElement(contextParent);
1✔
83
      }
84
    }
85
    return namespaceContainer;
1✔
86
  }
87

88
  /**
89
   * Returns list of defined package names
90
   *
91
   * @param project project to search in
92
   * @return collection of package names
93
   */
94
  public static Collection<String> getKnownNamespaceNames(Project project) {
95
    Collection<String> keys = PerlNamespaceIndex.getInstance().getAllNames(project);
1✔
96
    keys.addAll(PerlLightNamespaceIndex.getInstance().getAllNames(project));
1✔
97
    return keys;
1✔
98
  }
99

100
  public static @NotNull Collection<String> getKnownNamespaceNames(@NotNull GlobalSearchScope scope) {
101
    Collection<String> keys = PerlNamespaceIndex.getInstance().getAllNames(scope);
1✔
102
    keys.addAll(PerlLightNamespaceIndex.getInstance().getAllNames(scope));
1✔
103
    return keys;
1✔
104
  }
105

106
  @SuppressWarnings("UnusedReturnValue")
107
  public static boolean processChildNamespaces(@NotNull String parentPackageName,
108
                                               @NotNull Project project,
109
                                               @NotNull GlobalSearchScope scope,
110
                                               @NotNull Processor<? super PerlNamespaceDefinitionElement> processor) {
111
    return PerlNamespaceDescendantsIndex.getInstance().processElements(project, parentPackageName, scope, processor) &&
1✔
112
           PerlLightNamespaceDescendantsIndex.getInstance().processLightElements(project, parentPackageName, scope, processor);
1✔
113
  }
114

115

116
  /**
117
   * Returns list of derived classes
118
   *
119
   * @param project project to search in
120
   * @return collection of definitions
121
   */
122
  public static @NotNull List<PerlNamespaceDefinitionElement> getChildNamespaces(@NotNull Project project,
123
                                                                                 @Nullable String packageName) {
124
    if (StringUtil.isEmpty(packageName)) {
1✔
125
      return Collections.emptyList();
×
126
    }
127
    List<PerlNamespaceDefinitionElement> list = getChildNamespaces(project, packageName, GlobalSearchScope.projectScope(project));
1✔
128
    if (list.isEmpty()) {
1✔
129
      list = getChildNamespaces(project, packageName, GlobalSearchScope.allScope(project));
1✔
130
    }
131
    return list;
1✔
132
  }
133

134
  public static @NotNull List<PerlNamespaceDefinitionElement> getChildNamespaces(@NotNull Project project,
135
                                                                                 @NotNull String packageName,
136
                                                                                 @NotNull GlobalSearchScope scope) {
137
    ArrayList<PerlNamespaceDefinitionElement> elements = new ArrayList<>();
1✔
138
    processChildNamespaces(packageName, project, scope, elements::add);
1✔
139
    return elements;
1✔
140
  }
141

142
  /**
143
   * Translates package relative name to the package name Foo/Bar.pm => Foo::Bar
144
   *
145
   * @param packagePath package relative path
146
   * @return canonical package name
147
   */
148
  public static String getPackageNameByPath(final String packagePath) {
149
    String result = PerlPackageUtilCore.PATH_TO_PACKAGE_NAME_MAP.get(packagePath);
1✔
150

151
    if (result == null) {
1✔
152
      String path = packagePath.replace("\\", "/");
1✔
153
      result = PerlPackageUtilCore.getCanonicalNamespaceName(
1✔
154
        StringUtil.join(path.replaceFirst("\\.pm$", "").split("/"), PerlPackageUtilCore.NAMESPACE_SEPARATOR));
1✔
155
      PerlPackageUtilCore.PATH_TO_PACKAGE_NAME_MAP.put(packagePath, result);
1✔
156
    }
157
    return result;
1✔
158
  }
159

160
  public static boolean processPackageFilesForPsiElement(@NotNull PsiElement element,
161
                                                         @NotNull PairProcessor<? super String, ? super VirtualFile> processor) {
162
    return processIncFilesForPsiElement(
1✔
163
      element,
164
      (file, classRoot) -> {
165
        String relativePath = VfsUtilCore.getRelativePath(file, classRoot);
1✔
166
        String packageName = getPackageNameByPath(relativePath);
1✔
167
        return processor.process(packageName, file);
1✔
168
      },
169
      PerlFileTypePackage.INSTANCE)
170
      ;
171
  }
172

173
  @SuppressWarnings("UnusedReturnValue")
174
  public static boolean processIncFilesForPsiElement(@NotNull PsiElement element,
175
                                                     @NotNull ClassRootVirtualFileProcessor processor,
176
                                                     @NotNull FileType fileType) {
177
    List<VirtualFile> incDirsForPsiElement = getIncDirsForPsiElement(element);
1✔
178
    Project project = element.getProject();
1✔
179
    Function<VirtualFile, VirtualFile> rootsComputator = PerlDirectoryIndex.getInstance(project).createRootComputator(incDirsForPsiElement);
1✔
180
    return FileTypeIndex.processFiles(
1✔
181
      fileType,
182
      virtualFile -> {
183
        ProgressManager.checkCanceled();
1✔
184
        VirtualFile incDir = rootsComputator.apply(virtualFile);
1✔
185
        return incDir == null || processor.process(virtualFile, incDir);
1✔
186
      },
187
      GlobalSearchScope.allScope(project));
1✔
188
  }
189

190
  public static void processNotOverridedMethods(final PerlNamespaceDefinitionElement namespaceDefinition,
191
                                                Processor<? super PerlSubElement> processor) {
192
    if (namespaceDefinition != null) {
1✔
193
      PsiFile containingFile = namespaceDefinition.getContainingFile();
1✔
194
      String packageName = namespaceDefinition.getNamespaceName();
1✔
195
      if (packageName == null) {
1✔
196
        return;
×
197
      }
198

199
      Set<String> namesSet = new HashSet<>();
1✔
200
      // collecting overrided
201
      for (PerlSubDefinitionElement subDefinitionBase : PsiTreeUtil.findChildrenOfType(containingFile, PerlSubDefinitionElement.class)) {
1✔
202
        if (subDefinitionBase.isValid() && StringUtil.equals(packageName, subDefinitionBase.getNamespaceName())) {
1✔
203
          namesSet.add(subDefinitionBase.getSubName());
1✔
204
        }
205
      }
1✔
206

207
      processParentClassesSubs(
1✔
208
        namespaceDefinition,
209
        namesSet,
210
        new HashSet<>(),
211
        processor
212
      );
213
    }
214
  }
1✔
215

216
  public static void processParentClassesSubs(PerlNamespaceDefinitionElement childClass,
217
                                              Set<? super String> processedSubsNames,
218
                                              Set<? super PerlNamespaceDefinitionElement> recursionMap,
219
                                              Processor<? super PerlSubElement> processor
220
  ) {
221
    if (childClass == null || recursionMap.contains(childClass)) {
1✔
222
      return;
×
223
    }
224
    recursionMap.add(childClass);
1✔
225

226
    for (PerlNamespaceDefinitionElement parentNamespace : PerlNamespaceDefinitionHandler.instance(childClass)
1✔
227
      .getParentNamespaceDefinitions(childClass)) {
1✔
228
      for (PsiElement subDefinitionBase : collectNamespaceSubs(parentNamespace)) {
1✔
229
        ProgressManager.checkCanceled();
1✔
230
        String subName = ((PerlSubElement)subDefinitionBase).getSubName();
1✔
231
        if (subDefinitionBase.isValid() &&
1✔
232
            ((PerlSubElement)subDefinitionBase).isMethod() &&
1✔
233
            !processedSubsNames.contains(subName)
1✔
234
          ) {
235
          processedSubsNames.add(subName);
1✔
236
          processor.process(((PerlSubElement)subDefinitionBase));
1✔
237
        }
238
      }
1✔
239
      processParentClassesSubs(
1✔
240
        parentNamespace,
241
        processedSubsNames,
242
        recursionMap,
243
        processor
244
      );
245
    }
1✔
246
  }
1✔
247

248
  public static List<PsiElement> collectNamespaceSubs(final @NotNull PsiElement namespace) {
249
    return CachedValuesManager.getCachedValue(
1✔
250
      namespace,
251
      () -> CachedValueProvider.Result
1✔
252
        .create(PerlPsiUtil.collectNamespaceMembers(namespace, PerlSubElement.class), namespace));
1✔
253
  }
254

255
  public static @Nullable PsiFile getPackagePsiFileByPackageName(Project project, String packageName) {
256
    VirtualFile packageVirtualFile = getPackageVirtualFileByPackageName(project, packageName);
1✔
257

258
    if (packageVirtualFile != null) {
1✔
259
      return PsiManager.getInstance(project).findFile(packageVirtualFile);
×
260
    }
261

262
    return null;
1✔
263
  }
264

265
  public static @Nullable VirtualFile getPackageVirtualFileByPackageName(Project project, String packageName) {
266
    if (StringUtil.isEmpty(packageName)) {
1✔
267
      return null;
×
268
    }
269

270
    String packagePath = PerlPackageUtilCore.getPackagePathByName(packageName);
1✔
271
    for (VirtualFile classRoot : PerlProjectManager.getInstance(project).getAllLibraryRoots()) {
1✔
272
      VirtualFile targetFile = classRoot.findFileByRelativePath(packagePath);
1✔
273
      if (targetFile != null) {
1✔
274
        return targetFile;
1✔
275
      }
276
    }
1✔
277
    return null;
1✔
278
  }
279

280
  /**
281
   * Resolving canonical package name to a psi file
282
   *
283
   * @param psiFile              base file
284
   * @param canonicalPackageName package name in canonical form
285
   * @return vartual file
286
   */
287
  public static @Nullable PsiFile resolvePackageNameToPsi(@NotNull PsiFile psiFile, String canonicalPackageName) {
288
    // resolves to a psi file
289
    return resolveRelativePathToPsi(psiFile, PerlPackageUtilCore.getPackagePathByName(canonicalPackageName));
1✔
290
  }
291

292
  /**
293
   * Resolving relative path to a psi file
294
   *
295
   * @param psiFile      base file
296
   * @param relativePath relative path
297
   * @return vartual file
298
   */
299
  public static @Nullable PsiFile resolveRelativePathToPsi(@NotNull PsiFile psiFile, String relativePath) {
300
    VirtualFile targetFile = resolveRelativePathToVirtualFile(psiFile, relativePath);
1✔
301

302
    if (targetFile != null && targetFile.exists()) {
1✔
303
      return PsiManager.getInstance(psiFile.getProject()).findFile(targetFile);
1✔
304
    }
305

306
    return null;
1✔
307
  }
308

309
  /**
310
   * Resolving relative path to a virtual file
311
   *
312
   * @param psiFile      base file
313
   * @param relativePath relative path
314
   * @return vartual file
315
   */
316
  public static @Nullable VirtualFile resolveRelativePathToVirtualFile(@NotNull PsiFile psiFile, String relativePath) {
317
    if (relativePath == null) {
1✔
318
      return null;
×
319
    }
320
    for (VirtualFile classRoot : getIncDirsForPsiElement(psiFile)) {
1✔
321
      if (classRoot == null) {
1✔
322
        continue;
×
323
      }
324
      VirtualFile targetFile = classRoot.findFileByRelativePath(relativePath);
1✔
325
      if (targetFile == null) {
1✔
326
        continue;
1✔
327
      }
328
      String foundRelativePath = VfsUtilCore.getRelativePath(targetFile, classRoot);
1✔
329

330
      if (StringUtil.isNotEmpty(foundRelativePath) && StringUtil.equals(foundRelativePath, relativePath)) {
1✔
331
        return targetFile;
1✔
332
      }
333
    }
×
334

335
    return null;
1✔
336
  }
337

338
  /**
339
   * Returns List of lib directories including class roots, current directory and use lib ones
340
   *
341
   * @param psiElement to resolve for
342
   * @return list of lib dirs
343
   */
344
  @VisibleForTesting
345
  public static @NotNull List<VirtualFile> getIncDirsForPsiElement(@NotNull PsiElement psiElement) {
346
    PsiFile psiFile = psiElement.getContainingFile().getOriginalFile();
1✔
347
    List<VirtualFile> result = new ArrayList<>();
1✔
348

349
    for (PerlUseStatementElement useStatement : PsiTreeUtil.findChildrenOfType(psiFile, PerlUseStatementElement.class)) {
1✔
350
      PerlPackageProcessor packageProcessor = useStatement.getPackageProcessor();
1✔
351
      if (packageProcessor instanceof PerlLibProvider perlLibProvider) {
1✔
352
        perlLibProvider.addLibDirs(useStatement, result);
×
353
      }
354
    }
1✔
355

356
    // classpath
357
    result.addAll(PerlProjectManager.getInstance(psiElement.getProject()).getAllLibraryRoots());
1✔
358

359
    // current dir
360
    if (PerlSharedSettings.getInstance(psiFile.getProject()).getTargetPerlVersion().lesserThan(PerlVersion.V5_26)) {
1✔
361
      VirtualFile virtualFile = psiFile.getVirtualFile();
1✔
362
      if (virtualFile != null && virtualFile.getParent() != null) {
1✔
363
        result.add(virtualFile.getParent());
1✔
364
      }
365
    }
366

367
    return result;
1✔
368
  }
369

370
  /**
371
   * Checks if sequence looks like a fqn
372
   *
373
   * @param text sequence to check
374
   * @return true if it's foo::bar
375
   */
376
  public static boolean isFullQualifiedName(String text) {
377
    return text.length() > 1 && StringUtil.containsAnyChar(text, ":'");
1✔
378
  }
379

380
  @Contract("null->null")
381
  public static @Nullable VirtualFile getClosestIncRoot(@Nullable PsiFile psiFile) {
382
    return psiFile == null ? null : getClosestIncRoot(psiFile.getProject(), PsiUtilCore.getVirtualFile(psiFile));
1✔
383
  }
384

385
  /**
386
   * @return innermost @INC root for a file
387
   * @apiNote this method may work wrong, because it is not accounts for dynamic lib paths, e.g. use lib
388
   */
389
  @Contract("_,null->null")
390
  public static @Nullable VirtualFile getClosestIncRoot(@NotNull Project project, @Nullable VirtualFile file) {
391
    return PerlDirectoryIndex.getInstance(project).getRoot(file);
1✔
392
  }
393

394
  /**
395
   * @return innermost @INC root for a file by path
396
   * @apiNote this method may work wrong, because it is not accounts for dynamic lib paths, e.g. use lib
397
   */
398
  public static @Nullable VirtualFile getClosestIncRoot(@NotNull Project project, @NotNull String filePath) {
399
    return getClosestIncRoot(project, VfsUtil.findFileByIoFile(new File(filePath), false));
×
400
  }
401

402
  public static boolean processCallables(@NotNull Project project,
403
                                         @NotNull GlobalSearchScope searchScope,
404
                                         @NotNull String canonicalName,
405
                                         @NotNull Processor<? super PerlCallableElement> processor) {
406
    if (!PerlSubUtil.processSubDefinitions(project, canonicalName, searchScope, processor)) {
1✔
407
      return false;
1✔
408
    }
409
    if (!PerlSubUtil.processSubDeclarations(project, canonicalName, searchScope, processor)) {
1✔
410
      return false;
1✔
411
    }
412
    for (PerlGlobVariableElement target : PerlGlobUtil.getGlobsDefinitions(project, canonicalName, searchScope)) {
1✔
413
      if (!processor.process(target)) {
1✔
414
        return false;
×
415
      }
416
    }
1✔
417
    return true;
1✔
418
  }
419

420
  public static boolean processCallablesInNamespace(@NotNull Project project,
421
                                                    @NotNull GlobalSearchScope searchScope,
422
                                                    @NotNull String packageName,
423
                                                    @NotNull Processor<? super PerlCallableElement> processor) {
424
    return PerlSubUtil.processRelatedSubsInPackage(project, searchScope, packageName, processor) &&
1✔
425
           PerlGlobNamespaceStubIndex.getInstance().processElements(project, packageName, searchScope, processor);
1✔
426
  }
427

428
  @SuppressWarnings("UnusedReturnValue")
429
  public static boolean processCallablesNamespaceNames(@NotNull PerlValueResolver resolver,
430
                                                       @NotNull String callableName,
431
                                                       @NotNull Processor<? super PerlCallableElement> processor) {
432
    var project = resolver.getProject();
1✔
433
    return
1✔
434
      PerlCallableNamesIndex.getInstance().processElements(project, callableName, resolver.getResolveScope(), processor) &&
1✔
435
      PerlLightCallableNamesIndex.getInstance().processLightElements(project, callableName, resolver.getResolveScope(), processor);
1✔
436
  }
437

438
  public interface ClassRootVirtualFileProcessor {
439
    boolean process(VirtualFile file, VirtualFile classRoot);
440
  }
441
}
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