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

Camelcade / Perl5-IDEA / #525521576

05 Jun 2025 06:17AM UTC coverage: 82.298% (-0.02%) from 82.318%
#525521576

push

github

hurricup
Localized strings and improved annotations

26 of 41 new or added lines in 19 files covered. (63.41%)

22 existing lines in 6 files now uncovered.

30837 of 37470 relevant lines covered (82.3%)

0.82 hits per line

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

79.87
/plugin/core/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
  public static final PerlCodeGenerator INSTANCE = new PerlCodeGeneratorImpl();
1✔
62

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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