• 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

79.75
/plugin/backend/src/main/java/com/perl5/lang/perl/extensions/generation/PerlCodeGeneratorImpl.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.extensions.generation;
18

19
import com.intellij.ide.IdeBundle;
20
import com.intellij.ide.util.MemberChooser;
21
import com.intellij.openapi.application.ApplicationManager;
22
import com.intellij.openapi.editor.Document;
23
import com.intellij.openapi.editor.Editor;
24
import com.intellij.openapi.editor.ScrollType;
25
import com.intellij.openapi.fileTypes.FileType;
26
import com.intellij.openapi.project.Project;
27
import com.intellij.openapi.ui.DialogWrapper;
28
import com.intellij.openapi.ui.Messages;
29
import com.intellij.openapi.util.NlsContexts;
30
import com.intellij.openapi.util.TextRange;
31
import com.intellij.openapi.util.text.StringUtil;
32
import com.intellij.psi.PsiDocumentManager;
33
import com.intellij.psi.PsiElement;
34
import com.intellij.psi.PsiFile;
35
import com.intellij.psi.util.PsiTreeUtil;
36
import com.intellij.ui.SpeedSearchComparator;
37
import com.perl5.PerlBundle;
38
import com.perl5.PerlIcons;
39
import com.perl5.lang.perl.extensions.PerlCodeGenerator;
40
import com.perl5.lang.perl.idea.codeInsight.PerlMethodMember;
41
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlValue;
42
import com.perl5.lang.perl.parser.PerlParserUtil;
43
import com.perl5.lang.perl.psi.PerlMethodDefinition;
44
import com.perl5.lang.perl.psi.PerlNamespaceDefinitionElement;
45
import com.perl5.lang.perl.psi.PerlSubDefinitionElement;
46
import com.perl5.lang.perl.psi.PerlSubElement;
47
import com.perl5.lang.perl.psi.utils.PerlElementFactory;
48
import com.perl5.lang.perl.psi.utils.PerlSubAnnotations;
49
import com.perl5.lang.perl.psi.utils.PerlSubArgument;
50
import com.perl5.lang.perl.util.PerlPackageUtil;
51
import org.jetbrains.annotations.NotNull;
52
import org.jetbrains.annotations.Nullable;
53
import org.jetbrains.annotations.TestOnly;
54

55
import java.util.*;
56
import java.util.function.UnaryOperator;
57

58

59
public class PerlCodeGeneratorImpl implements PerlCodeGenerator {
1✔
60
  private static UnaryOperator<List<PerlMethodMember>> ourTestChooser = null;
1✔
61

62
  @Override
63
  public @Nullable String getOverrideCodeText(PsiElement subBase) {
64
    if (subBase instanceof PerlSubElement perlSubBase) {
1✔
65
      StringBuilder code = new StringBuilder();
1✔
66
      code.append("#@override\n");
1✔
67

68
      PerlSubAnnotations annotations = perlSubBase.getAnnotations();
1✔
69
      if (annotations != null) {
1✔
70
        if (annotations.isDeprecated()) {
1✔
71
          code.append("#@deprecated\n");
×
72
        }
73
        if (annotations.isAbstract()) {
1✔
74
          code.append("#@abstract\n");
×
75
        }
76
        if (annotations.isMethod() || subBase instanceof PerlMethodDefinition) {
1✔
77
          code.append("#@method\n");
1✔
78
        }
79
        PerlValue returnValue = annotations.getReturnValue();
1✔
80
        if (!returnValue.isUnknown()) {
1✔
81
          code.append("#@returns ");
×
82
          code.append(returnValue.toCode());
×
83
          code.append("\n");
×
84
        }
85
      }
86

87
      code.append("sub ");
1✔
88
      code.append(perlSubBase.getSubName());
1✔
89
      code.append("{\n");
1✔
90

91
      List<String> superArgs = new ArrayList<>();
1✔
92
      List<PerlSubArgument> arguments = Collections.emptyList();
1✔
93

94
      if (perlSubBase instanceof PerlSubDefinitionElement subDefinitionElement) {
1✔
95
        //noinspection unchecked
96
        arguments = subDefinitionElement.getSubArgumentsList();
1✔
97

98
        if (!arguments.isEmpty()) {
1✔
99
          boolean useShift = false;
1✔
100

101
          for (PerlSubArgument argument : arguments) {
1✔
102
            if (StringUtil.isNotEmpty(argument.getVariableClass())) {
1✔
103
              useShift = true;
×
104
              break;
×
105
            }
106
          }
1✔
107

108
          if (useShift) {
1✔
109
            for (PerlSubArgument argument : arguments) {
×
110
              if (!argument.isEmpty()) {
×
111
                code.append("my ");
×
112
                code.append(argument.getVariableClass());
×
113
                code.append(" ");
×
114
                String superArg = argument.toStringShort();
×
115
                superArgs.add(superArg);
×
116
                code.append(superArg);
×
117
                code.append(" = ");
×
118
              }
119
              code.append("shift;\n");
×
120
            }
×
121
          }
122
          else {
123
            code.append("my ");
1✔
124
            code.append('(');
1✔
125
            boolean insertComma = false;
1✔
126
            for (PerlSubArgument argument : arguments) {
1✔
127
              if (insertComma) {
1✔
128
                code.append(", ");
1✔
129
              }
130
              else {
131
                insertComma = true;
1✔
132
              }
133

134
              String superArg = argument.toStringShort();
1✔
135
              superArgs.add(superArg);
1✔
136
              code.append(superArg);
1✔
137
            }
1✔
138
            code.append(") = @_;\n");
1✔
139
          }
140
        }
1✔
141
        else {
142
          code.append("my ($self) = @_;\n");
1✔
143
        }
144
      }
145

146
      if (!superArgs.isEmpty()) {
1✔
147
        superArgs.removeFirst();
1✔
148
      }
149

150
      if (!arguments.isEmpty() && !arguments.getFirst().isEmpty()) {
1✔
151
        //noinspection StringConcatenationInsideStringBufferAppend
152
        code.append(
1✔
153
          arguments.getFirst().toStringShort() + "->SUPER::" + perlSubBase.getSubName() + "(" + StringUtil.join(superArgs, ", ") + ");\n");
1✔
154
      }
155
      code.append("}");
1✔
156
      return code.toString();
1✔
157
    }
158
    return null;
×
159
  }
160

161
  @Override
162
  public void generateOverrideMethod(PsiElement anchor, Editor editor) {
163
    if (anchor != null) {
1✔
164
      final List<PerlMethodMember> subDefinitions = new ArrayList<>();
1✔
165

166
      PerlPackageUtil.processNotOverridedMethods(
1✔
167
        PsiTreeUtil.getParentOfType(anchor, PerlNamespaceDefinitionElement.class),
1✔
168
        subDefinitionBase ->
169
        {
170
          subDefinitions.add(new PerlMethodMember(subDefinitionBase));
1✔
171
          return true;
1✔
172
        }
173
      );
174

175
      List<PerlMethodMember> selectedElements = getMembersToOverride(anchor, subDefinitions);
1✔
176
      if (selectedElements == null) {
1✔
177
        return;
×
178
      }
179
      StringBuilder generatedCode = new StringBuilder();
1✔
180
      for (PerlMethodMember methodMember : selectedElements) {
1✔
181
        String code = getOverrideCodeText(methodMember.getPsiElement());
1✔
182
        if (StringUtil.isNotEmpty(code)) {
1✔
183
          generatedCode.append(code);
1✔
184
          generatedCode.append("\n\n");
1✔
185
        }
186
      }
1✔
187

188
      insertCodeAfterElement(anchor, generatedCode.toString(), editor);
1✔
189
    }
190
  }
1✔
191

192
  private @Nullable List<PerlMethodMember> getMembersToOverride(@NotNull PsiElement anchor,
193
                                                                @NotNull List<PerlMethodMember> subDefinitions) {
194
    if (ourTestChooser != null) {
1✔
195
      return ourTestChooser.apply(subDefinitions);
1✔
196
    }
197

198
    final MemberChooser<PerlMethodMember> chooser =
×
199
      new MemberChooser<>(subDefinitions.toArray(PerlMethodMember.EMPTY_ARRAY), false, true,
×
200
                          anchor.getProject()) {
×
201
        @Override
202
        protected SpeedSearchComparator getSpeedSearchComparator() {
203
          return new SpeedSearchComparator(false) {
×
204
            @Override
205
            public @Nullable Iterable<TextRange> matchingFragments(@NotNull String pattern, @NotNull String text) {
206
              return super.matchingFragments(PerlMethodMember.trimUnderscores(pattern), text);
×
207
            }
208
          };
209
        }
210

211
        @Override
212
        protected ShowContainersAction getShowContainersAction() {
213
          return new ShowContainersAction(() -> IdeBundle.message("action.show.classes"), PerlIcons.PACKAGE_GUTTER_ICON);
×
214
        }
215
      };
216

217
    chooser.setTitle(PerlBundle.message("dialog.title.override.implement.method"));
×
218
    chooser.setCopyJavadocVisible(false);
×
219
    chooser.show();
×
220
    if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
×
221
      return null;
×
222
    }
223

224
    return chooser.getSelectedElements();
×
225
  }
226

227
  @Override
228
  public void generateSetters(PsiElement anchor, Editor editor) {
229
    StringBuilder code = new StringBuilder();
1✔
230

231
    for (String name : askFieldsNames(anchor.getProject(), PerlBundle.message("dialog.message.type.comma.separated.setters.names"),
1✔
232
                                      PerlBundle.message("dialog.title.generating.setters"))) {
1✔
233
      code.append(getSetterCode(name));
1✔
234
    }
1✔
235

236
    if (!code.isEmpty()) {
1✔
237
      insertCodeAfterElement(anchor, code.toString(), editor);
1✔
238
    }
239
  }
1✔
240

241
  @Override
242
  public void generateGetters(PsiElement anchor, Editor editor) {
243
    StringBuilder code = new StringBuilder();
1✔
244

245
    for (String name : askFieldsNames(anchor.getProject(), PerlBundle.message("dialog.message.type.comma.separated.getters.names"),
1✔
246
                                      PerlBundle.message("dialog.title.generating.getters"))) {
1✔
247
      code.append(getGetterCode(name));
1✔
248
    }
1✔
249

250
    if (!code.isEmpty()) {
1✔
251
      insertCodeAfterElement(anchor, code.toString(), editor);
1✔
252
    }
253
  }
1✔
254

255
  @Override
256
  public void generateGettersAndSetters(PsiElement anchor, Editor editor) {
257
    StringBuilder code = new StringBuilder();
1✔
258

259
    for (String name : askFieldsNames(anchor.getProject(), PerlBundle.message("dialog.message.type.comma.separated.accessors.names"),
1✔
260
                                      PerlBundle.message("dialog.title.generating.getters.setters"))) {
1✔
261
      code.append(getGetterCode(name));
1✔
262
      code.append(getSetterCode(name));
1✔
263
    }
1✔
264

265
    if (!code.isEmpty()) {
1✔
266
      insertCodeAfterElement(anchor, code.toString(), editor);
1✔
267
    }
268
  }
1✔
269

270
  @Override
271
  public void generateConstructor(PsiElement anchor, Editor editor) {
272
    insertCodeAfterElement(anchor, getConstructorCode(), editor);
1✔
273
  }
1✔
274

275
  protected List<String> askFieldsNames(@NotNull Project project,
276
                                        @NlsContexts.DialogMessage String promptText,
277
                                        @NlsContexts.DialogTitle String promptTitle
278
  ) {
279
    Set<String> result = new HashSet<>();
1✔
280
    String name = Messages.showInputDialog(project, promptText, promptTitle, Messages.getQuestionIcon(), "", null);
1✔
281

282
    if (!StringUtil.isEmpty(name)) {
1✔
283

284
      for (String nameChunk : name.split("[ ,]+")) {
1✔
285
        if (!nameChunk.isEmpty() && PerlParserUtil.isIdentifier(nameChunk)) {
1✔
286
          result.add(nameChunk);
1✔
287
        }
288
      }
289
    }
290
    return new ArrayList<>(result);
1✔
291
  }
292

293
  protected void insertCodeAfterElement(PsiElement anchor, String code, Editor editor) {
294
    ApplicationManager.getApplication().runWriteAction(() ->
1✔
295
                                                       {
296
                                                         FileType fileType = anchor.getContainingFile().getFileType();
1✔
297
                                                         final PsiDocumentManager manager =
1✔
298
                                                           PsiDocumentManager.getInstance(anchor.getProject());
1✔
299
                                                         final Document document = manager.getDocument(anchor.getContainingFile());
1✔
300

301
                                                         if (!code.isEmpty() && document != null) {
1✔
302
                                                           manager.doPostponedOperationsAndUnblockDocument(document);
1✔
303

304
                                                           PsiFile newFile =
1✔
305
                                                             PerlElementFactory.createFile(anchor.getProject(), "\n" + code, fileType);
1✔
306
                                                           PsiElement container = anchor.getParent();
1✔
307
                                                           int newOffset = anchor.getTextOffset() + anchor.getTextLength();
1✔
308

309
                                                           if (newFile.getFirstChild() != null && newFile.getLastChild() != null) {
1✔
310
                                                             container
1✔
311
                                                               .addRangeAfter(newFile.getFirstChild(), newFile.getLastChild(), anchor);
1✔
312
                                                           }
313

314
                                                           manager.commitDocument(document);
1✔
315
                                                           editor.getCaretModel().moveToOffset(newOffset);
1✔
316
                                                           editor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
1✔
317
                                                         }
318
                                                       });
1✔
319
  }
1✔
320

321
  public static String getGetterCode(String name) {
322
    return "sub get_" + name + "\n" +
1✔
323
           "{\n" +
324
           "        return $_[0]->{" + name + "};\n" +
325
           "}\n";
326
  }
327

328
  public static String getSetterCode(String name) {
329
    return "sub set_" + name + "\n" +
1✔
330
           "{\n" +
331
           "        my ($self, $new_value) = @_;\n" +
332
           "        $$self{" + name + "} = $new_value;\n" +
333
           "        return $self;\n" +
334
           "}\n";
335
  }
336

337
  @SuppressWarnings("SameReturnValue")
338
  public static String getConstructorCode() {
339
    return """
1✔
340
      
341
      sub new
342
      {
343
        my ($proto) = @_;
344
        my $self = bless {}, $proto;
345
        return $self;
346
      }
347
      
348
      """;
349
  }
350

351
  @TestOnly
352
  public static void withChooser(@NotNull UnaryOperator<List<PerlMethodMember>> testChooser, @NotNull Runnable runnable) {
353
    ourTestChooser = testChooser;
1✔
354
    try {
355
      runnable.run();
1✔
356
    }
357
    finally {
358
      ourTestChooser = null;
1✔
359
    }
360
  }
1✔
361
}
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