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

Camelcade / Perl5-IDEA / #525521557

29 May 2025 01:55PM UTC coverage: 82.275% (+0.001%) from 82.274%
#525521557

push

github

hurricup
Migrated to ProjectActivity

0 of 1 new or added line in 1 file covered. (0.0%)

104 existing lines in 12 files now uncovered.

30882 of 37535 relevant lines covered (82.28%)

0.82 hits per line

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

88.57
/plugin/core/src/main/java/com/perl5/lang/perl/documentation/PerlDocumentationProvider.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.documentation;
18

19
import com.intellij.openapi.editor.Editor;
20
import com.intellij.openapi.util.text.StringUtil;
21
import com.intellij.psi.PsiElement;
22
import com.intellij.psi.PsiFile;
23
import com.intellij.psi.PsiManager;
24
import com.intellij.psi.tree.IElementType;
25
import com.intellij.psi.tree.TokenSet;
26
import com.intellij.psi.util.PsiUtilCore;
27
import com.perl5.lang.perl.PerlLanguage;
28
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlValuesManager;
29
import com.perl5.lang.perl.lexer.PerlElementTypes;
30
import com.perl5.lang.perl.lexer.PerlTokenSets;
31
import com.perl5.lang.perl.psi.*;
32
import com.perl5.lang.perl.psi.impl.PerlBuiltInSubDefinition;
33
import com.perl5.lang.perl.psi.impl.PerlFileImpl;
34
import com.perl5.lang.pod.PodLanguage;
35
import com.perl5.lang.pod.idea.documentation.PodDocumentationProvider;
36
import com.perl5.lang.pod.parser.psi.PodLinkDescriptor;
37
import org.jetbrains.annotations.Contract;
38
import org.jetbrains.annotations.Nls;
39
import org.jetbrains.annotations.NotNull;
40
import org.jetbrains.annotations.Nullable;
41

42
import static com.perl5.lang.perl.documentation.PerlDocUtil.SWITCH_DOC_LINK;
43
import static com.perl5.lang.perl.lexer.PerlTokenSets.*;
44
import static com.perl5.lang.perl.parser.MooseParserExtension.*;
45
import static com.perl5.lang.perl.util.PerlPackageUtil.FUNCTION_PARAMETERS;
46
import static com.perl5.lang.pod.lexer.PodElementTypes.POD_OUTER;
47

48
public class PerlDocumentationProvider extends PerlDocumentationProviderBase implements PerlElementTypes {
1✔
49
  private static final TokenSet FORCE_AS_OPERATORS_TOKENSET = TokenSet.orSet(
1✔
50
    HEREDOC_BODIES_TOKENSET,
51
    SPECIAL_STRING_TOKENS,
52
    TokenSet.create(
1✔
53
      STRING_CHAR_NAME,
54

55
      RESERVED_Q,
56
      RESERVED_QQ,
57
      RESERVED_QX,
58
      RESERVED_QW,
59
      RESERVED_TR,
60
      RESERVED_Y,
61
      HEREDOC_OPENER,
62
      HEREDOC_END,
63
      HEREDOC_END_INDENTABLE,
64

65
      RESERVED_S,
66
      RESERVED_M,
67
      RESERVED_QR,
68

69
      LEFT_ANGLE,
70
      RIGHT_ANGLE
71
    )
72
  );
73
  private static final TokenSet FORCE_AS_FUNC_TOKENSET = TokenSet.orSet(TAGS_TOKEN_SET, TokenSet.create(BLOCK_NAME, OPERATOR_FILETEST));
1✔
74
  private static final String SECTION_DESCRIPTION = "DESCRIPTION";
75

76
  @Override
77
  public @Nullable String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) {
78
    if (isWrongElement(originalElement)) {
×
79
      return null;
×
80
    }
81

82
    if (originalElement instanceof PerlVariableNameElement) {
×
83
      return getQuickNavigateInfo(element, originalElement.getParent());
×
84
    }
85
    var perlValue = PerlValuesManager.from(originalElement);
×
86
    return !perlValue.isUnknown() ? perlValue.getPresentableText() : null;
×
87
  }
88

89
  private static boolean isWrongElement(@Nullable PsiElement element) {
90
    return element == null || !element.isValid() || element.getLanguage() != PerlLanguage.INSTANCE;
1✔
91
  }
92

93
  @Override
94
  public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element) {
95
    if (object instanceof PsiElement psiElement && psiElement.isValid()) {
1✔
96
      return findPodElement(psiElement);
1✔
97
    }
98
    return super.getDocumentationElementForLookupItem(psiManager, object, element);
1✔
99
  }
100

101
  @Override
102
  public @Nullable PsiElement getCustomDocumentationElement(@NotNull Editor editor,
103
                                                            @NotNull PsiFile file,
104
                                                            @Nullable PsiElement contextElement,
105
                                                            int targetOffset) {
106
    if (isWrongElement(contextElement)) {
1✔
107
      return null;
1✔
108
    }
109

110
    PsiElement functionParametersDoc = computeFunctionParametersDoc(contextElement);
1✔
111
    if (functionParametersDoc != null) {
1✔
112
      return functionParametersDoc;
1✔
113
    }
114

115
    IElementType elementType = PsiUtilCore.getElementType(contextElement);
1✔
116

117
    if (elementType == RESERVED_ASYNC) {
1✔
118
      return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create("Future::AsyncAwait", "async"), contextElement, false);
1✔
119
    }
120
    if (elementType == REGEX_MODIFIER) {
1✔
121
      return PerlDocUtil.getRegexModifierDoc(contextElement);
×
122
    }
123
    else if (elementType == REGEX_TOKEN) {
1✔
124
      return PerlDocUtil.resolveDoc("perlretut", null, contextElement, false);
×
125
    }
126
    else if (elementType == VERSION_ELEMENT) {
1✔
127
      return PerlDocUtil.resolveDoc("perldata", "Version Strings", contextElement, false);
×
128
    }
129
    else if (isFunc(contextElement)) {
1✔
130
      return PerlDocUtil.getPerlFuncDoc(contextElement);
1✔
131
    }
132
    else if (elementType == STRING_SPECIAL_BACKREF) {
1✔
133
      return PerlDocUtil.resolveDoc(PerlDocUtil.PERL_OP, "the replacement of s///", contextElement, true);
1✔
134
    }
135
    else if (PerlDocUtil.isNumericArgumentToOperator(contextElement)) {
1✔
136
      return PerlDocUtil.getPerlOpDoc(contextElement.getParent().getFirstChild());
1✔
137
    }
138
    else if (isOp(contextElement)) {
1✔
139
      return PerlDocUtil.getPerlOpDoc(contextElement);
1✔
140
    }
141

142
    return null;
1✔
143
  }
144

145
  /**
146
   * @return some {@code Function::Parameters} specific documentation if available
147
   */
148
  private @Nullable PsiElement computeFunctionParametersDoc(@NotNull PsiElement contextElement) {
149
    IElementType elementType = PsiUtilCore.getElementType(contextElement);
1✔
150
    if (elementType == RESERVED_AFTER_FP) {
1✔
151
      return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, KEYWORD_AFTER), contextElement, false);
1✔
152
    }
153
    if (elementType == RESERVED_BEFORE_FP) {
1✔
154
      return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, KEYWORD_BEFORE), contextElement, false);
1✔
155
    }
156
    if (elementType == RESERVED_AROUND_FP) {
1✔
157
      return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, KEYWORD_AROUND), contextElement, false);
1✔
158
    }
159
    if (elementType == RESERVED_AUGMENT_FP) {
1✔
160
      return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, KEYWORD_AUGMENT), contextElement, false);
1✔
161
    }
162
    if (elementType == RESERVED_FUN || elementType == RESERVED_METHOD || elementType == RESERVED_METHOD_FP) {
1✔
163
      return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, SECTION_DESCRIPTION), contextElement, false);
1✔
164
    }
165
    if (elementType == RESERVED_OVERRIDE_FP) {
1✔
166
      return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, KEYWORD_OVERRIDE), contextElement, false);
1✔
167
    }
168
    IElementType parentElementType = PsiUtilCore.getElementType(contextElement.getParent());
1✔
169
    if (elementType == COLON) {
1✔
170
      if (parentElementType == METHOD_SIGNATURE_INVOCANT) {
1✔
171
        return PerlDocUtil
1✔
172
          .resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, "Simple parameter lists"), contextElement, false);
1✔
173
      }
174
      else if (parentElementType == AROUND_SIGNATURE_INVOCANTS) {
1✔
175
        return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, KEYWORD_AROUND), contextElement, false);
1✔
176
      }
177
      else if (parentElementType == SIGNATURE_ELEMENT) {
1✔
178
        return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, "Named parameters"), contextElement, false);
1✔
179
      }
180
    }
181
    // fixme these may occur in regular sub signature
182
    if (elementType == OPERATOR_ASSIGN && parentElementType == SIGNATURE_ELEMENT) {
1✔
183
      return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, "Default arguments"), contextElement, false);
1✔
184
    }
185
    if (PerlTokenSets.SIGILS.contains(elementType) && parentElementType == SUB_SIGNATURE_ELEMENT_IGNORE) {
1✔
186
      return PerlDocUtil.resolveDescriptor(PodLinkDescriptor.create(FUNCTION_PARAMETERS, "Unnamed parameters"), contextElement, false);
1✔
187
    }
188
    return null;
1✔
189
  }
190

191
  @Override
192
  public @Nullable @Nls String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
193
    return PodDocumentationProvider.doGenerateDoc(findPodElement(element));
1✔
194
  }
195

196
  protected static boolean isFunc(PsiElement element) {
197
    IElementType elementType = element.getNode().getElementType();
1✔
198
    return FORCE_AS_FUNC_TOKENSET.contains(elementType) ||
1✔
199
           !FORCE_AS_OPERATORS_TOKENSET.contains(elementType) && (
1✔
200
             PerlTokenSets.DEFAULT_KEYWORDS_TOKENSET.contains(elementType) ||
1✔
201
             element instanceof PerlSubNameElement subNameElement && subNameElement.isBuiltIn()
1✔
202
           );
203
  }
204

205
  protected static boolean isOp(PsiElement element) {
206
    IElementType elementType = element.getNode().getElementType();
1✔
207
    return FORCE_AS_OPERATORS_TOKENSET.contains(elementType) ||
1✔
208
           !FORCE_AS_FUNC_TOKENSET.contains(elementType) && (
1✔
209
             PerlTokenSets.OPERATORS_TOKENSET.contains(elementType)
1✔
210
           );
211
  }
212

213
  /**
214
   * @return corresponding pod element for the {@code element} if any
215
   */
216
  @Contract("null->null")
217
  public static @Nullable PsiElement findPodElement(@Nullable PsiElement element) {
218
    return switch (element) {
1✔
219
      case PerlBuiltInSubDefinition definition -> {
1✔
220
        String subName = StringUtil.notNullize(definition.getName());
1✔
221
        if ("default".equals(subName)) {
1✔
222
          yield PerlDocUtil.resolveDescriptor(SWITCH_DOC_LINK, element, false);
1✔
223
        }
224
        else {
225
          yield PerlDocUtil.getPerlFuncDocFromText(element, subName);
1✔
226
        }
227
      }
228
      case PerlSubElement subElement -> findPodElement(subElement);
1✔
229
      case PerlFileImpl file -> findPodElement(file);
1✔
230
      case PerlNamespaceDefinitionElement definitionElement -> findPodElement(definitionElement);
1✔
231
      case PerlVariable variable -> PerlDocUtil.getPerlVarDoc(variable);
1✔
232
      case null, default -> null;
1✔
233
    };
234
  }
235

236
  /**
237
   * Finds documentation for sub declaration or definition
238
   */
239
  private static @Nullable PsiElement findPodElement(@NotNull PerlSubElement perlSub) {
240
    String namespaceName = perlSub.getNamespaceName();
1✔
241
    String subName = perlSub.getSubName();
1✔
242
    if (StringUtil.isNotEmpty(namespaceName) && StringUtil.isNotEmpty(subName)) {
1✔
243
      PsiElement docElement = PerlDocUtil.resolveDoc(namespaceName, subName, perlSub, false);
1✔
244
      if (docElement != null) {
1✔
245
        return docElement;
1✔
246
      }
247
    }
248
    return PerlDocUtil.findPrependingPodBlock(perlSub);
1✔
249
  }
250

251
  /**
252
   * Finds  documentation for namespace definition
253
   */
254
  private static @Nullable PsiElement findPodElement(@NotNull PerlNamespaceDefinitionElement namespaceDefinition) {
255
    String namespaceName = namespaceDefinition.getNamespaceName();
1✔
256
    if (StringUtil.isNotEmpty(namespaceName)) {
1✔
257
      PsiElement docElement = PerlDocUtil.resolveDoc(namespaceName, null, namespaceDefinition, false);
1✔
258
      if (docElement != null) {
1✔
259
        return docElement;
1✔
260
      }
261
    }
UNCOV
262
    return PerlDocUtil.findPrependingPodBlock(namespaceDefinition);
×
263
  }
264

265
  /**
266
   * Finds  documentation for a file
267
   */
268
  private static @Nullable PsiElement findPodElement(@NotNull PerlFileImpl perlFile) {
269
    PsiFile nestedPodFile = perlFile.getViewProvider().getPsi(PodLanguage.INSTANCE);
1✔
270

271
    if (nestedPodFile != null) {
1✔
272
      PsiElement firstChild = nestedPodFile.getFirstChild();
1✔
273
      if (firstChild != null && (firstChild.getNextSibling() != null || PsiUtilCore.getElementType(firstChild) != POD_OUTER)) {
1✔
274
        return nestedPodFile;
1✔
275
      }
276
    }
277

UNCOV
278
    String filePackageName = perlFile.getFilePackageName();
×
279
    return StringUtil.isNotEmpty(filePackageName) ? PerlDocUtil.resolveDoc(filePackageName, null, perlFile, true) : null;
×
280
  }
281
}
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