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

Camelcade / Perl5-IDEA / #525521535

17 May 2025 12:51PM UTC coverage: 82.204% (+0.005%) from 82.199%
#525521535

push

github

hurricup
Suppressed deprecation warning in PerlImplicitVariableDeclaration

We can't avoid implementing this for now

30876 of 37560 relevant lines covered (82.2%)

0.82 hits per line

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

96.27
/plugin/core/src/main/java/com/perl5/lang/perl/parser/moose/psi/impl/PerlMooseAttributeHandler.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.parser.moose.psi.impl;
18

19
import com.intellij.openapi.util.AtomicNotNullLazyValue;
20
import com.intellij.openapi.util.Pair;
21
import com.intellij.openapi.util.text.StringUtil;
22
import com.intellij.psi.ElementManipulators;
23
import com.intellij.psi.PsiElement;
24
import com.intellij.util.ObjectUtils;
25
import com.intellij.util.containers.ContainerUtil;
26
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlScalarValue;
27
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlSmartGetterValue;
28
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlValue;
29
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlValuesManager;
30
import com.perl5.lang.perl.psi.*;
31
import com.perl5.lang.perl.psi.impl.PerlSubCallElement;
32
import com.perl5.lang.perl.psi.light.PerlDelegatingLightNamedElement;
33
import com.perl5.lang.perl.psi.light.PerlLightMethodDefinitionElement;
34
import com.perl5.lang.perl.psi.stubs.calls.PerlSubCallElementStub;
35
import com.perl5.lang.perl.psi.stubs.subsdefinitions.PerlSubDefinitionStub;
36
import com.perl5.lang.perl.psi.utils.PerlResolveUtil;
37
import com.perl5.lang.perl.psi.utils.PerlSubAnnotations;
38
import com.perl5.lang.perl.psi.utils.PerlSubArgument;
39
import com.perl5.lang.perl.util.PerlArrayUtil;
40
import com.perl5.lang.perl.util.PerlHashEntry;
41
import com.perl5.lang.perl.util.PerlHashUtil;
42
import com.perl5.lang.perl.util.PerlPackageUtil;
43
import org.jetbrains.annotations.NonNls;
44
import org.jetbrains.annotations.NotNull;
45
import org.jetbrains.annotations.Nullable;
46

47
import java.util.*;
48
import java.util.stream.Collectors;
49

50
import static com.perl5.lang.perl.psi.stubs.PerlStubElementTypes.LIGHT_ATTRIBUTE_DEFINITION;
51
import static com.perl5.lang.perl.psi.stubs.PerlStubElementTypes.LIGHT_METHOD_DEFINITION;
52

53
public class PerlMooseAttributeHandler extends PerlSubCallHandlerWithEmptyData {
1✔
54
  private static final @NonNls String WRITER_KEY = "writer";
55
  private static final @NonNls String ACCESSOR_KEY = "accessor";
56
  private static final @NonNls String READER_KEY = "reader";
57
  private static final @NonNls String PREDICATE_KEY = "predicate";
58
  private static final @NonNls String CLEARER_KEY = "clearer";
59
  private static final @NonNls List<String> MOOSE_SUB_NAMES_KEYS = Arrays.asList(
1✔
60
    READER_KEY, WRITER_KEY, ACCESSOR_KEY, PREDICATE_KEY, CLEARER_KEY
61
  );
62
  private static final @NonNls String RW_KEY = "rw";
63
  private static final @NonNls String RWP_KEY = "rwp";
64
  private static final @NonNls String IS_KEY = "is";
65
  private static final @NonNls String ISA_KEY = "isa";
66
  private static final @NonNls String DOES_KEY = "does";
67
  private static final @NonNls String HANDLES_KEY = "handles";
68
  private static final @NonNls String PROTECTED_MUTATOR_PREFIX = "_set_";
69
  private static final @NonNls String MOO_CLEARER_PREFIX = "clear_";
70
  private static final @NonNls String _MOO_CLEARER_PREFIX = "_" + MOO_CLEARER_PREFIX;
71
  private static final @NonNls String MOO_PREDICATE_PREFIX = "has_";
72
  private static final @NonNls String _MOO_PREDICATE_PREFIX = "_" + MOO_PREDICATE_PREFIX;
73

74
  @Override
75
  public @NotNull List<? extends PerlDelegatingLightNamedElement<?>> computeLightElementsFromPsi(@NotNull PerlSubCallElement subCallElement) {
76
    Pair<List<PsiElement>, List<PsiElement>> lists = getIdentifiersAndListElements(subCallElement);
1✔
77
    if (lists == null) {
1✔
78
      return Collections.emptyList();
1✔
79
    }
80
    return lists.second.size() < 3 ?
1✔
81
           createMojoAttributes(subCallElement, lists) : createMooseAttributes(subCallElement, lists.first, lists.second);
1✔
82
  }
83

84
  @Override
85
  public @NotNull List<? extends PerlDelegatingLightNamedElement<?>> computeLightElementsFromStubs(@NotNull PerlSubCallElement psiElement,
86
                                                                                                   @NotNull PerlSubCallElementStub stubElement) {
87
    return stubElement.getLightNamedElementsStubs().stream()
1✔
88
      .map(childStub -> {
1✔
89
        var stubType = childStub.getElementType();
1✔
90
        if (stubType == LIGHT_METHOD_DEFINITION) {
1✔
91
          return new PerlLightMethodDefinitionElement<>(psiElement, (PerlSubDefinitionStub)childStub);
1✔
92
        }
93
        else if (stubType == LIGHT_ATTRIBUTE_DEFINITION) {
1✔
94
          return new PerlAttributeDefinition(psiElement, (PerlSubDefinitionStub)childStub);
1✔
95
        }
96
        throw new IllegalArgumentException("Unexpected stubElement type: " + stubType);
×
97
      })
98
      .collect(Collectors.toList());
1✔
99
  }
100

101
  /**
102
   * @return pair of lists: identifiers lists and list of has arguments
103
   * null if something went wrong
104
   */
105
  private @Nullable Pair<List<PsiElement>, List<PsiElement>> getIdentifiersAndListElements(@NotNull PerlSubCallElement subCallElement) {
106
    PsiPerlCallArguments arguments = subCallElement.getCallArguments();
1✔
107
    if (arguments == null) {
1✔
108
      return null;
1✔
109
    }
110
    List<PsiElement> listElements = PerlArrayUtil.collectListElements(arguments.getFirstChild());
1✔
111
    if (listElements.isEmpty()) {
1✔
112
      return null;
1✔
113
    }
114
    PsiElement namesContainer = listElements.getFirst();
1✔
115
    if (namesContainer instanceof PsiPerlAnonArray anonArray) {
1✔
116
      namesContainer = anonArray.getExpr();
1✔
117
    }
118
    List<PsiElement> identifiers = ContainerUtil.filter(PerlArrayUtil.collectListElements(namesContainer),
1✔
119
                                                        subCallElement::isAcceptableIdentifierElement);
1✔
120
    if (identifiers.isEmpty()) {
1✔
121
      return null;
1✔
122
    }
123
    return Pair.create(identifiers, listElements);
1✔
124
  }
125

126
  private @NotNull List<PerlDelegatingLightNamedElement<?>> createMojoAttributes(@NotNull PerlSubCallElement subCallElement,
127
                                                                                 @NotNull Pair<? extends List<PsiElement>, ? extends List<PsiElement>> lists) {
128
    List<PsiElement> arguments = lists.second.subList(1, lists.second.size());
1✔
129
    PsiElement argument = ContainerUtil.getFirstItem(arguments);
1✔
130
    PerlSubExpr subExpr = ObjectUtils.tryCast(argument, PerlSubExpr.class);
1✔
131

132
    List<PerlDelegatingLightNamedElement<?>> result = new ArrayList<>();
1✔
133
    String namespaceName = PerlPackageUtil.getContextNamespaceName(subCallElement);
1✔
134
    for (PsiElement identifier : lists.first) {
1✔
135
      AtomicNotNullLazyValue<PerlValue> valueProvider = AtomicNotNullLazyValue.createValue(() -> PerlSmartGetterValue.create(
1✔
136
        subExpr != null ? PerlResolveUtil.computeReturnValueFromControlFlow(subExpr) :
1✔
137
        argument != null ? PerlValuesManager.from(argument) : PerlScalarValue.create(namespaceName)));
1✔
138
      PerlLightMethodDefinitionElement<PerlSubCallElement> newMethod = new PerlLightMethodDefinitionElement<>(
1✔
139
        subCallElement,
140
        ElementManipulators.getValueText(identifier),
1✔
141
        LIGHT_METHOD_DEFINITION,
142
        identifier,
143
        namespaceName,
144
        Arrays.asList(PerlSubArgument.self(), PerlSubArgument.optionalScalar(PerlSubArgument.NEW_VALUE_VALUE)),
1✔
145
        PerlSubAnnotations.tryToFindAnnotations(identifier, subCallElement.getParent()),
1✔
146
        valueProvider,
147
        subExpr == null ? null : subExpr.getBlock()
1✔
148
      );
149
      result.add(newMethod);
1✔
150
    }
1✔
151

152
    return result;
1✔
153
  }
154

155
  public @NotNull List<String> getAttributesNames(@NotNull PerlSubCallElement subCallElement) {
156
    // fixme we have no stubs here
157
    Pair<List<PsiElement>, List<PsiElement>> lists = getIdentifiersAndListElements(subCallElement);
1✔
158
    if (lists == null || lists.first.isEmpty()) {
1✔
159
      return Collections.emptyList();
×
160
    }
161
    return lists.first.stream().map(ElementManipulators::getValueText).collect(Collectors.toList());
1✔
162
  }
163

164
  private @NotNull List<PerlDelegatingLightNamedElement<?>> createMooseAttributes(@NotNull PerlSubCallElement subCallElement,
165
                                                                                  @NotNull List<? extends PsiElement> identifiers,
166
                                                                                  @NotNull List<PsiElement> listElements) {
167

168
    List<PerlDelegatingLightNamedElement<?>> result = new ArrayList<>();
1✔
169
    String packageName = PerlPackageUtil.getContextNamespaceName(subCallElement);
1✔
170

171
    Map<String, PerlHashEntry> parameters = PerlHashUtil.packToHash(listElements.subList(1, listElements.size()));
1✔
172
    // handling is
173
    PerlHashEntry isParameter = parameters.get(IS_KEY);
1✔
174
    boolean isWritableProtected = isParameter != null && StringUtil.equals(RWP_KEY, isParameter.getValueString());
1✔
175
    boolean isWritable = isParameter != null && (StringUtil.equals(RW_KEY, isParameter.getValueString()) || isWritableProtected);
1✔
176

177
    PsiElement forcedIdentifier = null;
1✔
178

179
    // handling isa and does
180
    PerlHashEntry isaEntry = parameters.get(ISA_KEY);
1✔
181
    String valueClass = null;
1✔
182
    if (isaEntry == null) {
1✔
183
      isaEntry = parameters.get(DOES_KEY);
1✔
184
    }
185

186
    if (isaEntry != null && subCallElement.isAcceptableIdentifierElement(isaEntry.valueElement)) {
1✔
187
      valueClass = isaEntry.getValueString();
1✔
188
      if (StringUtil.isEmpty(valueClass)) {
1✔
189
        valueClass = null;
×
190
      }
191
    }
192

193
    // handling accessor, reader, etc.
194
    boolean createMooClearer = false;
1✔
195
    boolean createMooPredicate = false;
1✔
196

197
    List<PerlLightMethodDefinitionElement<?>> secondaryResult = new ArrayList<>();
1✔
198
    for (String key : MOOSE_SUB_NAMES_KEYS) {
1✔
199
      PerlHashEntry entry = parameters.get(key);
1✔
200
      if (entry == null) {
1✔
201
        continue;
1✔
202
      }
203

204
      String methodName = entry.getValueString();
1✔
205
      if (StringUtil.isEmpty(methodName)) {
1✔
206
        if (entry.valueElement instanceof PsiPerlNumberConstant && "1".equals(entry.valueElement.getText())) {
1✔
207
          if (key.equals(CLEARER_KEY)) {
1✔
208
            createMooClearer = true;
1✔
209
          }
210
          else if (key.equals(PREDICATE_KEY)) {
1✔
211
            createMooPredicate = true;
1✔
212
          }
213
        }
214
        continue;
215
      }
216

217
      if (!isWritable && key.equals(WRITER_KEY)) {
1✔
218
        continue;
1✔
219
      }
220

221
      if (!isWritable && key.equals(READER_KEY) || key.equals(ACCESSOR_KEY)) {
1✔
222
        forcedIdentifier = entry.valueElement;
1✔
223
        continue;
1✔
224
      }
225

226
      if (!subCallElement.isAcceptableIdentifierElement(entry.valueElement)) {
1✔
227
        continue;
×
228
      }
229

230
      // fixme we could optimize new_value with subclassing and hardcoding of signature
231
      PsiElement identifier = entry.getNonNullValueElement();
1✔
232
      PerlLightMethodDefinitionElement<PerlSubCallElement> secondaryElement = new PerlLightMethodDefinitionElement<>(
1✔
233
        subCallElement,
234
        ElementManipulators.getValueText(identifier),
1✔
235
        LIGHT_METHOD_DEFINITION,
236
        identifier,
237
        packageName,
238
        key.equals(WRITER_KEY) ? Arrays
1✔
239
          .asList(PerlSubArgument.self(), PerlSubArgument.optionalScalar(PerlSubArgument.NEW_VALUE_VALUE, valueClass))
1✔
240
                               : Collections.emptyList(),
1✔
241
        PerlSubAnnotations.tryToFindAnnotations(identifier, entry.keyElement, subCallElement.getParent())
1✔
242
      );
243

244
      if (key.equals(READER_KEY) && valueClass != null) {
1✔
245
        secondaryElement.setReturnValueFromCode(PerlScalarValue.create(valueClass));
×
246
      }
247

248
      secondaryResult.add(
1✔
249
        secondaryElement
250
      );
251
    }
1✔
252

253
    // handle handles
254
    PerlHashEntry handlesEntry = parameters.get(HANDLES_KEY);
1✔
255
    if (handlesEntry != null) {
1✔
256
      // to show proper signatures, we need an access to delegates, what requires indexes; we should do this in runtime, not indexing, but store delegation target
257
      if (handlesEntry.valueElement instanceof PsiPerlAnonHash) {
1✔
258
        // handle handles HASH
259
        Map<String, PerlHashEntry> delegatesMap = PerlHashUtil.collectHashMap(handlesEntry.valueElement);
1✔
260
        for (PerlHashEntry delegateEntry : delegatesMap.values()) {
1✔
261
          if (!subCallElement.isAcceptableIdentifierElement(delegateEntry.keyElement)) {
1✔
262
            continue;
×
263
          }
264
          secondaryResult.add(new PerlLightMethodDefinitionElement<>(
1✔
265
            subCallElement,
266
            ElementManipulators.getValueText(delegateEntry.keyElement),
1✔
267
            LIGHT_METHOD_DEFINITION,
268
            delegateEntry.keyElement,
269
            packageName,
270
            Collections.emptyList(),
1✔
271
            PerlSubAnnotations.tryToFindAnnotations(delegateEntry.keyElement, handlesEntry.keyElement, subCallElement.getParent())
1✔
272
          ));
273
        }
1✔
274
      }
1✔
275
      else if (handlesEntry.valueElement instanceof PsiPerlAnonArray anonArray) {
1✔
276
        // handle handles ARRAY
277
        List<PsiElement> delegatesIdentifiers = PerlArrayUtil.collectListElements(anonArray.getExpr());
1✔
278
        for (PsiElement identifier : delegatesIdentifiers) {
1✔
279
          if (!subCallElement.isAcceptableIdentifierElement(identifier)) {
1✔
280
            continue;
1✔
281
          }
282
          secondaryResult.add(new PerlLightMethodDefinitionElement<>(
1✔
283
            subCallElement,
284
            ElementManipulators.getValueText(identifier),
1✔
285
            LIGHT_METHOD_DEFINITION,
286
            identifier,
287
            packageName,
288
            Collections.emptyList(),
1✔
289
            PerlSubAnnotations.tryToFindAnnotations(identifier, handlesEntry.keyElement, subCallElement.getParent())
1✔
290
          ));
291
        }
1✔
292
      }
293
    }
294

295
    // handle required this should alter consttructor, no idea how for now
296
    // handle HANDLES ROLE OR ROLETYPE requires index
297
    // handle HANDLES DUCKTYPE requires index
298
    // handle HANDLES REGEXP this requires regexp & resolve
299

300
    for (PsiElement identifier : identifiers) {
1✔
301
      if (forcedIdentifier != null) {
1✔
302
        identifier = forcedIdentifier;
1✔
303
      }
304
      if (!subCallElement.isAcceptableIdentifierElement(identifier)) {
1✔
305
        continue;
1✔
306
      }
307
      List<PerlSubArgument> subArguments = isWritable && !isWritableProtected
1✔
308
                                           ? Arrays
309
                                             .asList(PerlSubArgument.self(),
1✔
310
                                                     PerlSubArgument.optionalScalar(PerlSubArgument.NEW_VALUE_VALUE, valueClass))
1✔
311
                                           : Collections.emptyList();
1✔
312
      var identifierText = ElementManipulators.getValueText(identifier);
1✔
313
      var identifierAnnotations = PerlSubAnnotations.tryToFindAnnotations(identifier, subCallElement.getParent());
1✔
314
      PerlAttributeDefinition newElement = new PerlAttributeDefinition(
1✔
315
        subCallElement,
316
        PerlAttributeDefinition.DEFAULT_NAME_COMPUTATION.fun(identifierText),
1✔
317
        LIGHT_ATTRIBUTE_DEFINITION,
318
        identifier,
319
        packageName,
320
        subArguments,
321
        identifierAnnotations
322
      );
323
      if (valueClass != null) {
1✔
324
        PerlValue returnValue = PerlScalarValue.create(valueClass);
1✔
325
        newElement.setReturnValueFromCode(returnValue);
1✔
326
      }
327
      result.add(newElement);
1✔
328

329
      if (isWritableProtected) {
1✔
330
        result.add(new PerlLightMethodDefinitionElement<>(
1✔
331
          subCallElement,
332
          PROTECTED_MUTATOR_PREFIX + identifierText,
333
          LIGHT_METHOD_DEFINITION,
334
          identifier,
335
          packageName,
336
          Arrays.asList(PerlSubArgument.self(),
1✔
337
                        PerlSubArgument.mandatoryScalar(PerlSubArgument.NEW_VALUE_VALUE, StringUtil.notNullize(valueClass))),
1✔
338
          identifierAnnotations
339
        ));
340
      }
341

342
      if (createMooClearer) {
1✔
343
        var clearerName = identifierText.startsWith("_") ?
1✔
344
                          _MOO_CLEARER_PREFIX + identifierText.substring(1) : MOO_CLEARER_PREFIX + identifierText;
1✔
345
        result.add(new PerlLightMethodDefinitionElement<>(
1✔
346
          subCallElement,
347
          clearerName,
348
          LIGHT_METHOD_DEFINITION,
349
          identifier,
350
          packageName,
351
          Collections.emptyList(),
1✔
352
          identifierAnnotations
353
        ));
354
      }
355
      if (createMooPredicate) {
1✔
356
        var predicateName = identifierText.startsWith("_") ?
1✔
357
                            _MOO_PREDICATE_PREFIX + identifierText.substring(1) : MOO_PREDICATE_PREFIX + identifierText;
1✔
358
        result.add(new PerlLightMethodDefinitionElement<>(
1✔
359
          subCallElement,
360
          predicateName,
361
          LIGHT_METHOD_DEFINITION,
362
          identifier,
363
          packageName,
364
          Collections.emptyList(),
1✔
365
          identifierAnnotations
366
        ));
367
      }
368

369
      result.addAll(secondaryResult);
1✔
370
      secondaryResult.clear();
1✔
371
    }
1✔
372

373
    return result;
1✔
374
  }
375

376
  @SuppressWarnings("BooleanMethodIsAlwaysInverted")
377
  public static boolean isMooseAttributeWrapper(@Nullable PsiElement psiElement) {
378
    return psiElement instanceof PerlSubCallElement &&
1✔
379
           ((PerlSubCallElement)psiElement).getHandler() instanceof PerlMooseAttributeHandler;
1✔
380
  }
381

382
  public static PerlMooseAttributeHandler notNullFrom(@NotNull PerlSubCallElement subCallElement) {
383
    PerlSubCallHandler<?> provider = subCallElement.getHandler();
1✔
384
    assert provider instanceof PerlMooseAttributeHandler : "Got wrong provider: " + provider;
1✔
385
    return ((PerlMooseAttributeHandler)provider);
1✔
386
  }
387
}
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