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

Camelcade / Perl5-IDEA / #525521660

24 Aug 2025 01:28PM UTC coverage: 75.89% (-6.3%) from 82.227%
#525521660

push

github

hurricup
Migrated coverage reporting to https://github.com/nbaztec/coveralls-jacoco-gradle-plugin

See: https://github.com/kt3k/coveralls-gradle-plugin/issues/119

14751 of 22639 branches covered (65.16%)

Branch coverage included in aggregate %.

31091 of 37767 relevant lines covered (82.32%)

0.82 hits per line

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

80.17
/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.psi.*;
46
import com.perl5.lang.perl.psi.impl.PerlFileImpl;
47
import com.perl5.lang.perl.psi.impl.PerlUseStatementElement;
48
import com.perl5.lang.perl.psi.stubs.globs.PerlGlobNamespaceStubIndex;
49
import com.perl5.lang.perl.psi.stubs.namespaces.PerlLightNamespaceDescendantsIndex;
50
import com.perl5.lang.perl.psi.stubs.namespaces.PerlLightNamespaceIndex;
51
import com.perl5.lang.perl.psi.stubs.namespaces.PerlNamespaceDescendantsIndex;
52
import com.perl5.lang.perl.psi.stubs.namespaces.PerlNamespaceIndex;
53
import com.perl5.lang.perl.psi.stubs.subsdefinitions.PerlCallableNamesIndex;
54
import com.perl5.lang.perl.psi.stubs.subsdefinitions.PerlLightCallableNamesIndex;
55
import com.perl5.lang.perl.psi.utils.PerlPsiUtil;
56
import org.jetbrains.annotations.Contract;
57
import org.jetbrains.annotations.NotNull;
58
import org.jetbrains.annotations.Nullable;
59
import org.jetbrains.annotations.VisibleForTesting;
60

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

65

66
public final class PerlPackageUtil {
67

68
  private PerlPackageUtil() {
69
  }
70

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

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

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

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

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

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

114

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

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

141
  public static boolean processPackageFilesForPsiElement(@NotNull PsiElement element,
142
                                                         @NotNull PairProcessor<? super String, ? super VirtualFile> processor) {
143
    return processIncFilesForPsiElement(
1✔
144
      element,
145
      (file, classRoot) -> {
146
        String relativePath = VfsUtilCore.getRelativePath(file, classRoot);
1✔
147
        String packageName = PerlPackageUtilCore.getPackageNameByPath(relativePath);
1✔
148
        return processor.process(packageName, file);
1✔
149
      },
150
      PerlFileTypePackage.INSTANCE)
151
      ;
152
  }
153

154
  @SuppressWarnings("UnusedReturnValue")
155
  public static boolean processIncFilesForPsiElement(@NotNull PsiElement element,
156
                                                     @NotNull ClassRootVirtualFileProcessor processor,
157
                                                     @NotNull FileType fileType) {
158
    List<VirtualFile> incDirsForPsiElement = getIncDirsForPsiElement(element);
1✔
159
    Project project = element.getProject();
1✔
160
    Function<VirtualFile, VirtualFile> rootsComputator = PerlDirectoryIndex.getInstance(project).createRootComputator(incDirsForPsiElement);
1✔
161
    return FileTypeIndex.processFiles(
1✔
162
      fileType,
163
      virtualFile -> {
164
        ProgressManager.checkCanceled();
1✔
165
        VirtualFile incDir = rootsComputator.apply(virtualFile);
1✔
166
        return incDir == null || processor.process(virtualFile, incDir);
1!
167
      },
168
      GlobalSearchScope.allScope(project));
1✔
169
  }
170

171
  public static void processNotOverridedMethods(final PerlNamespaceDefinitionElement namespaceDefinition,
172
                                                Processor<? super PerlSubElement> processor) {
173
    if (namespaceDefinition != null) {
1!
174
      PsiFile containingFile = namespaceDefinition.getContainingFile();
1✔
175
      String packageName = namespaceDefinition.getNamespaceName();
1✔
176
      if (packageName == null) {
1!
177
        return;
×
178
      }
179

180
      Set<String> namesSet = new HashSet<>();
1✔
181
      // collecting overrided
182
      for (PerlSubDefinitionElement subDefinitionBase : PsiTreeUtil.findChildrenOfType(containingFile, PerlSubDefinitionElement.class)) {
1✔
183
        if (subDefinitionBase.isValid() && StringUtil.equals(packageName, subDefinitionBase.getNamespaceName())) {
1!
184
          namesSet.add(subDefinitionBase.getSubName());
1✔
185
        }
186
      }
1✔
187

188
      processParentClassesSubs(
1✔
189
        namespaceDefinition,
190
        namesSet,
191
        new HashSet<>(),
192
        processor
193
      );
194
    }
195
  }
1✔
196

197
  public static void processParentClassesSubs(PerlNamespaceDefinitionElement childClass,
198
                                              Set<? super String> processedSubsNames,
199
                                              Set<? super PerlNamespaceDefinitionElement> recursionMap,
200
                                              Processor<? super PerlSubElement> processor
201
  ) {
202
    if (childClass == null || recursionMap.contains(childClass)) {
1!
203
      return;
×
204
    }
205
    recursionMap.add(childClass);
1✔
206

207
    for (PerlNamespaceDefinitionElement parentNamespace : PerlNamespaceDefinitionHandler.instance(childClass)
1✔
208
      .getParentNamespaceDefinitions(childClass)) {
1✔
209
      for (PsiElement subDefinitionBase : collectNamespaceSubs(parentNamespace)) {
1✔
210
        ProgressManager.checkCanceled();
1✔
211
        String subName = ((PerlSubElement)subDefinitionBase).getSubName();
1✔
212
        if (subDefinitionBase.isValid() &&
1!
213
            ((PerlSubElement)subDefinitionBase).isMethod() &&
1!
214
            !processedSubsNames.contains(subName)
1!
215
          ) {
216
          processedSubsNames.add(subName);
1✔
217
          processor.process(((PerlSubElement)subDefinitionBase));
1✔
218
        }
219
      }
1✔
220
      processParentClassesSubs(
1✔
221
        parentNamespace,
222
        processedSubsNames,
223
        recursionMap,
224
        processor
225
      );
226
    }
1✔
227
  }
1✔
228

229
  public static List<PsiElement> collectNamespaceSubs(final @NotNull PsiElement namespace) {
230
    return CachedValuesManager.getCachedValue(
1✔
231
      namespace,
232
      () -> CachedValueProvider.Result
1✔
233
        .create(PerlPsiUtil.collectNamespaceMembers(namespace, PerlSubElement.class), namespace));
1✔
234
  }
235

236
  public static @Nullable PsiFile getPackagePsiFileByPackageName(Project project, String packageName) {
237
    VirtualFile packageVirtualFile = getPackageVirtualFileByPackageName(project, packageName);
1✔
238

239
    if (packageVirtualFile != null) {
1!
240
      return PsiManager.getInstance(project).findFile(packageVirtualFile);
×
241
    }
242

243
    return null;
1✔
244
  }
245

246
  public static @Nullable VirtualFile getPackageVirtualFileByPackageName(Project project, String packageName) {
247
    if (StringUtil.isEmpty(packageName)) {
1!
248
      return null;
×
249
    }
250

251
    String packagePath = PerlPackageUtilCore.getPackagePathByName(packageName);
1✔
252
    for (VirtualFile classRoot : PerlProjectManager.getInstance(project).getAllLibraryRoots()) {
1✔
253
      VirtualFile targetFile = classRoot.findFileByRelativePath(packagePath);
1✔
254
      if (targetFile != null) {
1✔
255
        return targetFile;
1✔
256
      }
257
    }
1✔
258
    return null;
1✔
259
  }
260

261
  /**
262
   * Resolving canonical package name to a psi file
263
   *
264
   * @param psiFile              base file
265
   * @param canonicalPackageName package name in canonical form
266
   * @return vartual file
267
   */
268
  public static @Nullable PsiFile resolvePackageNameToPsi(@NotNull PsiFile psiFile, String canonicalPackageName) {
269
    // resolves to a psi file
270
    return resolveRelativePathToPsi(psiFile, PerlPackageUtilCore.getPackagePathByName(canonicalPackageName));
1✔
271
  }
272

273
  /**
274
   * Resolving relative path to a psi file
275
   *
276
   * @param psiFile      base file
277
   * @param relativePath relative path
278
   * @return vartual file
279
   */
280
  public static @Nullable PsiFile resolveRelativePathToPsi(@NotNull PsiFile psiFile, String relativePath) {
281
    VirtualFile targetFile = resolveRelativePathToVirtualFile(psiFile, relativePath);
1✔
282

283
    if (targetFile != null && targetFile.exists()) {
1!
284
      return PsiManager.getInstance(psiFile.getProject()).findFile(targetFile);
1✔
285
    }
286

287
    return null;
1✔
288
  }
289

290
  /**
291
   * Resolving relative path to a virtual file
292
   *
293
   * @param psiFile      base file
294
   * @param relativePath relative path
295
   * @return vartual file
296
   */
297
  public static @Nullable VirtualFile resolveRelativePathToVirtualFile(@NotNull PsiFile psiFile, String relativePath) {
298
    if (relativePath == null) {
1!
299
      return null;
×
300
    }
301
    for (VirtualFile classRoot : getIncDirsForPsiElement(psiFile)) {
1✔
302
      if (classRoot == null) {
1!
303
        continue;
×
304
      }
305
      VirtualFile targetFile = classRoot.findFileByRelativePath(relativePath);
1✔
306
      if (targetFile == null) {
1✔
307
        continue;
1✔
308
      }
309
      String foundRelativePath = VfsUtilCore.getRelativePath(targetFile, classRoot);
1✔
310

311
      if (StringUtil.isNotEmpty(foundRelativePath) && StringUtil.equals(foundRelativePath, relativePath)) {
1!
312
        return targetFile;
1✔
313
      }
314
    }
×
315

316
    return null;
1✔
317
  }
318

319
  /**
320
   * Returns List of lib directories including class roots, current directory and use lib ones
321
   *
322
   * @param psiElement to resolve for
323
   * @return list of lib dirs
324
   */
325
  @VisibleForTesting
326
  public static @NotNull List<VirtualFile> getIncDirsForPsiElement(@NotNull PsiElement psiElement) {
327
    PsiFile psiFile = psiElement.getContainingFile().getOriginalFile();
1✔
328
    List<VirtualFile> result = new ArrayList<>();
1✔
329

330
    for (PerlUseStatementElement useStatement : PsiTreeUtil.findChildrenOfType(psiFile, PerlUseStatementElement.class)) {
1✔
331
      PerlPackageProcessor packageProcessor = useStatement.getPackageProcessor();
1✔
332
      if (packageProcessor instanceof PerlLibProvider perlLibProvider) {
1!
333
        perlLibProvider.addLibDirs(useStatement, result);
×
334
      }
335
    }
1✔
336

337
    // classpath
338
    result.addAll(PerlProjectManager.getInstance(psiElement.getProject()).getAllLibraryRoots());
1✔
339

340
    // current dir
341
    if (PerlSharedSettings.getInstance(psiFile.getProject()).getTargetPerlVersion().lesserThan(PerlVersion.V5_26)) {
1✔
342
      VirtualFile virtualFile = psiFile.getVirtualFile();
1✔
343
      if (virtualFile != null && virtualFile.getParent() != null) {
1!
344
        result.add(virtualFile.getParent());
1✔
345
      }
346
    }
347

348
    return result;
1!
349
  }
350

351
  /**
352
   * Checks if sequence looks like a fqn
353
   *
354
   * @param text sequence to check
355
   * @return true if it's foo::bar
356
   */
357
  public static boolean isFullQualifiedName(String text) {
358
    return text.length() > 1 && StringUtil.containsAnyChar(text, ":'");
1✔
359
  }
360

361
  @Contract("null->null")
362
  public static @Nullable VirtualFile getClosestIncRoot(@Nullable PsiFile psiFile) {
363
    return psiFile == null ? null : getClosestIncRoot(psiFile.getProject(), PsiUtilCore.getVirtualFile(psiFile));
1!
364
  }
365

366
  /**
367
   * @return innermost @INC root for a file
368
   * @apiNote this method may work wrong, because it is not accounts for dynamic lib paths, e.g. use lib
369
   */
370
  @Contract("_,null->null")
371
  public static @Nullable VirtualFile getClosestIncRoot(@NotNull Project project, @Nullable VirtualFile file) {
372
    return PerlDirectoryIndex.getInstance(project).getRoot(file);
1✔
373
  }
374

375
  /**
376
   * @return innermost @INC root for a file by path
377
   * @apiNote this method may work wrong, because it is not accounts for dynamic lib paths, e.g. use lib
378
   */
379
  public static @Nullable VirtualFile getClosestIncRoot(@NotNull Project project, @NotNull String filePath) {
380
    return getClosestIncRoot(project, VfsUtil.findFileByIoFile(new File(filePath), false));
×
381
  }
382

383
  public static boolean processCallables(@NotNull Project project,
384
                                         @NotNull GlobalSearchScope searchScope,
385
                                         @NotNull String canonicalName,
386
                                         @NotNull Processor<? super PerlCallableElement> processor) {
387
    if (!PerlSubUtil.processSubDefinitions(project, canonicalName, searchScope, processor)) {
1✔
388
      return false;
1✔
389
    }
390
    if (!PerlSubUtil.processSubDeclarations(project, canonicalName, searchScope, processor)) {
1✔
391
      return false;
1✔
392
    }
393
    for (PerlGlobVariableElement target : PerlGlobUtil.getGlobsDefinitions(project, canonicalName, searchScope)) {
1✔
394
      if (!processor.process(target)) {
1!
395
        return false;
×
396
      }
397
    }
1✔
398
    return true;
1✔
399
  }
400

401
  public static boolean processCallablesInNamespace(@NotNull Project project,
402
                                                    @NotNull GlobalSearchScope searchScope,
403
                                                    @NotNull String packageName,
404
                                                    @NotNull Processor<? super PerlCallableElement> processor) {
405
    return PerlSubUtil.processRelatedSubsInPackage(project, searchScope, packageName, processor) &&
1!
406
           PerlGlobNamespaceStubIndex.getInstance().processElements(project, packageName, searchScope, processor);
1!
407
  }
408

409
  @SuppressWarnings("UnusedReturnValue")
410
  public static boolean processCallablesNamespaceNames(@NotNull PerlValueResolver resolver,
411
                                                       @NotNull String callableName,
412
                                                       @NotNull Processor<? super PerlCallableElement> processor) {
413
    var project = resolver.getProject();
1✔
414
    return
1✔
415
      PerlCallableNamesIndex.getInstance().processElements(project, callableName, resolver.getResolveScope(), processor) &&
1!
416
      PerlLightCallableNamesIndex.getInstance().processLightElements(project, callableName, resolver.getResolveScope(), processor);
1!
417
  }
418

419
  public interface ClassRootVirtualFileProcessor {
420
    boolean process(VirtualFile file, VirtualFile classRoot);
421
  }
422
}
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